[Jifty-commit] r4568 - in jifty/branches/js-refactor: . contrib debian examples/HelloKitty/lib/HelloKitty examples/ShrinkURL examples/ShrinkURL/bin examples/ShrinkURL/doc examples/ShrinkURL/etc examples/ShrinkURL/lib examples/ShrinkURL/lib/ShrinkURL examples/ShrinkURL/lib/ShrinkURL/Action examples/ShrinkURL/lib/ShrinkURL/Model examples/ShrinkURL/log examples/ShrinkURL/share examples/ShrinkURL/share/po examples/ShrinkURL/share/web examples/ShrinkURL/share/web/static examples/ShrinkURL/share/web/templates examples/ShrinkURL/t examples/ShrinkURL/var examples/ShrinkURL/var/mason lib lib/Jifty lib/Jifty/Action lib/Jifty/Action/Record lib/Jifty/Filter lib/Jifty/Manual lib/Jifty/Model lib/Jifty/Module lib/Jifty/Param lib/Jifty/Plugin lib/Jifty/Plugin/ActorMetadata/Mixin/Model lib/Jifty/Plugin/Authentication lib/Jifty/Plugin/Authentication/CAS lib/Jifty/Plugin/Authentication/CAS/Action lib/Jifty/Plugin/Authentication/CAS/Mixin lib/Jifty/Plugin/Authentication/CAS/Mixin/Model lib/Jifty/Plugin/Authentication/Ldap lib/Jifty/Plugin/Authentication/Ldap/Action lib/Jifty/Plugin/Authentication/Ldap/Mixin lib/Jifty/Plugin/Authentication/Ldap/Mixin/Model lib/Jifty/Plugin/Authentication/Ldap/doc lib/Jifty/Plugin/Authentication/Password/Action lib/Jifty/Plugin/Authentication/Password/Mixin/Model lib/Jifty/Plugin/AutoReference lib/Jifty/Plugin/Chart lib/Jifty/Plugin/Chart/Renderer lib/Jifty/Plugin/Chart/Renderer/GD lib/Jifty/Plugin/CompressedCSSandJS lib/Jifty/Plugin/Feedback/Action lib/Jifty/Plugin/I18N lib/Jifty/Plugin/I18N/Action lib/Jifty/Plugin/LeakTracker lib/Jifty/Plugin/OAuth lib/Jifty/Plugin/OAuth/Action lib/Jifty/Plugin/OAuth/Model lib/Jifty/Plugin/REST lib/Jifty/Plugin/SkeletonApp lib/Jifty/Plugin/TabView lib/Jifty/Plugin/UUID lib/Jifty/Plugin/Userpic lib/Jifty/Request lib/Jifty/Script lib/Jifty/Server lib/Jifty/Test/WWW lib/Jifty/View lib/Jifty/View/Declare lib/Jifty/View/Mason lib/Jifty/View/Static lib/Jifty/Web lib/Jifty/Web/Form lib/Jifty/Web/Form/Field share/plugins/Jifty/Plugin/AutoReference share/plugins/Jifty/Plugin/AutoReference/web share/plugins/Jifty/Plugin/AutoReference/web/static share/plugins/Jifty/Plugin/AutoReference/web/static/js share/plugins/Jifty/Plugin/Chart share/plugins/Jifty/Plugin/Chart/web share/plugins/Jifty/Plugin/Chart/web/static share/plugins/Jifty/Plugin/Chart/web/static/css share/plugins/Jifty/Plugin/Chart/web/static/flash share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library share/plugins/Jifty/Plugin/Chart/web/static/js share/plugins/Jifty/Plugin/Chart/web/static/js/MochiKit share/plugins/Jifty/Plugin/Chart/web/static/js/PlotKit share/plugins/Jifty/Plugin/GoogleMap/web/static/js share/plugins/Jifty/Plugin/I18N share/plugins/Jifty/Plugin/I18N/web share/plugins/Jifty/Plugin/I18N/web/static share/plugins/Jifty/Plugin/I18N/web/static/js share/plugins/Jifty/Plugin/JQuery share/plugins/Jifty/Plugin/JQuery/web share/plugins/Jifty/Plugin/JQuery/web/static share/plugins/Jifty/Plugin/JQuery/web/static/js share/po share/web/static/css share/web/static/js share/web/static/js/scriptaculous t t/TestApp-Plugin-AppPluginHasModels/var/mason t/TestApp-Plugin-Chart/lib/TestApp/Plugin/Chart t/TestApp-Plugin-Chart/t t/TestApp-Plugin-Chart/var/mason t/TestApp-Plugin-CompressedCSSandJS t/TestApp-Plugin-CompressedCSSandJS/bin t/TestApp-Plugin-CompressedCSSandJS/doc t/TestApp-Plugin-CompressedCSSandJS/etc t/TestApp-Plugin-CompressedCSSandJS/lib t/TestApp-Plugin-CompressedCSSandJS/lib/TestApp t/TestApp-Plugin-CompressedCSSandJS/lib/TestApp/Plugin t/TestApp-Plugin-CompressedCSSandJS/lib/TestApp/Plugin/CompressedCSSandJS t/TestApp-Plugin-CompressedCSSandJS/log t/TestApp-Plugin-CompressedCSSandJS/share t/TestApp-Plugin-CompressedCSSandJS/share/po t/TestApp-Plugin-CompressedCSSandJS/share/web t/TestApp-Plugin-CompressedCSSandJS/share/web/static t/TestApp-Plugin-CompressedCSSandJS/share/web/templates t/TestApp-Plugin-CompressedCSSandJS/t t/TestApp-Plugin-CompressedCSSandJS/var t/TestApp-Plugin-JQuery t/TestApp-Plugin-JQuery/bin t/TestApp-Plugin-JQuery/doc t/TestApp-Plugin-JQuery/etc t/TestApp-Plugin-JQuery/lib t/TestApp-Plugin-JQuery/lib/TestApp t/TestApp-Plugin-JQuery/lib/TestApp/Plugin t/TestApp-Plugin-JQuery/lib/TestApp/Plugin/JQuery t/TestApp-Plugin-JQuery/lib/TestApp/Plugin/JQuery/Action t/TestApp-Plugin-JQuery/lib/TestApp/Plugin/JQuery/Model t/TestApp-Plugin-JQuery/log t/TestApp-Plugin-JQuery/share t/TestApp-Plugin-JQuery/share/po t/TestApp-Plugin-JQuery/share/web t/TestApp-Plugin-JQuery/share/web/static t/TestApp-Plugin-JQuery/share/web/templates t/TestApp-Plugin-JQuery/t t/TestApp-Plugin-JQuery/var t/TestApp-Plugin-OAuth t/TestApp-Plugin-OAuth/bin t/TestApp-Plugin-OAuth/doc t/TestApp-Plugin-OAuth/etc t/TestApp-Plugin-OAuth/lib t/TestApp-Plugin-OAuth/lib/TestApp t/TestApp-Plugin-OAuth/lib/TestApp/Plugin t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Action t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Model t/TestApp-Plugin-OAuth/log t/TestApp-Plugin-OAuth/share t/TestApp-Plugin-OAuth/share/po t/TestApp-Plugin-OAuth/share/web t/TestApp-Plugin-OAuth/share/web/static t/TestApp-Plugin-OAuth/share/web/templates t/TestApp-Plugin-OAuth/t t/TestApp-Plugin-OAuth/var t/TestApp-Plugin-OnClick t/TestApp-Plugin-OnClick/bin t/TestApp-Plugin-OnClick/doc t/TestApp-Plugin-OnClick/etc t/TestApp-Plugin-OnClick/lib t/TestApp-Plugin-OnClick/lib/TestApp t/TestApp-Plugin-OnClick/lib/TestApp/Plugin t/TestApp-Plugin-OnClick/lib/TestApp/Plugin/OnClick t/TestApp-Plugin-OnClick/log t/TestApp-Plugin-OnClick/share t/TestApp-Plugin-OnClick/share/po t/TestApp-Plugin-OnClick/share/web t/TestApp-Plugin-OnClick/share/web/static t/TestApp-Plugin-OnClick/share/web/static/js t/TestApp-Plugin-OnClick/share/web/templates t/TestApp-Plugin-OnClick/t t/TestApp-Plugin-OnClick/var t/TestApp-Plugin-SinglePage t/TestApp-Plugin-SinglePage/bin t/TestApp-Plugin-SinglePage/doc t/TestApp-Plugin-SinglePage/etc t/TestApp-Plugin-SinglePage/lib t/TestApp-Plugin-SinglePage/lib/TestApp t/TestApp-Plugin-SinglePage/lib/TestApp/Plugin t/TestApp-Plugin-SinglePage/lib/TestApp/Plugin/Model t/TestApp-Plugin-SinglePage/lib/TestApp/Plugin/SinglePage t/TestApp-Plugin-SinglePage/lib/TestApp/Plugin/SinglePage/Model t/TestApp-Plugin-SinglePage/log t/TestApp-Plugin-SinglePage/share t/TestApp-Plugin-SinglePage/share/po t/TestApp-Plugin-SinglePage/share/web t/TestApp-Plugin-SinglePage/share/web/static t/TestApp-Plugin-SinglePage/share/web/templates t/TestApp-Plugin-SinglePage/t t/TestApp-Plugin-SinglePage/var t/TestApp/etc t/TestApp/lib/TestApp t/TestApp/lib/TestApp/Action t/TestApp/lib/TestApp/Model t/TestApp/share/web/templates/_elements t/TestApp/t t/TestApp/t/config t/clientside t/lib/Jifty

jifty-commit at lists.jifty.org jifty-commit at lists.jifty.org
Thu Nov 29 12:25:27 EST 2007


Author: jesse
Date: Thu Nov 29 12:25:16 2007
New Revision: 4568

Added:
   jifty/branches/js-refactor/contrib/
   jifty/branches/js-refactor/contrib/jifty_completion.sh
   jifty/branches/js-refactor/examples/ShrinkURL/
   jifty/branches/js-refactor/examples/ShrinkURL/Makefile.PL
   jifty/branches/js-refactor/examples/ShrinkURL/bin/
   jifty/branches/js-refactor/examples/ShrinkURL/bin/jifty   (contents, props changed)
   jifty/branches/js-refactor/examples/ShrinkURL/doc/
   jifty/branches/js-refactor/examples/ShrinkURL/etc/
   jifty/branches/js-refactor/examples/ShrinkURL/etc/config.yml
   jifty/branches/js-refactor/examples/ShrinkURL/lib/
   jifty/branches/js-refactor/examples/ShrinkURL/lib/ShrinkURL/
   jifty/branches/js-refactor/examples/ShrinkURL/lib/ShrinkURL/Action/
   jifty/branches/js-refactor/examples/ShrinkURL/lib/ShrinkURL/Action/CreateShrunkenURL.pm
   jifty/branches/js-refactor/examples/ShrinkURL/lib/ShrinkURL/Dispatcher.pm
   jifty/branches/js-refactor/examples/ShrinkURL/lib/ShrinkURL/Model/
   jifty/branches/js-refactor/examples/ShrinkURL/lib/ShrinkURL/Model/ShrunkenURL.pm
   jifty/branches/js-refactor/examples/ShrinkURL/lib/ShrinkURL/View.pm
   jifty/branches/js-refactor/examples/ShrinkURL/log/
   jifty/branches/js-refactor/examples/ShrinkURL/share/
   jifty/branches/js-refactor/examples/ShrinkURL/share/po/
   jifty/branches/js-refactor/examples/ShrinkURL/share/web/
   jifty/branches/js-refactor/examples/ShrinkURL/share/web/static/
   jifty/branches/js-refactor/examples/ShrinkURL/share/web/templates/
   jifty/branches/js-refactor/examples/ShrinkURL/t/
   jifty/branches/js-refactor/examples/ShrinkURL/var/
   jifty/branches/js-refactor/examples/ShrinkURL/var/mason/
   jifty/branches/js-refactor/lib/Jifty/Manual.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/CAS/
   jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/CAS.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/CAS/Action/
   jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/CAS/Action/CASLogin.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/CAS/Action/CASLogout.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/CAS/Dispatcher.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/CAS/Mixin/
   jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/CAS/Mixin/Model/
   jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/CAS/Mixin/Model/User.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/Ldap/
   jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/Ldap.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/Ldap/Action/
   jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/Ldap/Action/LDAPLogin.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/Ldap/Action/LDAPLogout.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/Ldap/Dispatcher.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/Ldap/Mixin/
   jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/Ldap/Mixin/Model/
   jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/Ldap/Mixin/Model/User.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/Ldap/View.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/Ldap/doc/
   jifty/branches/js-refactor/lib/Jifty/Plugin/AutoReference/
   jifty/branches/js-refactor/lib/Jifty/Plugin/AutoReference.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/AutoReference/Widget.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/Chart/Renderer/GD/
   jifty/branches/js-refactor/lib/Jifty/Plugin/Chart/Renderer/GD/Graph.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/Chart/Renderer/SimpleBars.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/I18N/
   jifty/branches/js-refactor/lib/Jifty/Plugin/I18N.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/I18N/Action/
   jifty/branches/js-refactor/lib/Jifty/Plugin/I18N/Action/SetLang.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/JQuery.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/LeakTracker/
   jifty/branches/js-refactor/lib/Jifty/Plugin/LeakTracker.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/LeakTracker/Dispatcher.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/LeakTracker/View.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/OAuth/
   jifty/branches/js-refactor/lib/Jifty/Plugin/OAuth.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/OAuth/Action/
   jifty/branches/js-refactor/lib/Jifty/Plugin/OAuth/Action/AuthorizeRequestToken.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/OAuth/Dispatcher.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/OAuth/Model/
   jifty/branches/js-refactor/lib/Jifty/Plugin/OAuth/Model/AccessToken.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/OAuth/Model/Consumer.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/OAuth/Model/RequestToken.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/OAuth/Token.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/OAuth/View.pm
   jifty/branches/js-refactor/lib/Jifty/Test/WWW/Declare.pm
   jifty/branches/js-refactor/lib/Jifty/Test/WWW/Selenium.pm
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/AutoReference/
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/AutoReference/web/
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/AutoReference/web/static/
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/AutoReference/web/static/js/
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/AutoReference/web/static/js/autoreference.js
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/css/
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/css/simple_bars.css
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts.swf   (contents, props changed)
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/arno.swf   (contents, props changed)
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/arst.swf   (contents, props changed)
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/brfl.swf   (contents, props changed)
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/brno.swf   (contents, props changed)
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/brst.swf   (contents, props changed)
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/cl3d.swf   (contents, props changed)
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/clfl.swf   (contents, props changed)
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/clno.swf   (contents, props changed)
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/clp3.swf   (contents, props changed)
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/cls3.swf   (contents, props changed)
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/clst.swf   (contents, props changed)
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/cnno.swf   (contents, props changed)
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/lnno.swf   (contents, props changed)
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/mxno.swf   (contents, props changed)
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/pi3d.swf   (contents, props changed)
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/pino.swf   (contents, props changed)
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/pono.swf   (contents, props changed)
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/scno.swf   (contents, props changed)
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/js/
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/js/MochiKit/
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/js/MochiKit/MochiKit.js
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/js/MochiKit/__package__.js
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/js/PlotKit/
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/js/PlotKit/PlotKit_Packed-20060807-custom.js
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/js/PlotKit/excanvas.js   (contents, props changed)
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/js/chart_img_behaviour.js
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/js/mochikit.noexport.js
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/js/simple_bars.js
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/I18N/
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/I18N/web/
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/I18N/web/static/
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/I18N/web/static/js/
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/I18N/web/static/js/loc.js
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/JQuery/
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/JQuery/web/
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/JQuery/web/static/
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/JQuery/web/static/js/
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/JQuery/web/static/js/jquery.js
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/JQuery/web/static/js/noConflict.js
   jifty/branches/js-refactor/t/TestApp-Plugin-Chart/t/gd_graph.t
   jifty/branches/js-refactor/t/TestApp-Plugin-CompressedCSSandJS/
   jifty/branches/js-refactor/t/TestApp-Plugin-CompressedCSSandJS/Makefile.PL
   jifty/branches/js-refactor/t/TestApp-Plugin-CompressedCSSandJS/bin/
   jifty/branches/js-refactor/t/TestApp-Plugin-CompressedCSSandJS/bin/jifty   (contents, props changed)
   jifty/branches/js-refactor/t/TestApp-Plugin-CompressedCSSandJS/doc/
   jifty/branches/js-refactor/t/TestApp-Plugin-CompressedCSSandJS/etc/
   jifty/branches/js-refactor/t/TestApp-Plugin-CompressedCSSandJS/etc/config.yml
   jifty/branches/js-refactor/t/TestApp-Plugin-CompressedCSSandJS/lib/
   jifty/branches/js-refactor/t/TestApp-Plugin-CompressedCSSandJS/lib/TestApp/
   jifty/branches/js-refactor/t/TestApp-Plugin-CompressedCSSandJS/lib/TestApp/Plugin/
   jifty/branches/js-refactor/t/TestApp-Plugin-CompressedCSSandJS/lib/TestApp/Plugin/CompressedCSSandJS/
   jifty/branches/js-refactor/t/TestApp-Plugin-CompressedCSSandJS/log/
   jifty/branches/js-refactor/t/TestApp-Plugin-CompressedCSSandJS/share/
   jifty/branches/js-refactor/t/TestApp-Plugin-CompressedCSSandJS/share/po/
   jifty/branches/js-refactor/t/TestApp-Plugin-CompressedCSSandJS/share/web/
   jifty/branches/js-refactor/t/TestApp-Plugin-CompressedCSSandJS/share/web/static/
   jifty/branches/js-refactor/t/TestApp-Plugin-CompressedCSSandJS/share/web/templates/
   jifty/branches/js-refactor/t/TestApp-Plugin-CompressedCSSandJS/t/
   jifty/branches/js-refactor/t/TestApp-Plugin-CompressedCSSandJS/t/css.t
   jifty/branches/js-refactor/t/TestApp-Plugin-CompressedCSSandJS/var/
   jifty/branches/js-refactor/t/TestApp-Plugin-JQuery/
   jifty/branches/js-refactor/t/TestApp-Plugin-JQuery/Makefile.PL
   jifty/branches/js-refactor/t/TestApp-Plugin-JQuery/bin/
   jifty/branches/js-refactor/t/TestApp-Plugin-JQuery/bin/jifty   (contents, props changed)
   jifty/branches/js-refactor/t/TestApp-Plugin-JQuery/doc/
   jifty/branches/js-refactor/t/TestApp-Plugin-JQuery/etc/
   jifty/branches/js-refactor/t/TestApp-Plugin-JQuery/etc/config.yml
   jifty/branches/js-refactor/t/TestApp-Plugin-JQuery/lib/
   jifty/branches/js-refactor/t/TestApp-Plugin-JQuery/lib/TestApp/
   jifty/branches/js-refactor/t/TestApp-Plugin-JQuery/lib/TestApp/Plugin/
   jifty/branches/js-refactor/t/TestApp-Plugin-JQuery/lib/TestApp/Plugin/JQuery/
   jifty/branches/js-refactor/t/TestApp-Plugin-JQuery/lib/TestApp/Plugin/JQuery/Action/
   jifty/branches/js-refactor/t/TestApp-Plugin-JQuery/lib/TestApp/Plugin/JQuery/Model/
   jifty/branches/js-refactor/t/TestApp-Plugin-JQuery/log/
   jifty/branches/js-refactor/t/TestApp-Plugin-JQuery/share/
   jifty/branches/js-refactor/t/TestApp-Plugin-JQuery/share/po/
   jifty/branches/js-refactor/t/TestApp-Plugin-JQuery/share/web/
   jifty/branches/js-refactor/t/TestApp-Plugin-JQuery/share/web/static/
   jifty/branches/js-refactor/t/TestApp-Plugin-JQuery/share/web/templates/
   jifty/branches/js-refactor/t/TestApp-Plugin-JQuery/t/
   jifty/branches/js-refactor/t/TestApp-Plugin-JQuery/t/jquery.t
   jifty/branches/js-refactor/t/TestApp-Plugin-JQuery/var/
   jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/
   jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/Makefile.PL
   jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/bin/
   jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/bin/jifty   (contents, props changed)
   jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/doc/
   jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/etc/
   jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/etc/config.yml
   jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/lib/
   jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/lib/TestApp/
   jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/
   jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/
   jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Action/
   jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Dispatcher.pm
   jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Model/
   jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Model/User.pm
   jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Test.pm
   jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/View.pm
   jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/log/
   jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/share/
   jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/share/po/
   jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/share/web/
   jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/share/web/static/
   jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/share/web/templates/
   jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/t/
   jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/t/00-test-setup.t
   jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/t/01-basic.t
   jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/t/02-request-token.t
   jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/t/03-authorize.t
   jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/t/04-access-token.t
   jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/t/05-protected-resource.t
   jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/t/id_rsa
   jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/t/id_rsa.pub
   jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/var/
   jifty/branches/js-refactor/t/TestApp-Plugin-OnClick/
   jifty/branches/js-refactor/t/TestApp-Plugin-OnClick/Makefile.PL
   jifty/branches/js-refactor/t/TestApp-Plugin-OnClick/bin/
   jifty/branches/js-refactor/t/TestApp-Plugin-OnClick/bin/jifty   (contents, props changed)
   jifty/branches/js-refactor/t/TestApp-Plugin-OnClick/doc/
   jifty/branches/js-refactor/t/TestApp-Plugin-OnClick/etc/
   jifty/branches/js-refactor/t/TestApp-Plugin-OnClick/etc/config.yml
   jifty/branches/js-refactor/t/TestApp-Plugin-OnClick/lib/
   jifty/branches/js-refactor/t/TestApp-Plugin-OnClick/lib/TestApp/
   jifty/branches/js-refactor/t/TestApp-Plugin-OnClick/lib/TestApp/Plugin/
   jifty/branches/js-refactor/t/TestApp-Plugin-OnClick/lib/TestApp/Plugin/OnClick/
   jifty/branches/js-refactor/t/TestApp-Plugin-OnClick/log/
   jifty/branches/js-refactor/t/TestApp-Plugin-OnClick/share/
   jifty/branches/js-refactor/t/TestApp-Plugin-OnClick/share/po/
   jifty/branches/js-refactor/t/TestApp-Plugin-OnClick/share/web/
   jifty/branches/js-refactor/t/TestApp-Plugin-OnClick/share/web/static/
   jifty/branches/js-refactor/t/TestApp-Plugin-OnClick/share/web/static/js/
   jifty/branches/js-refactor/t/TestApp-Plugin-OnClick/share/web/templates/
   jifty/branches/js-refactor/t/TestApp-Plugin-OnClick/share/web/templates/content.html
   jifty/branches/js-refactor/t/TestApp-Plugin-OnClick/share/web/templates/content1.html
   jifty/branches/js-refactor/t/TestApp-Plugin-OnClick/share/web/templates/onclick.html
   jifty/branches/js-refactor/t/TestApp-Plugin-OnClick/t/
   jifty/branches/js-refactor/t/TestApp-Plugin-OnClick/t/onclick.t
   jifty/branches/js-refactor/t/TestApp-Plugin-OnClick/var/
   jifty/branches/js-refactor/t/TestApp-Plugin-SinglePage/
   jifty/branches/js-refactor/t/TestApp-Plugin-SinglePage/Makefile.PL
   jifty/branches/js-refactor/t/TestApp-Plugin-SinglePage/bin/
   jifty/branches/js-refactor/t/TestApp-Plugin-SinglePage/bin/jifty   (contents, props changed)
   jifty/branches/js-refactor/t/TestApp-Plugin-SinglePage/doc/
   jifty/branches/js-refactor/t/TestApp-Plugin-SinglePage/etc/
   jifty/branches/js-refactor/t/TestApp-Plugin-SinglePage/etc/config.yml
   jifty/branches/js-refactor/t/TestApp-Plugin-SinglePage/lib/
   jifty/branches/js-refactor/t/TestApp-Plugin-SinglePage/lib/TestApp/
   jifty/branches/js-refactor/t/TestApp-Plugin-SinglePage/lib/TestApp/Plugin/
   jifty/branches/js-refactor/t/TestApp-Plugin-SinglePage/lib/TestApp/Plugin/Model/
   jifty/branches/js-refactor/t/TestApp-Plugin-SinglePage/lib/TestApp/Plugin/SinglePage/
   jifty/branches/js-refactor/t/TestApp-Plugin-SinglePage/lib/TestApp/Plugin/SinglePage/Model/
   jifty/branches/js-refactor/t/TestApp-Plugin-SinglePage/lib/TestApp/Plugin/SinglePage/Model/User.pm
   jifty/branches/js-refactor/t/TestApp-Plugin-SinglePage/lib/TestApp/Plugin/SinglePage/View.pm
   jifty/branches/js-refactor/t/TestApp-Plugin-SinglePage/log/
   jifty/branches/js-refactor/t/TestApp-Plugin-SinglePage/share/
   jifty/branches/js-refactor/t/TestApp-Plugin-SinglePage/share/po/
   jifty/branches/js-refactor/t/TestApp-Plugin-SinglePage/share/web/
   jifty/branches/js-refactor/t/TestApp-Plugin-SinglePage/share/web/static/
   jifty/branches/js-refactor/t/TestApp-Plugin-SinglePage/share/web/templates/
   jifty/branches/js-refactor/t/TestApp-Plugin-SinglePage/t/
   jifty/branches/js-refactor/t/TestApp-Plugin-SinglePage/t/statevar.t
   jifty/branches/js-refactor/t/TestApp-Plugin-SinglePage/var/
   jifty/branches/js-refactor/t/TestApp/etc/
   jifty/branches/js-refactor/t/TestApp/etc/config.yml
   jifty/branches/js-refactor/t/TestApp/etc/site_config.yml
   jifty/branches/js-refactor/t/TestApp/lib/TestApp/Action/SayHi.pm
   jifty/branches/js-refactor/t/TestApp/lib/TestApp/Model/OtherThingy.pm
   jifty/branches/js-refactor/t/TestApp/lib/TestApp/Model/Thingy.pm
   jifty/branches/js-refactor/t/TestApp/share/web/templates/_elements/
   jifty/branches/js-refactor/t/TestApp/share/web/templates/_elements/wrapper
   jifty/branches/js-refactor/t/TestApp/t/18-test-www-declare.t
   jifty/branches/js-refactor/t/TestApp/t/19-rightsfrom.t
   jifty/branches/js-refactor/t/TestApp/t/config/
   jifty/branches/js-refactor/t/TestApp/t/config/01-basic.t
   jifty/branches/js-refactor/t/TestApp/t/config/02-individual.t
   jifty/branches/js-refactor/t/TestApp/t/config/02-individual.t-config.yml
   jifty/branches/js-refactor/t/TestApp/t/config/03-nosubtest.t
   jifty/branches/js-refactor/t/TestApp/t/config/test_config.yml
   jifty/branches/js-refactor/t/TestApp/t/crud.t
   jifty/branches/js-refactor/t/TestApp/t/test_config.yml
   jifty/branches/js-refactor/t/TestApp/t/use_mason_wrapper.t
Removed:
   jifty/branches/js-refactor/t/TestApp-Plugin-AppPluginHasModels/var/mason/
   jifty/branches/js-refactor/t/TestApp-Plugin-Chart/var/mason/
Modified:
   jifty/branches/js-refactor/   (props changed)
   jifty/branches/js-refactor/AUTHORS
   jifty/branches/js-refactor/Changelog
   jifty/branches/js-refactor/MANIFEST
   jifty/branches/js-refactor/META.yml
   jifty/branches/js-refactor/Makefile.PL
   jifty/branches/js-refactor/SIGNATURE
   jifty/branches/js-refactor/debian/changelog
   jifty/branches/js-refactor/debian/control
   jifty/branches/js-refactor/examples/HelloKitty/lib/HelloKitty/View.pm
   jifty/branches/js-refactor/lib/Jifty.pm
   jifty/branches/js-refactor/lib/Jifty/API.pm
   jifty/branches/js-refactor/lib/Jifty/Action.pm
   jifty/branches/js-refactor/lib/Jifty/Action/Autocomplete.pm
   jifty/branches/js-refactor/lib/Jifty/Action/Record.pm
   jifty/branches/js-refactor/lib/Jifty/Action/Record/Create.pm
   jifty/branches/js-refactor/lib/Jifty/Action/Record/Delete.pm
   jifty/branches/js-refactor/lib/Jifty/Action/Record/Search.pm
   jifty/branches/js-refactor/lib/Jifty/Action/Record/Update.pm
   jifty/branches/js-refactor/lib/Jifty/Action/Redirect.pm
   jifty/branches/js-refactor/lib/Jifty/Bootstrap.pm
   jifty/branches/js-refactor/lib/Jifty/ClassLoader.pm
   jifty/branches/js-refactor/lib/Jifty/Client.pm
   jifty/branches/js-refactor/lib/Jifty/Collection.pm
   jifty/branches/js-refactor/lib/Jifty/Config.pm
   jifty/branches/js-refactor/lib/Jifty/Continuation.pm
   jifty/branches/js-refactor/lib/Jifty/CurrentUser.pm
   jifty/branches/js-refactor/lib/Jifty/DateTime.pm
   jifty/branches/js-refactor/lib/Jifty/Dispatcher.pm
   jifty/branches/js-refactor/lib/Jifty/Everything.pm
   jifty/branches/js-refactor/lib/Jifty/Filter/DateTime.pm
   jifty/branches/js-refactor/lib/Jifty/Handle.pm
   jifty/branches/js-refactor/lib/Jifty/Handler.pm
   jifty/branches/js-refactor/lib/Jifty/I18N.pm
   jifty/branches/js-refactor/lib/Jifty/JSON.pm
   jifty/branches/js-refactor/lib/Jifty/LetMe.pm   (props changed)
   jifty/branches/js-refactor/lib/Jifty/Manual/Glossary.pod
   jifty/branches/js-refactor/lib/Jifty/Model/Session.pm
   jifty/branches/js-refactor/lib/Jifty/Module/Pluggable.pm
   jifty/branches/js-refactor/lib/Jifty/Notification.pm
   jifty/branches/js-refactor/lib/Jifty/Param/Schema.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/ActorMetadata/Mixin/Model/ActorMetadata.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/Password/Action/SendPasswordReminder.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/Password/Mixin/Model/User.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/Chart/   (props changed)
   jifty/branches/js-refactor/lib/Jifty/Plugin/Chart.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/Chart/Dispatcher.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/Chart/Renderer.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/Chart/Renderer/Chart.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/Chart/View.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/Chart/Web.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/CompressedCSSandJS.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/CompressedCSSandJS/Dispatcher.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/Feedback.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/Feedback/Action/SendFeedback.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/REST/Dispatcher.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/SinglePage.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/SkeletonApp/Dispatcher.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/SkeletonApp/View.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/TabView/View.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/UUID.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/UUID/Widget.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/Userpic/Dispatcher.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/Userpic/View.pm
   jifty/branches/js-refactor/lib/Jifty/Plugin/Userpic/Widget.pm
   jifty/branches/js-refactor/lib/Jifty/Record.pm
   jifty/branches/js-refactor/lib/Jifty/Request.pm
   jifty/branches/js-refactor/lib/Jifty/Request/Mapper.pm
   jifty/branches/js-refactor/lib/Jifty/RightsFrom.pm
   jifty/branches/js-refactor/lib/Jifty/Schema.pm
   jifty/branches/js-refactor/lib/Jifty/Script.pm
   jifty/branches/js-refactor/lib/Jifty/Script/App.pm
   jifty/branches/js-refactor/lib/Jifty/Script/FastCGI.pm
   jifty/branches/js-refactor/lib/Jifty/Script/Po.pm
   jifty/branches/js-refactor/lib/Jifty/Script/Schema.pm
   jifty/branches/js-refactor/lib/Jifty/Server/Prefork.pm
   jifty/branches/js-refactor/lib/Jifty/Test.pm
   jifty/branches/js-refactor/lib/Jifty/Test/WWW/Mechanize.pm
   jifty/branches/js-refactor/lib/Jifty/Util.pm
   jifty/branches/js-refactor/lib/Jifty/View/Declare.pm
   jifty/branches/js-refactor/lib/Jifty/View/Declare/BaseClass.pm
   jifty/branches/js-refactor/lib/Jifty/View/Declare/CRUD.pm
   jifty/branches/js-refactor/lib/Jifty/View/Declare/Compile.pm
   jifty/branches/js-refactor/lib/Jifty/View/Declare/Handler.pm
   jifty/branches/js-refactor/lib/Jifty/View/Declare/Helpers.pm
   jifty/branches/js-refactor/lib/Jifty/View/Declare/Page.pm
   jifty/branches/js-refactor/lib/Jifty/View/Mason/Handler.pm
   jifty/branches/js-refactor/lib/Jifty/View/Static/Handler.pm
   jifty/branches/js-refactor/lib/Jifty/Web.pm
   jifty/branches/js-refactor/lib/Jifty/Web/Form.pm
   jifty/branches/js-refactor/lib/Jifty/Web/Form/Clickable.pm
   jifty/branches/js-refactor/lib/Jifty/Web/Form/Element.pm
   jifty/branches/js-refactor/lib/Jifty/Web/Form/Field.pm
   jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Button.pm
   jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Checkbox.pm
   jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Collection.pm
   jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Combobox.pm
   jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Date.pm
   jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Hidden.pm
   jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Radio.pm
   jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/ResetButton.pm
   jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Select.pm
   jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Text.pm
   jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Textarea.pm
   jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Unrendered.pm
   jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Upload.pm
   jifty/branches/js-refactor/lib/Jifty/Web/Form/Link.pm
   jifty/branches/js-refactor/lib/Jifty/Web/Menu.pm
   jifty/branches/js-refactor/lib/Jifty/Web/PageRegion.pm
   jifty/branches/js-refactor/lib/Jifty/Web/Session.pm
   jifty/branches/js-refactor/share/plugins/Jifty/Plugin/GoogleMap/web/static/js/google_map.js
   jifty/branches/js-refactor/share/po/fr.po
   jifty/branches/js-refactor/share/po/ja.po
   jifty/branches/js-refactor/share/po/zh_cn.po
   jifty/branches/js-refactor/share/po/zh_tw.po
   jifty/branches/js-refactor/share/web/static/css/autocomplete.css
   jifty/branches/js-refactor/share/web/static/css/notices.css
   jifty/branches/js-refactor/share/web/static/js/calendar.js
   jifty/branches/js-refactor/share/web/static/js/jifty.js
   jifty/branches/js-refactor/share/web/static/js/prototype.js
   jifty/branches/js-refactor/share/web/static/js/scriptaculous/effects.js
   jifty/branches/js-refactor/share/web/templates/autohandler
   jifty/branches/js-refactor/t/13-sessions.t
   jifty/branches/js-refactor/t/99-pod-coverage.t
   jifty/branches/js-refactor/t/DateTime.t
   jifty/branches/js-refactor/t/TestApp-Plugin-Chart/lib/TestApp/Plugin/Chart/View.pm
   jifty/branches/js-refactor/t/TestApp-Plugin-Chart/t/chart.t
   jifty/branches/js-refactor/t/TestApp/lib/TestApp/Model/User.pm
   jifty/branches/js-refactor/t/TestApp/lib/TestApp/View.pm
   jifty/branches/js-refactor/t/TestApp/t/00-model-User.t
   jifty/branches/js-refactor/t/TestApp/t/11-current_user.t
   jifty/branches/js-refactor/t/TestApp/t/before_access.t
   jifty/branches/js-refactor/t/clientside/td.t
   jifty/branches/js-refactor/t/lib/Jifty/SubTest.pm

Log:
Mergedown from trunk 
 
 r65010 at pinglin (orig r3747):  sterling | 2007-07-31 22:32:37 -0400
 
 r65011 at pinglin (orig r3748):  sterling | 2007-07-31 22:32:48 -0400
  r8268 at dynpc145:  andrew | 2007-07-31 21:28:14 -0500
  Added a hack to chart.t (forcing an early load of GD) to avoid the segfault that was causing it to fail. Removed the TODO block from the test.
 
 r65012 at pinglin (orig r3749):  sterling | 2007-07-31 22:33:01 -0400
  r8269 at dynpc145:  andrew | 2007-07-31 21:29:42 -0500
  Fixed the way arguments are passed to the render() method in Jifty::Plugin::Chart::Web.
 
 r65013 at pinglin (orig r3750):  sterling | 2007-07-31 22:33:09 -0400
  r8270 at dynpc145:  andrew | 2007-07-31 21:31:01 -0500
  Moved the chart/* dispatch to chart/chart/* to make room for alternate charting mechanisms.
 
 r65014 at pinglin (orig r3751):  sterling | 2007-07-31 22:33:17 -0400
  r8271 at dynpc145:  andrew | 2007-07-31 21:31:21 -0500
  Added a renderer for GD::Graph
 
 r65015 at pinglin (orig r3752):  sterling | 2007-07-31 22:33:32 -0400
  r8272 at dynpc145:  andrew | 2007-07-31 21:31:41 -0500
  Updated the module recommendations for the Chart plugin.
 
 r65016 at pinglin (orig r3753):  sterling | 2007-07-31 22:47:45 -0400
  r8289 at dynpc145:  andrew | 2007-07-31 21:42:52 -0500
  Updated POD and removed an unnecessary extra subroutine call.
 
 r65017 at pinglin (orig r3754):  sterling | 2007-07-31 22:47:56 -0400
  r8290 at dynpc145:  andrew | 2007-07-31 21:44:59 -0500
  Fixed POD coverage issue.
 
 r65018 at pinglin (orig r3755):  sterling | 2007-07-31 22:48:02 -0400
  r8291 at dynpc145:  andrew | 2007-07-31 21:47:05 -0500
  Fixed an eensy POD bug.
 
 r65019 at pinglin (orig r3756):  sterling | 2007-07-31 23:03:26 -0400
  r8296 at dynpc145:  andrew | 2007-07-31 22:03:08 -0500
  Made the chart plugin test smarter and added one for the GD::Graph renderer.
 
 r65020 at pinglin (orig r3757):  trs | 2007-07-31 23:41:50 -0400
  r25774 at zot:  tom | 2007-07-31 23:40:12 -0400
  Basic PlotKit renderer for Chart plugin
 
 r65021 at pinglin (orig r3758):  trs | 2007-08-01 02:49:40 -0400
  r25776 at zot:  tom | 2007-08-01 02:49:29 -0400
  - Uncomment neccessary require
  - Make sure to handle undefined stuff
 
 r65038 at pinglin (orig r3760):  jesse | 2007-08-01 15:15:14 -0400
 
 r65039 at pinglin (orig r3761):  jesse | 2007-08-01 15:15:32 -0400
  r65037 at pinglin:  jesse | 2007-08-01 15:11:00 -0400
   * clean up load_by_kv
 
 r65074 at pinglin (orig r3762):  sartak | 2007-08-01 15:31:06 -0400
  r29755 at caladan:  sartak | 2007-08-01 15:30:21 -0400
  Add failing tests for load_by_kv with chr(0)
 
 r65075 at pinglin (orig r3763):  trs | 2007-08-01 22:33:42 -0400
  r25802 at zot:  tom | 2007-08-01 22:33:03 -0400
  *Very* custom packed PlotKit (from svn) that no longer depends on MochiKit exporting functions into the global namespace
  
  Still need to solve the issue of why MochiKit blows up when included in our honkin' JS file...
 
 r65076 at pinglin (orig r3764):  trs | 2007-08-02 02:28:00 -0400
  r25809 at zot:  tom | 2007-08-02 02:25:34 -0400
  Don't mess with the data structure if it's already what plotkit expects
 
 r65077 at pinglin (orig r3765):  sterling | 2007-08-02 09:27:53 -0400
  r8303 at dynpc145:  andrew | 2007-08-02 08:26:56 -0500
  Add explicit hide/show for the error, warning, and canonicalization note divs. This solves some long time ugliness on IE. However, it is not perfect as I am still getting some rendering issues on IE. Does not seem to break anything on FF.
 
 r65173 at pinglin (orig r3767):  sterling | 2007-08-03 12:10:43 -0400
  r8325 at riddle:  andrew | 2007-08-03 11:09:49 -0500
  Making the IMG-based chart renderers capable of handling CSS styling with some added behaviour.
 
 r65174 at pinglin (orig r3768):  sterling | 2007-08-03 12:40:37 -0400
  r8328 at riddle:  andrew | 2007-08-03 11:40:04 -0500
  Added a renderer parameter to the chart() method and add per-renderer initialization.
 
 r65187 at pinglin (orig r3781):  sterling | 2007-08-03 17:31:50 -0400
  r8333 at riddle:  andrew | 2007-08-03 16:30:37 -0500
  Fix failing dependency test because Chart uses GD to fix testing.
 
 r65188 at pinglin (orig r3782):  sterling | 2007-08-03 17:32:01 -0400
  r8334 at riddle:  andrew | 2007-08-03 16:31:13 -0500
  Updated the behaviour script used by IMG chart renderers to make it more URI aware.
 
 r65193 at pinglin (orig r3787):  sterling | 2007-08-04 18:30:52 -0400
  r8351 at dynpc145:  andrew | 2007-08-04 16:13:23 -0500
  Standardizing the chart types across the three current renderers.
 
 r65194 at pinglin (orig r3788):  sterling | 2007-08-04 18:31:00 -0400
  r8370 at dynpc145:  andrew | 2007-08-04 17:29:36 -0500
   r8356 at dynpc145:  andrew | 2007-08-04 16:33:13 -0500
   Added better error handling on renderer require.
  
 
 r65195 at pinglin (orig r3789):  sterling | 2007-08-04 18:31:06 -0400
  r8371 at dynpc145:  andrew | 2007-08-04 17:30:09 -0500
   r8357 at dynpc145:  andrew | 2007-08-04 16:33:37 -0500
   Fixed typos in the new type handling code and fixed error handling.
  
 
 r65196 at pinglin (orig r3790):  sterling | 2007-08-04 18:31:18 -0400
  r8372 at dynpc145:  andrew | 2007-08-04 17:30:09 -0500
   r8358 at dynpc145:  andrew | 2007-08-04 16:46:12 -0500
   Fixing my previous brain damage and additional typos.
  
 
 r65197 at pinglin (orig r3791):  sterling | 2007-08-04 18:31:26 -0400
  r8373 at dynpc145:  andrew | 2007-08-04 17:30:10 -0500
   r8359 at dynpc145:  andrew | 2007-08-04 16:53:49 -0500
   Improved the way the DIV tag is generated for PlotKit.
  
 
 r65198 at pinglin (orig r3792):  sterling | 2007-08-04 18:31:35 -0400
  r8374 at dynpc145:  andrew | 2007-08-04 17:30:10 -0500
   r8360 at dynpc145:  andrew | 2007-08-04 16:59:22 -0500
   Removed some redundant code from the PlotKit renderer and added support for options to the GD::Graph and Chart renderers.
  
 
 r65200 at pinglin (orig r3794):  sterling | 2007-08-05 17:24:28 -0400
  r8386 at dynpc145:  andrew | 2007-08-05 16:23:50 -0500
  Adding the SimpleBars renderer as a decent, dead-simple HTML-based renderer for HorizontalBars and a prototype for using tables for client-side chart configuration.
 
 r65201 at pinglin (orig r3795):  sterling | 2007-08-05 19:07:44 -0400
  r8392 at dynpc145:  andrew | 2007-08-05 18:06:59 -0500
   r8388 at dynpc145:  andrew | 2007-08-05 17:19:52 -0500
   Make sure we do not attempt to render 0 pixel values no matter what.
  
 
 r65202 at pinglin (orig r3796):  trs | 2007-08-05 20:34:44 -0400
  r25915 at zot:  tom | 2007-08-05 20:34:08 -0400
  Render onAvailable instead of on window load so that we work in regions
 
 r65203 at pinglin (orig r3797):  trs | 2007-08-05 21:35:49 -0400
  r25919 at zot:  tom | 2007-08-05 21:35:39 -0400
  Use PlotKit.Base.map explicitly
 
 r65840 at pinglin (orig r3820):  trs | 2007-08-06 17:18:23 -0400
  r25943 at zot:  tom | 2007-08-06 17:17:51 -0400
  Without the conditional we'll hide errors as soon as we display them
 
 r65841 at pinglin (orig r3821):  trs | 2007-08-06 19:38:53 -0400
  r25961 at zot:  tom | 2007-08-06 19:38:14 -0400
  Fix view CRUD template's method of getting the record
 
 r65842 at pinglin (orig r3822):  trs | 2007-08-08 00:54:59 -0400
  r25997 at zot:  tom | 2007-08-08 00:54:25 -0400
  Bunch of updates to the chart plugin
  
      - Refactored dispatcher
      - Added XML SWF renderer
      - Renderers are now passed the configuration hash when init'd
  
 
 r65843 at pinglin (orig r3823):  trs | 2007-08-08 01:01:40 -0400
  r25999 at zot:  tom | 2007-08-08 01:01:30 -0400
  - Treat the width and height appropriately
  - Add the XML::Simple dep
 
 r65844 at pinglin (orig r3824):  trs | 2007-08-08 01:12:55 -0400
  r26001 at zot:  tom | 2007-08-08 01:12:44 -0400
  Don't specify an align attribute
 
 r65845 at pinglin (orig r3825):  trs | 2007-08-08 01:46:05 -0400
  r26005 at zot:  tom | 2007-08-08 01:45:55 -0400
  Whoops.  Forgot to check in the actual XML SWF library.  This is version 4.6.
 
 r65857 at pinglin (orig r3837):  agentz | 2007-08-10 05:09:35 -0400
  r1254 at agentz-office:  agentz | 2007-08-10 17:08:14 +0800
  Makefile.PL - do NOT skip any tests under t/ unintentionally ;)
 
 r65858 at pinglin (orig r3838):  clkao | 2007-08-10 05:27:34 -0400
 Correct a crud component path.
 r65859 at pinglin (orig r3839):  clkao | 2007-08-10 05:42:40 -0400
 In action argument creation from model, do not assume refers_to
 always want a select based on id which we might not be referring to.
 Allow user to override render_as for refers_to columns.
 
 r65862 at pinglin (orig r3842):  sterling | 2007-08-10 09:57:59 -0400
  r8486 at riddle:  andrew | 2007-08-10 08:57:04 -0500
  Removing extra comma from JavaScript list.
 
 r65863 at pinglin (orig r3843):  sterling | 2007-08-10 10:19:16 -0400
  r8491 at riddle:  andrew | 2007-08-10 09:18:27 -0500
  Chart plugin configuration updates:
   * Deprecating the renderer option.
   * Adding the DefaultRenderer option to replace renderer.
   * Adding the PreloadRenderers option to allow additional renderers to be preloaded.
   * Updated the documentation.
   * Made sure that the configuration is always passed to the renderer constructor, even if they are loaded late.
 
 r65864 at pinglin (orig r3844):  sartak | 2007-08-10 12:20:53 -0400
  r30163 at caladan:  sartak | 2007-08-10 12:20:01 -0400
  todo-ify failing tests in t/13-sessions.t
 
 r65865 at pinglin (orig r3845):  sterling | 2007-08-10 12:26:54 -0400
  r8499 at riddle:  andrew | 2007-08-10 11:26:05 -0500
  Fixing the chart plugin tests to match up with changes that have happened to the API since they were written.
 
 r65866 at pinglin (orig r3846):  sterling | 2007-08-10 16:50:53 -0400
  r8506 at riddle:  andrew | 2007-08-10 15:49:46 -0500
  Fix to a JavaScript bug performing the rerendering of IMG graphs.
 
 r65868 at pinglin (orig r3848):  clkao | 2007-08-11 03:48:56 -0400
 Follow up to r3839, do not change the logic if refers_to is not jifty::record
 r65869 at pinglin (orig r3849):  agentz | 2007-08-11 07:54:48 -0400
  r1257 at agentz-office:  agentz | 2007-08-10 19:23:18 +0800
  fixed Jifty::View::Declare::Helpers since we now install tag subs directly to the target package instead of using @EXPORT. We now makes use of T::D::Tags's @TagSubs struct
 
 r65870 at pinglin (orig r3850):  agentz | 2007-08-11 07:57:41 -0400
  r1268 at agentz-office:  agentz | 2007-08-11 19:54:25 +0800
  made jifty trunk work with earlier versions of TD
 
 r65871 at pinglin (orig r3851):  agentz | 2007-08-11 08:01:07 -0400
  r1272 at agentz-office:  agentz | 2007-08-11 20:00:22 +0800
  'svk push' sometimes gives me trouble...and i dunno why...
 
 r65883 at pinglin (orig r3858):  sterling | 2007-08-12 16:23:46 -0400
  r8534 at riddle:  andrew | 2007-08-12 15:13:21 -0500
  Improving some of the Pod, code comments, and minor perl tidying.
 
 r65884 at pinglin (orig r3859):  sterling | 2007-08-12 16:24:45 -0400
 
 r65885 at pinglin (orig r3860):  sterling | 2007-08-12 16:31:45 -0400
  r8541 at riddle:  andrew | 2007-08-12 15:31:19 -0500
  Fixing error that occurred during previous push.
 
 r65886 at pinglin (orig r3861):  sterling | 2007-08-12 18:13:31 -0400
  r8577 at riddle:  andrew | 2007-08-12 17:12:34 -0500
  Fixing POD issues in CRUD, adding some code comments, and minor perl tidying.
 
 r65887 at pinglin (orig r3862):  sterling | 2007-08-12 18:32:08 -0400
  r8579 at riddle:  andrew | 2007-08-12 17:31:33 -0500
  Additional fix to POD coverage for the CRUDView attribute.
 
 r65888 at pinglin (orig r3863):  sterling | 2007-08-12 19:01:50 -0400
  r8581 at riddle:  andrew | 2007-08-12 18:01:14 -0500
  Added more POD to fix coverage issues and added a description to the client_cacheable and client_cache_content methods of PageRegion.
 
 r65902 at pinglin (orig r3864):  sterling | 2007-08-12 21:17:57 -0400
  r8583 at riddle:  andrew | 2007-08-12 20:17:16 -0500
  Improved code comments and minor perl tidy.
 
 r65910 at pinglin (orig r3872):  agentz | 2007-08-13 02:18:39 -0400
  r1287 at agentz-office:  agentz | 2007-08-13 14:18:06 +0800
  trivial edits
 
 r65911 at pinglin (orig r3873):  agentz | 2007-08-13 02:34:11 -0400
  r1297 at agentz-office:  agentz | 2007-08-13 14:33:35 +0800
  Makefile.PL - Jifty now depends on TD 0.26
 
 r66123 at pinglin (orig r3877):  sterling | 2007-08-13 21:54:57 -0400
  r8534 at dynpc145:  andrew | 2007-08-12 15:13:21 -0500
  Improving some of the Pod, code comments, and minor perl tidying.
 
 r66124 at pinglin (orig r3878):  sterling | 2007-08-13 21:56:46 -0400
 
 r66125 at pinglin (orig r3879):  sterling | 2007-08-13 21:56:52 -0400
  r8541 at dynpc145:  andrew | 2007-08-12 15:31:19 -0500
  Fixing error that occurred during previous push.
 
 r66126 at pinglin (orig r3880):  sterling | 2007-08-13 21:58:40 -0400
  r8577 at dynpc145:  andrew | 2007-08-12 17:12:34 -0500
  Fixing POD issues in CRUD, adding some code comments, and minor perl tidying.
 
 r66127 at pinglin (orig r3881):  sterling | 2007-08-13 21:58:53 -0400
  r8579 at dynpc145:  andrew | 2007-08-12 17:31:33 -0500
  Additional fix to POD coverage for the CRUDView attribute.
 
 r66128 at pinglin (orig r3882):  sterling | 2007-08-13 21:59:04 -0400
  r8581 at dynpc145:  andrew | 2007-08-12 18:01:14 -0500
  Added more POD to fix coverage issues and added a description to the client_cacheable and client_cache_content methods of PageRegion.
 
 r66129 at pinglin (orig r3883):  sterling | 2007-08-13 21:59:16 -0400
  r8583 at dynpc145:  andrew | 2007-08-12 20:17:16 -0500
  Improved code comments and minor perl tidy.
 
 r66130 at pinglin (orig r3884):  sterling | 2007-08-13 21:59:32 -0400
  r8619 at dynpc145:  andrew | 2007-08-13 19:51:58 -0500
  Cleaning up the Pod for Jifty::Bootstrap
 
 r66131 at pinglin (orig r3885):  sterling | 2007-08-13 21:59:57 -0400
  r8620 at dynpc145:  andrew | 2007-08-13 20:51:09 -0500
  Major Pod improvement to the class loader, many more helpful code comments, and some tidying for the class loader. Phew.
 
 r66132 at pinglin (orig r3886):  sterling | 2007-08-13 22:16:58 -0400
  r8630 at dynpc145:  andrew | 2007-08-13 21:16:20 -0500
  Updating Pod and adding code comments to Jifty::Client.
 
 r66133 at pinglin (orig r3887):  efunneko | 2007-08-13 22:38:51 -0400
 Added myself to AUTHORS
 r66136 at pinglin (orig r3890):  sterling | 2007-08-13 22:46:42 -0400
  r8635 at dynpc145:  andrew | 2007-08-13 21:46:18 -0500
  Cleaning up the Pod and adding a few code comments to Jifty::Collection.
 
 r66140 at pinglin (orig r3894):  sterling | 2007-08-13 23:49:27 -0400
  r8641 at dynpc145:  andrew | 2007-08-13 22:49:12 -0500
  Updating Pod and source comments for Jifty::Config. Performed some Perl tidying and added a new section describing why Jifty uses three levels of configuration files (may need additional editting).
 
 r66145 at pinglin (orig r3899):  sterling | 2007-08-14 08:48:29 -0400
  r8648 at dynpc145:  andrew | 2007-08-14 07:48:11 -0500
  Fixing POD test failure.
 
 r66146 at pinglin (orig r3900):  efunneko | 2007-08-14 13:16:43 -0400
 Added support for more javascript triggers for most form elements:  onclick onchange ondblclick onmousedown onmouseup onmouseover onmousemove onmouseout onfocus onblur onkeypress onkeydown onkeyup onselect
 r66149 at pinglin (orig r3903):  efunneko | 2007-08-14 20:16:51 -0400
 Removed some duplicated code
 r66152 at pinglin (orig r3906):  trs | 2007-08-15 23:02:54 -0400
  r26160 at zot:  tom | 2007-08-15 23:02:16 -0400
  Give the browser (particularly Safari) some more "settle time"
 
 r66159 at pinglin (orig r3913):  sterling | 2007-08-16 23:08:51 -0400
  r8678 at dynpc145:  andrew | 2007-08-14 19:26:11 -0500
  Adding tangent and return to the glossary.
 
 r66160 at pinglin (orig r3914):  sterling | 2007-08-16 23:09:08 -0400
  r8695 at dynpc145:  andrew | 2007-08-16 22:07:42 -0500
  Fixes to use_mason_wrapper():
   * Added a test to make sure use_mason_wrapper() works.
   * Added $jifty_internal_request to note whether a Mason request is internal or not.
   * Altered the autohandler to use $jifty_internal_request when blocking access to /_elements/
   * Fixed error handling in the autohandler to redirect to /__jifty/error/requested_private_component rather than /errors/requested_private_component
 
 r66161 at pinglin (orig r3915):  sterling | 2007-08-19 09:39:34 -0400
  r8812 at riddle:  andrew | 2007-08-19 08:38:43 -0500
  Added a new helper, new_record_action(), that wraps new_action() with additional help creating Create, Update, Delete, and Search actions for models.
 
 r66162 at pinglin (orig r3916):  sterling | 2007-08-20 13:09:31 -0400
  r8814 at riddle:  andrew | 2007-08-20 12:08:34 -0500
  Adding a plugin for using the jQuery Javascript library with Jifty.
 
 r66165 at pinglin (orig r3919):  sterling | 2007-08-20 16:55:02 -0400
  r8818 at riddle:  andrew | 2007-08-20 15:29:01 -0500
  Adding documentation for Action and Static attributes to resolve POD coverage test failures.
 
 r66166 at pinglin (orig r3920):  sterling | 2007-08-20 16:55:08 -0400
  r8819 at riddle:  andrew | 2007-08-20 15:38:38 -0500
  Cleaning up documentation to fix pod coverage test failures.
 
 r66167 at pinglin (orig r3921):  sterling | 2007-08-20 16:55:14 -0400
  r8820 at riddle:  andrew | 2007-08-20 15:54:10 -0500
  Fixing pod coverage problems with the jQuery plugin.
 
 r66168 at pinglin (orig r3922):  sterling | 2007-08-20 16:57:37 -0400
  r8824 at riddle:  andrew | 2007-08-20 15:57:13 -0500
  Undoing previous accidental patch to Jifty::Continuation because I have not finished proofing it.
 
 r66171 at pinglin (orig r3924):  jesse | 2007-08-20 19:17:48 -0400
  r65916 at 000-176-581:  jesse | 2007-08-13 16:49:12 -0400
  * POD fixes 
 
 r66172 at pinglin (orig r3925):  jesse | 2007-08-20 19:18:11 -0400
  r65917 at 000-176-581:  jesse | 2007-08-13 17:08:59 -0400
  * The image column isn't at all baked. it was a bad fork of userpic
 
 r66197 at pinglin (orig r3926):  yves | 2007-08-21 10:49:01 -0400
 debian packaging
 
 r66519 at pinglin (orig r3929):  clkao | 2007-08-21 22:03:01 -0400
 require Class::Trigger 0.12 for abortable triggers used by css/js compression plugin.
 r66520 at pinglin (orig r3930):  clkao | 2007-08-21 22:12:04 -0400
 GD is not mandatory requirement.  Skip chart test if not found.
 r66521 at pinglin (orig r3931):  clkao | 2007-08-21 22:13:42 -0400
 var/mason shouldn't be here
 r66522 at pinglin (orig r3932):  clkao | 2007-08-21 22:17:35 -0400
 ditto for chart.t
 r66523 at pinglin (orig r3933):  efunneko | 2007-08-22 10:42:15 -0400
 Fixed a problem where some disabled elements weren't re-enabled after a submission when the disabled elements were outside of the fragment being refreshed.
 r66524 at pinglin (orig r3934):  audreyt | 2007-08-22 11:03:57 -0400
 * Jifty::View::Declare::CRUD - I18N.
 r66525 at pinglin (orig r3935):  audreyt | 2007-08-22 11:06:44 -0400
 * Jifty::View::Declare - Work around Perl 5.9.5 bug by avoid punning
   the constant name BaseClass with the subclass name ::BaseClass.
 r66526 at pinglin (orig r3936):  clkao | 2007-08-22 11:22:40 -0400
 properly skip client side td test if new B is not found.
 r66527 at pinglin (orig r3937):  audreyt | 2007-08-22 11:38:30 -0400
 * Jifty::Plugin::SkeletonApp::Dispatcher - 
   Do not override the 'Home' menu item if the app had set it already.
 r66528 at pinglin (orig r3938):  sartak | 2007-08-22 11:56:46 -0400
  r30488 at caladan:  sartak | 2007-08-22 11:55:55 -0400
  Add friendly_date method to Jifty::DateTime which special-cases yesterday/today/tomorrow
 
 r66530 at pinglin (orig r3940):  clkao | 2007-08-23 04:40:04 -0400
 Jifty.Subs needs outs_raw.
 r66531 at pinglin (orig r3941):  clkao | 2007-08-23 10:31:08 -0400
 respect initial PATH in env under fastcgi.
 r66532 at pinglin (orig r3942):  clkao | 2007-08-23 10:32:51 -0400
 bad mason dir.
 r66533 at pinglin (orig r3943):  clkao | 2007-08-23 10:40:57 -0400
 don't use pager to determine items as list_items might be called
 directly and no pager has been set on the collection.
 
 r66534 at pinglin (orig r3944):  clkao | 2007-08-23 10:42:20 -0400
 update tabview plugin to use new relative path syntax for td show().
 r66538 at pinglin (orig r3945):  jesse | 2007-08-23 12:47:40 -0400
  r66536 at pinglin:  jesse | 2007-08-23 12:19:20 -0400
  * Minor pod additions. removing unneeded wrappers
 
 r66541 at pinglin (orig r3946):  jesse | 2007-08-23 14:02:19 -0400
  r66539 at pinglin:  jesse | 2007-08-23 12:58:28 -0400
  * WWW::Mechanize versions before 1.30 had broken gzip behaviour which broke tests
 
 r66542 at pinglin (orig r3947):  jesse | 2007-08-23 14:02:33 -0400
  r66540 at pinglin:  jesse | 2007-08-23 13:56:06 -0400
  * Remove cargo-culting changing of $0 from Jifty::SubTest. It doesn't affect test output but does break things
 
 r66544 at pinglin (orig r3948):  sunnavy | 2007-08-23 22:37:49 -0400
 more translations in zh_cn.po
 r66545 at pinglin (orig r3949):  sunnavy | 2007-08-23 23:44:24 -0400
 changed View.pm in HelloKitty in accordance with current Jifty libs
 r66546 at pinglin (orig r3950):  jesse | 2007-08-23 23:49:09 -0400
  r66543 at pinglin:  jesse | 2007-08-23 23:48:47 -0400
  * New changelog generation tool
 
 r66557 at pinglin (orig r3954):  jesse | 2007-08-24 00:19:23 -0400
  r66553 at pinglin:  jesse | 2007-08-24 00:17:00 -0400
   *releng
 
 r66558 at pinglin (orig r3955):  jesse | 2007-08-24 00:19:47 -0400
  r66555 at pinglin:  jesse | 2007-08-24 00:17:26 -0400
   *signature
 
 r66578 at pinglin (orig r3958):  ishigaki | 2007-08-24 01:28:00 -0400
 t/TestApp-Plugin-JQuery/etc/config.yml: colons in the database name is not allowed on win32
 r66579 at pinglin (orig r3959):  agentz | 2007-08-24 01:31:15 -0400
  r1340 at agentz-office:  agentz | 2007-08-24 13:29:53 +0800
  updated View/Declare/Helpers.pm to use @Template::Declare::Tags::TAG_SUB_LIST instead of TagSubs
 
 r66594 at pinglin (orig r3974):  clkao | 2007-08-24 17:47:08 -0400
 In td's page { foo => 'bar', title => 'blah' } content {} style,
 pass the metadata to the content code as the second argument.
 
 r66595 at pinglin (orig r3975):  clkao | 2007-08-24 17:48:21 -0400
 Make spa support "normal" form_submit of a form with submit_to
 that has no action.
 
 r66598 at pinglin (orig r3978):  clkao | 2007-08-25 05:12:30 -0400
 Don't deal with empty submit_to.
 r66599 at pinglin (orig r3979):  clkao | 2007-08-25 06:07:47 -0400
 actually we want to use sp_submit whenever we have submit_to and
 no onclick.
 r66600 at pinglin (orig r3980):  clkao | 2007-08-25 06:10:10 -0400
 pesky mason files.
 r66601 at pinglin (orig r3981):  clkao | 2007-08-25 17:27:57 -0400
 minor cleanups to the tabview plugin.
 r66602 at pinglin (orig r3982):  clkao | 2007-08-26 06:21:03 -0400
 Allow a more flexible way to use tabview, such using custom labels.
 
 r66603 at pinglin (orig r3983):  clkao | 2007-08-26 06:35:36 -0400
 tabview: make name and path optional.
 r66604 at pinglin (orig r3984):  sterling | 2007-08-26 17:28:29 -0400
  r8648 at riddle:  andrew | 2007-08-14 07:48:11 -0500
  Fixing POD test failure.
 
 r66605 at pinglin (orig r3985):  sterling | 2007-08-26 17:29:11 -0400
  r8695 at riddle:  andrew | 2007-08-16 22:07:42 -0500
  Fixes to use_mason_wrapper():
   * Added a test to make sure use_mason_wrapper() works.
   * Added $jifty_internal_request to note whether a Mason request is internal or not.
   * Altered the autohandler to use $jifty_internal_request when blocking access to /_elements/
   * Fixed error handling in the autohandler to redirect to /__jifty/error/requested_private_component rather than /errors/requested_private_component
 
 r66606 at pinglin (orig r3986):  sterling | 2007-08-26 17:29:17 -0400
  r8812 at riddle:  andrew | 2007-08-19 08:38:43 -0500
  Added a new helper, new_record_action(), that wraps new_action() with additional help creating Create, Update, Delete, and Search actions for models.
 
 r66607 at pinglin (orig r3987):  sterling | 2007-08-26 17:31:29 -0400
  r8814 at riddle:  andrew | 2007-08-20 12:08:34 -0500
  Adding a plugin for using the jQuery Javascript library with Jifty.
 
 r66608 at pinglin (orig r3988):  sterling | 2007-08-26 17:31:36 -0400
  r8818 at riddle:  andrew | 2007-08-20 15:29:01 -0500
  Adding documentation for Action and Static attributes to resolve POD coverage test failures.
 
 r66609 at pinglin (orig r3989):  sterling | 2007-08-26 17:33:35 -0400
  r8819 at riddle:  andrew | 2007-08-20 15:38:38 -0500
  Cleaning up documentation to fix pod coverage test failures.
 
 r66610 at pinglin (orig r3990):  sterling | 2007-08-26 17:33:44 -0400
  r8820 at riddle:  andrew | 2007-08-20 15:54:10 -0500
  Fixing pod coverage problems with the jQuery plugin.
 
 r66611 at pinglin (orig r3991):  sterling | 2007-08-26 17:34:04 -0400
  r8824 at riddle:  andrew | 2007-08-20 15:57:13 -0500
  Undoing previous accidental patch to Jifty::Continuation because I have not finished proofing it.
 
 r66612 at pinglin (orig r3992):  sterling | 2007-08-26 17:34:24 -0400
  r8977 at riddle:  andrew | 2007-08-26 16:28:08 -0500
  Removing Test::Log4perl. It isn't used anywhere anymore.
 
 r66613 at pinglin (orig r3993):  sterling | 2007-08-26 17:39:45 -0400
  r8988 at riddle:  andrew | 2007-08-26 16:39:26 -0500
  Reverting the addition of these files which were added by a push error.
 
 r66950 at pinglin (orig r4017):  sterling | 2007-08-30 22:26:52 -0400
  r11089 at riddle:  andrew | 2007-08-30 21:26:36 -0500
  Making Jifty::Web::Menu more extensible.
 
 r66951 at pinglin (orig r4018):  sartak | 2007-08-31 14:51:04 -0400
  r40422 at onn:  sartak | 2007-08-31 14:50:15 -0400
  Add (grr, passing) tests for setting columns in the test app
 
 r66952 at pinglin (orig r4019):  audreyt | 2007-08-31 17:21:56 -0400
 * Jifty::Notification - Minor doc fix - The html_body method was
   misspelled as html-body.
 r66953 at pinglin (orig r4020):  sartak | 2007-08-31 17:30:44 -0400
  r40432 at onn:  sartak | 2007-08-31 17:30:24 -0400
  In Jifty::DateTime->new, set the timezone to floating if it looks like just a date
 
 r66954 at pinglin (orig r4021):  audreyt | 2007-08-31 17:36:37 -0400
 * SendFeedback.pm: Take away the phrase "hiveminder" from the documentation.
 r66960 at pinglin (orig r4027):  clkao | 2007-09-01 15:12:49 -0400
 spa: don't translate javascript: links.
 
 r66961 at pinglin (orig r4028):  clkao | 2007-09-01 15:15:52 -0400
 tabview: fix the logic for defered tabs.
 
 r66962 at pinglin (orig r4029):  clkao | 2007-09-02 16:11:58 -0400
 Name the first Jifty->new during test 'pre_init', and tell plugins
 not to register triggers just yet to avoid duplicated triggers.
 
 r66963 at pinglin (orig r4030):  clkao | 2007-09-02 16:35:45 -0400
 Change how plugin is initialized so _pre_init is actually
 noted.
 
 r66964 at pinglin (orig r4031):  clkao | 2007-09-02 18:32:52 -0400
 First cut of Selenium testing support in Jifty.
 
 r66965 at pinglin (orig r4032):  ruz | 2007-09-02 19:47:58 -0400
 * don't gen a collection class if there is no model
 r66968 at pinglin (orig r4035):  sterling | 2007-09-02 23:18:41 -0400
  r11133 at dynpc145:  andrew | 2007-09-02 22:17:41 -0500
  Additional selector to make sure that autocomplete hides .hidden_value on .inline forms.
 
 r66971 at pinglin (orig r4038):  clkao | 2007-09-03 16:30:09 -0400
 my computer is slow. be more patient about selenium rc startup.
 r66972 at pinglin (orig r4039):  ruz | 2007-09-03 16:55:55 -0400
 * collection generator may be called when we're loading models
   and not all models have been loaded to the moment, so we have
   to try to require the model before testing if it esists.
 r66973 at pinglin (orig r4040):  ishigaki | 2007-09-04 07:33:00 -0400
 Jifty::DBI now  uses Class::Trigger instead of Jifty::DBi::Class::Trigger (since rev3968)
 r66974 at pinglin (orig r4041):  ishigaki | 2007-09-04 07:48:01 -0400
 added Test::Log4perl dep again; it is surely used at t/TestApp/t/02-dispatch-show-rule-in-wrong-ruleset.t
 r66976 at pinglin (orig r4043):  falcone | 2007-09-04 15:13:10 -0400
  r24400 at ketch:  falcone | 2007-09-04 15:10:44 -0400
  * revert changes to ClassLoader.  Collection classes are failing to be generated
    in some TD views
 
 r66978 at pinglin (orig r4045):  sterling | 2007-09-04 18:36:53 -0400
  r11186 at riddle:  andrew | 2007-09-04 17:35:47 -0500
  Added requirement for URI 1.31, which adds uri_escape_utf8().
 
 r67019 at pinglin (orig r4048):  jesse | 2007-09-06 14:51:56 -0400
  r66786 at pinglin:  jesse | 2007-08-30 20:05:58 -0400
   * we want to compute UUIDs every time. really. it's not useful to always have the same uuid
 
 r67034 at pinglin (orig r4052):  clkao | 2007-09-07 06:03:33 -0400
 default not to preserve state when spa is replacing __page
 r67035 at pinglin (orig r4053):  clkao | 2007-09-07 06:21:33 -0400
 Do not do clever & broken input_name for buttons when preserve_state is false.
 r67040 at pinglin (orig r4058):  sartak | 2007-09-07 11:40:03 -0400
  r42421 at onn:  sartak | 2007-09-07 11:39:31 -0400
  Revert clkao's fixes for now because they break apps :/
 
 r67041 at pinglin (orig r4059):  jesse | 2007-09-07 16:08:37 -0400
  r67030 at pinglin:  jesse | 2007-09-07 16:07:47 -0400
  * Clean up Jifty::Web initialization a bit.
 
 r67070 at pinglin (orig r4060):  clkao | 2007-09-07 18:19:30 -0400
 actormetadata: provide a current_user_is_owner method.
 
 r67071 at pinglin (orig r4061):  clkao | 2007-09-07 18:23:57 -0400
 webservices_rediret: don't redirect if there's any failed action.
 
 r67072 at pinglin (orig r4062):  clkao | 2007-09-08 09:42:57 -0400
 first cut of tests for singlepage app plugin.
 r67084 at pinglin (orig r4063):  sterling | 2007-09-10 11:47:38 -0400
  r11815 at riddle:  andrew | 2007-09-10 10:46:00 -0500
  Improving POD and minor tidying.
 
 r67090 at pinglin (orig r4069):  sartak | 2007-09-10 14:59:26 -0400
  r42468 at onn:  sartak | 2007-09-10 14:58:49 -0400
  friendly_date should be relative to the user, not floating
 
 r67096 at pinglin (orig r4070):  jesse | 2007-09-10 22:04:29 -0400
  r67092 at pinglin:  jesse | 2007-09-10 18:26:34 -0400
  * Some databases have schema creation single-threadedness. If they block, wait and retry for up to a minute
 
 r67097 at pinglin (orig r4071):  jesse | 2007-09-10 22:04:38 -0400
  r67093 at pinglin:  jesse | 2007-09-10 18:26:51 -0400
  * pod fixes
 
 r67098 at pinglin (orig r4072):  jesse | 2007-09-10 22:04:44 -0400
  r67094 at pinglin:  jesse | 2007-09-10 18:27:22 -0400
  * When testing, don't just use a single global test database. Instead, use one per testfile. This allows some measure of parallelization
 
 r67099 at pinglin (orig r4073):  jesse | 2007-09-10 22:04:54 -0400
  r67095 at pinglin:  jesse | 2007-09-10 22:01:20 -0400
   * tabview shouldn't force absolute paths to relative ones
 
 r67273 at pinglin (orig r4075):  sterling | 2007-09-11 12:50:51 -0400
  r11935 at dynpc145:  andrew | 2007-09-11 11:50:08 -0500
   * Ripping out new_record_action() from Jifty::Web.
   * Re-implementing it as as_create_action(), as_update_action(), as_delete_action, and as_search_action() in Jifty::Record and Jifty::Collection.
 
 r67274 at pinglin (orig r4076):  sterling | 2007-09-11 13:05:17 -0400
  r11937 at dynpc145:  andrew | 2007-09-11 12:04:53 -0500
  Making the as_*_action() methods accept a paramhash for additional new_action() parameters.
 
 r67275 at pinglin (orig r4077):  sartak | 2007-09-11 13:33:46 -0400
  r42507 at onn:  sartak | 2007-09-11 12:19:53 -0400
  Use ->clone on DateTime objects to avoid possible DST issues, etc
 
 r67276 at pinglin (orig r4078):  sterling | 2007-09-11 13:38:43 -0400
  r11939 at dynpc145:  andrew | 2007-09-11 12:13:59 -0500
  Clearing out old new_record_action() tests.
 
 r67277 at pinglin (orig r4079):  sterling | 2007-09-11 13:38:49 -0400
  r11940 at dynpc145:  andrew | 2007-09-11 12:14:37 -0500
  Clearing out old new_record_action() tests.
 
 r67278 at pinglin (orig r4080):  sterling | 2007-09-11 13:38:57 -0400
  r11941 at dynpc145:  andrew | 2007-09-11 12:38:20 -0500
   * Simplifying the CRUD code for generating the record_class and making it more flexible.
   * Adding a stub for CRUD testing.
 
 r67279 at pinglin (orig r4081):  sterling | 2007-09-11 13:56:15 -0400
  r11947 at dynpc145:  andrew | 2007-09-11 12:55:30 -0500
  POD clean-up, some code commenting, and minor tidying.
 
 r67281 at pinglin (orig r4083):  sterling | 2007-09-11 16:04:38 -0400
  r11951 at dynpc145:  andrew | 2007-09-11 15:04:01 -0500
  Clean up the Pod, added a Synopsis, added a Why? section, added code comments, and some code tidying.
 
 r67282 at pinglin (orig r4084):  sterling | 2007-09-11 17:38:58 -0400
  r11956 at dynpc145:  andrew | 2007-09-11 16:38:08 -0500
  Adding another CRUD setup example to the synopsis.
 
 r67283 at pinglin (orig r4085):  sterling | 2007-09-11 18:16:14 -0400
  r11959 at dynpc145:  andrew | 2007-09-11 17:15:52 -0500
  Don't override object_type if the CRUD view package already defines object_type.
 
 r67294 at pinglin (orig r4096):  sterling | 2007-09-11 22:51:52 -0400
  r12003 at dynpc145:  andrew | 2007-09-11 21:44:01 -0500
  Fixing a bug that prevents any but the first plugin being checked for schema updates.
 
 r67295 at pinglin (orig r4097):  sterling | 2007-09-11 22:52:01 -0400
  r12004 at dynpc145:  andrew | 2007-09-11 21:46:41 -0500
  Plugin upgrade incorrectly assumes a DB version of 0.0.1 when none is found.
 
 r67296 at pinglin (orig r4098):  sterling | 2007-09-11 22:54:25 -0400
  r12007 at dynpc145:  andrew | 2007-09-11 21:54:00 -0500
  Bring the default back, but at 0.0.0 to prevent uninitialized complaints.
 
 r67300 at pinglin (orig r4102):  sterling | 2007-09-12 16:43:13 -0400
  r12040 at riddle:  andrew | 2007-09-12 15:41:46 -0500
  Added missing jifty-result-popup div to the page detritus section of Jifty::View::Declare::Page.
 
 r67302 at pinglin (orig r4104):  sterling | 2007-09-12 23:23:28 -0400
  r12048 at dynpc145:  andrew | 2007-09-12 21:21:59 -0500
  Fixing a typo in the plugin DB version metadata key affecting upgrades.
 
 r67303 at pinglin (orig r4105):  sterling | 2007-09-12 23:23:35 -0400
  r12049 at dynpc145:  andrew | 2007-09-12 22:19:16 -0500
  Improving schema setup for plugins that are turned on after the application is initially deployed.
 
 r67304 at pinglin (orig r4106):  sterling | 2007-09-13 11:33:54 -0400
  r12057 at dynpc145:  andrew | 2007-09-13 10:22:35 -0500
  Removing the display style from the jifty-result-popup, which is breaking it.
 
 r67306 at pinglin (orig r4108):  sterling | 2007-09-13 12:04:19 -0400
  r12066 at dynpc145:  andrew | 2007-09-13 11:03:51 -0500
  Clean up the masonXXXXXXXXXX temp directory after each test run.
 
 r67308 at pinglin (orig r4110):  clkao | 2007-09-13 16:24:14 -0400
 Handle region redirect with continuation return (for spa).
 
 r67309 at pinglin (orig r4111):  clkao | 2007-09-13 16:33:15 -0400
 Remove debug info.
 r67310 at pinglin (orig r4112):  clkao | 2007-09-13 18:40:15 -0400
 Patch by Alex to make sp_submit_form to be happier with J:V vars.
 r67311 at pinglin (orig r4113):  sterling | 2007-09-13 23:19:05 -0400
  r12105 at riddle:  andrew | 2007-09-13 22:14:55 -0500
  Make find_plugins() sensitive to wantarray.
 
 r67318 at pinglin (orig r4114):  sartak | 2007-09-14 17:35:37 -0400
  r42507 at onn:  sartak | 2007-09-11 12:19:53 -0400
  Use ->clone on DateTime objects to avoid possible DST issues, etc
 
 r67319 at pinglin (orig r4115):  sartak | 2007-09-14 17:36:16 -0400
  r42673 at onn:  sartak | 2007-09-14 17:34:32 -0400
  The REST dispatcher should warn $@ if an error occurs
 
 r67320 at pinglin (orig r4116):  sartak | 2007-09-15 03:00:08 -0400
  r42711 at onn:  sartak | 2007-09-15 02:59:55 -0400
  Fix mismerge..
 
 r67321 at pinglin (orig r4117):  clkao | 2007-09-15 09:51:12 -0400
 When replacing a region, use the technique described in
 
 http://blog.stevenlevithan.com/archives/faster-than-innerhtml
 
 to improve performance.  
 
 r67322 at pinglin (orig r4118):  clkao | 2007-09-16 13:57:14 -0400
 Revert r4117 as the element readiness state is not compatible with what it was.
 r67325 at pinglin (orig r4119):  jesse | 2007-09-16 21:08:09 -0400
  r67324 at pinglin:  jesse | 2007-09-16 21:05:58 -0400
  Added an app_page_footer page call to jifty view declare page
 
 r67327 at pinglin (orig r4120):  sunnavy | 2007-09-17 09:05:10 -0400
 a tiny change and typo fix
 r67330 at pinglin (orig r4122):  sartak | 2007-09-17 12:44:14 -0400
  r42743 at onn:  sartak | 2007-09-17 12:44:04 -0400
  Add a 'now' method to Jifty::DateTime which sets the timezone to the current user's timezone.
  DateTime::now passes time_zone => UTC to Jifty::DateTime::now, which caused it to skip over the currentuser/timezone magic.
 
 r67331 at pinglin (orig r4123):  sartak | 2007-09-17 12:56:33 -0400
  r42750 at onn:  sartak | 2007-09-17 12:56:10 -0400
  Add some more tests for Jifty::DateTime in the TestApp
 
 r67332 at pinglin (orig r4124):  sartak | 2007-09-17 14:27:25 -0400
  r42755 at onn:  sartak | 2007-09-17 14:26:59 -0400
  Add support for "title" attribute on elements, which shows tooltips
 
 r67333 at pinglin (orig r4125):  jesse | 2007-09-17 18:06:07 -0400
  r67329 at 000-251-384:  jesse | 2007-09-17 18:05:43 -0400
  * The cache was actually a huge performance problem
 
 r67336 at pinglin (orig r4126):  jesse | 2007-09-17 18:15:33 -0400
  r67334 at 000-251-384:  jesse | 2007-09-17 18:15:06 -0400
  * Don't do weird things with loading model subclasses
 
 r67337 at pinglin (orig r4127):  jesse | 2007-09-17 18:15:41 -0400
  r67335 at 000-251-384:  jesse | 2007-09-17 18:15:15 -0400
  * Class::Accessor::Fast cleanups
 
 r67339 at pinglin (orig r4128):  jesse | 2007-09-17 22:23:05 -0400
  r67338 at pinglin:  jesse | 2007-09-17 22:19:33 -0400
  * Use much less UNIVERSAL::require (to shave a second or two off start times)
 
 r67411 at pinglin (orig r4130):  sartak | 2007-09-18 16:07:02 -0400
  r42743 at onn:  sartak | 2007-09-17 12:44:04 -0400
  Add a 'now' method to Jifty::DateTime which sets the timezone to the current user's timezone.
  DateTime::now passes time_zone => UTC to Jifty::DateTime::now, which caused it to skip over the currentuser/timezone magic.
 
 r67412 at pinglin (orig r4131):  sartak | 2007-09-18 16:07:09 -0400
  r42750 at onn:  sartak | 2007-09-17 12:56:10 -0400
  Add some more tests for Jifty::DateTime in the TestApp
 
 r67413 at pinglin (orig r4132):  sartak | 2007-09-18 16:07:13 -0400
  r42755 at onn:  sartak | 2007-09-17 14:26:59 -0400
  Add support for "title" attribute on elements, which shows tooltips
 
 r67414 at pinglin (orig r4133):  sartak | 2007-09-18 16:07:18 -0400
  r42785 at onn:  sartak | 2007-09-18 16:06:26 -0400
  Add new LeakDetector plugin. It kinda sorta works :)
 
 r67415 at pinglin (orig r4134):  sartak | 2007-09-18 16:27:33 -0400
  r42796 at onn:  sartak | 2007-09-18 16:27:18 -0400
  Some cleanups in LeakDetector
 
 r67416 at pinglin (orig r4135):  sartak | 2007-09-18 16:35:55 -0400
  r42798 at onn:  sartak | 2007-09-18 16:35:47 -0400
  More cleanup, hide zero-leak requests by default
 
 r67417 at pinglin (orig r4136):  sartak | 2007-09-18 16:46:01 -0400
  r42800 at onn:  sartak | 2007-09-18 16:45:51 -0400
  Include the list of object types leaked in the request report
 
 r67431 at pinglin (orig r4137):  sartak | 2007-09-19 13:34:21 -0400
  r42802 at onn:  sartak | 2007-09-18 17:01:23 -0400
  Move files from LeakDetector to LeakTracker (because that's the term Cat uses)
 
 r67432 at pinglin (orig r4138):  sartak | 2007-09-19 13:34:24 -0400
  r42803 at onn:  sartak | 2007-09-18 17:02:47 -0400
  Fix package names, add copyright notices
 
 r67433 at pinglin (orig r4139):  sartak | 2007-09-19 13:34:29 -0400
  r42805 at onn:  sartak | 2007-09-19 10:51:27 -0400
  Move memleak deps into its own Makefile.PL section
 
 r67434 at pinglin (orig r4140):  sartak | 2007-09-19 13:34:36 -0400
  r42812 at onn:  sartak | 2007-09-19 11:02:34 -0400
  POD for LeakTracker's subs
 
 r67435 at pinglin (orig r4141):  jesse | 2007-09-19 22:43:18 -0400
  r67430 at pinglin:  jesse | 2007-09-19 22:41:42 -0400
  * Switch to a single call to Module::Pluggable to help improve startup performance.
 
 r67450 at pinglin (orig r4143):  jesse | 2007-09-20 12:00:20 -0400
  r67448 at pinglin:  jesse | 2007-09-20 11:59:56 -0400
  * Some parts of that last commit to use only one Module::Pluggable weren't as hot as they appeared to be;
 
 r67564 at pinglin (orig r4144):  clkao | 2007-09-24 03:54:07 -0400
 allow onsubmit in form to include custom javascript.
 
 r67569 at pinglin (orig r4149):  clkao | 2007-09-24 19:29:17 -0400
 In Jifty::Model::Session:
 * Turn session_id, data_key, key_type into case_sensitive,
   so we don't do useless tolower on loading sessions.
 
 * index session_id by default.
 
 r67570 at pinglin (orig r4150):  clkao | 2007-09-24 20:09:37 -0400
 Update zh-tw po to include translations for
 authentication::password plugin.
 
 r67571 at pinglin (orig r4151):  jesse | 2007-09-25 00:14:36 -0400
  r67563 at pinglin:  jesse | 2007-09-25 00:13:14 -0400
  * Updated the JSON and YAML transports for web service requests to allow complex data structures in actions
 
 r67624 at pinglin (orig r4152):  sunnavy | 2007-09-26 08:52:27 -0400
 doc fixes
 r67625 at pinglin (orig r4153):  sartak | 2007-09-26 14:57:39 -0400
  r42802 at onn:  sartak | 2007-09-18 17:01:23 -0400
  Move files from LeakDetector to LeakTracker (because that's the term Cat uses)
 
 r67626 at pinglin (orig r4154):  sartak | 2007-09-26 14:57:48 -0400
  r42803 at onn:  sartak | 2007-09-18 17:02:47 -0400
  Fix package names, add copyright notices
 
 r67627 at pinglin (orig r4155):  sartak | 2007-09-26 14:57:53 -0400
  r42805 at onn:  sartak | 2007-09-19 10:51:27 -0400
  Move memleak deps into its own Makefile.PL section
 
 r67628 at pinglin (orig r4156):  sartak | 2007-09-26 14:57:57 -0400
  r42812 at onn:  sartak | 2007-09-19 11:02:34 -0400
  POD for LeakTracker's subs
 
 r67629 at pinglin (orig r4157):  sartak | 2007-09-26 14:58:31 -0400
  r43101 at onn:  sartak | 2007-09-26 14:47:29 -0400
  Initial implementation of Jifty::Test::WWW::Declare, with basic tests in TestApp
 
 r67630 at pinglin (orig r4158):  sartak | 2007-09-26 14:58:38 -0400
  r43102 at onn:  sartak | 2007-09-26 14:57:22 -0400
  Override T:W:D's get with ours which prepends the server's URL
  Start documentation
 
 r67632 at pinglin (orig r4160):  clkao | 2007-09-27 14:50:29 -0400
 New as_string method for Jifty::Web::Form::Link.
 
 r67633 at pinglin (orig r4161):  sunnavy | 2007-09-28 10:28:15 -0400
 doc fixes
 r67634 at pinglin (orig r4162):  jesse | 2007-09-28 14:44:05 -0400
  r67623 at pinglin:  jesse | 2007-09-28 14:43:47 -0400
  * When loading a web request from a data structure, don't totally throw away the path it was requested at.
 
 r67905 at pinglin (orig r4165):  jesse | 2007-10-01 15:52:42 -0400
  r27276 at hualien:  jesse | 2007-10-01 15:50:22 -0400
  * Fixes to several of my fixes for Jifty::Request::load_from_data_structure
 
 r67908 at pinglin (orig r4168):  alexmv | 2007-10-01 17:45:26 -0400
  r22186 at zoq-fot-pik:  chmrr | 2007-10-01 17:41:16 -0400
   * Force a clone which doesn't have as many bugs
 
 r67910 at pinglin (orig r4170):  jesse | 2007-10-02 00:55:17 -0400
  r27295 at hualien:  jesse | 2007-10-02 00:50:39 -0400
  * Rather than helpfully failing to load model classes that have compiletime errors, actually die with the error that caused the class not to load.
 
 r67940 at pinglin (orig r4200):  jesse | 2007-10-03 13:07:01 -0400
  r27368 at hualien:  jesse | 2007-10-03 13:06:49 -0400
  * Allow users to name their apps in lowercase (as pragmas ?!)
      - Reported by SteveH++
      
 
 r67941 at pinglin (orig r4201):  sterling | 2007-10-03 14:43:26 -0400
  r12827 at riddle:  andrew | 2007-10-03 13:41:53 -0500
  Use blessed() instead of ref() to keep an if statement from tripping on ->isa().
 
 r67944 at pinglin (orig r4204):  alexmv | 2007-10-03 22:55:38 -0400
  r23400 at zoq-fot-pik:  chmrr | 2007-10-03 22:53:18 -0400
   * Recursively transform content structures
   * Set a flag when we change the method, in case apps care
 
 r67946 at pinglin (orig r4206):  sterling | 2007-10-03 23:59:11 -0400
  r12837 at riddle:  andrew | 2007-10-03 22:57:39 -0500
  Added a new plugin that provides an autocomplete widget specially for record references. Helpful in cases when a select is just be too big to contemplate.
 
 r67947 at pinglin (orig r4207):  sterling | 2007-10-04 00:01:59 -0400
  r12840 at riddle:  andrew | 2007-10-03 23:01:09 -0500
  Adding missing JavaScript file and cleaning up a method left-over from testing.
 
 r67948 at pinglin (orig r4208):  clkao | 2007-10-04 03:25:39 -0400
 Fix a bug that after you access a static css file, it breaks the
 compressed css by not actually squishing main.css, hence requests
 static css files under __jifty/css.
 
 r67952 at pinglin (orig r4212):  sterling | 2007-10-04 11:33:24 -0400
  r6447 at viper:  andrew | 2007-10-04 10:33:05 -0500
  Fixing the data fetcher on the AutoReference box so that it sends the text being typed rather than the current value in the hidden field.
 
 r67988 at pinglin (orig r4215):  audreyt | 2007-10-09 14:17:34 -0400
 * Fix Jifty::Server::Prefork's document so it mentions
   Net::Server::PreFork, instead of Net::Server::Prefork.
   (Yes this is very confusing.)
 r67989 at pinglin (orig r4216):  sartak | 2007-10-09 17:42:26 -0400
 
 r67990 at pinglin (orig r4217):  sartak | 2007-10-09 17:42:30 -0400
  r43559 at onn:  sartak | 2007-10-09 17:21:52 -0400
  Add Jifty::Util->is_app_root to determine whether a path looks enough like the app root
 
 r67991 at pinglin (orig r4218):  sartak | 2007-10-09 17:42:35 -0400
  r43560 at onn:  sartak | 2007-10-09 17:42:13 -0400
  Add the ability for apps to have very fine-grained testing (such as overriding config for a subdir of t/, or even a particular test file).
  Still needs tests and I suppose some more doc :)
 
 r67992 at pinglin (orig r4219):  sartak | 2007-10-09 18:13:19 -0400
  r43565 at onn:  sartak | 2007-10-09 18:13:07 -0400
  Revert my weird revert/checkin..
 
 r67993 at pinglin (orig r4220):  sartak | 2007-10-09 19:40:34 -0400
  r43567 at onn:  sartak | 2007-10-09 19:40:28 -0400
  Test config loading is now complete code-wise :)
  And it has tests now too! Just need to doc it in manual or Jifty::Config
 
 r67994 at pinglin (orig r4221):  sartak | 2007-10-09 19:56:14 -0400
  r43569 at onn:  sartak | 2007-10-09 19:56:09 -0400
  Fix the problem of tests having both config and a correctly-relative $0
  Jifty::SubTest now makes available the old Cwd (which as 03-nosubtest demonstrates doesn't break apps that don't use Jifty::SubTest)
  Also a bit of doc in Jifty::Config
  The Feature is Done. :)
 
 r68047 at pinglin (orig r4222):  sunnavy | 2007-10-10 03:41:43 -0400
 don't warn if database doesn't exist when dropping database
 r68049 at pinglin (orig r4224):  sunnavy | 2007-10-10 10:58:47 -0400
 warnings should be to STDERR
 r68050 at pinglin (orig r4225):  audreyt | 2007-10-10 12:24:11 -0400
 * The JIFTY_SITE_CONFIG environment variable was silently ignored.
 r68051 at pinglin (orig r4226):  sunnavy | 2007-10-10 14:13:24 -0400
 no need to print STDERR, we can warn in $SIG{__WARN__}
 r68052 at pinglin (orig r4227):  alexmv | 2007-10-10 22:44:04 -0400
  r23483 at zoq-fot-pik:  chmrr | 2007-10-10 22:41:42 -0400
   * Mailboxes need to have unique names for parallel testing
 
 r68053 at pinglin (orig r4228):  alexmv | 2007-10-10 22:44:21 -0400
  r23484 at zoq-fot-pik:  chmrr | 2007-10-10 22:42:01 -0400
   * Just a perltidy
 
 r68054 at pinglin (orig r4229):  alexmv | 2007-10-10 22:44:40 -0400
  r23485 at zoq-fot-pik:  chmrr | 2007-10-10 22:43:00 -0400
   * Pg wants to connect to template1, but CREATE DATABASE foo TEMPLATE template0
 
 r68132 at pinglin (orig r4231):  falcone | 2007-10-11 15:10:39 -0400
  r25357 at ketch:  falcone | 2007-10-11 15:05:07 -0400
  * add a bug tag
 
 r68133 at pinglin (orig r4232):  falcone | 2007-10-11 15:10:58 -0400
  r25358 at ketch:  falcone | 2007-10-11 15:06:00 -0400
  * make format_entry return text rather than printing
  * use the returned message to generate the changelog rather than printing to STDOUT
 
 r68134 at pinglin (orig r4233):  falcone | 2007-10-11 15:27:26 -0400
  r25361 at ketch:  falcone | 2007-10-11 15:26:34 -0400
  * usage documentation
 
 r68135 at pinglin (orig r4234):  falcone | 2007-10-11 16:07:09 -0400
  r25363 at ketch:  falcone | 2007-10-11 16:05:54 -0400
  * talk about how to do iterative editing
 
 r68136 at pinglin (orig r4235):  falcone | 2007-10-11 16:07:49 -0400
  r25364 at ketch:  falcone | 2007-10-11 16:06:15 -0400
  * add an exclude command for logs you don't need to ship
 
 r68137 at pinglin (orig r4236):  falcone | 2007-10-11 16:08:02 -0400
  r25365 at ketch:  falcone | 2007-10-11 16:06:34 -0400
  * fix so we don't try to print to a bogus filehandle
 
 r68138 at pinglin (orig r4237):  falcone | 2007-10-11 16:18:32 -0400
  r25369 at ketch:  falcone | 2007-10-11 16:18:00 -0400
  * use existing t-discard tag
  * don't autoskip things, just group them together and you can
    delete them 
 
 r68139 at pinglin (orig r4238):  alexmv | 2007-10-11 23:39:44 -0400
  r23516 at zoq-fot-pik:  chmrr | 2007-10-11 23:39:14 -0400
   * We depend on the bugfix that File::Spec->rel2abs("/foo","/foo") eq "."
 
 r68140 at pinglin (orig r4239):  falcone | 2007-10-12 10:15:54 -0400
  r25389 at ketch:  falcone | 2007-10-12 10:15:06 -0400
  * remove doubled loop over log messages
  * explicitly loop over uncategorized first, then offer
    the user to review all the changes again
  * reformatting
 
 r68141 at pinglin (orig r4240):  clkao | 2007-10-15 07:53:15 -0400
 Add cdn option to CompressedCSSandJS plugin.
 r68143 at pinglin (orig r4241):  jesse | 2007-10-15 08:32:46 -0400
  r68130 at 70:  jesse | 2007-10-15 13:28:14 +0100
  * Updating the Jifty web menu component to respect classes added by users
 
 r68144 at pinglin (orig r4242):  jesse | 2007-10-15 08:33:06 -0400
  r68131 at 70:  jesse | 2007-10-15 13:29:26 +0100
  * Cache gzipped output from the compressedcssandjs plugin
 
 r68159 at pinglin (orig r4243):  clkao | 2007-10-16 07:07:17 -0400
 Don't require recipients in Jifty::Notificaiton be objects.
 r68161 at pinglin (orig r4245):  sartak | 2007-10-16 17:31:35 -0400
  r43759 at onn:  sartak | 2007-10-16 17:31:18 -0400
  Make sure Jifty::Filter::DateTime doesn't disturb Floating datetimes
  Otherwise, dates would get set to the current_user's timezone, throwing a wrench in the works
 
 r68163 at pinglin (orig r4246):  jesse | 2007-10-17 10:31:10 -0400
  r68158 at pinglin:  jesse | 2007-10-17 15:29:38 +0100
  * Added the ability to craft a Jifty button with a "submit" link which submits hardcoded arguments.
 
 r68167 at pinglin (orig r4247):  jesse | 2007-10-17 11:46:16 -0400
  r68164 at pinglin:  jesse | 2007-10-17 16:34:30 +0100
  * Fixing long-broken support for the "JIFTY_FASTTEST" env variable
 
 r68168 at pinglin (orig r4248):  jesse | 2007-10-17 11:46:27 -0400
  r68165 at pinglin:  jesse | 2007-10-17 16:35:39 +0100
  added a bit more semantic markup to the crud view
 
 r68169 at pinglin (orig r4249):  jesse | 2007-10-17 11:46:35 -0400
  r68166 at pinglin:  jesse | 2007-10-17 16:45:22 +0100
  
  * working toward onclick => { submit => { action => $action,    
                                  arguments => { foo => 'value', bar => 'other value'} }}
  
  Jifty's js still needs help
 
 r68170 at pinglin (orig r4250):  clkao | 2007-10-17 13:04:25 -0400
 Make action_arguments effective from js update()
 r68171 at pinglin (orig r4251):  alexmv | 2007-10-17 13:14:10 -0400
  r23709 at zoq-fot-pik:  chmrr | 2007-10-17 13:13:45 -0400
   * Untabify
 
 r68172 at pinglin (orig r4252):  sartak | 2007-10-17 15:43:22 -0400
  r43816 at onn:  sartak | 2007-10-17 15:43:11 -0400
  Start adding an OAuth plugin
 
 r68173 at pinglin (orig r4253):  sartak | 2007-10-17 18:08:30 -0400
  r43829 at onn:  sartak | 2007-10-17 18:08:12 -0400
  Some more fleshing out of the OAuth plugin
 
 r68326 at pinglin (orig r4254):  sartak | 2007-10-18 15:36:32 -0400
  r43835 at onn:  sartak | 2007-10-18 15:36:22 -0400
  Many improvements, including some pages
 
 r68327 at pinglin (orig r4255):  sartak | 2007-10-18 16:57:03 -0400
  r43843 at onn:  sartak | 2007-10-18 16:56:56 -0400
  Include the http method (GET, POST, etc) in the debug message
 
 r68328 at pinglin (orig r4256):  sartak | 2007-10-18 17:13:32 -0400
  r43845 at onn:  sartak | 2007-10-18 17:13:26 -0400
  Turns out we can't (so easily) have the URLs be configurable. Not too much of a problem though
  Other fixes
 
 r68329 at pinglin (orig r4257):  sartak | 2007-10-18 19:20:24 -0400
  r43847 at onn:  sartak | 2007-10-18 19:20:16 -0400
  Start writing tests
  Change timestamp to (ugh) time_stamp, because timestamp is a reserved word in a few SQL apps
 
 r68330 at pinglin (orig r4258):  sartak | 2007-10-18 21:52:03 -0400
  r43849 at onn:  sartak | 2007-10-18 21:51:56 -0400
  Expand the tests, now they actually test something real!
  So right now the OAuth plugin is rejecting a few kinds of bad requests (e.g. unknown consumer) while accepting a good request. Now to make more requests of each type :)
 
 r68331 at pinglin (orig r4259):  sartak | 2007-10-18 23:34:56 -0400
  r43851 at onn:  sartak | 2007-10-18 23:34:48 -0400
  The first few test scripts are now complete, and uncovered many bugs. Yay.
 
 r68332 at pinglin (orig r4260):  sartak | 2007-10-19 00:30:18 -0400
  r43853 at onn:  sartak | 2007-10-19 00:30:11 -0400
  Now we send responses to token requests (and test them)
  What's left:
      Authorizing request tokens is coded, but it's slightly flawed somehow
      Getting access tokens is coded, but not tested
      No code yet for the actual accessing of resources
 
 r68333 at pinglin (orig r4261):  sartak | 2007-10-19 11:50:33 -0400
  r43871 at onn:  sartak | 2007-10-19 11:50:24 -0400
  Require Net::OAuth 0.04 because 0.03 needs my patch
  Don't run tests if Net::OAuth is uninstalled
 
 r68334 at pinglin (orig r4262):  clkao | 2007-10-19 12:21:59 -0400
 * Delay i18n handle init until we have session.
 * SkeletonApp dispatcher rule to set language in session.
 
 r68335 at pinglin (orig r4263):  audreyt | 2007-10-19 13:48:09 -0400
 * Jifty::I18N - Implement the L10N.AllowedLang key to limit
   the set of languages available to the user.
   Use case: ja.po is incomplete and we wish to hide it from users.
   Requested by: clkao
 r68336 at pinglin (orig r4264):  sartak | 2007-10-19 16:22:19 -0400
  r43888 at onn:  sartak | 2007-10-19 16:22:10 -0400
  Refactor timestamp and nonce out of tokens and into consumers
  Thanks to hannesty for discussing how it works
 
 r68338 at pinglin (orig r4266):  sartak | 2007-10-19 17:18:43 -0400
  r43894 at onn:  sartak | 2007-10-19 17:18:30 -0400
  Users can now authorize/deny request tokens
 
 r68339 at pinglin (orig r4267):  sartak | 2007-10-19 18:19:55 -0400
  r43905 at onn:  sartak | 2007-10-19 18:19:45 -0400
  Doc
 
 r68340 at pinglin (orig r4268):  sartak | 2007-10-19 23:26:41 -0400
  r43907 at onn:  sartak | 2007-10-19 23:26:32 -0400
  Another example app: ShrinkURL, which is basically (surprise!) tinyurl
 
 r68341 at pinglin (orig r4269):  sartak | 2007-10-19 23:37:59 -0400
  r43909 at onn:  sartak | 2007-10-19 23:37:52 -0400
  More comments
 
 r68344 at pinglin (orig r4272):  sartak | 2007-10-20 00:34:31 -0400
  r43915 at onn:  sartak | 2007-10-20 00:34:20 -0400
  A lot of POD for OAuth, and some tiny fixes elsewhere
 
 r68345 at pinglin (orig r4273):  sartak | 2007-10-20 00:44:16 -0400
  r43917 at onn:  sartak | 2007-10-20 00:44:07 -0400
  Somehow this file got duplicated. I might've done it. shrug
 
 r68346 at pinglin (orig r4274):  sartak | 2007-10-20 00:47:37 -0400
  r43919 at onn:  sartak | 2007-10-20 00:47:22 -0400
  Add Dispatcher, revert some changes that aren't ready (weird ClassLoader errors? shrug)
 
 r68347 at pinglin (orig r4275):  sartak | 2007-10-20 00:54:38 -0400
  r43921 at onn:  sartak | 2007-10-20 00:54:31 -0400
  Dispatcher and config fixes, start adding a new test file
 
 r68348 at pinglin (orig r4276):  clkao | 2007-10-20 04:19:40 -0400
 simplify ccjs plugin with static handler method call.
 r68349 at pinglin (orig r4277):  clkao | 2007-10-20 04:28:15 -0400
 oops, should look for js files under js.
 r68350 at pinglin (orig r4278):  clkao | 2007-10-20 05:01:36 -0400
 Jifty::I18N: provide available_languages method.
 r68351 at pinglin (orig r4279):  clkao | 2007-10-20 05:15:33 -0400
 bin/jifty po now takes two more options:
 * --dir for additional directories to look at so javascript
   files can be scanned.
 
 * --js for generating json dictionaries for messages appeared
   in javascript files declared with Jifty::Web.
 
 r68352 at pinglin (orig r4280):  clkao | 2007-10-20 09:56:48 -0400
 Provide get_current language method in Jifty::I18N.
 r68355 at pinglin (orig r4283):  clkao | 2007-10-21 12:48:36 -0400
 our version of prototype can't deal with synchronous request.
 r68356 at pinglin (orig r4284):  clkao | 2007-10-21 12:52:49 -0400
 Support serving json version of po and localization in javascript
 space.
 
 r68357 at pinglin (orig r4285):  audreyt | 2007-10-21 16:08:25 -0400
 * Jifty::Request::Mapper - Avoid uninitialized warnings when
   $args{destination} ends up undefined.
 r68358 at pinglin (orig r4286):  audreyt | 2007-10-21 16:25:26 -0400
 * Jifty::I18N::promote_encoding:
 
     Multi-part form data have no notion of charsets, so we return the string
     verbatim here, to avoid the "Unquoted / not allowed in Content-Type"
     warnings when the Base64-encoded MIME boundary string contains "/".
 
   Prompted by this Content-Type header found in real world:
     multipart/form-data; boundary=----WebKitFormBoundaryRqXyEnBQ/5VSsexe
 
   which triggered this error:
     -- 2007/10/22 04:19:30 WARN> Carp.pm:46 Carp::carp
     Unquoted / not allowed in Content-Type! at /usr/local/lib/perl5/site_perl/5.9.5/Jifty/I18N.pm line 226
     -- 2007/10/22 04:19:30 WARN> Carp.pm:46 Carp::carp
     Illegal Content-Type parameter /5VSsexe at /usr/local/lib/perl5/site_perl/5.9.5/Jifty/I18N.pm line 226
 
 r68359 at pinglin (orig r4287):  clkao | 2007-10-22 00:09:54 -0400
 zh_tw l10n for Authenication::Password plugin.
 r68360 at pinglin (orig r4288):  sunnavy | 2007-10-22 03:44:24 -0400
 typo fix
 r68361 at pinglin (orig r4289):  clkao | 2007-10-22 12:05:27 -0400
 rollback r4242 which cached the gzipped js/css even through
 develmode reload.
 
 r68363 at pinglin (orig r4290):  clkao | 2007-10-22 12:16:41 -0400
 12:09 < obra> Aaaaaaaaaaaaaaaaaaaaaaaaa
 r68366 at pinglin (orig r4291):  audreyt | 2007-10-22 13:03:43 -0400
 * Jifty::I18N - POD fixup.
 r68367 at pinglin (orig r4292):  jesse | 2007-10-22 13:07:27 -0400
  r68158 at pinglin:  jesse | 2007-10-17 10:29:38 -0400
  * Added the ability to craft a Jifty button with a "submit" link which submits hardcoded arguments.
 
 r68368 at pinglin (orig r4293):  jesse | 2007-10-22 13:07:46 -0400
  r68164 at pinglin:  jesse | 2007-10-17 11:34:30 -0400
  * Fixing long-broken support for the "JIFTY_FASTTEST" env variable
 
 r68369 at pinglin (orig r4294):  jesse | 2007-10-22 13:07:51 -0400
  r68165 at pinglin:  jesse | 2007-10-17 11:35:39 -0400
  added a bit more semantic markup to the crud view
 
 r68377 at pinglin (orig r4295):  jesse | 2007-10-22 13:10:34 -0400
  r68166 at pinglin:  jesse | 2007-10-17 11:45:22 -0400
  
  * working toward onclick => { submit => { action => $action,    
                                  arguments => { foo => 'value', bar => 'other value'} }}
  
  Jifty's js still needs help
  r68365 at pinglin:  jesse | 2007-10-22 13:06:27 -0400
  * Fixes to not cache compressed css+js while in devel mode.
  * Refactoring js compression code into the plugin where it belongs.
 
 r68410 at pinglin (orig r4296):  clkao | 2007-10-23 04:04:22 -0400
 allow switching current language for Localization.
 r68411 at pinglin (orig r4297):  ishigaki | 2007-10-23 08:29:43 -0400
 Modules other than Net::OAuth::Request in Net-OAuth distro don't have version
 r68412 at pinglin (orig r4298):  sartak | 2007-10-23 12:20:03 -0400
  r44064 at onn:  sartak | 2007-10-23 12:19:36 -0400
  Ah ha, plugins need to be very careful about loading appclass models, because classloader isn't set up for them yet
 
 r68413 at pinglin (orig r4299):  ishigaki | 2007-10-23 12:42:30 -0400
 updated ja.po
 r68460 at pinglin (orig r4300):  clkao | 2007-10-24 03:08:22 -0400
 loc.js cleanups.
 r68461 at pinglin (orig r4301):  sterling | 2007-10-24 11:30:34 -0400
  r13634 at riddle:  andrew | 2007-10-24 10:29:05 -0500
  Adding File::Temp 0.15 requirement for cleanup().
 
 r68462 at pinglin (orig r4302):  sterling | 2007-10-24 11:30:42 -0400
  r13635 at riddle:  andrew | 2007-10-24 10:29:46 -0500
  Minor edit: fixing reference to Jifty::Request::Action to Jifty::Action
 
 r68463 at pinglin (orig r4303):  sterling | 2007-10-24 12:24:02 -0400
  r13638 at riddle:  andrew | 2007-10-24 11:23:50 -0500
  Undoing 4302. There is such a class. Thanks to chmrr.
 
 r68464 at pinglin (orig r4304):  alexmv | 2007-10-24 14:02:04 -0400
  r23967 at zoq-fot-pik:  chmrr | 2007-10-24 13:57:17 -0400
   * Menu class is undef by default; fall back to the empty string
 
 r68466 at pinglin (orig r4306):  sartak | 2007-10-24 14:07:57 -0400
  r44126 at onn:  sartak | 2007-10-24 14:07:37 -0400
  Bump JDBI dep to 0.45 (Filter::DateTime changes)
 
 r68467 at pinglin (orig r4307):  sunnavy | 2007-10-24 16:50:36 -0400
 added TestApp-Plugin-OnClick, initially for the update of prototype.js
 r68468 at pinglin (orig r4308):  sartak | 2007-10-24 17:25:05 -0400
  r44156 at onn:  sartak | 2007-10-24 17:24:51 -0400
  Add a hook to change a menu item's label 
 
 r68471 at pinglin (orig r4311):  sartak | 2007-10-25 14:28:39 -0400
  r44178 at onn:  sartak | 2007-10-25 14:28:11 -0400
  Remove the unused drop-column logic from jifty schema --setup, since ->columns returns only active cols
 
 r68598 at pinglin (orig r4316):  sterling | 2007-10-26 15:39:43 -0400
  r13658 at riddle:  andrew | 2007-10-26 14:24:15 -0500
  Fix it so that AutoReference fields show the _brief_description rather than the id in read mode.
 
 r68599 at pinglin (orig r4317):  sterling | 2007-10-26 15:40:02 -0400
  r13659 at riddle:  andrew | 2007-10-26 14:38:32 -0500
  Expanded the previous fix to cover sticky values and the initial value in the input tag.
 
 r68657 at pinglin (orig r4319):  sterling | 2007-10-26 17:06:39 -0400
  r13664 at riddle:  andrew | 2007-10-26 16:06:06 -0500
  Modified the JS to ignore the [id:1] text at the end of the references.
 
 r68658 at pinglin (orig r4320):  sterling | 2007-10-27 02:04:44 -0400
  r13668 at dynpc145:  andrew | 2007-10-27 01:03:49 -0500
  Fixed AutoReference so that it is possible to set the field to empty if not mandatory.
 
 r68660 at pinglin (orig r4322):  ruz | 2007-10-28 02:24:12 -0400
 * if refers_to is not mandatory field then add 'no_value' option to select box
 r68661 at pinglin (orig r4323):  clkao | 2007-10-28 23:50:52 -0400
 Provide an after_include_javascript for plugins to do javascript initialisation.
 r68662 at pinglin (orig r4324):  clkao | 2007-10-29 00:13:52 -0400
 Jifty::Plugin::I18N:
 - provides SetLang action
 - provides loc.js and other facilities to l10n your javascript along with po.
 
 r68663 at pinglin (orig r4325):  sartak | 2007-10-29 00:15:30 -0400
  r44250 at onn:  sartak | 2007-10-29 00:15:09 -0400
  Skip OAuth tests if Crypt::OpenSSL::RSA is unavailable
  RSA should be optional, will fix later
 
 r69504 at pinglin (orig r4334):  sartak | 2007-10-30 13:08:52 -0400
  r44431 at onn:  sartak | 2007-10-30 13:08:19 -0400
  Get rid of spurious warning in Auth::Password plugin
 
 r69505 at pinglin (orig r4335):  alexmv | 2007-10-30 13:16:38 -0400
  r24200 at zoq-fot-pik:  chmrr | 2007-10-30 13:13:53 -0400
   * Fix test count
 
 r69506 at pinglin (orig r4336):  alexmv | 2007-10-30 18:22:53 -0400
  r24221 at zoq-fot-pik:  chmrr | 2007-10-30 18:21:04 -0400
   * Work around a "feature" in Hash::Merge < 0.10
 
 r69507 at pinglin (orig r4337):  sartak | 2007-10-30 18:34:34 -0400
  r44433 at onn:  sartak | 2007-10-30 14:17:57 -0400
  Wrote most of the authorization tests
 
 r69508 at pinglin (orig r4338):  sartak | 2007-10-30 18:34:53 -0400
 
 r69509 at pinglin (orig r4339):  sartak | 2007-10-30 18:35:14 -0400
  r44443 at onn:  sartak | 2007-10-30 18:34:06 -0400
  More tests, start implementing callbacks, but failing :)
 
 r69510 at pinglin (orig r4340):  agentz | 2007-10-31 04:46:06 -0400
  r2877 at agentz-office:  agentz | 2007-10-31 16:44:52 +0800
  updated my email address in AUTHORS to agentzh at agentzh.org
 
 r69511 at pinglin (orig r4341):  clkao | 2007-10-31 05:03:02 -0400
 * I18N::Action::SetLang: scalar::defer is trigger some oddness,
   rewrite available languages as normal runtime code, and cached.
 
 r69512 at pinglin (orig r4342):  sterling | 2007-10-31 12:59:29 -0400
  r13658 at riddle:  andrew | 2007-10-26 14:24:15 -0500
  Fix it so that AutoReference fields show the _brief_description rather than the id in read mode.
 
 r69513 at pinglin (orig r4343):  sterling | 2007-10-31 12:59:35 -0400
  r13659 at riddle:  andrew | 2007-10-26 14:38:32 -0500
  Expanded the previous fix to cover sticky values and the initial value in the input tag.
 
 r69514 at pinglin (orig r4344):  sterling | 2007-10-31 12:59:39 -0400
  r13664 at riddle:  andrew | 2007-10-26 16:06:06 -0500
  Modified the JS to ignore the [id:1] text at the end of the references.
 
 r69515 at pinglin (orig r4345):  sterling | 2007-10-31 12:59:44 -0400
  r13668 at riddle:  andrew | 2007-10-27 01:03:49 -0500
  Fixed AutoReference so that it is possible to set the field to empty if not mandatory.
 
 r69516 at pinglin (orig r4346):  sterling | 2007-10-31 13:00:05 -0400
  r13792 at riddle:  andrew | 2007-10-31 11:57:22 -0500
  Fixing POD coverage errors in the AutoReference plugin.
 
 r69517 at pinglin (orig r4347):  sartak | 2007-10-31 13:21:39 -0400
  r44449 at onn:  sartak | 2007-10-31 13:21:07 -0400
  Implement callback URLs and test them
 
 r69519 at pinglin (orig r4349):  sunnavy | 2007-10-31 15:20:42 -0400
 we need DateTime::Locale
 r69520 at pinglin (orig r4350):  sartak | 2007-10-31 17:15:18 -0400
  r44469 at onn:  sartak | 2007-10-31 17:14:59 -0400
  Most AccessToken tests done :) first implementation was right on
 
 r69521 at pinglin (orig r4351):  sartak | 2007-10-31 17:15:50 -0400
  r44473 at onn:  sartak | 2007-10-31 17:15:32 -0400
  Whoops, committed in a subdirectory
 
 r69522 at pinglin (orig r4352):  sartak | 2007-10-31 18:07:41 -0400
  r44475 at onn:  sartak | 2007-10-31 18:07:21 -0400
  Various refactors and cleanups
  Request tokens are now properly set to "used"
  Life of a request token lengthened on authorize
  Fixed duplicate timestamp/nonce check (and test)
 
 r69523 at pinglin (orig r4353):  audreyt | 2007-11-01 13:13:52 -0400
 * Jifty::I18N::promote_encoding - r4286 by yours truly broke 
   auto-decoding for POST requests.
   
   This is a better fix to avoid the "Unquoted / not allowed in
   Content-Type" warning (which arugably is a Email::MIME::ContentType
   glitch), by scanning the Content-Type header for the substring
   "charset" before parsing it.
 
   This way, when "charset" is not found in that header,
   we still fallback to Encode::Guess, regardless of whether
   the request was of type "multipart/form-data".
 r69524 at pinglin (orig r4354):  alexmv | 2007-11-01 14:42:40 -0400
  r24221 at zoq-fot-pik:  chmrr | 2007-10-30 18:21:04 -0400
   * Work around a "feature" in Hash::Merge < 0.10
 
 r69525 at pinglin (orig r4355):  alexmv | 2007-11-01 14:43:37 -0400
 
 r69526 at pinglin (orig r4356):  alexmv | 2007-11-01 14:43:47 -0400
 
 r69527 at pinglin (orig r4357):  alexmv | 2007-11-01 14:43:51 -0400
  r24273 at zoq-fot-pik:  chmrr | 2007-11-01 14:42:20 -0400
   * Horrible Hash::Merge workaround, part 2
 
 r69631 at pinglin (orig r4359):  sartak | 2007-11-02 14:43:38 -0400
  r44548 at onn:  sartak | 2007-11-02 14:43:04 -0400
  Correctly skip OAuth tests in the absence of Net::OAuth
 
 r69632 at pinglin (orig r4360):  sartak | 2007-11-02 14:48:06 -0400
  r44550 at onn:  sartak | 2007-11-02 14:47:40 -0400
  Make Test::WWW::Declare an optional dep for now
 
 r69635 at pinglin (orig r4363):  sterling | 2007-11-02 16:16:18 -0400
  r13658 at riddle:  andrew | 2007-10-26 14:24:15 -0500
  Fix it so that AutoReference fields show the _brief_description rather than the id in read mode.
 
 r69636 at pinglin (orig r4364):  sterling | 2007-11-02 16:17:37 -0400
  r13659 at riddle:  andrew | 2007-10-26 14:38:32 -0500
  Expanded the previous fix to cover sticky values and the initial value in the input tag.
 
 r69637 at pinglin (orig r4365):  sterling | 2007-11-02 16:17:45 -0400
  r13664 at riddle:  andrew | 2007-10-26 16:06:06 -0500
  Modified the JS to ignore the [id:1] text at the end of the references.
 
 r69638 at pinglin (orig r4366):  sterling | 2007-11-02 16:17:52 -0400
  r13668 at riddle:  andrew | 2007-10-27 01:03:49 -0500
  Fixed AutoReference so that it is possible to set the field to empty if not mandatory.
 
 r69639 at pinglin (orig r4367):  sterling | 2007-11-02 16:18:46 -0400
  r13817 at riddle:  andrew | 2007-11-02 14:58:30 -0500
  Fixed a problem where AutoReference breaks in Search actions.
 
 r69642 at pinglin (orig r4370):  clkao | 2007-11-04 02:15:12 -0500
 Add an additional trigger in Clickable for SinglePage to filter
 out state variables for region-__page.
 
 r69643 at pinglin (orig r4371):  clkao | 2007-11-04 06:23:36 -0500
 I18N plugin:
 - log when user is somehow able to pass in disallowed lang.
 - inline the initial dictionary json.
 
 r71464 at pinglin (orig r4376):  sartak | 2007-11-05 20:09:00 -0500
  r44469 at onn:  sartak | 2007-10-31 17:14:59 -0400
  Most AccessToken tests done :) first implementation was right on
 
 r71465 at pinglin (orig r4377):  sartak | 2007-11-05 20:09:12 -0500
  r44473 at onn:  sartak | 2007-10-31 17:15:32 -0400
  Whoops, committed in a subdirectory
 
 r71466 at pinglin (orig r4378):  sartak | 2007-11-05 20:09:23 -0500
  r44475 at onn:  sartak | 2007-10-31 18:07:21 -0400
  Various refactors and cleanups
  Request tokens are now properly set to "used"
  Life of a request token lengthened on authorize
  Fixed duplicate timestamp/nonce check (and test)
 
 r71467 at pinglin (orig r4379):  sartak | 2007-11-05 20:09:50 -0500
  r44697 at onn:  sartak | 2007-11-05 20:08:14 -0500
  Add a Jifty::DateTime set_current_user_timezone method
 
 r71468 at pinglin (orig r4380):  sartak | 2007-11-05 20:25:16 -0500
  r44707 at onn:  sartak | 2007-11-05 20:24:45 -0500
  Add Jifty::DateTime::from_epoch which does our usual timezone magic, use set_current_user_timezone more, too
 
 r71469 at pinglin (orig r4381):  clkao | 2007-11-06 03:11:39 -0500
 Add a ForwardCompatible config in Database section.  The usecase
 is that when you switch your checkout to something with older
 version of app schema, you can declare it is compatiable with
 the future version that is currently in db.
 
 r71470 at pinglin (orig r4382):  clkao | 2007-11-06 03:29:55 -0500
 support SELENIUM_RC_TEST_AGAINST and SELENIUM_RC_BROWSER environment variable, and allow args to be passed into selenium rc invoker.
 r71471 at pinglin (orig r4383):  alexmv | 2007-11-06 13:41:25 -0500
  r24425 at zoq-fot-pik:  chmrr | 2007-11-06 13:37:43 -0500
   * Remove debugging statement
 
 r71472 at pinglin (orig r4384):  clkao | 2007-11-06 17:23:51 -0500
 I18n: fix json dictionary fallback.
 r71473 at pinglin (orig r4385):  clkao | 2007-11-06 17:28:02 -0500
 I18N: load json dict from absolute path.
 r71474 at pinglin (orig r4386):  sartak | 2007-11-06 21:45:50 -0500
  r44749 at onn:  sartak | 2007-11-06 21:45:14 -0500
  First cut of protected resource requests
 
 r71486 at pinglin (orig r4391):  jesse | 2007-11-07 12:47:02 -0500
  r71485 at pinglin:  jesse | 2007-11-07 12:46:14 -0500
  * only run pod coverage tests if you're a developer
 
 r71640 at pinglin (orig r4395):  sartak | 2007-11-09 13:31:35 -0500
  r44778 at onn:  sartak | 2007-11-07 14:41:01 -0500
  Refactor the tests
 
 r71641 at pinglin (orig r4396):  sartak | 2007-11-09 13:31:43 -0500
  r44779 at onn:  sartak | 2007-11-07 14:50:59 -0500
  Whoops, the consumer and user were using the same mech. But fixing that didn't break anything :)
 
 r71642 at pinglin (orig r4397):  sartak | 2007-11-09 13:31:49 -0500
  r44785 at onn:  sartak | 2007-11-07 15:36:47 -0500
  Begin adding tests for protected resource requests
 
 r71643 at pinglin (orig r4398):  sartak | 2007-11-09 13:31:54 -0500
  r44786 at onn:  sartak | 2007-11-07 15:37:39 -0500
  Various improvements of try_oauth so it gets further (but not all the way) in the correct codepath
 
 r71644 at pinglin (orig r4399):  sartak | 2007-11-09 13:32:06 -0500
  r44787 at onn:  sartak | 2007-11-07 15:49:58 -0500
  More fixes to get protected resource requests going
  We might need a WWW::Mechanize::OAuth subclass, so that each get/post will be properly wrapped as an OAuth request
 
 r71650 at pinglin (orig r4405):  sartak | 2007-11-09 20:56:11 -0500
  r44970 at onn:  sartak | 2007-11-09 20:47:18 -0500
  Add another changelog sorting program, this one I think works a bit better :)
 
 r71651 at pinglin (orig r4406):  sartak | 2007-11-09 20:56:24 -0500
  r44971 at onn:  sartak | 2007-11-09 20:47:42 -0500
  Sort change groups by header, typo fix
 
 r71652 at pinglin (orig r4407):  sartak | 2007-11-09 20:56:27 -0500
  r44972 at onn:  sartak | 2007-11-09 20:55:15 -0500
  Add 'performance' tag to sort-changelog
 
 r71653 at pinglin (orig r4408):  sartak | 2007-11-09 21:08:31 -0500
  r44981 at onn:  sartak | 2007-11-09 21:08:04 -0500
  Add a Jifty::Manual which lists each of the manuals with a short description
  Secret confession: I was tired of perldoc Jifty::Manual::<tab> failing :)
 
 r71654 at pinglin (orig r4409):  gugod | 2007-11-10 03:04:39 -0500
 When it's a ::Create action, $event_info won't have a record_id
 there, cause a fatal error when it's published latter.
 
 r71655 at pinglin (orig r4410):  sunnavy | 2007-11-10 05:04:05 -0500
 refactor Jifty::Test a bit
 r71659 at pinglin (orig r4414):  clkao | 2007-11-10 11:33:08 -0500
 older Email::LocalDelivery is buggy.
 r71660 at pinglin (orig r4415):  jesse | 2007-11-11 00:47:37 -0500
  r71589 at pinglin:  jesse | 2007-11-09 12:51:51 -0500
  * Get rid of another place where we had a custom accessor that meant 'arguments' - for great consistency
 
 r71661 at pinglin (orig r4416):  jesse | 2007-11-11 00:47:57 -0500
  r71590 at pinglin:  jesse | 2007-11-09 12:52:30 -0500
  * Upgrade a region error from a debug to a warning. 
 
 r71662 at pinglin (orig r4417):  jesse | 2007-11-11 00:48:05 -0500
  r71639 at pinglin:  jesse | 2007-11-11 00:46:59 -0500
  * Fix compatibility with Test::WWW::Mechanize
 
 r71808 at pinglin (orig r4418):  c9s | 2007-11-12 00:58:25 -0500
 A command line completion script is added into contrib/
 r71811 at pinglin (orig r4421):  clkao | 2007-11-12 13:17:49 -0500
 In TD View handler, don't just return without printing
 headers if the response content is empty, as when you
 access /__jifty/empty.
 
 r71814 at pinglin (orig r4424):  chapman | 2007-11-13 11:53:51 -0500
 Fixed the feedback plugin so it works even if the current
 user doesn't have permission to read their own email.
 
 r71817 at pinglin (orig r4427):  alexmv | 2007-11-13 13:37:29 -0500
  r24696 at zoq-fot-pik:  chmrr | 2007-11-13 13:36:31 -0500
   * POD re-wrapping
   * perltidy
   * Use Jifty::DBI's new ->_new_record_args and _new_collection_args
   * Bump the JDBI dep accordingly
 
 r71819 at pinglin (orig r4429):  sunnavy | 2007-11-13 14:28:29 -0500
 require CSS::Squish 0.07, no need to call _resolve_file any more
 r71820 at pinglin (orig r4430):  alexmv | 2007-11-13 14:31:50 -0500
  r24705 at zoq-fot-pik:  chmrr | 2007-11-13 14:31:20 -0500
   * <embed> isn't a tag which gets a close on it
 
 r71821 at pinglin (orig r4431):  ruz | 2007-11-14 10:17:30 -0500
 * use object calls instead of instance calls
 * use better handling of roots so server relative paths
   work as expected unless you're bind to a location
 r71927 at pinglin (orig r4432):  sterling | 2007-11-14 21:32:17 -0500
  r14053 at dynpc145:  andrew | 2007-11-14 20:26:49 -0600
  Adding a description to Jifty::Everything docs.
 
 r71928 at pinglin (orig r4433):  sterling | 2007-11-14 21:32:27 -0500
  r14054 at dynpc145:  andrew | 2007-11-14 20:31:17 -0600
  Added a synopsis and license section to docs.
 
 r71936 at pinglin (orig r4435):  sunnavy | 2007-11-15 12:30:49 -0500
 merged prototype-1.6 to trunk
 r71938 at pinglin (orig r4437):  sunnavy | 2007-11-15 12:57:16 -0500
 not dep test for bin/sort-changelog
 r72011 at pinglin (orig r4439):  alexmv | 2007-11-15 15:22:53 -0500
  r24785 at zoq-fot-pik:  chmrr | 2007-11-15 15:20:31 -0500
   * Finalize triggers if possible (supported in forthcoming
     Class::Trigger release)
   * Override JDBI's default of having refers_to null return undef,
     instead of object with no id
   * RightsFrom 'foo_id' should work like RightsFrom 'foo'
 
 r72012 at pinglin (orig r4440):  sartak | 2007-11-15 15:42:44 -0500
  r44970 at onn:  sartak | 2007-11-09 20:47:18 -0500
  Add another changelog sorting program, this one I think works a bit better :)
 
 r72013 at pinglin (orig r4441):  sartak | 2007-11-15 15:42:51 -0500
  r44971 at onn:  sartak | 2007-11-09 20:47:42 -0500
  Sort change groups by header, typo fix
 
 r72014 at pinglin (orig r4442):  sartak | 2007-11-15 15:42:56 -0500
  r44972 at onn:  sartak | 2007-11-09 20:55:15 -0500
  Add 'performance' tag to sort-changelog
 
 r72015 at pinglin (orig r4443):  sartak | 2007-11-15 15:43:00 -0500
  r44981 at onn:  sartak | 2007-11-09 21:08:04 -0500
  Add a Jifty::Manual which lists each of the manuals with a short description
  Secret confession: I was tired of perldoc Jifty::Manual::<tab> failing :)
 
 r72016 at pinglin (orig r4444):  sartak | 2007-11-15 15:43:10 -0500
  r44996 at onn:  sartak | 2007-11-11 16:41:01 -0500
  Add changelogger dependencies
 
 r72017 at pinglin (orig r4445):  sartak | 2007-11-15 15:44:23 -0500
 
 r72021 at pinglin (orig r4449):  sartak | 2007-11-15 15:58:49 -0500
  r45207 at onn:  sartak | 2007-11-15 15:58:10 -0500
  Add TestMode: 1 to the default test config
 
 r72023 at pinglin (orig r4451):  sunnavy | 2007-11-16 12:35:57 -0500
 tiny typo fix
 r72024 at pinglin (orig r4452):  sartak | 2007-11-16 14:44:20 -0500
  r45262 at onn:  sartak | 2007-11-16 14:43:36 -0500
  Remove bin/*-changelog, as they've been moved into their own dist, App-Changelogger
 
 r72025 at pinglin (orig r4453):  sunnavy | 2007-11-16 14:54:06 -0500
 clean dependency test since changelogger has been moved out
 r72026 at pinglin (orig r4454):  sartak | 2007-11-16 15:23:18 -0500
  r45265 at onn:  sartak | 2007-11-16 14:56:01 -0500
  Remove the bin/*-changelog workaround in t/01-deps
 
 r72027 at pinglin (orig r4455):  sartak | 2007-11-16 15:23:33 -0500
  r45267 at onn:  sartak | 2007-11-16 15:21:43 -0500
  carp, don't warn, for a "title" Mason/TD warning
 
 r72030 at pinglin (orig r4458):  sartak | 2007-11-16 16:24:06 -0500
  r45274 at onn:  sartak | 2007-11-16 16:23:30 -0500
  Bump to 0.71116, update Changelog, SIGNATURE, MANIFEST, etc
 
 r72031 at pinglin (orig r4459):  sartak | 2007-11-16 18:39:04 -0500
  r45277 at onn:  sartak | 2007-11-16 18:38:57 -0500
  Revert r4435 because it breaks Safari 3.0.4
 
 r72032 at pinglin (orig r4460):  sterling | 2007-11-17 23:53:50 -0500
  r14120 at dynpc145:  andrew | 2007-11-17 22:53:30 -0600
  Adding SYNOPSIS, LICENSE, and code comments.
 
 r72033 at pinglin (orig r4461):  sterling | 2007-11-17 23:55:24 -0500
  r14122 at dynpc145:  andrew | 2007-11-17 22:55:17 -0600
  Stripping off the execute flag in SVN.
 
 r72122 at pinglin (orig r4462):  jesse | 2007-11-18 16:39:16 -0500
  r72119 at pinglin:  jesse | 2007-11-18 12:46:06 -0600
  * Added a new 'AutoUpgrade' option for Jifty and Application schemas, 
  so you don't need to manually upgrade every time jifty or your app version bumps
 
 r72142 at pinglin (orig r4463):  yves | 2007-11-19 06:29:56 -0500
 update fr.po and debian control
 
 r72145 at pinglin (orig r4466):  yves | 2007-11-19 12:17:15 -0500
 first release for an experimental mixin ldap release
 
 r72146 at pinglin (orig r4467):  sartak | 2007-11-19 18:46:25 -0500
  r45310 at onn:  sartak | 2007-11-19 18:46:02 -0500
  Add better support for dates in the REST plugin, rewrite a somewhat hairy method
 
 r72162 at pinglin (orig r4470):  ishigaki | 2007-11-20 01:48:17 -0500
 added "rel" attribute to Jifty::Web::Form::Link/Element, to allow <a rel="nofollow"...> or <a rel="next"...>
 r72273 at pinglin (orig r4505):  sterling | 2007-11-20 14:38:15 -0500
  r14171 at dynpc145:  andrew | 2007-11-20 13:38:04 -0600
  Assume the page is "1" if no value for page is given to the pager in CRUD.
 
 r72276 at pinglin (orig r4508):  falcone | 2007-11-20 16:02:19 -0500
  r26763 at ketch:  falcone | 2007-11-20 16:00:31 -0500
  * mention Net::LDAP because of the new Plugin so we don't fail 01-dependencies
 
 r72327 at pinglin (orig r4536):  clkao | 2007-11-25 17:39:42 -0500
 r4534 belongs to trunk.
 
  In ClassLoader, if an action matches Create* that isn't about
  creating a model, don't just return but continue to see if there's
  any matching actions from plugins.
  
  This solves CreateOpenIDUser in particular.
 
 r72328 at pinglin (orig r4537):  jesse | 2007-11-26 00:48:08 -0500
  r72323 at pinglin:  jesse | 2007-11-26 00:39:46 -0500
  * A bit of refactoring to expose some more overridable templates in CRUD view
 
 r72336 at pinglin (orig r4538):  yves | 2007-11-26 11:41:32 -0500
 add Jasig CAS plugin with mixin model user
 
 r72338 at pinglin (orig r4540):  jesse | 2007-11-26 13:38:31 -0500
  r72334 at pinglin:  jesse | 2007-11-26 11:42:35 -0500
  * upgraded request logging from DEBUG to INFO
 
 r72339 at pinglin (orig r4541):  jesse | 2007-11-26 13:38:41 -0500
  r72335 at pinglin:  jesse | 2007-11-26 13:29:51 -0500
  * More trying to force caching of static content.
  
 
 r72374 at pinglin (orig r4543):  sunnavy | 2007-11-26 14:40:28 -0500
 merged prototpe-1.6 to trunk again, wish it's all right this time :)
 r72375 at pinglin (orig r4544):  sartak | 2007-11-26 15:14:15 -0500
  r45601 at onn:  sartak | 2007-11-26 15:13:53 -0500
  Remove some debugging statements
 
 r72381 at pinglin (orig r4545):  gugod | 2007-11-27 01:33:54 -0500
 Unbreak OpenID plugin with new Jifty::API implementation.
 
 Give fully-qualified action class name here because Jifty::API does
 not resolve action class names to those ones provided by plugins
 (those under Jifty::Plugin::*::Action namespace) This is maybe a
 bug, but need further discussion.
 
 r72382 at pinglin (orig r4546):  sunnavy | 2007-11-27 11:07:17 -0500
 make a looser check for Content-Type since firefox 3 will append charset stuff to it sometimes
 r72404 at pinglin (orig r4547):  sartak | 2007-11-27 20:40:29 -0500
  r45691 at onn:  sartak | 2007-11-27 20:40:06 -0500
  Add an 'as_user' method and have 'as_superuser' use it
 
 r72420 at pinglin (orig r4551):  gugod | 2007-11-28 05:45:03 -0500
 revert its previous revision for it's fixed earler in classloader.
 
 r72434 at pinglin (orig r4553):  sartak | 2007-11-28 16:40:25 -0500
  r45710 at onn:  sartak | 2007-11-28 16:40:03 -0500
  Don't send cookies for static files. We suspect that doing so was screwing up non-compliant proxies that were caching the cookie and causing.. "amusing" security problems
 
 r72441 at pinglin (orig r4560):  jesse | 2007-11-28 18:39:44 -0500
  r72432 at pinglin:  jesse | 2007-11-28 18:34:07 -0500
  * Somewhat more correct handling of follow_link_ok. (Better compat with WWW::Mechanize's syntax)
  
 


Modified: jifty/branches/js-refactor/AUTHORS
==============================================================================
--- jifty/branches/js-refactor/AUTHORS	(original)
+++ jifty/branches/js-refactor/AUTHORS	Thu Nov 29 12:25:16 2007
@@ -19,7 +19,7 @@
 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>
+Agent Zhang <agentzh at agentzh.org>
 Pawel Murias <pmurias at woobling.org>
 Kevin Falcone <falcone at bestpractical.com>
 Andrew Sterling Hanenkamp <sterling at hanenkamp.com>
@@ -31,3 +31,6 @@
 Andreas Koenig <andreas.koenig.7os6VVqR at franz.ak.mind.de>
 sunnavy <sunnavy at gmail.com>
 Shawn M Moore <sartak at gmail.com>
+Edward Funnekotter <efunneko at gmail.com>
+Cornelius Lin <c9s at aiink.com>
+Todd Chapman <todd at chaka.net>

Modified: jifty/branches/js-refactor/Changelog
==============================================================================
--- jifty/branches/js-refactor/Changelog	(original)
+++ jifty/branches/js-refactor/Changelog	Thu Nov 29 12:25:16 2007
@@ -1,3 +1,1625 @@
+Jifty 0.71116
+
+I18N
+====
+
+ * Delay i18n handle init until we have session. SkeletonApp dispatcher rule
+   to set language in session. - clkao
+
+ * I18N plugin: log when user is somehow able to pass in disallowed lang.
+   inline the initial dictionary json. - clkao
+
+ * I18N: load json dict from absolute path. - clkao
+
+ * I18N::Action::SetLang: scalar::defer is trigger some oddness, rewrite
+   available languages as normal runtime code, and cached. - clkao
+
+ * I18n: fix json dictionary fallback. - clkao
+
+ * Jifty::I18N - POD fixup. - audreyt
+
+ * Jifty::I18N: Implement the L10N.AllowedLang key to limit the set of
+   languages available to the user. Use case: ja.po is incomplete and we wish
+   to hide it from users. Requested by: clkao - audreyt
+
+ * Jifty::I18N: provide available_languages method. - clkao
+
+ * Jifty::I18N::promote_encoding - r4286 by yours truly broke auto- decoding
+   for POST requests. This is a better fix to avoid the "Unquoted / not
+   allowed in Content-Type" warning (which arugably is a
+   Email::MIME::ContentType glitch), by scanning the Content- Type header for
+   the substring "charset" before parsing it. This way, when "charset" is not
+   found in that header, we still fallback to Encode::Guess, regardless of
+   whether the request was of type "multipart/form-data". - audreyt
+
+ * Jifty::I18N::promote_encoding: Multi-part form data have no notion of
+   charsets, so we return the string verbatim here, to avoid the "Unquoted /
+   not allowed in Content-Type" warnings when the Base64- encoded MIME
+   boundary string contains "/". Prompted by this Content- Type header found
+   in real world: multipart/form-data; boundary=----
+   WebKitFormBoundaryRqXyEnBQ/5VSsexe which triggered this error: --
+   2007/10/22 04:19:30 WARN> Carp.pm:46 Carp::carp Unquoted / not allowed in
+   Content-Type! at /usr/local/lib/perl5/site_perl/5.9.5/Jifty/I18N.pm line
+   226 -- 2007/10/22 04:19:30 WARN> Carp.pm:46 Carp::carp Illegal Content-
+   Type parameter /5VSsexe at
+   /usr/local/lib/perl5/site_perl/5.9.5/Jifty/I18N.pm line 226 - audreyt
+
+ * Jifty::Plugin::I18N: provides SetLang action. provides loc.js and other
+   facilities to l10n your javascript along with po. - clkao
+
+ * Provide get_current language method in Jifty::I18N. - clkao
+
+ * Support serving json version of po and localization in javascript
+   space. - clkao
+
+ * Update zh-tw po to include translations for authentication::password
+   plugin. - clkao
+
+ * allow switching current language for Localization. - clkao
+
+ * bin/jifty po now takes two more options: --dir for additional directories
+   to look at so javascript files can be scanned. --js for generating json
+   dictionaries for messages appeared in javascript files declared with
+   Jifty::Web. - clkao
+
+ * loc.js cleanups. - clkao
+
+ * updated ja.po - ishigaki
+
+ * zh_tw l10n for Authenication::Password plugin. - clkao
+
+BUGFIX
+======
+
+ * Added missing jifty-result-popup div to the page detritus section of
+   Jifty::View::Declare::Page. - sterling
+
+ * Allow users to name their apps in lowercase (as pragmas ?!). Reported by
+   SteveH++ - jesse
+
+ * Bring the default back, but at 0.0.0 to prevent uninitialized complaints.
+   - sterling
+
+ * Class::Accessor::Fast cleanups - jesse
+
+ * Clean up Jifty::Web initialization a bit. - jesse
+
+ * Clean up the masonXXXXXXXXXX temp directory after each test run. - sterling
+
+ * Do not do clever & broken input_name for buttons when preserve_state is
+   false. - clkao
+
+ * Don't do weird things with loading model subclasses - jesse
+
+ * Don't override object_type if the CRUD view package already defines
+   object_type. - sterling
+
+ * Fix compatibility with Test::WWW::Mechanize - jesse
+
+ * Fix mismerge.. - sartak
+
+ * Fixes to several of my fixes for
+   Jifty::Request::load_from_data_structure - jesse
+
+ * Fixing a bug that prevents any but the first plugin being checked for
+   schema updates. - sterling
+
+ * Fixing a typo in the plugin DB version metadata key affecting upgrades.
+   - sterling
+
+ * Horrible Hash::Merge workaround, part 2 - alexmv
+
+ * Improving schema setup for plugins that are turned on after the application
+   is initially deployed. - sterling
+
+ * Jifty::Request::Mapper - Avoid uninitialized warnings when
+   $args{destination} ends up undefined. - audreyt
+
+ * Make sure Jifty::Filter::DateTime doesn't disturb Floating datetimes
+   Otherwise, dates would get set to the current_user's timezone, throwing a
+   wrench in the works - sartak
+
+ * Menu class is undef by default; fall back to the empty string - alexmv
+
+ * Minor edit: fixing reference to Jifty::Request::Action to Jifty::Action
+   - sterling
+
+ * Pg wants to connect to template1, but CREATE DATABASE foo TEMPLATE
+   template0 - alexmv
+
+ * Plugin upgrade incorrectly assumes a DB version of 0.0.1 when none is
+   found. - sterling
+
+ * Rather than helpfully failing to load model classes that have
+   compiletime errors, actually die with the error that caused the class
+   not to load. - jesse
+
+ * Remove debug info. - clkao
+
+ * Remove debugging statement - alexmv
+
+ * Removing the display style from the jifty-result-popup, which is breaking
+   it. - sterling
+
+ * Revert clkao's fixes for now because they break apps :/ - sartak
+
+ * Revert r4117 as the element readiness state is not compatible with what it
+   was. - clkao
+
+ * Reverting the addition of these files which were added by a push error.
+   - sterling
+
+ * Some databases have schema creation single-threadedness. If they block,
+   wait and retry for up to a minute - jesse
+
+ * Some parts of that last commit to use only one Module::Pluggable weren't as
+   hot as they appeared to be; - jesse
+
+ * The JIFTY_SITE_CONFIG environment variable was silently ignored. - audreyt
+
+ * Undoing 4302. There is such a class. Thanks to chmrr. - sterling
+
+ * Undoing previous accidental patch to Jifty::Continuation because I have not
+   finished proofing it. - sterling
+
+ * Use ->clone on DateTime objects to avoid possible DST issues, etc - sartak
+
+ * Use blessed() instead of ref() to keep an if statement from tripping on
+   ->isa(). - sterling
+
+ * We depend on the bugfix that
+   File::Spec->rel2abs("/foo","/foo") eq "." - alexmv
+
+ * When it's a ::Create action, $event_info won't have a record_id there,
+   cause a fatal error when it's published latter. - gugod
+
+ * When loading a web request from a data structure, don't totally throw away
+   the path it was requested at. - jesse
+
+ * Work around a "feature" in Hash::Merge < 0.10 - alexmv
+
+ * carp, don't warn, for a "title" Mason/TD warning - sartak
+
+ * collection generator may be called when we're loading models and not all
+   models have been loaded to the moment, so we have to try to require the
+   model before testing if it esists. - ruz
+
+ * don't gen a collection class if there is no model - ruz
+
+ * don't warn if database doesn't exist when dropping database - sunnavy
+
+ * friendly_date should be relative to the user, not floating - sartak
+
+ * no need to print STDERR, we can warn in $SIG{__WARN__} - sunnavy
+
+ * oops, should look for js files under js. - clkao
+
+ * our version of prototype can't deal with synchronous request. - clkao
+
+ * revert changes to ClassLoader. Collection classes are failing to be
+   generated in some TD views - falcone
+
+ * rollback r4242 which cached the gzipped js/css even through develmode
+   reload. - clkao
+
+ * tabview shouldn't force absolute paths to relative ones - jesse
+
+ * warnings should be to STDERR - sunnavy
+
+ * we want to compute UUIDs every time. really. it's not useful to always have
+   the same uuid - jesse
+
+ * webservices_rediret: don't redirect if there's any failed action. - clkao
+
+CORE
+====
+
+ * Add Jifty::DateTime::from_epoch which does our usual timezone magic, use
+   set_current_user_timezone more, too - sartak
+
+ * Add Jifty::Util->is_app_root to determine whether a path looks enough like
+   the app root - sartak
+
+ * Add a 'now' method to Jifty::DateTime which sets the timezone to the
+   current user's timezone. DateTime::now passes time_zone => UTC to
+   Jifty::DateTime::now, which caused it to skip over the currentuser/timezone
+   magic. - sartak
+
+ * Add a ForwardCompatible config in Database section. The usecase is that
+   when you switch your checkout to something with older version of app
+   schema, you can declare it is compatiable with the future version that is
+   currently in db. - clkao
+
+ * Add a Jifty::DateTime set_current_user_timezone method - sartak
+
+ * Add the ability for apps to have very fine-grained testing (such as
+   overriding config for a subdir of t/, or even a particular test file).
+   Still needs tests and I suppose some more doc :) - sartak
+
+ * Added the ability to craft a Jifty button with a "submit" link which
+   submits hardcoded arguments. - jesse
+
+ * Change how plugin is initialized so _pre_init is actually noted. - clkao
+
+ * Don't require recipients in Jifty::Notificaiton be objects. - clkao
+
+ * Finalize triggers if possible (supported in forthcoming Class::Trigger
+   release). Override JDBI's default of having refers_to null return undef,
+   instead of object with no id. RightsFrom 'foo_id' should work like
+   RightsFrom 'foo' - alexmv
+
+ * Fix the problem of tests having both config and a correctly-relative $0
+   Jifty::SubTest now makes available the old Cwd (which as 03-nosubtest
+   demonstrates doesn't break apps that don't use Jifty::SubTest) Also a bit
+   of doc in Jifty::Config The Feature is Done. :) - sartak
+
+ * Fixes to not cache compressed css+js while in devel mode. Refactoring js
+   compression code into the plugin where it belongs. - jesse
+
+ * Get rid of another place where we had a custom accessor that meant
+   'arguments' - for great consistency - jesse
+
+ * In Jifty::DateTime->new, set the timezone to floating if it looks like just
+   a date - sartak
+
+ * Initial implementation of Jifty::Test::WWW::Declare, with basic tests in
+   TestApp - sartak
+
+ * Just a perltidy - alexmv
+
+ * Make action_arguments effective from js update() - clkao
+
+ * Making the as_*_action() methods accept a paramhash for additional
+   new_action() parameters. - sterling
+
+ * New as_string method for Jifty::Web::Form::Link. - clkao
+
+ * Override T:W:D's get with ours which prepends the server's URL Start
+   documentation - sartak
+
+ * Recursively transform content structures. Set a flag when we change the
+   method, in case apps care - alexmv
+
+ * Remove the unused drop-column logic from jifty schema --setup, since
+   ->columns returns only active cols - sartak
+
+ * Ripping out new_record_action() from Jifty::Web. Re-implementing it as
+   as_create_action(), as_update_action(), as_delete_action, and
+   as_search_action() in Jifty::Record and Jifty::Collection. - sterling
+
+ * Test config loading is now complete code-wise :) And it has tests now too!
+   Just need to doc it in manual or Jifty::Config - sartak
+
+ * Untabify - alexmv
+
+ * Updated the JSON and YAML transports for web service requests to allow
+   complex data structures in actions - jesse
+
+ * Updating the Jifty web menu component to respect classes added by
+   users - jesse
+
+ * allow onsubmit in form to include custom javascript. - clkao
+
+ * if refers_to is not mandatory field then add 'no_value' option to
+   select box - ruz
+
+ * merged prototype-1.6 to trunk - sunnavy
+
+ * use object calls instead of instance calls. use better handling of roots
+   so server relative paths work as expected unless you're bind to a
+   location - ruz
+
+ * working toward onclick => { submit => { action => $action, arguments
+   => { foo => 'value', bar => 'other value'} }} Jifty's js still needs
+   help - jesse
+
+CRUD
+====
+
+ * added a bit more semantic markup to the crud view - jesse
+
+DOC
+===
+
+ * A lot of POD for OAuth, and some tiny fixes elsewhere - sartak
+
+ * Add a Jifty::Manual which lists each of the manuals with a short
+   description. Secret confession: I was tired of perldoc Jifty::Manual::<tab>
+   failing :) - sartak
+
+ * Added a synopsis and license section to docs. - sterling
+
+ * Adding a description to Jifty::Everything docs. - sterling
+
+ * Adding another CRUD setup example to the synopsis. - sterling
+
+ * Adding documentation for Action and Static attributes to resolve POD
+   coverage test failures. - sterling
+
+ * Another example app: ShrinkURL, which is basically (surprise!)
+   tinyurl - sartak
+
+ * Clean up the Pod, added a Synopsis, added a Why? section, added code
+   comments, and some code tidying. - sterling
+
+ * Cleaning up documentation to fix pod coverage test failures. - sterling
+
+ * Doc - sartak
+
+ * Fix Jifty::Server::Prefork's document so it mentions Net::Server::PreFork,
+   instead of Net::Server::Prefork. (Yes this is very confusing.) - audreyt
+
+ * Fix package names, add copyright notices - sartak
+
+ * Fixing POD coverage errors in the AutoReference plugin. - sterling
+
+ * Fixing pod coverage problems with the jQuery plugin. - sterling
+
+ * Improving POD and minor tidying. - sterling
+
+ * Include the http method (GET, POST, etc) in the debug message - sartak
+
+ * Jifty::Notification - Minor doc fix - The html_body method was misspelled
+   as html-body. - audreyt
+
+ * More comments - sartak
+
+ * POD clean-up, some code commenting, and minor tidying. - sterling
+
+ * POD for LeakTracker's subs - sartak
+
+ * POD re-wrapping. perltidy. Use Jifty::DBI's new ->_new_record_args and
+   _new_collection_args. Bump the JDBI dep accordingly - alexmv
+
+ * SendFeedback.pm: Take away the phrase "hiveminder" from the
+   documentation. - audreyt
+
+ * Simplifying the CRUD code for generating the record_class and making it
+   more flexible. Adding a stub for CRUD testing. - sterling
+
+ * a tiny change and typo fix - sunnavy
+
+ * doc fixes - sunnavy
+
+ * pod fixes - jesse
+
+ * tiny typo fix - sunnavy
+
+ * typo fix - sunnavy
+
+ * updated my email address in AUTHORS to agentzh at agentzh.org - agentz
+
+INSTALL
+=======
+
+ * Add changelogger dependencies - sartak
+
+ * Added requirement for URI 1.31, which adds uri_escape_utf8(). - sterling
+
+ * Adding File::Temp 0.15 requirement for cleanup(). - sterling
+
+ * Bump JDBI dep to 0.45 (Filter::DateTime changes) - sartak
+
+ * Force a clone which doesn't have as many bugs - alexmv
+
+ * Jifty::DBI now uses Class::Trigger instead of Jifty::DBi::Class::Trigger
+   (since rev3968) - ishigaki
+
+ * Make Test::WWW::Declare an optional dep for now - sartak
+
+ * Modules other than Net::OAuth::Request in Net-OAuth distro don't have
+   version - ishigaki
+
+ * Move memleak deps into its own Makefile.PL section - sartak
+
+ * Removing Test::Log4perl. It isn't used anywhere anymore. - sterling
+
+ * Require Net::OAuth 0.04 because 0.03 needs my patch. Don't run tests if
+   Net::OAuth is uninstalled - sartak
+
+ * added Test::Log4perl dep again; it is surely used at t/TestApp/t/02-dispatch-show-rule-in-wrong-
+   ruleset.t - ishigaki
+
+ * older Email::LocalDelivery is buggy. - clkao
+
+ * require CSS::Squish 0.07, no need to call _resolve_file any more - sunnavy
+
+ * we need DateTime::Locale - sunnavy
+
+MISC
+====
+
+ * A command line completion script is added into contrib/ - c9s
+
+ * Add 'performance' tag to sort-changelog - sartak
+
+ * Add another changelog sorting program, this one I think works a bit better
+   :) - sartak
+
+ * Remove bin/*-changelog, as they've been moved into their own dist, App-
+   Changelogger - sartak
+
+ * Sort change groups by header, typo fix - sartak
+
+ * add a bug tag - falcone
+
+ * add an exclude command for logs you don't need to ship - falcone
+
+ * fix so we don't try to print to a bogus filehandle - falcone
+
+ * make format_entry return text rather than printing. use the returned
+   message to generate the changelog rather than printing to STDOUT - falcone
+
+ * remove doubled loop over log messages. explicitly loop over
+   uncategorized first, then offer the user to review all the changes
+   again. reformatting - falcone
+
+ * talk about how to do iterative editing - falcone
+
+ * usage documentation - falcone
+
+ * use existing t-discard tag. don't autoskip things, just group them together
+   and you can delete them - falcone
+
+PERFORMANCE
+===========
+
+ * The cache was actually a huge performance problem - jesse
+
+ * Use much less UNIVERSAL::require (to shave a second or two off start
+   times) - jesse
+
+ * When replacing a region, use the technique described in
+   http://blog.stevenlevithan.com/archives/faster-than-innerhtml to improve
+   performance. - clkao
+
+ * Cache gzipped output from the compressedcssandjs plugin - jesse
+
+ * Fix a bug that after you access a static css file, it breaks the compressed
+   css by not actually squishing main.css, hence requests static css files
+   under __jifty/css. - clkao
+
+ * In Jifty::Model::Session: Turn session_id, data_key, key_type into
+   case_sensitive, so we don't do useless tolower on loading sessions. index
+   session_id by default. - clkao
+
+ * Switch to a single call to Module::Pluggable to help improve startup
+   performance. - jesse
+
+PLUGIN
+======
+
+ * Add an additional trigger in Clickable for SinglePage to filter out state
+   variables for region-__page. - clkao
+
+ * Add cdn option to CompressedCSSandJS plugin. - clkao
+
+ * Add new LeakDetector plugin. It kinda sorta works :) - sartak
+
+ * Added a new plugin that provides an autocomplete widget specially for
+   record references. Helpful in cases when a select is just be too big to
+   contemplate. - sterling
+
+ * Adding missing JavaScript file and cleaning up a method left-over from
+   testing. - sterling
+
+ * Expanded the previous fix to cover sticky values and the initial value in
+   the input tag. - sterling
+
+ * Fix it so that AutoReference fields show the _brief_description rather than
+   the id in read mode. - sterling
+
+ * Fixed AutoReference so that it is possible to set the field to empty if not
+   mandatory. - sterling
+
+ * Fixed a problem where AutoReference breaks in Search actions. - sterling
+
+ * Fixed the feedback plugin so it works even if the current user doesn't have
+   permission to read their own email. - chapman
+
+ * Fixing the data fetcher on the AutoReference box so that it sends the text
+   being typed rather than the current value in the hidden field. - sterling
+
+ * Get rid of spurious warning in Auth::Password plugin - sartak
+
+ * Handle region redirect with continuation return (for spa). - clkao
+
+ * Implement callback URLs and test them - sartak
+
+ * Include the list of object types leaked in the request report - sartak
+
+ * Make find_plugins() sensitive to wantarray. - sterling
+
+ * Many improvements, including some pages - sartak
+
+ * Modified the JS to ignore the [id:1] text at the end of the references.
+   - sterling
+
+ * More cleanup, hide zero-leak requests by default - sartak
+
+ * More fixes to get protected resource requests going. We might need a
+   WWW::Mechanize::OAuth subclass, so that each get/post will be properly
+   wrapped as an OAuth request - sartak
+
+ * Move files from LeakDetector to LeakTracker (because that's the term Cat
+   uses) - sartak
+
+ * Name the first Jifty->new during test 'pre_init', and tell plugins not to
+   register triggers just yet to avoid duplicated triggers. - clkao
+
+ * Now we send responses to token requests (and test them) What's left:
+   Authorizing request tokens is coded, but it's slightly flawed somehow
+   Getting access tokens is coded, but not tested No code yet for the actual
+   accessing of resources - sartak
+
+ * Patch by Alex to make sp_submit_form to be happier with J:V vars. - clkao
+
+ * Provide an after_include_javascript for plugins to do javascript
+   initialisation. - clkao
+
+ * Refactor timestamp and nonce out of tokens and into consumers. Thanks to
+   hannesty for discussing how it should work - sartak
+
+ * Some cleanups in LeakDetector - sartak
+
+ * Some more fleshing out of the OAuth plugin - sartak
+
+ * Somehow this file got duplicated. I might've done it. shrug - sartak
+
+ * Start adding an OAuth plugin - sartak
+
+ * Start writing tests Change timestamp to (ugh) time_stamp, because timestamp
+   is a reserved word in a few SQL apps - sartak
+
+ * The REST dispatcher should warn $@ if an error occurs - sartak
+
+ * Turns out we can't (so easily) have the URLs be configurable. Not too much
+   of a problem though Other fixes - sartak
+
+ * Users can now authorize/deny request tokens - sartak
+
+ * Various improvements of try_oauth so it gets further (but not all the way)
+   in the correct codepath - sartak
+
+ * Various refactors and cleanups Request tokens are now properly set to
+   "used" Life of a request token lengthened on authorize Fixed duplicate
+   timestamp/nonce check (and test) - sartak
+
+ * Whoops, committed in a subdirectory - sartak
+
+ * Whoops, the consumer and user were using the same mech. But fixing that
+   didn't break anything :) - sartak
+
+ * actormetadata: provide a current_user_is_owner method. - clkao
+
+ * default not to preserve state when spa is replacing __page - clkao
+
+ * simplify ccjs plugin with static handler method call. - clkao
+
+ * spa: don't translate javascript: links. - clkao
+
+ * tabview: fix the logic for defered tabs. - clkao
+
+TESTING
+=======
+
+ * Add (grr, passing) tests for setting columns in the test app - sartak
+
+ * Add TestMode: 1 to the default test config - sartak
+
+ * Add some more tests for Jifty::DateTime in the TestApp - sartak
+
+ * Begin adding tests for protected resource requests - sartak
+
+ * Clearing out old new_record_action() tests. - sterling
+
+ * Correctly skip OAuth tests in the absence of Net::OAuth - sartak
+
+ * Dispatcher and config fixes, start adding a new test file - sartak
+
+ * Expand the tests, now they actually test something real! So right now the
+   OAuth plugin is rejecting a few kinds of bad requests (e.g. unknown
+   consumer) while accepting a good request. Now to make more requests of each
+   type :) - sartak
+
+ * First cut of Selenium testing support in Jifty. - clkao
+
+ * Fix test count - alexmv
+
+ * Fixing long-broken support for the "JIFTY_FASTTEST" env variable - jesse
+
+ * Mailboxes need to have unique names for parallel testing - alexmv
+
+ * More tests, start implementing callbacks, but failing :) - sartak
+
+ * Most AccessToken tests done :) first implementation was right on - sartak
+
+ * Refactor the tests - sartak
+
+ * Skip OAuth tests if Crypt::OpenSSL::RSA is unavailable RSA should be
+   optional, will fix later - sartak
+
+ * The first few test scripts are now complete, and uncovered many bugs.
+   Yay. - sartak
+
+ * When testing, don't just use a single global test database. Instead, use
+   one per testfile. This allows some measure of parallelization - jesse
+
+ * Wrote most of the authorization tests - sartak
+
+ * added TestApp-Plugin-OnClick, initially for the update of
+   prototype.js - sunnavy
+
+ * clean dependency test since changelogger has been moved out - sunnavy
+
+ * first cut of tests for singlepage app plugin. - clkao
+
+ * my computer is slow. be more patient about selenium rc startup. - clkao
+
+ * not dep test for bin/sort-changelog - sunnavy
+
+ * only run pod coverage tests if you're a developer - jesse
+
+ * refactor Jifty::Test a bit - sunnavy
+
+ * support SELENIUM_RC_TEST_AGAINST and SELENIUM_RC_BROWSER environment
+   variable, and allow args to be passed into selenium rc invoker. - clkao
+
+VIEW
+====
+
+ * <embed> isn't a tag which gets a close on it - alexmv
+
+ * Add a hook to change a menu item's label - sartak
+
+ * Add support for "title" attribute on elements, which shows tooltips -
+   sartak
+
+ * Added an app_page_footer page call to jifty view declare page - jesse
+
+ * Additional selector to make sure that autocomplete hides .hidden_value on
+   .inline forms. - sterling
+
+ * In TD View handler, don't just return without printing headers if the
+   response content is empty, as when you access /__jifty/empty. - clkao
+
+ * Making Jifty::Web::Menu more extensible. - sterling
+
+ * Upgrade a region error from a debug to a warning. - jesse
+
+
+
+
+
+Jifty 0.70824
+
+INSTALL
+=======
+
+ * WWW::Mechanize versions before 1.30 had broken gzip behaviour which broke
+   tests - jesse
+
+ * properly skip client side td test if new B is not found. - clkao
+
+ * GD is not mandatory requirement. Skip chart test if not found. - clkao
+
+ * require Class::Trigger 0.12 for abortable triggers used by css/js
+   compression plugin. - clkao
+
+ * debian packaging - yves
+
+ * Makefile.PL - Jifty now depends on TD 0.26 - agentz
+
+ * r1254 at agentz-office: agentz | 2007-08-10 17:08:14 +0800 Makefile.PL - do
+   NOT skip any tests under t/ unintentionally ;) - agentz
+
+ * Updated the module recommendations for the Chart plugin. - sterling
+
+ * Adding Image::Info dependency used during testing of Jifty::Plugin::Chart
+   - sterling
+
+ * add missing Test::Log4perl dep and update the Jifty::DBI requirement -
+   falcone
+
+ * Changing the Jifty console requirement back to default => 0. - sterling
+
+ * debian dependancies
+* T::D 0.21 needed to pass tests - yves
+
+ * add IPC::Run3 to dependency. - clkao
+
+ * Update dep to include version of Module::Pluggable that is patched so that
+   compile errors are caught - trs
+
+ * Add dependency on Class::Trigger - alexmv
+
+ * here sunnavy comes, ;-) - sunnavy
+
+ * typo in recommends - falcone
+
+ * Add myself to AUTHORS - andk
+
+ * Better diagnostic messages in the dependency checking script. Thanks to
+   Andreas Koenig - jesse
+
+ * don't index plugins' test directories - jesse
+
+
+TESTING
+=======
+
+ * Add failing tests for load_by_kv with chr(0) - sartak
+
+ * clean up load_by_kv - jesse
+
+ * The warnings come from the server code after the fork. Test::Log4Perl isn't
+   going to catch them. - jesse
+
+ - Fix our test mech to not choke on non-default cookie names
+ - Clean up page headers
+ - Remove unecessary var - trs
+
+ * Fix the template subclassing tests. - clkao
+
+ * Helper method for mounting crud view. - clkao
+
+ * Add a little pod coverage - sartak
+
+ * Make sure there's always a stash during testing since we don't always go
+   through the web - sartak
+
+ * Pass defaults to render_region in test. - clkao
+
+ * the actual templates for the 16- test.t/TestApp - clkao
+
+ * failing tests for region and show inconsistency. - clkao
+
+ * make a chdir conform to the rest of the test suite and use Jifty::SubTest
+   chdir noticed by Andreas Koenig during his cleanups - falcone
+
+ * add tests for UTF-8 related things - ruz
+
+ * a failing test about utf8 when using T::D - yves
+
+*                 we need bytes in escape_uri then 'use bytes'
+* resulting string is always valid ascii string,
+  so we shouldn't forcibly set utf flag - ruz
+
+ * fast test support: truncate tables rather than recreating database when
+   JIFTY_FAST_TEST is specified. - clkao
+
+VIEW
+====
+
+ * update tabview plugin to use new relative path syntax for td show(). -
+   clkao
+
+ * don't use pager to determine items as list_items might be called directly
+   and no pager has been set on the collection. - clkao
+
+ * Jifty::Plugin::SkeletonApp::Dispatcher - Do not override the 'Home' menu
+   item if the app had set it already. - audreyt
+
+ * Jifty::View::Declare::CRUD - I18N. - audreyt
+
+ * Fixed a problem where some disabled elements weren't re-enabled after a
+   submission when the disabled elements were outside of the fragment being
+   refreshed. - efunneko
+
+ * Added a new helper, new_record_action(), that wraps new_action() with
+   additional help creating Create, Update, Delete, and Search actions for
+   models. - sterling
+
+ * Fixes to use_mason_wrapper():
+  * Added a test to make sure use_mason_wrapper() works.
+  * Added $jifty_internal_request to note whether a Mason request is internal or not.
+  * Altered the autohandler to use $jifty_internal_request when blocking access to /_elements/
+  * Fixed error handling in the autohandler to redirect to /__jifty/error/requested_private_component rather than /errors/requested_private_component - sterling
+
+ * Give the browser (particularly Safari) some more "settle time" - trs
+
+ * Added support for more javascript triggers for most form elements: onclick
+   onchange ondblclick onmousedown onmouseup onmouseover onmousemove
+   onmouseout onfocus onblur onkeypress onkeydown onkeyup onselect - efunneko
+
+ * Fix to a JavaScript bug performing the rerendering of IMG graphs. -
+   sterling
+
+ * Without the conditional we'll hide errors as soon as we display them - trs
+
+ * Add explicit hide/show for the error, warning, and canonicalization note
+   divs. This solves some long time ugliness on IE. However, it is not perfect
+   as I am still getting some rendering issues on IE. Does not seem to break
+   anything on FF. - sterling
+
+ * Fix a fragment update regression caused by the trimclient merge. - clkao
+
+*              Make enter key submit in the address search popup.
+* Fix the close link in ambigious address popup. - clkao
+
+ * Add some classes - trs
+
+ - CRUD view: Minor refactoring for easy wording change
+ - Provide the D in CRUD - trs
+
+ * CRUD: Don't bother showing edit link if current user can't - clkao
+
+ - Plugin CSS and JS (which should be put in
+   share/plugin/Jifty/Plugin/Foo/web/static/{css,js}) is now compressed by
+   CCJS
+ - CCJS can compress more than just main.css by calling Jifty->web->add_css("file.css") - trs
+
+ * in REST record_to_data, skip container columns. - clkao
+
+ * r60264 at 102: jesse | 2007-07-08 21:52:09 -0700
+  * Template::Declare behaviour change for "show 'foo'" to now show foo at the current template depth. - jesse
+
+ * r24794 at zot: tom | 2007-06-21 02:46:02 -0400 Restore and update the OOM
+   patch to the YUI calendar - trs
+
+ * Convert to using the form of declaring titles that works when using the
+   mason wrapper with TD - trs
+
+ * CRUD plugin cleanup and refactoring - jesse
+
+ * CRUD View: Also export display_columns so it's not required to
+   override it - trs
+
+*       New Template::Declare page syntax for additional code block run
+         before wrapper, and have the returned value passed to wrapper:
+
+template foo => 
+  page { title => 'this is title', other => 'foo' }
+  content {
+    h1 { 'blah }
+  }; - clkao
+
+ * Fix jifty->web->out from without td used with mason wrapper. - clkao
+
+ * try not to interfere when a user clicks on a link with a modifier key
+   pressed - jesse
+
+ * YourApp::View->use_mason_wrapper now lets your write page {} in TD but uses
+   your mason wrapper. - clkao
+
+ * Do not call update() when ctrl-clicked - clkao
+
+ * Fix javascript emission when CompressedCSSandJS plugin is not loaded
+   noticed by dpavlin++. - clkao
+
+ * Refactor the CRUD view to make it easier to customize the output - jesse
+
+ * when rendering the keybindings div, reset Jifty.KeyBindings. Ideally we
+   might want to keep the state with the div so it resets itself when
+   replaced. - clkao
+
+ * For post fragment update script evaluation, use YUI OnAvailable rather than
+   hardcoded setTimeout. - clkao
+
+ * switch url from an attribute to a child node for ajax redirects. (The old
+   behaviour didn't work in safari) - jesse
+
+ * add_javascript method to simplify adding JS libs and updated doc - trs
+
+ * Fix update() called from non-form. - clkao
+
+ * Implement "submit => undef" in onclick hook where it should submit all
+   actions. - clkao
+
+ * Let the code calling the wrapper specify a page class to use to support
+   multiple page types - trs
+
+ * Make redirect inside region work. - clkao
+
+ * Make Jifty->web->out work during the dispatch rule stage. - clkao
+
+ * Optionally use app's View::Page class for wrapper. - clkao
+
+ * refactor the TD wrapper into a class. - clkao
+
+ * Abstract render_footer.
+* Header is always considered done when sending out nontoplevel wrapper in spa. - clkao
+
+ * r57907 at pinglin: jesse | 2007-06-03 16:59:42 -0400
+  * The SPA templates no longer include css and js twice - jesse
+
+ * Template::DEeclare now gets a hashref of all the arguments set() in the
+   dispatcher. - jesse
+
+ * Fix search collection for CRUD. - clkao
+
+ * Better factored CRUD library, ready for local overriding - jesse
+
+ * Calendar: Our default canonicalizer is yyyy-mm-dd, while the calendar
+   generates single-character months and dates sometimes. Standardize on the
+   two-digit form. - alexmv
+
+ * CRUD view and a working example (sitenews) - jesse
+
+ * Always use an application's 'wrapper' in preference to the Jifty default
+  - jesse
+
+ * clean url for submenu - yves
+
+ * add a render_as_classical_menu to have the same menu with T::D view than
+   older mason _elements/nav. - yves
+
+ * Stop a certain class of validator error that causes an infinite loop in
+   IE - jesse
+
+ * CRUD builder search works
+  - jesse
+
+ * Jifty::View::Declare::CRUD lifted from BabelBee
+  - jesse
+
+ * add the ability to have some code before a javascript click : onclick => [
+   { beforeclick => "<somecode>;" }, { args => ... - yves
+
+ * More refactoring in support of adding new view handlers. - jesse
+
+U-PUBSUB
+========
+
+ * Jifty.Subs needs outs_raw. - clkao
+
+DOC
+===
+
+ * Minor pod additions. removing unneeded wrappers - jesse
+
+ * Adding documentation for Action and Static attributes to resolve POD
+   coverage test failures. - sterling
+
+ * Adding tangent and return to the glossary. - sterling
+
+ * Updating Pod and source comments for Jifty::Config. Performed some Perl
+   tidying and added a new section describing why Jifty uses three levels of
+   configuration files (may need additional editting). - sterling
+
+ * Cleaning up the Pod and adding a few code comments to Jifty::Collection.
+   - sterling
+
+ * Added myself to AUTHORS - efunneko
+
+ * Updating Pod and adding code comments to Jifty::Client. - sterling
+
+ * Major Pod improvement to the class loader, many more helpful code comments,
+   and some tidying for the class loader. Phew. - sterling
+
+ * Cleaning up the Pod for Jifty::Bootstrap - sterling
+
+ * Improved code comments and minor perl tidy. - sterling
+
+ * Added more POD to fix coverage issues and added a description to the
+   client_cacheable and client_cache_content methods of PageRegion. - sterling
+
+ * Additional fix to POD coverage for the CRUDView attribute. - sterling
+
+ * Fixing POD issues in CRUD, adding some code comments, and minor perl
+   tidying. - sterling
+
+ * Fixing error that occurred during previous push. - sterling
+
+ * Improving some of the Pod, code comments, and minor perl tidying. -
+   sterling
+
+ * Improved code comments and minor perl tidy. - sterling
+
+ * Added more POD to fix coverage issues and added a description to the
+   client_cacheable and client_cache_content methods of PageRegion. - sterling
+
+ * Additional fix to POD coverage for the CRUDView attribute. - sterling
+
+ * Fixing POD issues in CRUD, adding some code comments, and minor perl
+   tidying. - sterling
+
+ * Improving some of the Pod, code comments, and minor perl tidying. -
+   sterling
+
+ * add myself to AUTHORS :) - sartak
+
+ * added some pod to help make pod tests pass - jesse
+
+ * Added a recipe for using "result_of" to fetch information about actions for
+   use in appends. - sterling
+
+ * Added NAME sections and short descriptions for the CPAN module summary.
+   - sterling
+
+ * Adding documentation to Jifty::Plugin::Debug to fix POD coverage failures.
+   - sterling
+
+ * Added an additional see also to Jifty::Manual::AccessControl to the User
+   and Authentication::Password plugins. - sterling
+
+ * The true return value of the module needs to not be part of the POD
+  * Fix a POD nitpick - alexmv
+
+ * word-wrapping some POD - nelhage
+
+ * Fleshing out and cleaning up the documentation for
+   Jifty::Plugin::Authentication::Password. - sterling
+
+ * Fleshing out and cleaning up the documentation for Jifty::Plugin::User.
+   - sterling
+
+ * Added a many-to-many relationship recipe to the cookbook. - sterling
+
+ * added see also section to Jifty::Upgrade - bartb
+
+ * Now with more POD! (and passing POD tests) - trs
+
+ * Extracting talks from the jifty dist - jesse
+
+*              r58374 at pinglin: jesse | 2007-06-14 20:17:33 -0400
+  * Full doc for the existing CRUD templates - jesse
+
+ * Cleaning up and improving some of the view documentation. - sterling
+
+ * Yada Sample application: Example::Todo is too hard to type. Yada! - clkao
+
+ * The cookbook was wrong about the auth recipe - jesse
+
+ * More comment about the JAFF handling special case from update(). - clkao
+
+ * Clarifying comments about the plugins lists - trs
+
+ * document why the validate hack exists. - clkao
+
+ * exceedingly minor POD fixes. - diakopter
+
+ * fix some pod bugs in Jifty/API.pm - sunnavy
+
+ * update zh_cn.po - sunnavy
+
+ * documentation fix that seems to have been forgotten when length was renamed
+   max_length - andk
+
+ * add 'Login on demand' section to Cookbook - ruz
+
+ * Added section on dynamically created binary content in the cookbook - alech
+
+ * add docs to pass the pod-coverage tests - falcone
+
+ * small update to ru.po - ruz
+
+ * add docs to escape_uri
+* fix it in the way need it to work, it's escape_uri_utf8. there is no
+  way to escape binary and text scalars in one sub, we need the latter. - ruz
+
+ * ru.po - ruz
+
+ * linked POD++ - ruz
+
+PLUGINS
+=======
+ * Plugins may now have regular models. These are deployed after
+  the schema is setup after adding the plugin to installation. 
+  Plugins can bootstrap and upgrade their schema with their own 
+  version numbers just like applications (though with slightly 
+  different details). - sterling
+
+
+ * Adding a plugin for using the jQuery Javascript library with Jifty.
+   - sterling
+
+ * Fixing the chart plugin tests to match up with changes that have happened
+   to the API since they were written. - sterling
+
+ * Chart plugin configuration updates:
+  * Deprecating the renderer option.
+  * Adding the DefaultRenderer option to replace renderer.
+  * Adding the PreloadRenderers option to allow additional renderers to be preloaded.
+  * Updated the documentation.
+  * Made sure that the configuration is always passed to the renderer constructor, even if they are loaded late. - sterling
+
+ * Chart Plugin: Removing extra comma from JavaScript list. - sterling
+
+ * Chart Plugin: Whoops. Forgot to check in the actual XML SWF library. This
+   is version 4.6. - trs
+
+ - Chart Plugin: Treat the width and height appropriately
+ - Chart Plugin:  Add the XML::Simple dep - trs
+
+ * Bunch of updates to the chart plugin
+ 
+     - Refactored dispatcher
+     - Added XML SWF renderer
+     - Renderers are now passed the configuration hash when init'd
+  - trs
+
+ * Chart Plugin: Use PlotKit.Base.map explicitly - trs
+
+ * Chart Plugin: Render onAvailable instead of on window load so that we work
+   in regions - trs
+
+ * Chart Plugin: Make sure we do not attempt to render 0 pixel values no
+   matter what.
+  - sterling
+
+ * Chart Plugin: Adding the SimpleBars renderer as a decent, dead-simple HTML-
+   based renderer for HorizontalBars and a prototype for using tables for client-
+   side chart configuration. - sterling
+
+ * Chart Plugin: Removed some redundant code from the PlotKit renderer and
+   added support for options to the GD::Graph and Chart renderers.
+  - sterling
+
+ * Chart Plugin: Improved the way the DIV tag is generated for PlotKit.
+  - sterling
+
+ * Chart Plugin: Added better error handling on renderer require.
+  - sterling
+
+ * Chart Plugin: Standardizing the chart types across the three current
+   renderers. - sterling
+
+ * Chart Plugin: Updated the behaviour script used by IMG chart renderers to
+   make it more URI aware. - sterling
+
+ * Chart Plugin: Fix failing dependency test because Chart uses GD to fix
+   testing. - sterling
+
+ * Chart Plugin: Added a renderer parameter to the chart() method and add per-
+   renderer initialization. - sterling
+
+ * Chart Plugin: Making the IMG-based chart renderers capable of handling CSS
+   styling with some added behaviour. - sterling
+
+ * Chart Plugin: Don't mess with the data structure if it's already what
+   plotkit expects - trs
+
+ * Chart Plugin: *Very* custom packed PlotKit (from svn) that no longer
+   depends on MochiKit exporting functions into the global namespace. Still
+   need to solve the issue of why MochiKit blows up when included in our
+   honkin' JS file... - trs
+
+ * Chart Plugin: Uncomment neccessary require. Make sure to handle undefined
+   stuff - trs
+
+ * Basic PlotKit renderer for Chart plugin - trs
+
+ * Made the chart plugin test smarter and added one for the GD::Graph
+   renderer. - sterling
+
+ * Fixed an eensy POD bug. - sterling
+
+ * Fixed POD coverage issue. - sterling
+
+ * Updated POD and removed an unnecessary extra subroutine call. - sterling
+
+ * Added a renderer for GD::Graph - sterling
+
+ * Moved the chart/* dispatch to chart/chart/* to make room for alternate
+   charting mechanisms. - sterling
+
+ * Fixed the way arguments are passed to the render() method in
+   Jifty::Plugin::Chart::Web. - sterling
+
+ * Added a hack to chart.t (forcing an early load of GD) to avoid the
+   segfault that was causing it to fail. Removed the TODO block from the
+   test. - sterling
+
+ * Regarding Jifty::Plugin::Chart: Added better comments. Fixed some
+   error handling. Switched to using scalar_png(). Switched to using -
+   >require rather than an eval to load Chart classes. Eliminated the
+   need for IO::String. Moved some processing out of View and into
+   Dispatcher. - sterling
+
+ * Adding a test suite for Jifty::Plugin::Chart, but it is having weird
+   troubles loading Chart::* because that seems to disconnect the server
+   output or something. - sterling
+
+ * Added the Chart::Base recommendation for the Chart plugin. - sterling
+
+ * Added documentation to the experimental Chart plugin. - sterling
+
+ * Adding a plugin for rendering charts of data. - sterling
+
+ * Updated tabview plugin to work when it's not already inside a region
+  - jesse
+
+ * Fix IE issues with search div. - clkao
+
+ * i18n for feedback plugin. - clkao
+
+ * Incredibly naive "image column" userpic plugin.  - jesse
+
+ * ActorMetadata plugin: Asset renamed to ActorMetadata
+  - jesse
+
+ * First cut of GoogleMap plugin. - clkao
+
+ * Facebook plugin: Provide a way to link existing users with a Facebook
+   account - trs
+
+ * Facebook plugin: More pod, forceable login - trs
+
+ * Basic Facebook auth plugin - trs
+
+ * OpenID Plugin: Add a "next" parameter so one can specify the next page to
+   go after openid login. - gugod
+
+ * debug plugin for logging dispatched rules and current user. - clkao
+
+ * Password Auth: Plugin view class should respect app page. - clkao
+
+ * Got the tabview plugin working with non-CRUD code.
+ * got the tabview plugin preloading the first tab, even if it's marked as a region rather than a static tab - jesse
+
+ * Single Page App: Unbreak nojs form submitting when SPA is enabled. - clkao
+
+ * CompressedCSSandJS: support optional javascript minifier. - clkao
+
+ * Move the javascript concatenating logic from Jifty::Web to the
+   plugin. - clkao
+
+ * CodePress plugin: bump version to 0.02, remove requirement to modify
+   submit form button (and in process break next_page after it, because it
+   turns normal form submit into Ajax form submit which doesn't handle
+   redirects very well -- because it usually redirects to page which isn't
+   valid XML) - dpavlin
+
+ * Added a 'feedback' plugin
+  - jesse
+
+ * In SinglePage mode, allow Action::Redirect to happen within webservice and
+   have the client js accepts it. - clkao
+
+ * CodePress plugin; add language accessor to select syntax highlight language
+   (instead of default generic), example of usage, replace onload with
+   DOM.Events - dpavlin
+
+ * CodePress plugin for web-based source code editor with syntax
+   highlighting - dpavlin
+
+ * Make SinglePage plugin configurable. - clkao
+
+ * First draft of SinglePage plugin. - clkao
+
+ * Make CompressCSSandJS optional.
+* always refresh css & js if in devel mode. - clkao
+
+ * OpenId Plugin: Fix has_alternative_auth. - clkao
+
+ * Authentication::Password plugin: The (unused) resend_confirmation template
+   was missing. - clkao
+
+ * User model mixin for OpenID plugin.
+* Make Authenticate::Password aware that there can be alternative
+  authentication systems.
+* Make OpenID plugin work even when Authenticate::Password is
+  enabled. - clkao
+
+ * OpenID Plugin: The minimum required OpenID View for your app to
+   mixin. - gugod
+
+ * TabView plugin. - clkao
+
+ * Site news plugin - jesse
+
+ * A Jifty console to provide quick diagnostic shell for debugging or
+   maintaining purpose.
+
+Yes, it's learnt from RoR, and it's good to have it when developing
+applications, or when you just start learning Jifty. - gugod
+
+ * This is the OpenID plugin code. Setting up your app to use OpenID isn't as
+   easy as we thought it to be. Will need a receipe to teach people how to
+   cook it. - gugod
+
+ * Login plugin: return id of the user from action - ruz
+
+ * REST dispatcher: always use warnings and strict. - jesse
+
+ * REST plugin: render referencing fields in a saner fashion. - clkao
+
+ * plugin to add a wiki toolbar to textarea - yves
+
+ * plugin to use Wyzz online wyziwig editor to render textaera for test and
+   comments - yves
+
+CORE
+====
+
+ * Remove cargo-culting changing of $0 from Jifty::SubTest. It doesn't affect
+   test output but does break things - jesse
+
+ * respect initial PATH in env under fastcgi. - clkao
+
+ * Add friendly_date method to Jifty::DateTime which special-cases
+   yesterday/today/tomorrow - sartak
+
+ * Jifty::View::Declare - Work around Perl 5.9.5 bug by avoid punning the
+   constant name BaseClass with the subclass name ::BaseClass. - audreyt
+
+ * todo-ify failing tests in t/13-sessions.t - sartak
+
+ * In action argument creation from model, do not assume refers_to always want
+   a select based on id which we might not be referring to. Allow user to
+   override render_as for refers_to columns. - clkao
+
+ * Correct a crud component path. - clkao
+
+ * Fix view CRUD template's method of getting the record - trs
+
+ * First cut of a UUID column plugin, with a basic test in the user
+   model - jesse
+
+ * Add a load_by_kv to Jifty::Web::Session - sartak
+
+ * Resolve import conflicts now that T::D and J::V::D::Helpers have a thingy
+   with the same name - jesse
+
+ * Moniker bulletproofing. Suggested by Mikko Lapasti - jesse
+
+ * Push milestone 1 of trimclient to trunk. - clkao
+
+ * Now make sure it's actually UTF-8 - trs
+
+ * Make sure we get UTF-8 - trs
+
+ * Fixing API qualification to make it possible to access actions associated
+   with plugins via ->new_class. - sterling
+
+ * Don't mess with the HTML by default. This should likely become configurable
+   in the future. - trs
+
+ * Cleaned up the class loader a bit to make all the auto-generated methods
+   use the same name, remove an unnecessary elsif, and add a few more
+   comments. - sterling
+
+ * Need parens - trs
+
+ * Fixed CRUD view to no longer require you to specify a base_path for the
+   plugin's view. It was redundant and we could intuit it. - jesse
+
+ * Refactor CRUD view to be more subclassable - Extract out per_page - trs
+
+ * type can be empty for container columns. - clkao
+
+ * support external javascripts. - clkao
+
+ * Added support for application-specific plugins (i.e., App::Plugin::XXX) and
+   plugins named using the fully-qualified Jifty::Plugin::XXX name. - sterling
+
+ * check if current_user->can('user_object') when tring to figure out
+   timezone - dpavlin
+
+ * Altered the ClassLoader to create Collections for models deeper under the
+   model namespace,like "App::Model::Foo::Bar". These is consistent with the
+   handling for actions and other components already in place. - sterling
+
+ * Move pulling action defaults from action in request into the action's new,
+   instead of a "private" argument.
+  * Remove an extra place where we default the moniker to
+    _generate_moniker
+  * A bunch of POD (re)wrapping. - alexmv
+
+ * Recent YAML::Syck's get confused by trying to be too smart with
+   $test_config, which is a filehandle which *also* stringifies to the path.
+   Force stringification to get consistent (working) results. - alexmv
+
+*       No matter what _resurect_current_user is intended for, it ought
+          to be spelled _resurrect_current_user instead. :-) - audreyt
+
+ * Mapper edge case failure (when no 'name' was given) - alexmv
+
+ * add {_resurect_current_user} field into user module - ruz
+
+ * protect ourself from circular references between User and CurrentUser
+
+we do it in user_object, so solution is generic should work in any case, but
+only if people use the method to set user_object and user model uses _current_user
+key to store reference to the CurrentUser - ruz
+
+ * Use Jifty::Util->share_root for finding plugin share roots so that if Jifty
+   isn't installed it still DTRT
+ * Only calculate static roots once and report on plugins adding roots (like the mason handler does) - trs
+
+ * Allow app changeable cookie names - trs
+
+ * Web::Form::Link - When escape_label was set, the tooltip property was (very
+   sillily!) first escaped, then discarded away, displaying the unescaped text
+   instead. It now escapes properly.
+* Also make tooltips with value '0' display properly. - audreyt
+
+ * Allow scheme to be specified for Jifty->web->url. This functionality was
+   taken out during the move from "heuristics" to URI.pm, but for a non-
+   apparent reason. - trs
+
+ * HTTPS and HTTP adjectives for dispatcher rules - trs
+
+ * Jifty->web->form->next_page got dropped when you submit only a specific
+   action with Jifty->web->form->submit. This commit autoadds next_page if you
+   submit only a subset of actions. - jesse
+
+ * _redirect expects a local path, not a fully formed URL. (This only actually
+   broke internal redirects) - jesse
+
+ * Always give the dispatcher unescaped path like fastcgi does, from
+   standalone, webservice, and region entrance. - clkao
+
+ * work around annoying Module::Pluggable bug - sky
+
+ * Make multiple use base lines into one for readability. - clkao
+
+ * Admin UI: Don't trip over classes we can't require - jesse
+
+ * Quiet a dispatcher warning - jesse
+
+ * misc webservices_redirect cleanup. - clkao
+
+ * Refactor handle_request to extract out methods
+  * Only warn about denied actions on validate if they're also actions we want to run
+  - jesse
+
+ * Back out a Croak back to a die. (We're using it for exception handling, not
+   to-user reporting) - jesse
+
+ * Merge from fragcont branch to include minimum support of continuation in
+   webservices requests to make SinglePageApp plugin work. - clkao
+
+ * Only squelch "can't locate" errors relating to the class we're trying to
+   require. If it's something else, it's likely a module use'd by the module
+   we're requiring. - trs
+
+ * correct en.po charset. - clkao
+
+ * Only call LML->import once ever. - clkao
+
+ * Kill 5% of startup time by not validating during DateTime::Locale. - clkao
+
+ * Disable in-region redirect unless SPA is enabled. - clkao
+
+ * Move methods unrelated to mason to Jifty::View. - clkao
+
+ * Added support for "redirects" on fragment calls (which really just do an
+   internal replace) - jesse
+
+ * Really fix the region rewriting logic. - clkao
+
+ * new Jifty->find_plugin method. - clkao
+
+ * add a config file version, so we can change old defaults.
+  - jesse
+
+ * Correct app_class usage. - clkao
+
+ * CurrentUser->username now uses brief_description on user_object. - clkao
+
+ * In ClassLoader, be quiet when we are just trying to see if a module
+   exists. - clkao
+
+ * When we convert a model into an action, don't deref and then reref the
+   array of possible valid values. We lose any attempt at possible magic that
+   we might have. - jesse
+
+ * A move to hand on the rendering of JDBI::Collection columns to app
+   developers.
+    
+    We used to just ignore the rendering of any columns that refers to
+    a JDBI::Collection, because there's no trivial way to do so. 
+    
+    However, if the column comes with a "render_as", we can assume that
+    the app developer know what he/she is doing.  So we just render it
+    as whatever specified.
+    
+    One scenario is that a collection of tags is rendered as a normal
+    text input field, and develoeprs create tags records in their Tag
+    model in before_create() method. With ajax canonicalize_tags this
+    could be a simple and smart interface, and not difficult to implement
+    at all.
+    
+    I'm sure that it'll open the chance to let developers write their
+    own collection rendering modules like I did before. If we keep
+    being ignoring, this won't even happend. - gugod
+    
+ * add data to the result indicating which requested actions were denied and
+   mark them failed. Options for a better denied message coming soon - falcone
+
+ * Extract the model list from Script::Schema to Schema - jesse
+
+ * Incremental extraction of schema management from
+   Jifty::Script::Schema - jesse
+
+ * Cause more compiliation failures to actually stop the app from running
+  - jesse
+
+ * Cause more compiliation failures to actually stop the app from running
+  - jesse
+
+ * Cope better with malformed fragment requests - alexmv
+
+ * Push app_root/lib onto @INC when we create a new Jifty object - alexmv
+
+ * Moved the commands for add/drop column to Jifty::Record (they should move
+   to JDBI eventually)
+ * Added a drop_table to Jifty::Record
+  - jesse
+
+ * Added the before_access trigger and a simple test for it. Also updated
+   TestApp and the current_user test to make them compatible with the new
+   before_access test. - sterling
+
+ * '*' matches only a single level in the dispatcher. '**' matches all level.s
+   We want to run the "Home" rule at all dispatcher levels.
+ 
+  - jesse
+
+ * Make compile errors in autorequired modules fatal - jesse
+
+ * FCGI.pm ties our streams and its implementation doesn't have support for
+   setting IO layers with binmode, but we can do the same using
+   Encode::encode. We just turn on raw mode on STDOUT and convert to octets
+   ourself using Encode.pm and charset definition from the content type
+   field. - ruz
+
+ * Extensive UTF8 improvement: to sanity through insanity
+
+* control mode of output handles, if content type has charset defined
+    then we set :encoding(<charset>) output layer (or :utf8), otherwise
+    binary
+  * regions are special as we print out them into STDOUT, but sometimes
+    need them as a string. We localize STDOUT and get data, however because
+    of the above canonicalization we get octets or binary, so we check
+    again the current content type. If the type contains charset definition
+    then we decode octets back into perl string(in terms of perl unicode support),
+    otherwise we leave things as is.
+  
+  jifty is sane when apps' developers are sane
+  * never use 'bytes' pragma
+  * avoid using 'encoding' pragma
+  * use perl strings in jifty
+  ** when you get a text data from external sources then Encode::decode it
+  * set output encoding with $r->content_type('type/subtype; charset=XXX')
+  ** by default it's UTF-8
+  ** you can use cp1251 (or other) and things should work, user will get
+     data in cp1251 and browser should display it right
+  ** don't Encode::encode things before output
+  
+  everybody have own critirea of sanity
+  * if you think that something is wrong then add tests to jifty 
+  
+     - ruz
+
+ * we shouldn't silence utf8 warnings - ruz
+
+ * utf8::downgrade converts to octets only if string had been upgraded, what
+   is not always true for 'perl strings' - ruz
+
+ * escape_utf8
+  * don't use bytes just escape things doesn't matter if it's
+    flagged string or not, perl must do the right thing.
+  * don't localize ref, use it directly, afaik smaller
+    memory footprint - ruz
+  
+ * utf8::downgrade doesn't like strings as FAIL_OK, only integers, 1 is not
+   that cool as 'FAILURE IS OK', but works - ruz
+
+ * initial environment that makes fastcgi work got deleted - jesse
+
+*      This module is for rendering a collection of input fields at once
+        as a single widget. The major goal is to let developer say like:
+   
+     column bars =>
+       refers_to "My::Model::BarCollection",
+       render as "Collection"
+       availables are defer {
+           retrieve_some_bars()
+       };
+   
+   in their model class, and it'll just display a nice form to input
+   the value for a list of available bars. - gugod
+   
+
+ * fixed Jifty::View::Declare::Helpers since we now install tag subs directly
+   to the target package instead of using @EXPORT. We now makes use of
+   T::D::Tags's @TagSubs struct - agentz
+
+
+
 Jifty 0.70422
 
 [Password Authentication plugin]

Modified: jifty/branches/js-refactor/MANIFEST
==============================================================================
--- jifty/branches/js-refactor/MANIFEST	(original)
+++ jifty/branches/js-refactor/MANIFEST	Thu Nov 29 12:25:16 2007
@@ -5,6 +5,7 @@
 bin/service
 bin/xgettext
 Changelog
+contrib/jifty_completion.sh
 debian/changelog
 debian/compat
 debian/control
@@ -67,17 +68,6 @@
 examples/Doxory/share/po/zh_cn.po
 examples/Doxory/share/po/zh_tw.po
 examples/Doxory/t/00-model-User.t
-examples/Example-Todo/bin/jifty
-examples/Example-Todo/etc/config.yml
-examples/Example-Todo/example_todo
-examples/Example-Todo/inc/Module/Install.pm
-examples/Example-Todo/inc/Module/Install/Base.pm
-examples/Example-Todo/inc/Module/Install/Metadata.pm
-examples/Example-Todo/inc/Module/Install/WriteAll.pm
-examples/Example-Todo/lib/Example/Todo/Model/Todo.pm
-examples/Example-Todo/Makefile.PL
-examples/Example-Todo/META.yml
-examples/Example-Todo/t/00-model-Todo.t
 examples/HelloKitty/bin/jifty
 examples/HelloKitty/etc/config.yml
 examples/HelloKitty/hellokitty
@@ -103,6 +93,33 @@
 examples/Ping/share/web/templates/index.html
 examples/Ping/t/00compile.t
 examples/Ping/t/01startup.t
+examples/ShrinkURL/bin/jifty
+examples/ShrinkURL/etc/config.yml
+examples/ShrinkURL/lib/ShrinkURL/Action/CreateShrunkenURL.pm
+examples/ShrinkURL/lib/ShrinkURL/Dispatcher.pm
+examples/ShrinkURL/lib/ShrinkURL/Model/ShrunkenURL.pm
+examples/ShrinkURL/lib/ShrinkURL/View.pm
+examples/ShrinkURL/Makefile.PL
+examples/Yada/bin/jifty
+examples/Yada/etc/config.yml
+examples/Yada/inc/Module/Install.pm
+examples/Yada/inc/Module/Install/Base.pm
+examples/Yada/inc/Module/Install/Can.pm
+examples/Yada/inc/Module/Install/Fetch.pm
+examples/Yada/inc/Module/Install/Makefile.pm
+examples/Yada/inc/Module/Install/Metadata.pm
+examples/Yada/inc/Module/Install/Win32.pm
+examples/Yada/inc/Module/Install/WriteAll.pm
+examples/Yada/lib/Example/Todo/Model/Todo.pm
+examples/Yada/lib/Yada.pm
+examples/Yada/lib/Yada/Model/Todo.pm
+examples/Yada/lib/Yada/Model/User.pm
+examples/Yada/lib/Yada/View.pm
+examples/Yada/lib/Yada/View/Todo.pm
+examples/Yada/Makefile.PL
+examples/Yada/META.yml
+examples/Yada/share/web/static/js/Asynapse/REST.js
+examples/Yada/t/00-model-Todo.t
 inc/Module/AutoInstall.pm
 inc/Module/Install.pm
 inc/Module/Install/AutoInstall.pm
@@ -146,6 +163,7 @@
 lib/Jifty/JSON.pm
 lib/Jifty/LetMe.pm
 lib/Jifty/Logger.pm
+lib/Jifty/Manual.pm
 lib/Jifty/Manual/AccessControl.pod
 lib/Jifty/Manual/Actions.pod
 lib/Jifty/Manual/Continuations.pod
@@ -174,7 +192,15 @@
 lib/Jifty/Param.pm
 lib/Jifty/Param/Schema.pm
 lib/Jifty/Plugin.pm
+lib/Jifty/Plugin/ActorMetadata.pm
+lib/Jifty/Plugin/ActorMetadata/Mixin/Model/ActorMetadata.pm
 lib/Jifty/Plugin/AdminUI.pm
+lib/Jifty/Plugin/Authentication/Facebook.pm
+lib/Jifty/Plugin/Authentication/Facebook/Action/LinkFacebookUser.pm
+lib/Jifty/Plugin/Authentication/Facebook/Action/LoginFacebookUser.pm
+lib/Jifty/Plugin/Authentication/Facebook/Dispatcher.pm
+lib/Jifty/Plugin/Authentication/Facebook/Mixin/Model/User.pm
+lib/Jifty/Plugin/Authentication/Facebook/View.pm
 lib/Jifty/Plugin/Authentication/Password.pm
 lib/Jifty/Plugin/Authentication/Password/Action/ConfirmEmail.pm
 lib/Jifty/Plugin/Authentication/Password/Action/GeneratePasswordToken.pm
@@ -190,23 +216,76 @@
 lib/Jifty/Plugin/Authentication/Password/Notification/ConfirmEmail.pm
 lib/Jifty/Plugin/Authentication/Password/Notification/ConfirmLostPassword.pm
 lib/Jifty/Plugin/Authentication/Password/View.pm
+lib/Jifty/Plugin/AutoReference.pm
+lib/Jifty/Plugin/AutoReference/Widget.pm
+lib/Jifty/Plugin/Chart.pm
+lib/Jifty/Plugin/Chart/Dispatcher.pm
+lib/Jifty/Plugin/Chart/Renderer.pm
+lib/Jifty/Plugin/Chart/Renderer/Chart.pm
+lib/Jifty/Plugin/Chart/Renderer/GD/Graph.pm
+lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
+lib/Jifty/Plugin/Chart/Renderer/SimpleBars.pm
+lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
+lib/Jifty/Plugin/Chart/View.pm
+lib/Jifty/Plugin/Chart/Web.pm
 lib/Jifty/Plugin/ClassLoader.pm
 lib/Jifty/Plugin/CompressedCSSandJS.pm
 lib/Jifty/Plugin/CompressedCSSandJS/Dispatcher.pm
+lib/Jifty/Plugin/Debug.pm
+lib/Jifty/Plugin/Debug/Dispatcher.pm
 lib/Jifty/Plugin/ErrorTemplates.pm
 lib/Jifty/Plugin/ErrorTemplates/View.pm
+lib/Jifty/Plugin/Feedback.pm
+lib/Jifty/Plugin/Feedback/Action/SendFeedback.pm
+lib/Jifty/Plugin/Feedback/View.pm
+lib/Jifty/Plugin/GoogleMap.pm
+lib/Jifty/Plugin/GoogleMap/Widget.pm
 lib/Jifty/Plugin/Halo.pm
+lib/Jifty/Plugin/I18N.pm
+lib/Jifty/Plugin/I18N/Action/SetLang.pm
+lib/Jifty/Plugin/JQuery.pm
+lib/Jifty/Plugin/LeakTracker.pm
+lib/Jifty/Plugin/LeakTracker/Dispatcher.pm
+lib/Jifty/Plugin/LeakTracker/View.pm
 lib/Jifty/Plugin/LetMe.pm
 lib/Jifty/Plugin/LetMe/Dispatcher.pm
+lib/Jifty/Plugin/OAuth.pm
+lib/Jifty/Plugin/OAuth/Action/AuthorizeRequestToken.pm
+lib/Jifty/Plugin/OAuth/Dispatcher.pm
+lib/Jifty/Plugin/OAuth/Model/AccessToken.pm
+lib/Jifty/Plugin/OAuth/Model/Consumer.pm
+lib/Jifty/Plugin/OAuth/Model/RequestToken.pm
+lib/Jifty/Plugin/OAuth/Token.pm
+lib/Jifty/Plugin/OAuth/View.pm
 lib/Jifty/Plugin/OnlineDocs.pm
 lib/Jifty/Plugin/OnlineDocs/Dispatcher.pm
+lib/Jifty/Plugin/OpenID.pm
+lib/Jifty/Plugin/OpenID/Action/AuthenticateOpenID.pm
+lib/Jifty/Plugin/OpenID/Action/CreateOpenIDUser.pm
+lib/Jifty/Plugin/OpenID/Action/VerifyOpenID.pm
+lib/Jifty/Plugin/OpenID/Dispatcher.pm
+lib/Jifty/Plugin/OpenID/Mixin/Model/User.pm
+lib/Jifty/Plugin/OpenID/View.pm
 lib/Jifty/Plugin/REST.pm
 lib/Jifty/Plugin/REST/Dispatcher.pm
+lib/Jifty/Plugin/SinglePage.pm
+lib/Jifty/Plugin/SinglePage/Dispatcher.pm
+lib/Jifty/Plugin/SiteNews.pm
+lib/Jifty/Plugin/SiteNews/Dispatcher.pm
+lib/Jifty/Plugin/SiteNews/Mixin/Model/News.pm
+lib/Jifty/Plugin/SiteNews/View/News.pm
 lib/Jifty/Plugin/SkeletonApp.pm
 lib/Jifty/Plugin/SkeletonApp/Dispatcher.pm
 lib/Jifty/Plugin/SkeletonApp/View.pm
+lib/Jifty/Plugin/TabView/View.pm
 lib/Jifty/Plugin/User.pm
 lib/Jifty/Plugin/User/Mixin/Model/User.pm
+lib/Jifty/Plugin/Userpic.pm
+lib/Jifty/Plugin/Userpic/Dispatcher.pm
+lib/Jifty/Plugin/Userpic/View.pm
+lib/Jifty/Plugin/Userpic/Widget.pm
+lib/Jifty/Plugin/UUID.pm
+lib/Jifty/Plugin/UUID/Widget.pm
 lib/Jifty/Plugin/Yullio/View.pm
 lib/Jifty/Record.pm
 lib/Jifty/Request.pm
@@ -214,10 +293,12 @@
 lib/Jifty/Response.pm
 lib/Jifty/Result.pm
 lib/Jifty/RightsFrom.pm
+lib/Jifty/Schema.pm
 lib/Jifty/Script.pm
 lib/Jifty/Script/Action.pm
 lib/Jifty/Script/Adopt.pm
 lib/Jifty/Script/App.pm
+lib/Jifty/Script/Console.pm
 lib/Jifty/Script/Deps.pm
 lib/Jifty/Script/Env.pm
 lib/Jifty/Script/FastCGI.pm
@@ -235,16 +316,22 @@
 lib/Jifty/Subs.pm
 lib/Jifty/Subs/Render.pm
 lib/Jifty/Test.pm
+lib/Jifty/Test/WWW/Declare.pm
 lib/Jifty/Test/WWW/Mechanize.pm
+lib/Jifty/Test/WWW/Selenium.pm
 lib/Jifty/TestServer.pm
 lib/Jifty/Upgrade.pm
 lib/Jifty/Upgrade/Internal.pm
 lib/Jifty/Util.pm
+lib/Jifty/View.pm
 lib/Jifty/View/Declare.pm
 lib/Jifty/View/Declare/BaseClass.pm
+lib/Jifty/View/Declare/Compile.pm
 lib/Jifty/View/Declare/CoreTemplates.pm
+lib/Jifty/View/Declare/CRUD.pm
 lib/Jifty/View/Declare/Handler.pm
 lib/Jifty/View/Declare/Helpers.pm
+lib/Jifty/View/Declare/Page.pm
 lib/Jifty/View/Mason/Handler.pm
 lib/Jifty/View/Static/Handler.pm
 lib/Jifty/Web.pm
@@ -254,6 +341,7 @@
 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/Collection.pm
 lib/Jifty/Web/Form/Field/Combobox.pm
 lib/Jifty/Web/Form/Field/Date.pm
 lib/Jifty/Web/Form/Field/Hidden.pm
@@ -352,6 +440,47 @@
 plugins/AuthzLDAP/share/po/fr.po
 plugins/AuthzLDAP/share/web/templates/error/AccessDenied
 plugins/AuthzLDAP/t/00-load.t
+plugins/CodePress/doc/index.html
+plugins/CodePress/lib/Jifty/Plugin/CodePress.pm
+plugins/CodePress/lib/Jifty/Plugin/CodePress/Textarea.pm
+plugins/CodePress/Makefile.PL
+plugins/CodePress/share/web/static/codepress/codepress.css
+plugins/CodePress/share/web/static/codepress/codepress.html
+plugins/CodePress/share/web/static/codepress/engines/gecko.js
+plugins/CodePress/share/web/static/codepress/engines/khtml.js
+plugins/CodePress/share/web/static/codepress/engines/msie.js
+plugins/CodePress/share/web/static/codepress/engines/older.js
+plugins/CodePress/share/web/static/codepress/engines/opera.js
+plugins/CodePress/share/web/static/codepress/images/line-numbers.png
+plugins/CodePress/share/web/static/codepress/languages/asp.css
+plugins/CodePress/share/web/static/codepress/languages/asp.js
+plugins/CodePress/share/web/static/codepress/languages/csharp.css
+plugins/CodePress/share/web/static/codepress/languages/csharp.js
+plugins/CodePress/share/web/static/codepress/languages/css.css
+plugins/CodePress/share/web/static/codepress/languages/css.js
+plugins/CodePress/share/web/static/codepress/languages/generic.css
+plugins/CodePress/share/web/static/codepress/languages/generic.js
+plugins/CodePress/share/web/static/codepress/languages/html.css
+plugins/CodePress/share/web/static/codepress/languages/html.js
+plugins/CodePress/share/web/static/codepress/languages/java.css
+plugins/CodePress/share/web/static/codepress/languages/java.js
+plugins/CodePress/share/web/static/codepress/languages/javascript.css
+plugins/CodePress/share/web/static/codepress/languages/javascript.js
+plugins/CodePress/share/web/static/codepress/languages/perl.css
+plugins/CodePress/share/web/static/codepress/languages/perl.js
+plugins/CodePress/share/web/static/codepress/languages/php.css
+plugins/CodePress/share/web/static/codepress/languages/php.js
+plugins/CodePress/share/web/static/codepress/languages/ruby.css
+plugins/CodePress/share/web/static/codepress/languages/ruby.js
+plugins/CodePress/share/web/static/codepress/languages/sql.css
+plugins/CodePress/share/web/static/codepress/languages/sql.js
+plugins/CodePress/share/web/static/codepress/languages/text.css
+plugins/CodePress/share/web/static/codepress/languages/text.js
+plugins/CodePress/share/web/static/codepress/languages/vbscript.css
+plugins/CodePress/share/web/static/codepress/languages/vbscript.js
+plugins/CodePress/share/web/static/codepress/languages/xsl.css
+plugins/CodePress/share/web/static/codepress/languages/xsl.js
+plugins/CodePress/share/web/static/js/codepress.js
 plugins/DumpDispatcher/lib/Jifty/Plugin/DumpDispatcher.pm
 plugins/DumpDispatcher/Makefile.PL
 plugins/EditInPlace/debian/changelog
@@ -760,6 +889,71 @@
 plugins/ProfileBehaviour/Makefile.PL
 plugins/ProfileBehaviour/share/web/static/css/behaviour-profile.css
 plugins/ProfileBehaviour/share/web/static/js/behaviour.js
+plugins/WikiToolbar/lib/Jifty/Plugin/WikiToolbar.pm
+plugins/WikiToolbar/lib/Jifty/Plugin/WikiToolbar/Dispatcher.pm
+plugins/WikiToolbar/lib/Jifty/Plugin/WikiToolbar/Textarea.pm
+plugins/WikiToolbar/Makefile.PL
+plugins/WikiToolbar/share/web/static/img/wt/bold.png
+plugins/WikiToolbar/share/web/static/img/wt/box.png
+plugins/WikiToolbar/share/web/static/img/wt/center.png
+plugins/WikiToolbar/share/web/static/img/wt/code.png
+plugins/WikiToolbar/share/web/static/img/wt/code1.png
+plugins/WikiToolbar/share/web/static/img/wt/empty.png
+plugins/WikiToolbar/share/web/static/img/wt/h1.png
+plugins/WikiToolbar/share/web/static/img/wt/h2.png
+plugins/WikiToolbar/share/web/static/img/wt/h3.png
+plugins/WikiToolbar/share/web/static/img/wt/italic.png
+plugins/WikiToolbar/share/web/static/img/wt/link.png
+plugins/WikiToolbar/share/web/static/img/wt/linkextern.png
+plugins/WikiToolbar/share/web/static/img/wt/new.png
+plugins/WikiToolbar/share/web/static/img/wt/ol.png
+plugins/WikiToolbar/share/web/static/img/wt/red.png
+plugins/WikiToolbar/share/web/static/img/wt/strike.png
+plugins/WikiToolbar/share/web/static/img/wt/tab.png
+plugins/WikiToolbar/share/web/static/img/wt/ul.png
+plugins/WikiToolbar/share/web/static/img/wt/underline.png
+plugins/WikiToolbar/share/web/static/js/wikitoolbar.js
+plugins/WyzzEditor/lib/Jifty/Plugin/WyzzEditor.pm
+plugins/WyzzEditor/lib/Jifty/Plugin/WyzzEditor/Dispatcher.pm
+plugins/WyzzEditor/lib/Jifty/Plugin/WyzzEditor/Textarea.pm
+plugins/WyzzEditor/Makefile.PL
+plugins/WyzzEditor/share/web/static/img/wyzzicons/backcolor.gif
+plugins/WyzzEditor/share/web/static/img/wyzzicons/bold.gif
+plugins/WyzzEditor/share/web/static/img/wyzzicons/close.gif
+plugins/WyzzEditor/share/web/static/img/wyzzicons/copy.gif
+plugins/WyzzEditor/share/web/static/img/wyzzicons/cut.gif
+plugins/WyzzEditor/share/web/static/img/wyzzicons/downsize.gif
+plugins/WyzzEditor/share/web/static/img/wyzzicons/font.gif
+plugins/WyzzEditor/share/web/static/img/wyzzicons/forecolor.gif
+plugins/WyzzEditor/share/web/static/img/wyzzicons/headers.gif
+plugins/WyzzEditor/share/web/static/img/wyzzicons/help.gif
+plugins/WyzzEditor/share/web/static/img/wyzzicons/htmlmode.gif
+plugins/WyzzEditor/share/web/static/img/wyzzicons/indent.gif
+plugins/WyzzEditor/share/web/static/img/wyzzicons/inserthorizontalrule.gif
+plugins/WyzzEditor/share/web/static/img/wyzzicons/insertimage.gif
+plugins/WyzzEditor/share/web/static/img/wyzzicons/insertorderedlist.gif
+plugins/WyzzEditor/share/web/static/img/wyzzicons/insertunorderedlist.gif
+plugins/WyzzEditor/share/web/static/img/wyzzicons/italic.gif
+plugins/WyzzEditor/share/web/static/img/wyzzicons/justifycenter.gif
+plugins/WyzzEditor/share/web/static/img/wyzzicons/justifyfull.gif
+plugins/WyzzEditor/share/web/static/img/wyzzicons/justifyleft.gif
+plugins/WyzzEditor/share/web/static/img/wyzzicons/justifyright.gif
+plugins/WyzzEditor/share/web/static/img/wyzzicons/link.gif
+plugins/WyzzEditor/share/web/static/img/wyzzicons/outdent.gif
+plugins/WyzzEditor/share/web/static/img/wyzzicons/paste.gif
+plugins/WyzzEditor/share/web/static/img/wyzzicons/redo.gif
+plugins/WyzzEditor/share/web/static/img/wyzzicons/removeformat.gif
+plugins/WyzzEditor/share/web/static/img/wyzzicons/specialchar.gif
+plugins/WyzzEditor/share/web/static/img/wyzzicons/strikethrough.gif
+plugins/WyzzEditor/share/web/static/img/wyzzicons/subscript.gif
+plugins/WyzzEditor/share/web/static/img/wyzzicons/superscript.gif
+plugins/WyzzEditor/share/web/static/img/wyzzicons/underline.gif
+plugins/WyzzEditor/share/web/static/img/wyzzicons/undo.gif
+plugins/WyzzEditor/share/web/static/img/wyzzicons/upsize.gif
+plugins/WyzzEditor/share/web/static/img/wyzzicons/wyzzicon.gif
+plugins/WyzzEditor/share/web/static/js/wyzz.js
+plugins/WyzzEditor/share/web/static/wyzzstyles/editarea.css
+plugins/WyzzEditor/share/web/static/wyzzstyles/style.css
 README
 share/dtd/xhtml-lat1.ent
 share/dtd/xhtml-special.ent
@@ -776,6 +970,39 @@
 share/plugins/Jifty/Plugin/AdminUI/web/templates/__jifty/admin/fragments/list/view
 share/plugins/Jifty/Plugin/AdminUI/web/templates/__jifty/admin/index.html
 share/plugins/Jifty/Plugin/AdminUI/web/templates/__jifty/admin/model/dhandler
+share/plugins/Jifty/Plugin/AutoReference/web/static/js/autoreference.js
+share/plugins/Jifty/Plugin/Chart/web/static/css/simple_bars.css
+share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts.swf
+share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/arno.swf
+share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/arst.swf
+share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/brfl.swf
+share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/brno.swf
+share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/brst.swf
+share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/cl3d.swf
+share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/clfl.swf
+share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/clno.swf
+share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/clp3.swf
+share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/cls3.swf
+share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/clst.swf
+share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/cnno.swf
+share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/lnno.swf
+share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/mxno.swf
+share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/pi3d.swf
+share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/pino.swf
+share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/pono.swf
+share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/scno.swf
+share/plugins/Jifty/Plugin/Chart/web/static/js/chart_img_behaviour.js
+share/plugins/Jifty/Plugin/Chart/web/static/js/mochikit.noexport.js
+share/plugins/Jifty/Plugin/Chart/web/static/js/MochiKit/__package__.js
+share/plugins/Jifty/Plugin/Chart/web/static/js/MochiKit/MochiKit.js
+share/plugins/Jifty/Plugin/Chart/web/static/js/PlotKit/excanvas.js
+share/plugins/Jifty/Plugin/Chart/web/static/js/PlotKit/PlotKit_Packed-20060807-custom.js
+share/plugins/Jifty/Plugin/Chart/web/static/js/simple_bars.js
+share/plugins/Jifty/Plugin/GoogleMap/web/static/css/google_map.css
+share/plugins/Jifty/Plugin/GoogleMap/web/static/js/google_map.js
+share/plugins/Jifty/Plugin/I18N/web/static/js/loc.js
+share/plugins/Jifty/Plugin/JQuery/web/static/js/jquery.js
+share/plugins/Jifty/Plugin/JQuery/web/static/js/noConflict.js
 share/plugins/Jifty/Plugin/OnlineDocs/web/templates/__jifty/online_docs/autohandler
 share/plugins/Jifty/Plugin/OnlineDocs/web/templates/__jifty/online_docs/content.html
 share/plugins/Jifty/Plugin/OnlineDocs/web/templates/__jifty/online_docs/index.html
@@ -783,6 +1010,7 @@
 share/po/en.po
 share/po/fr.po
 share/po/ja.po
+share/po/ru.po
 share/po/zh_cn.po
 share/po/zh_tw.po
 share/web/static/css/app-base.css
@@ -874,12 +1102,14 @@
 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/template_declare.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/element-beta.js
 share/web/static/js/yui/event.js
 share/web/static/js/yui/menu.js
+share/web/static/js/yui/oom_select.patch
 share/web/static/js/yui/tabview.js
 share/web/static/js/yui/yahoo.js
 share/web/templates/=/subs
@@ -926,10 +1156,13 @@
 t/08-client.t
 t/09-url.t
 t/10-i18n.t
+t/10-utf8.t
 t/11-config-files.t
 t/12-param-schema.t
+t/13-sessions.t
 t/99-pod-coverage.t
 t/99-pod.t
+t/clientside/td.t
 t/Continuations/bin/jifty
 t/Continuations/lib/Continuations/Action/CrossBridge.pm
 t/Continuations/lib/Continuations/Action/GetGrail.pm
@@ -958,6 +1191,53 @@
 t/Mapper/t/00-prototype.t
 t/Mapper/t/01-raw-api.t
 t/Mapper/t/02-api.t
+t/TestApp-Plugin-AppPluginHasModels/bin/jifty
+t/TestApp-Plugin-AppPluginHasModels/etc/config.yml
+t/TestApp-Plugin-AppPluginHasModels/lib/TestApp/Plugin/AppPluginHasModels/Plugin/MyAppPlugin.pm
+t/TestApp-Plugin-AppPluginHasModels/lib/TestApp/Plugin/AppPluginHasModels/Plugin/MyAppPlugin/Model/Color.pm
+t/TestApp-Plugin-AppPluginHasModels/Makefile.PL
+t/TestApp-Plugin-AppPluginHasModels/t/plugin-model.t
+t/TestApp-Plugin-Chart/bin/jifty
+t/TestApp-Plugin-Chart/etc/config.yml
+t/TestApp-Plugin-Chart/lib/TestApp/Plugin/Chart/View.pm
+t/TestApp-Plugin-Chart/Makefile.PL
+t/TestApp-Plugin-Chart/t/chart.t
+t/TestApp-Plugin-Chart/t/gd_graph.t
+t/TestApp-Plugin-CompressedCSSandJS/bin/jifty
+t/TestApp-Plugin-CompressedCSSandJS/etc/config.yml
+t/TestApp-Plugin-CompressedCSSandJS/Makefile.PL
+t/TestApp-Plugin-CompressedCSSandJS/t/css.t
+t/TestApp-Plugin-JQuery/bin/jifty
+t/TestApp-Plugin-JQuery/etc/config.yml
+t/TestApp-Plugin-JQuery/Makefile.PL
+t/TestApp-Plugin-JQuery/t/jquery.t
+t/TestApp-Plugin-News/bin/jifty
+t/TestApp-Plugin-News/etc/config.yml
+t/TestApp-Plugin-News/lib/TestApp/Plugin/News/Model/News.pm
+t/TestApp-Plugin-News/lib/TestApp/Plugin/News/View.pm
+t/TestApp-Plugin-News/Makefile.PL
+t/TestApp-Plugin-OAuth/bin/jifty
+t/TestApp-Plugin-OAuth/etc/config.yml
+t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Dispatcher.pm
+t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Model/User.pm
+t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Test.pm
+t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/View.pm
+t/TestApp-Plugin-OAuth/Makefile.PL
+t/TestApp-Plugin-OAuth/t/00-test-setup.t
+t/TestApp-Plugin-OAuth/t/01-basic.t
+t/TestApp-Plugin-OAuth/t/02-request-token.t
+t/TestApp-Plugin-OAuth/t/03-authorize.t
+t/TestApp-Plugin-OAuth/t/04-access-token.t
+t/TestApp-Plugin-OAuth/t/05-protected-resource.t
+t/TestApp-Plugin-OAuth/t/id_rsa
+t/TestApp-Plugin-OAuth/t/id_rsa.pub
+t/TestApp-Plugin-OnClick/bin/jifty
+t/TestApp-Plugin-OnClick/etc/config.yml
+t/TestApp-Plugin-OnClick/Makefile.PL
+t/TestApp-Plugin-OnClick/share/web/templates/content.html
+t/TestApp-Plugin-OnClick/share/web/templates/content1.html
+t/TestApp-Plugin-OnClick/share/web/templates/onclick.html
+t/TestApp-Plugin-OnClick/t/onclick.t
 t/TestApp-Plugin-PasswordAuth/bin/jifty
 t/TestApp-Plugin-PasswordAuth/etc/config.yml
 t/TestApp-Plugin-PasswordAuth/lib/TestApp/Plugin/FasterSwallow.pm
@@ -970,6 +1250,7 @@
 t/TestApp-Plugin-PasswordAuth/t/00-model-User.t
 t/TestApp-Plugin-PasswordAuth/t/01-tokengen.t
 t/TestApp-Plugin-PasswordAuth/t/11-current_user.t
+t/TestApp-Plugin-PasswordAuth/t/12-i18n.t
 t/TestApp-Plugin-PasswordAuth/t/12-signup.t
 t/TestApp-Plugin-REST/bin/jifty
 t/TestApp-Plugin-REST/etc/config.yml
@@ -981,21 +1262,33 @@
 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-Plugin-SinglePage/bin/jifty
+t/TestApp-Plugin-SinglePage/etc/config.yml
+t/TestApp-Plugin-SinglePage/lib/TestApp/Plugin/SinglePage/Model/User.pm
+t/TestApp-Plugin-SinglePage/lib/TestApp/Plugin/SinglePage/View.pm
+t/TestApp-Plugin-SinglePage/Makefile.PL
 t/TestApp/bin/jifty
+t/TestApp/etc/config.yml
+t/TestApp/etc/site_config.yml
 t/TestApp/lib/TestApp/Action/DoSomething.pm
 t/TestApp/lib/TestApp/Action/DoSomethingElse.pm
+t/TestApp/lib/TestApp/Action/SayHi.pm
 t/TestApp/lib/TestApp/CurrentUser.pm
 t/TestApp/lib/TestApp/Dispatcher.pm
+t/TestApp/lib/TestApp/Model/OtherThingy.pm
+t/TestApp/lib/TestApp/Model/Thingy.pm
 t/TestApp/lib/TestApp/Model/User.pm
 t/TestApp/lib/TestApp/Upgrade.pm
 t/TestApp/lib/TestApp/View.pm
 t/TestApp/lib/TestApp/View/base.pm
 t/TestApp/lib/TestApp/View/instance.pm
 t/TestApp/share/web/static/images/pony.jpg
+t/TestApp/share/web/templates/_elements/wrapper
 t/TestApp/share/web/templates/concrete.html
 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/dispatch/protocol
 t/TestApp/share/web/templates/dosomethingelse
 t/TestApp/share/web/templates/editform
 t/TestApp/share/web/templates/index.html
@@ -1003,7 +1296,6 @@
 t/TestApp/share/web/templates/path_test/foo/index.html
 t/TestApp/share/web/templates/path_test/in_both
 t/TestApp/share/web/templates/path_test/mason_only
-t/TestApp/share/web/templates/redirected
 t/TestApp/share/web/templates/regions/list
 t/TestApp/share/web/templates/regions/long
 t/TestApp/share/web/templates/regions/short
@@ -1011,6 +1303,8 @@
 t/TestApp/t/00-model-User.t
 t/TestApp/t/00-prototype.t
 t/TestApp/t/01-config.t
+t/TestApp/t/02-dispatch-http.t
+t/TestApp/t/02-dispatch-https.t
 t/TestApp/t/02-dispatch-show-rule-in-wrong-ruleset.t
 t/TestApp/t/02-dispatch.t
 t/TestApp/t/03-static.t
@@ -1028,10 +1322,22 @@
 t/TestApp/t/13-page-regions.t
 t/TestApp/t/14-template-paths.t
 t/TestApp/t/15-template-subclass.t
+t/TestApp/t/16-template-region.t
+t/TestApp/t/17-template-region-internal-redirect.t
+t/TestApp/t/18-test-www-declare.t
+t/TestApp/t/19-rightsfrom.t
+t/TestApp/t/before_access.t
 t/TestApp/t/config-Cachable
 t/TestApp/t/config-Record
+t/TestApp/t/config/01-basic.t
+t/TestApp/t/config/02-individual.t
+t/TestApp/t/config/02-individual.t-config.yml
+t/TestApp/t/config/03-nosubtest.t
+t/TestApp/t/config/test_config.yml
+t/TestApp/t/crud.t
 t/TestApp/t/i18n-standalone.t
 t/TestApp/t/instance_id.t
 t/TestApp/t/regex_meta_in_path_info.t
+t/TestApp/t/test_config.yml
 t/TestApp/t/upgrade.t
-t/TestApp/testapp
+t/TestApp/t/use_mason_wrapper.t

Modified: jifty/branches/js-refactor/META.yml
==============================================================================
--- jifty/branches/js-refactor/META.yml	(original)
+++ jifty/branches/js-refactor/META.yml	Thu Nov 29 12:25:16 2007
@@ -2,7 +2,7 @@
 build_requires: 
   ExtUtils::MakeMaker: 6.11
 distribution_type: module
-generated_by: Module::Install version 0.670
+generated_by: Module::Install version 0.67
 license: Perl
 meta-spec: 
   url: http://module-build.sourceforge.net/META-spec-v1.3.html
@@ -22,14 +22,28 @@
 recommends: 
   Apache2::Const: 0
   Cache::FileCache: 0
+  Chart::Base: 0
   Class::Accessor::Named: 0
+  Crypt::OpenSSL::RSA: 0
   DBD::SQLite: 0
   Devel::Cover: 0
   Devel::EvalContext: 0
+  Devel::Events: 0.02
+  Devel::Events::Generator::Objects: 0
+  Devel::Events::Handler::ObjectTracker: 0
+  Devel::Size: 0
+  Digest::HMAC_SHA1: 0
+  GD: 0
+  GD::Graph: 0
+  Image::Info: 0
   LWPx::ParanoidAgent: 0
   Module::CoreList: 0
   Module::Install::Admin: 0.50
   Module::Refresh: 0.09
+  Net::OAuth::AccessTokenRequest: 0
+  Net::OAuth::ProtectedResourceRequest: 0
+  Net::OAuth::Request: 0.04
+  Net::OAuth::RequestTokenRequest: 0
   Net::OpenID::Consumer: 0
   Net::Server::Fork: 0
   Net::Server::PreFork: 0
@@ -39,18 +53,21 @@
   Test::HTTP::Server::Simple: 0.02
   Test::MockModule: 0.05
   Test::MockObject: 1.07
+  Test::WWW::Declare: 0.01
   WWW::Facebook::API: 0.3.6
+  XML::Simple: 0
 requires: 
   App::CLI: 0.03
   CGI: 3.19
   CGI::Cookie::Splitter: 0
-  CSS::Squish: 0.05
+  CSS::Squish: 0.07
   Cache::Cache: 0
   Calendar::Simple: 0
   Class::Accessor: 0
   Class::Container: 0
   Class::Data::Inheritable: 0
-  Class::Trigger: 0
+  Class::Trigger: 0.12
+  Clone: 0.27
   Compress::Zlib: 0
   Crypt::CBC: 0
   Crypt::Rijndael: 0
@@ -59,8 +76,9 @@
   Data::UUID: 0
   Date::Manip: 0
   DateTime: 0
+  DateTime::Locale: 0
   Email::Folder: 0
-  Email::LocalDelivery: 0
+  Email::LocalDelivery: 0.217
   Email::MIME: 0
   Email::MIME::ContentType: 0
   Email::MIME::CreateHTML: 0
@@ -73,6 +91,8 @@
   File::Find::Rule: 0
   File::MMagic: 0
   File::ShareDir: 0.04
+  File::Spec: 3.14
+  File::Temp: 0.15
   HTML::Entities: 0
   HTML::Lint: 0
   HTML::Mason: 1.3101
@@ -86,7 +106,7 @@
   IPC::PubSub: 0.23
   IPC::Run3: 0
   JSON::Syck: 0.15
-  Jifty::DBI: 0.42
+  Jifty::DBI: 0.47
   LWP::UserAgent: 0
   Locale::Maketext::Extract: 0.20
   Locale::Maketext::Lexicon: 0.60
@@ -105,16 +125,17 @@
   Scalar::Defer: 0.10
   Shell::Command: 0
   String::Koremutake: 0
-  Template::Declare: 0.21
+  Template::Declare: 0.26
   Test::Base: 0
   Test::Log4perl: 0
   Test::LongString: 0
   Test::More: 0.62
   Test::Pod::Coverage: 0
   Test::WWW::Mechanize: 1.04
+  Test::WWW::Selenium: 0
   UNIVERSAL::require: 0
-  URI: 0
-  WWW::Mechanize: 1.12
+  URI: 1.31
+  WWW::Mechanize: 1.3
   XML::Simple: 0
   XML::Writer: 0.601
   XML::XPath: 0
@@ -122,5 +143,5 @@
   YAML::Syck: 0.71
   perl: 5.8.3
   version: 0
-tests: t/*.t t/*/t/*.t
-version: 0.70422
+tests: t/*.t t/*/*.t t/*/*/*.t t/*/*/*/*.t
+version: 0.71116

Modified: jifty/branches/js-refactor/Makefile.PL
==============================================================================
--- jifty/branches/js-refactor/Makefile.PL	(original)
+++ jifty/branches/js-refactor/Makefile.PL	Thu Nov 29 12:25:16 2007
@@ -8,20 +8,22 @@
 requires('Class::Accessor'); # Class::Accessor::Fast
 requires('Class::Container');
 requires('Class::Data::Inheritable');
-requires('Class::Trigger');
+requires('Class::Trigger' => '0.12');
+requires('Clone' => '0.27');
 requires('CGI' => '3.19');
 requires('CGI::Cookie::Splitter');
 requires('Crypt::CBC');
 requires('Crypt::Rijndael');
 requires('Compress::Zlib');
-requires('CSS::Squish' => 0.05 );
+requires('CSS::Squish' => 0.07 );
 requires('DBD::SQLite' => 1.11 );
 requires('Data::Page');
 requires('Data::UUID');
 requires('DateTime');
+requires('DateTime::Locale');
 requires('Date::Manip');
 requires('Email::Folder');
-requires('Email::LocalDelivery');
+requires('Email::LocalDelivery' => 0.217 );
 requires('Email::MIME');
 requires('Email::MIME::Creator');
 requires('Email::MIME::ContentType');
@@ -34,6 +36,8 @@
 requires('File::Find::Rule');
 requires('File::MMagic');
 requires('File::ShareDir' => '0.04');
+requires('File::Spec' => '3.14');
+requires('File::Temp' => '0.15'); # we need cleanup()
 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
@@ -46,7 +50,7 @@
 requires('Hook::LexWrap');
 requires('IPC::PubSub' => '0.23' );
 requires('IPC::Run3');
-requires('Jifty::DBI' => '0.42' );            # Jifty::DBI::Collection Jifty::DBI::Handle Jifty::DBI::Record::Cachable Jifty::DBI::SchemaGenerator
+requires('Jifty::DBI' => '0.47' );            # 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' => '1.04');
@@ -64,16 +68,17 @@
 requires('Shell::Command');
 requires('String::Koremutake');
 requires('SQL::ReservedWords');
-requires('Template::Declare' => '0.21');                # Template::Declare::Tags
+requires('Template::Declare' => '0.26');                # Template::Declare::Tags
 requires('Test::Base');
-requires('Test::LongString');
 requires('Test::Log4perl');
+requires('Test::LongString');
 requires('Test::More' => 0.62 ),
 requires('Test::Pod::Coverage'),
 requires('Test::WWW::Mechanize' => 1.04 ),
-requires('WWW::Mechanize' => 1.12 ),
+requires('Test::WWW::Selenium'),
+requires('WWW::Mechanize' => 1.30 ),
 requires('UNIVERSAL::require');
-requires('URI');
+requires('URI' => 1.31);
 requires('XML::Writer' => '0.601');
 requires('XML::Simple');
 requires('XML::XPath');
@@ -106,7 +111,8 @@
         recommends('Test::HTML::Lint'),
         recommends('Test::MockModule' => '0.05'),
         recommends('Test::MockObject' => '1.07'),
-        recommends('Module::Refresh' => '0.09')
+        recommends('Module::Refresh' => '0.09'),
+        recommends('Test::WWW::Declare' => '0.01'),
     ],
     'Development of the jifty framework' => [
         -default => 0,
@@ -146,11 +152,39 @@
         -default => 0,
         recommends('Devel::EvalContext'),
     ],
-    'Chart Plugin' => [
+    'Chart Plugin (none of these must be installed for Charts to work)' => [
         -default => 0,
         recommends('Chart::Base'),
+        recommends('GD'),          # for a testing hack
+        recommends('GD::Graph'),
+        recommends('XML::Simple'),
         recommends('Image::Info'), # for testing
     ],
+    'Memory Leak Plugin' => [
+        -default => 0,
+        recommends('Devel::Events' => '0.02'),
+        recommends('Devel::Events::Handler::ObjectTracker'),
+        recommends('Devel::Events::Generator::Objects'),
+        recommends('Devel::Size'),
+    ],
+    'OAuth Plugin' => [
+        -default => 0,
+        recommends('Net::OAuth::Request' => '0.04'),
+        recommends('Net::OAuth::RequestTokenRequest'),
+        recommends('Net::OAuth::AccessTokenRequest'),
+        recommends('Net::OAuth::ProtectedResourceRequest'),
+
+        recommends('Crypt::OpenSSL::RSA'),
+        recommends('Digest::HMAC_SHA1'),
+    ],
+    'Ldap Plugin' => [
+        -default => 0,
+        recommends('Net::LDAP')
+    ],
+    'CAS Plugin' => [
+        -default => 0,
+        recommends('Authen::CAS::Client')
+    ]
 );
 
 
@@ -166,7 +200,7 @@
 install_share;
 
 # Test all of our sub-dist tests too
-tests('t/*.t t/*/t/*.t');
+tests('t/*.t t/*/*.t t/*/*/*.t t/*/*/*/*.t');
 
 WriteAll;
 

Modified: jifty/branches/js-refactor/SIGNATURE
==============================================================================
--- jifty/branches/js-refactor/SIGNATURE	(original)
+++ jifty/branches/js-refactor/SIGNATURE	Thu Nov 29 12:25:16 2007
@@ -14,22 +14,23 @@
 -----BEGIN PGP SIGNED MESSAGE-----
 Hash: SHA1
 
-SHA1 7ce15e058bb6a72aa237ac58e0fbce934b2fef5c AUTHORS
-SHA1 876166ca6f2cf7bfa6a6137070beb1b55b4bc4ce Changelog
-SHA1 ed26a91a1edcc0aed0509bc7384b2d24cfd13d89 MANIFEST
+SHA1 6f71fd6c08d5f693fa5da86b4d173b45d34106dd AUTHORS
+SHA1 4ac08eee20d796a783d41ea99614cfc74ebe4ffe Changelog
+SHA1 f653cf35f9324d8f9d4536bcc2af1df4205945cc MANIFEST
 SHA1 d4adbf5948041cd460da5cb7ad21394a790e2022 MANIFEST.SKIP
-SHA1 f1583a94e31ca844b8d9e2fcd4a239570495c6d3 META.yml
-SHA1 371fc084afa58a74bed4dee9bd09d5b1c544139c Makefile.PL
+SHA1 371972007acfccc3a5ec7182d04d491d86584984 META.yml
+SHA1 f17cae18efd6d5cad8a22e33b2f551124d2d9979 Makefile.PL
 SHA1 e395a2eabaf8faf8266dedc664c1eb52c6c589cf README
 SHA1 aaf8f7a1025fc97077072672f325e2a5f3c03a41 bin/build_par
-SHA1 f7f44f9a7337def0c97f981073e3ed970851d9ae bin/jifty
+SHA1 c1ff9ff7f2a88bc4306b3866b6b80fb9aa8e8423 bin/jifty
 SHA1 bc5d0dc181bffe0694e5282c2d2336eaec5a06ff bin/runcover
 SHA1 9a91a81e3db1a12368153fed9e504aad492cd971 bin/service
 SHA1 543a2677f66d3c8ca671b790509b6c1721ac6270 bin/xgettext
+SHA1 807b1f94b379fd537b780ecf43b0e6642ec00f4d contrib/jifty_completion.sh
 SHA1 1c042485ba8a21f0f124dd8ed412d43d3805430e debian/README
-SHA1 dee0467ffdb9142223c49789a447ba5dd299c28d debian/changelog
+SHA1 abd0bc47e8f6e42610dc1349564f9ab2a71d3857 debian/changelog
 SHA1 5d9474c0309b7ca09a182d888f73b37a8fe1362c debian/compat
-SHA1 c9a13c7f88cc191d5167bbdeebf5e7abbd776b0b debian/control
+SHA1 d12729a01ca782356f865b814b6ed0395220dfa0 debian/control
 SHA1 8fc130ffa6d53c47d94eab1616887c511d54d61f debian/rules
 SHA1 b8bb315ef8fbdd2f069b6339ad0461b5d933d7da doc/ajax-upgraded-links
 SHA1 a0d03921821ca39ea461f1f9fa812fdbb5f501a0 doc/building_a_par
@@ -88,22 +89,11 @@
 SHA1 149b55cfb72c0def0ab7b495445baf8c631ce621 examples/Doxory/share/po/zh_cn.po
 SHA1 bec6af6333016377d442c59bdaaf32ab65a31e4a examples/Doxory/share/po/zh_tw.po
 SHA1 561110ddd8574426cb578a3c81d356bdc4fa47f3 examples/Doxory/t/00-model-User.t
-SHA1 da39a3ee5e6b4b0d3255bfef95601890afd80709 examples/Example-Todo/META.yml
-SHA1 aef127e349b9e06adaad2bec98622582c175e795 examples/Example-Todo/Makefile.PL
-SHA1 f7f44f9a7337def0c97f981073e3ed970851d9ae examples/Example-Todo/bin/jifty
-SHA1 3edcfa5651b780a6e31e4c0e6ad28f3bf22c45ea examples/Example-Todo/etc/config.yml
-SHA1 751fdf0397a92d3bc7f034b3fd08b3438b0eb27f examples/Example-Todo/example_todo
-SHA1 9b2f9d83bcf77860f53a0c07c90a4a59ad9f5df1 examples/Example-Todo/inc/Module/Install.pm
-SHA1 abe32855d75ab13747cf65765af9947b7a8c3057 examples/Example-Todo/inc/Module/Install/Base.pm
-SHA1 381bb98ea3877bba49ae85e7a7ea130645fd3dbf examples/Example-Todo/inc/Module/Install/Metadata.pm
-SHA1 e827d6d43771032fa3df35c0ad5e5698d0e54cda examples/Example-Todo/inc/Module/Install/WriteAll.pm
-SHA1 46730854dc7241dc334b546eef267fde8df24aea examples/Example-Todo/lib/Example/Todo/Model/Todo.pm
-SHA1 61d12c9d3bb174aad4856349686c9e71e75fa7e0 examples/Example-Todo/t/00-model-Todo.t
 SHA1 3b22513afb560789fb0f38f50c55249d70f1b06d examples/HelloKitty/Makefile.PL
 SHA1 a7dc1f376cac630ea28d2965e561469deb951cc7 examples/HelloKitty/bin/jifty
 SHA1 dd97d225e86ab1cb13eabaab12a9b1bfb2410cff examples/HelloKitty/etc/config.yml
 SHA1 24d7e3ade49a5a3a7dd4f2dd74e836bafcbbbedf examples/HelloKitty/hellokitty
-SHA1 2ac96cfdb02f3a3fe5550ceb0442d9a30f7120c9 examples/HelloKitty/lib/HelloKitty/View.pm
+SHA1 c60d5ee79df486d88384b63731a883e3f922e94e examples/HelloKitty/lib/HelloKitty/View.pm
 SHA1 4db882e66864fa40a6532ce5b83699839804b062 examples/MyWeblog/Makefile.PL
 SHA1 a7dc1f376cac630ea28d2965e561469deb951cc7 examples/MyWeblog/bin/jifty
 SHA1 d1394cef410a14394a05e2a6e6a9b32c059cb5d6 examples/MyWeblog/etc/config.yml
@@ -124,57 +114,85 @@
 SHA1 05ac283a14e76750d63678d0864a0403aa18fd28 examples/Ping/share/web/templates/index.html
 SHA1 53c8822ddf426e82fe239e4470574913411b1355 examples/Ping/t/00compile.t
 SHA1 6d6c378447f9d74d53c06a2a027736b48b5d0670 examples/Ping/t/01startup.t
+SHA1 c291f424a0f57833ca391656383d3659ee30518f examples/ShrinkURL/Makefile.PL
+SHA1 c1ff9ff7f2a88bc4306b3866b6b80fb9aa8e8423 examples/ShrinkURL/bin/jifty
+SHA1 3167862eb1985f70a569077d4bdaabf2414e0be8 examples/ShrinkURL/etc/config.yml
+SHA1 f8e845578ba384150af148e1b5704ab67b79d365 examples/ShrinkURL/lib/ShrinkURL/Action/CreateShrunkenURL.pm
+SHA1 6c9d7448f280583d437ce45bfca5f6fa9e2a965f examples/ShrinkURL/lib/ShrinkURL/Dispatcher.pm
+SHA1 17c44063396555e73d65214d587b8231cdbed140 examples/ShrinkURL/lib/ShrinkURL/Model/ShrunkenURL.pm
+SHA1 d8b943e2fd860ac4215aec02cb86c95c4f859212 examples/ShrinkURL/lib/ShrinkURL/View.pm
+SHA1 0d7b61a9ed50444ec98fd289965a8ce9b6d672bd examples/Yada/META.yml
+SHA1 f3ef104950d63f97e7af2ed9bf7dc3f1cf2b80e2 examples/Yada/Makefile.PL
+SHA1 f7f44f9a7337def0c97f981073e3ed970851d9ae examples/Yada/bin/jifty
+SHA1 a9eb5dfc39cebc63c97f5424bb48f343ca025872 examples/Yada/etc/config.yml
+SHA1 527aa5e20eec84dc92664ac55a10884c08b740d1 examples/Yada/inc/Module/Install.pm
+SHA1 8ea4e37df83fd0c1c050be5c8da75545c3828d9b examples/Yada/inc/Module/Install/Base.pm
+SHA1 1da6031583c32f0d1ec073b8376102fc51427dcc examples/Yada/inc/Module/Install/Can.pm
+SHA1 b779375b90c16af2f31f38a1dd2b5df223c7f2fb examples/Yada/inc/Module/Install/Fetch.pm
+SHA1 2054450e1e9c1dd8056362bf4a64ae70d5d71476 examples/Yada/inc/Module/Install/Makefile.pm
+SHA1 5d6189b2cad15cf9932a28faafd55130c8247e83 examples/Yada/inc/Module/Install/Metadata.pm
+SHA1 02af973fae2ac3531fa6b704574b2b8cb2a08148 examples/Yada/inc/Module/Install/Win32.pm
+SHA1 3a2eab96e91cca8d99938cda7791759ae9d97b3a examples/Yada/inc/Module/Install/WriteAll.pm
+SHA1 46730854dc7241dc334b546eef267fde8df24aea examples/Yada/lib/Example/Todo/Model/Todo.pm
+SHA1 734d769faa9a5ae707d8e3bd742dad292742bacc examples/Yada/lib/Yada.pm
+SHA1 b5e82d9dee0d684c4fe0d1a7b858ce87c0c34fb0 examples/Yada/lib/Yada/Model/Todo.pm
+SHA1 652573c9942ef061f3f5c1586e17fb7459a1121f examples/Yada/lib/Yada/Model/User.pm
+SHA1 3a00cd6582f64cf415c67a42ff82dd443e70cf87 examples/Yada/lib/Yada/View.pm
+SHA1 d2cca2c7dc363386dbad6719ee7480308c6bcd6b examples/Yada/lib/Yada/View/Todo.pm
+SHA1 8c476f9ee97286679a557fe41e8142966c0bf1c6 examples/Yada/share/web/static/js/Asynapse/REST.js
+SHA1 d3d09b975c3cf85e257e9316fc2442f336fefd65 examples/Yada/t/00-model-Todo.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 78edb89a439463e44c33a72bbee84c54d0dc8aaf inc/Module/Install.pm
+SHA1 ae32c02b539901de91f06366ce9bdbb7a7bd040b inc/Module/Install/AutoInstall.pm
+SHA1 8ea4e37df83fd0c1c050be5c8da75545c3828d9b inc/Module/Install/Base.pm
+SHA1 1da6031583c32f0d1ec073b8376102fc51427dcc inc/Module/Install/Can.pm
+SHA1 b779375b90c16af2f31f38a1dd2b5df223c7f2fb inc/Module/Install/Fetch.pm
+SHA1 6bf0d0d100b94d1a2ce64d010c8813dec26ac480 inc/Module/Install/Include.pm
+SHA1 2054450e1e9c1dd8056362bf4a64ae70d5d71476 inc/Module/Install/Makefile.pm
+SHA1 5d6189b2cad15cf9932a28faafd55130c8247e83 inc/Module/Install/Metadata.pm
+SHA1 8015c2aceacb9b793e8a472036f93d0bac67c0db inc/Module/Install/Scripts.pm
+SHA1 13f313e303268b76397c6a6206bb65f2d8f1bc7b inc/Module/Install/Share.pm
+SHA1 02af973fae2ac3531fa6b704574b2b8cb2a08148 inc/Module/Install/Win32.pm
+SHA1 3a2eab96e91cca8d99938cda7791759ae9d97b3a inc/Module/Install/WriteAll.pm
 SHA1 c17e8f3cf8ebe1eb4929fd2bd2fd530a9de1abd0 lib/Email/Send/Jifty/Test.pm
-SHA1 0eb6271f949efa613d516c73cb672f7392c336b5 lib/Jifty.pm
-SHA1 337627c441c5639405a2d2cc751c63616d25c221 lib/Jifty/API.pm
-SHA1 7dc39eb45149bd3c9b7e1f19047657dbf84688df lib/Jifty/Action.pm
-SHA1 d73654ad2f7edc2f1661ab866b1db609d83806b3 lib/Jifty/Action/Autocomplete.pm
-SHA1 a96809977dac6f6c644b4b265ce510c13130264c lib/Jifty/Action/Record.pm
-SHA1 9afca5c5a9b57f11eb276afadea614b1be4570f6 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 0334c7ad4458bca073fa22372b4775937a0852e1 lib/Jifty/Bootstrap.pm
-SHA1 7a3849fb30ecb9bb7c0c382b7cc44b6216516de2 lib/Jifty/ClassLoader.pm
-SHA1 1009fa942a8cf3da853694f321d6f67d70613a79 lib/Jifty/Client.pm
-SHA1 a4e91b327848fe1c5b76e4ffaa926300e4c1ef2f lib/Jifty/Collection.pm
-SHA1 70ed11fa2fe5a196b5d5ee18e162e0f561dc7780 lib/Jifty/Config.pm
-SHA1 c6d8ac5f2dc450636478bc3051724f7eac74fce6 lib/Jifty/Continuation.pm
-SHA1 3fc7f4db180481c109248941a65f224c8e346aa8 lib/Jifty/CurrentUser.pm
-SHA1 c3ee8cd867481561dd7359d8f609687746e3b5dd lib/Jifty/DateTime.pm
-SHA1 24425ca3e82abf17e15182b8e13240d9ab102b47 lib/Jifty/Dispatcher.pm
-SHA1 c9c904906c35def343f14c0c216b62771c07f842 lib/Jifty/Event.pm
-SHA1 fb9f33e2838fbff0cd5b5a784adee8b0fc347ebc lib/Jifty/Event/Model.pm
-SHA1 121cc604741f5a674cbbc2a55dfb6d4c8cf11bb8 lib/Jifty/Everything.pm
-SHA1 818bd0aa6afeb39bf96e0068fe3222c74133b4d8 lib/Jifty/Filter/DateTime.pm
-SHA1 53c0eb3380e3f1611866694a6e1294f3537f0d76 lib/Jifty/Handle.pm
-SHA1 947e5aee7b981e19042f3343050a129600b88772 lib/Jifty/Handler.pm
-SHA1 c413c80506fe0a1da3154739249a9351982b1db2 lib/Jifty/I18N.pm
-SHA1 fe370e2c51ca0f20cb0bce133b8c9067c02a524a lib/Jifty/JSON.pm
-SHA1 93d009a7d9a25975786b9ded3dcd5fdac90c9389 lib/Jifty/LetMe.pm
+SHA1 a53c6f6ca0bc2da57af6ce4f3b1c852d854c05e8 lib/Jifty.pm
+SHA1 2d1948051cad65db5062c8f884ec42c85bd82ff1 lib/Jifty/API.pm
+SHA1 0cc91932ee9cf2a93da8004bf4e170f5de391652 lib/Jifty/Action.pm
+SHA1 14be4def02c11173c504eff60256df952b3c305f lib/Jifty/Action/Autocomplete.pm
+SHA1 c9e1cab1d36b20eb587baad7f99b98f03d564902 lib/Jifty/Action/Record.pm
+SHA1 47d9ab2a7703960be292d141e69fc65c528deb1e lib/Jifty/Action/Record/Create.pm
+SHA1 b5556afc5639dc3c5bd4ea8979ab6665ca6503d4 lib/Jifty/Action/Record/Delete.pm
+SHA1 4f2fabd16d6e799b51e7161e488e71c9c608e32a lib/Jifty/Action/Record/Search.pm
+SHA1 ac1190edc6cdbc2eea3077448c60c53b1b5ce524 lib/Jifty/Action/Record/Update.pm
+SHA1 90399bcddb1172f0c61a3fc357538ba08a3a8254 lib/Jifty/Action/Redirect.pm
+SHA1 57f95b0fbfd55d09d079d7c200d3a2451764c40a lib/Jifty/Bootstrap.pm
+SHA1 cf0c59ed858cf62c324e5a2f1195c241ce1da93a lib/Jifty/ClassLoader.pm
+SHA1 c8f230dab4bb43b096a2e5ef90fb9f5f5810ebde lib/Jifty/Client.pm
+SHA1 f2dbb796ad56ace11459e428a823141049633878 lib/Jifty/Collection.pm
+SHA1 2030ddcc399cb097a3fd93b8cb399bbe32ffe9bb lib/Jifty/Config.pm
+SHA1 ece59049dead78c5de734ea9027299de419d1149 lib/Jifty/Continuation.pm
+SHA1 52767831d767d8b48aee80df53dc9a1eb4ced0e6 lib/Jifty/CurrentUser.pm
+SHA1 1ff73e9d244e21fcd1e0f851feede37f069d8281 lib/Jifty/DateTime.pm
+SHA1 efae9f981fcfda810181aee8fa8bf7d3bed372ff lib/Jifty/Dispatcher.pm
+SHA1 e50bd211339e464cd5e17e05a12157e4f9bb5494 lib/Jifty/Event.pm
+SHA1 a956a886b552b6abf1e8c1485297c1bb86ff438c lib/Jifty/Event/Model.pm
+SHA1 8052721bf1354b96fdc98f23b0bffa32595d514f lib/Jifty/Everything.pm
+SHA1 6357a183fc7b6d7f49b314c40198550ba1b6f972 lib/Jifty/Filter/DateTime.pm
+SHA1 8d3265226fc12101b86b523979f86b63b7fe19b0 lib/Jifty/Handle.pm
+SHA1 6ba3aa606558b85b3806330ffcfcf7ad2b80da6b lib/Jifty/Handler.pm
+SHA1 551ca6606a300a0048feeee1e176422d2f0f9b5f lib/Jifty/I18N.pm
+SHA1 581bad0bbce1be704e73fca441ef8a4d211d48d3 lib/Jifty/JSON.pm
+SHA1 7083562103a0feb46db9bd0bd2323fe62dcb1b0c lib/Jifty/LetMe.pm
 SHA1 1687a979438f24c95a6b857ab5305910108bfb49 lib/Jifty/Logger.pm
+SHA1 cc180ff223629e6c1c7b5f2becac4bde27f0fa69 lib/Jifty/Manual.pm
 SHA1 9a01a2e586aa5799fdc0f04c6240220d9dd953b9 lib/Jifty/Manual/AccessControl.pod
 SHA1 ef9ff36385a9f780ac0204bffb9425818d78b789 lib/Jifty/Manual/Actions.pod
-SHA1 d320630f6613f4aa1ec3b9537129fd9ca847fb61 lib/Jifty/Manual/Continuations.pod
-SHA1 99848cf166e90d9c0963a65d09a627ba11a3613e lib/Jifty/Manual/Cookbook.pod
+SHA1 44c07eb236063f6978a945d0faabdcd2b4799126 lib/Jifty/Manual/Continuations.pod
+SHA1 f94a63d52dd3cb9abd6b10ce6436e24d47005649 lib/Jifty/Manual/Cookbook.pod
 SHA1 ace7259a8fc39a43174e3292a8003baa43670da8 lib/Jifty/Manual/Deploying.pod
 SHA1 38a90072f4a25eac8d4480b00290b069e6397673 lib/Jifty/Manual/FAQ.pod
-SHA1 910f64961a65608ab8575a08aa4eab6b1862579b lib/Jifty/Manual/Glossary.pod
-SHA1 f272be20cc67acaf01c116f432a8f42d82c57877 lib/Jifty/Manual/Models.pod
+SHA1 37fd5ebcc551ed3c507699eb1bc34700c37f661d lib/Jifty/Manual/Glossary.pod
+SHA1 f72721fce42a5faea2fad201d95bfe108f016039 lib/Jifty/Manual/Models.pod
 SHA1 1204d70c868084ac3114fae277e98a756f83f819 lib/Jifty/Manual/ObjectModel.pod
 SHA1 60ee994e58dfb4e9190badc16c96c4760e5669cb lib/Jifty/Manual/PageRegions.pod
 SHA1 8ba19a0760196ceb1afbeaa06e7314e254bef258 lib/Jifty/Manual/RequestHandling.pod
@@ -185,112 +203,182 @@
 SHA1 45f95f61826365d39a5eab2cc119967273d17077 lib/Jifty/Manual/Tutorial_ja.pod
 SHA1 2b4c79b6656658b7198ba579b58e3a599d404b01 lib/Jifty/Manual/Upgrading.pod
 SHA1 d0b7a4277c8a3d1a393d7c51fff48b059461f87c lib/Jifty/Manual/UsingCSSandJS.pod
-SHA1 59ce74460e9c1fe8aa1114c2e874348583b76548 lib/Jifty/Mason/Halo.pm
+SHA1 e8e0a5e342506f3656f7b642a0915b9339da3e2c lib/Jifty/Mason/Halo.pm
 SHA1 c7a9988b0826f9c55af2415c7d314ff546193cee lib/Jifty/Model/Metadata.pm
-SHA1 4b89786af61d1490286bc02c4c3a8edb95aa0b60 lib/Jifty/Model/Session.pm
-SHA1 0efcdf22d66e521cf250c1398caf3aba93ed795d lib/Jifty/Model/SessionCollection.pm
-SHA1 d15fc44fc8af2c4a7db3c2f08e1d69d6f397e734 lib/Jifty/Module/Pluggable.pm
-SHA1 f7c511c6b0c6ccfc3c5fdc0bf0a4e224d78a9c40 lib/Jifty/Notification.pm
-SHA1 7223070583b1b15f651db7a71b97e039e084aa4f lib/Jifty/Object.pm
+SHA1 9612083e329a3366aed947c725340b5b858eca66 lib/Jifty/Model/Session.pm
+SHA1 869954cc1c1a636bf45aa43fa3e565ba19e39007 lib/Jifty/Model/SessionCollection.pm
+SHA1 bb261929a2f58a0dbda883febc7842ae6db88aa4 lib/Jifty/Module/Pluggable.pm
+SHA1 b70ca16929be0c714bbb60403b253ceae5fb613e lib/Jifty/Notification.pm
+SHA1 445f707ba7577b529bf7ca9ddcd2587250d0b272 lib/Jifty/Object.pm
 SHA1 c3fde2a862013cd6284637d79c751c3c2e360720 lib/Jifty/Param.pm
-SHA1 c4c0387640f7eafb35b346798c5e150c6ecb7496 lib/Jifty/Param/Schema.pm
-SHA1 134c5535e4b1f5ef0a5a8f78259b36c4bf247bed lib/Jifty/Plugin.pm
+SHA1 07d789d9e304da8e632ce1236dda4a3e7fab8fc7 lib/Jifty/Param/Schema.pm
+SHA1 ea8bc6f6595d6dcab26a5457638bd233d6b476d4 lib/Jifty/Plugin.pm
+SHA1 e5af7b18d906a1e69fc96d97f13c3d003fe9f217 lib/Jifty/Plugin/ActorMetadata.pm
+SHA1 3932c22836be32e8d9ffcb2776dfb03ed4c8a2de lib/Jifty/Plugin/ActorMetadata/Mixin/Model/ActorMetadata.pm
 SHA1 328b724a1f266999eb1f12e60a7ac6fcc533d177 lib/Jifty/Plugin/AdminUI.pm
-SHA1 00354893bf1283e9cf1b52de5b41bdfb57a78811 lib/Jifty/Plugin/Authentication/Password.pm
-SHA1 9f078a684545d3a9c075d83812cffeca0a3b633e lib/Jifty/Plugin/Authentication/Password/Action/ConfirmEmail.pm
+SHA1 a4cf065e1e54ac09a715d89b107768b0ae19e0b6 lib/Jifty/Plugin/Authentication/Facebook.pm
+SHA1 eb0ac23054f98ef6ae30ae06a03adaf896fd601c lib/Jifty/Plugin/Authentication/Facebook/Action/LinkFacebookUser.pm
+SHA1 ac51405e49435ce2745458f7e1fa9f0534eceb53 lib/Jifty/Plugin/Authentication/Facebook/Action/LoginFacebookUser.pm
+SHA1 ec31d1cac498f4cb7763807eb5cf34aa8a9c01e5 lib/Jifty/Plugin/Authentication/Facebook/Dispatcher.pm
+SHA1 672cb1f77ca40e0a6fb2d8088cfb38ba9e31e0e8 lib/Jifty/Plugin/Authentication/Facebook/Mixin/Model/User.pm
+SHA1 1bfe9c2762606521f55072a011b9878234f3c5f8 lib/Jifty/Plugin/Authentication/Facebook/View.pm
+SHA1 e435ff3b0c7b8a79d5b1d25802f4e2918bc94087 lib/Jifty/Plugin/Authentication/Password.pm
+SHA1 e9c5a3f4263412e003ff55a88b794424608008dc lib/Jifty/Plugin/Authentication/Password/Action/ConfirmEmail.pm
 SHA1 de9208f8911bc09594299b618a7ce05c71cac57e lib/Jifty/Plugin/Authentication/Password/Action/GeneratePasswordToken.pm
-SHA1 2da111cb0154fe09287b9c8db251b5d8720d0379 lib/Jifty/Plugin/Authentication/Password/Action/Login.pm
+SHA1 d77cef14fd4a866d308c171a337a37c95a697179 lib/Jifty/Plugin/Authentication/Password/Action/Login.pm
 SHA1 3ccf33cfd732343cfeeda7f2acc1df7c7da721fe lib/Jifty/Plugin/Authentication/Password/Action/Logout.pm
 SHA1 074fe2fa59464a78b69558f2c20b39fe45a6e56e lib/Jifty/Plugin/Authentication/Password/Action/ResendConfirmation.pm
 SHA1 eb20b698db12db185f08e4d0e0a6c39dc2c1432f lib/Jifty/Plugin/Authentication/Password/Action/ResetLostPassword.pm
-SHA1 89d74967f85b631644d039393920646cfb772f2a lib/Jifty/Plugin/Authentication/Password/Action/SendAccountConfirmation.pm
-SHA1 28604b89817992c647042420090a59fee0fe06bd lib/Jifty/Plugin/Authentication/Password/Action/SendPasswordReminder.pm
+SHA1 204602d1b7b07f7c98b1369a25c18099e3ea5100 lib/Jifty/Plugin/Authentication/Password/Action/SendAccountConfirmation.pm
+SHA1 2a539002f3fd0b07c9132610f4cb01625aea3291 lib/Jifty/Plugin/Authentication/Password/Action/SendPasswordReminder.pm
 SHA1 3d0b444280c3b0ae577e0ada5cb3fd2eb8803f9f lib/Jifty/Plugin/Authentication/Password/Action/Signup.pm
-SHA1 b0ccc1d001aeca18ad7466b531081805408b2010 lib/Jifty/Plugin/Authentication/Password/Dispatcher.pm
-SHA1 3191af60483a20faf1d624da37929e3310ced14c lib/Jifty/Plugin/Authentication/Password/Mixin/Model/User.pm
+SHA1 e18879da7a75bd2fe96dcc380a72d005a3a82aad lib/Jifty/Plugin/Authentication/Password/Dispatcher.pm
+SHA1 60cd4b172ffd30856756a944075fb6d0c528d3ed lib/Jifty/Plugin/Authentication/Password/Mixin/Model/User.pm
 SHA1 9711ece6f3637e972708c8bda313aaa57fd1b405 lib/Jifty/Plugin/Authentication/Password/Notification/ConfirmEmail.pm
 SHA1 f8e1724e5d0d3e490439f0ad761a96155ff1b5a4 lib/Jifty/Plugin/Authentication/Password/Notification/ConfirmLostPassword.pm
-SHA1 91272a6c9926cdacbfc10afe8624a35868a8e8be lib/Jifty/Plugin/Authentication/Password/View.pm
+SHA1 ed84b2845c3b6b53e20f420862f8f8a23a569700 lib/Jifty/Plugin/Authentication/Password/View.pm
+SHA1 33e798ef8c54a46e9332c5c38a7dcfaf8b3aee26 lib/Jifty/Plugin/AutoReference.pm
+SHA1 bc86ea08d5055c8488f1836c8a3fa2c87975db30 lib/Jifty/Plugin/AutoReference/Widget.pm
+SHA1 632df071506dc232b7855f31e216a05055262874 lib/Jifty/Plugin/Chart.pm
+SHA1 8d018ee02a46e1ab758d2096b9a1039550dfe913 lib/Jifty/Plugin/Chart/Dispatcher.pm
+SHA1 4bc7a990867f0482b2c49f8cd28e328fb5947797 lib/Jifty/Plugin/Chart/Renderer.pm
+SHA1 8a69203b659fed3a62dc3e7678b37bc9e3edb0c6 lib/Jifty/Plugin/Chart/Renderer/Chart.pm
+SHA1 ec469e4ef188a1fdeadd208974ffa2c0afa79f2c lib/Jifty/Plugin/Chart/Renderer/GD/Graph.pm
+SHA1 5f5c9a658bef0074b24ec8567305085b28022cf2 lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
+SHA1 3e11c9832ed05ad5cbb4095081654d53284628e5 lib/Jifty/Plugin/Chart/Renderer/SimpleBars.pm
+SHA1 387e83601ac32063ed23927a90bce4c7c5b8fc5c lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
+SHA1 436d7ef252dfe555b0d0d56cf126d30cad59728f lib/Jifty/Plugin/Chart/View.pm
+SHA1 e842dafa977259712540df4639249689e3bd1c90 lib/Jifty/Plugin/Chart/Web.pm
 SHA1 da70c7fbcd78d7ae9bd53deb74a022d0eeea4a12 lib/Jifty/Plugin/ClassLoader.pm
-SHA1 25d4c8b9459c9c299fb181d41539475a854309f0 lib/Jifty/Plugin/CompressedCSSandJS.pm
-SHA1 3b6cb101e9e3465cefdb843fb9108cb126644284 lib/Jifty/Plugin/CompressedCSSandJS/Dispatcher.pm
+SHA1 f0390d573033b27d73e2ba1ab1dddaeabdf786df lib/Jifty/Plugin/CompressedCSSandJS.pm
+SHA1 3143510bdf480a1d42ea4199119d808443c3246b lib/Jifty/Plugin/CompressedCSSandJS/Dispatcher.pm
+SHA1 f7c65a1e0fd9a9cc3a69238e5d0c94cfdacbf484 lib/Jifty/Plugin/Debug.pm
+SHA1 bccc0b4e51396e7b3d95d43ce75774d922a4fc14 lib/Jifty/Plugin/Debug/Dispatcher.pm
 SHA1 b6a34618e94885dac38d0a6b501a63f5c0184008 lib/Jifty/Plugin/ErrorTemplates.pm
-SHA1 07037bce896482134852dcfb6c15a8bbb8fa6adc lib/Jifty/Plugin/ErrorTemplates/View.pm
+SHA1 23acc252dd633a0c6873acadced84729dd6be728 lib/Jifty/Plugin/ErrorTemplates/View.pm
+SHA1 2ce2a6c92f94c786e60d995ff88ac4c61538ef7a lib/Jifty/Plugin/Feedback.pm
+SHA1 c11790aee7ac9c20ea1dea6dbcdc392e25fb393b lib/Jifty/Plugin/Feedback/Action/SendFeedback.pm
+SHA1 1c214a74f063384a7e59e516f917131a2338ace3 lib/Jifty/Plugin/Feedback/View.pm
+SHA1 d4f28de89e6bc1bde247a7b5e4e78b19961772e0 lib/Jifty/Plugin/GoogleMap.pm
+SHA1 e79fe2283de73ff1d369e386129ff0adfdfad61b lib/Jifty/Plugin/GoogleMap/Widget.pm
 SHA1 f13f769cb4d6d529bcfc46647512544eaaa54774 lib/Jifty/Plugin/Halo.pm
+SHA1 2ba0c0d41adf3f9ae6102c4ee0339c69cb1fa41c lib/Jifty/Plugin/I18N.pm
+SHA1 9ab115aaab5a08b1b25e4acfdb338c736df13af1 lib/Jifty/Plugin/I18N/Action/SetLang.pm
+SHA1 668a78f235a1b025f9d15328f646df5578178f3b lib/Jifty/Plugin/JQuery.pm
+SHA1 2b148ed613cee998ac790f8dca27148fd2521b2d lib/Jifty/Plugin/LeakTracker.pm
+SHA1 c6cc61f0b8e1d83dfb6ab54653c65322df50cabc lib/Jifty/Plugin/LeakTracker/Dispatcher.pm
+SHA1 fc29cecc82021c3079b9375ca86b911c56e731ab lib/Jifty/Plugin/LeakTracker/View.pm
 SHA1 432d5c6343b4390dcd135918d7f729d2c01eee5c lib/Jifty/Plugin/LetMe.pm
 SHA1 8692eb8735e17f6f9a25f41c368da4a6839363c8 lib/Jifty/Plugin/LetMe/Dispatcher.pm
+SHA1 0179fb90782af6cf4f8f0336d9ce4123f2fa4ee6 lib/Jifty/Plugin/OAuth.pm
+SHA1 ec118e0358980bd9095d4258c025177b340d241e lib/Jifty/Plugin/OAuth/Action/AuthorizeRequestToken.pm
+SHA1 97383f671fd34c2ed8c26cc98f0d7e557e51268e lib/Jifty/Plugin/OAuth/Dispatcher.pm
+SHA1 906219fce92eca4d483e4b571280a60b5320ecd3 lib/Jifty/Plugin/OAuth/Model/AccessToken.pm
+SHA1 a0a2965b3511771a3ac32e659adb33bc572b6ebd lib/Jifty/Plugin/OAuth/Model/Consumer.pm
+SHA1 6d90773d54a5cea5fc7ea35c1240bd5b5987f1ba lib/Jifty/Plugin/OAuth/Model/RequestToken.pm
+SHA1 0ecb471c00653630443029761dce4816f8b308d3 lib/Jifty/Plugin/OAuth/Token.pm
+SHA1 ef4c52b621a5b0af3dcd5668036ac46d479a31c8 lib/Jifty/Plugin/OAuth/View.pm
 SHA1 af733b25eee3cc4e0e6b087a76bfd965534a2343 lib/Jifty/Plugin/OnlineDocs.pm
 SHA1 fcc3ccbee7c9646745fc79475d53f67e81d7c0db lib/Jifty/Plugin/OnlineDocs/Dispatcher.pm
+SHA1 c39eb405a1d25e84b582868e12279fcbdcede688 lib/Jifty/Plugin/OpenID.pm
+SHA1 006642ea3c45f3c0134488fcd465dcbc684712ef lib/Jifty/Plugin/OpenID/Action/AuthenticateOpenID.pm
+SHA1 fd06af13989202e7f5951ae4a244a051a4a8962b lib/Jifty/Plugin/OpenID/Action/CreateOpenIDUser.pm
+SHA1 f2a361896ac1b484cff868f901faca7b66d40eb6 lib/Jifty/Plugin/OpenID/Action/VerifyOpenID.pm
+SHA1 dc827e93d2ef9828fd5ffc7d0d5cc4c56a7ea3dc lib/Jifty/Plugin/OpenID/Dispatcher.pm
+SHA1 fe37000b633c08c77eaf6adb6cf0847a26450eb8 lib/Jifty/Plugin/OpenID/Mixin/Model/User.pm
+SHA1 1a30f243c1f3efa0fd045f922eb5f062c80d80b9 lib/Jifty/Plugin/OpenID/View.pm
 SHA1 df332565b291fd458fab232414b24283f379ac16 lib/Jifty/Plugin/REST.pm
-SHA1 053b6f7d44ade4ed7567a4a0c444e777108b26c3 lib/Jifty/Plugin/REST/Dispatcher.pm
+SHA1 e9e5b593b8f16289618b06ceb79a819fda1b00f3 lib/Jifty/Plugin/REST/Dispatcher.pm
+SHA1 decbf7728e192b9fcb3f8d330279315bae429c3f lib/Jifty/Plugin/SinglePage.pm
+SHA1 23720e49b68018925083f408d10de34d0f297042 lib/Jifty/Plugin/SinglePage/Dispatcher.pm
+SHA1 f866590b203c18013cc2f667de7b889e84895670 lib/Jifty/Plugin/SiteNews.pm
+SHA1 b72aeb9e89b787e0feb81e421c5e63de36f48726 lib/Jifty/Plugin/SiteNews/Dispatcher.pm
+SHA1 654eec3ba80aa6199c558ac13484308a214a1219 lib/Jifty/Plugin/SiteNews/Mixin/Model/News.pm
+SHA1 a3bf9e62b68d625ac0da1fd3b571d91258b303d1 lib/Jifty/Plugin/SiteNews/View/News.pm
 SHA1 7fa0c5fa1ef6dbe8f1c4a351133f74ca32a70507 lib/Jifty/Plugin/SkeletonApp.pm
-SHA1 1b07ce39502e28564f41d8dd27a4c0094de9f354 lib/Jifty/Plugin/SkeletonApp/Dispatcher.pm
-SHA1 81362e19c903ba6c98c3cd84bc7b4b8f5615d05e lib/Jifty/Plugin/SkeletonApp/View.pm
-SHA1 a51b4791f28c6f64ba00ac21fc05933b32e19015 lib/Jifty/Plugin/User.pm
-SHA1 a1c827266b0db910b02476eee76f6a5a4663e2ba lib/Jifty/Plugin/User/Mixin/Model/User.pm
+SHA1 63165112948dd17a86a0544ff60f82d4ed89a777 lib/Jifty/Plugin/SkeletonApp/Dispatcher.pm
+SHA1 af327531d97512491cf14c42bad1dc2c606eab89 lib/Jifty/Plugin/SkeletonApp/View.pm
+SHA1 812425a5f9a9a108a3d878e347e77dcb532ae19a lib/Jifty/Plugin/TabView/View.pm
+SHA1 df4d15dcb23b75e7b785cfbc282a3bc1e6c37a18 lib/Jifty/Plugin/UUID.pm
+SHA1 e58f034ec726d72becad8ff361097c4f115549f8 lib/Jifty/Plugin/UUID/Widget.pm
+SHA1 5ce0a4dd684e7a9029f67a3679f268acbd1cbf31 lib/Jifty/Plugin/User.pm
+SHA1 5811d61115c8162417885dec09180ab3f356a962 lib/Jifty/Plugin/User/Mixin/Model/User.pm
+SHA1 9aa1995736d5663c0f081301f917d767c2757de6 lib/Jifty/Plugin/Userpic.pm
+SHA1 263cb41986cec282d15556d7d54216118a325d00 lib/Jifty/Plugin/Userpic/Dispatcher.pm
+SHA1 37016b10d1f2f0cc479b549009d1307237099f22 lib/Jifty/Plugin/Userpic/View.pm
+SHA1 5712e2f84235c7b57e38ac85513bbecbdc15dfdd lib/Jifty/Plugin/Userpic/Widget.pm
 SHA1 c77500585cddf1c520b0438384481a1b68d13c57 lib/Jifty/Plugin/Yullio/View.pm
-SHA1 26b636ef6291098364119287e7363be9f58990f7 lib/Jifty/Record.pm
-SHA1 8ba38d25e9f6ead360e20baa457f119694584edd lib/Jifty/Request.pm
-SHA1 7e4d83147f5f665bb0e8d290ea70340b82a205c2 lib/Jifty/Request/Mapper.pm
+SHA1 9458fff3a4f56022672619c13b6a3db8e838d61f lib/Jifty/Record.pm
+SHA1 fc22db5499b913c86762c0e90c3b22a82e451e23 lib/Jifty/Request.pm
+SHA1 86d7ce58578cd3a06e17e7d3c8314e0741ba68f8 lib/Jifty/Request/Mapper.pm
 SHA1 0a92b4cdb402463e303b897195c9ad914767c27f lib/Jifty/Response.pm
 SHA1 669b0956c14105b4178875d6856b129b75941328 lib/Jifty/Result.pm
-SHA1 126fb131c36f6ff7dc6cc4193b0c9a402168a832 lib/Jifty/RightsFrom.pm
-SHA1 83f1a04d22cb553f0af2842b7fe603277441f72b lib/Jifty/Script.pm
+SHA1 d8bdfa7dfa44a99aa5ebf96b9e317ff31b54aad7 lib/Jifty/RightsFrom.pm
+SHA1 e153160eb4f058c779bf10bb823a47f7e2e0a77f lib/Jifty/Schema.pm
+SHA1 4d1aa083b1377fe5938f8e610d626995cb80d1fd lib/Jifty/Script.pm
 SHA1 f97fb67cd7db7aa2374ef4a0f1b8b9705e3ce153 lib/Jifty/Script/Action.pm
 SHA1 3aa3a449b282e05dfb312cf266572edabf2bae17 lib/Jifty/Script/Adopt.pm
-SHA1 84a9ddd8c2f9d738daed65201ae938cb5e0a45de lib/Jifty/Script/App.pm
-SHA1 1ad9937d9e28f1568954bcffac500be87f3cb355 lib/Jifty/Script/Deps.pm
+SHA1 36ab3422992cd7286a01a48019d64b5b31e86b55 lib/Jifty/Script/App.pm
+SHA1 f1c1078236ad2396a7c4b98979108980e3bcd25c lib/Jifty/Script/Console.pm
+SHA1 2ae45f322b3b5904225465b85edb544ba6216eaa lib/Jifty/Script/Deps.pm
 SHA1 3c26ea2d6e25c42ecd7edae41d4ad2fc552f1451 lib/Jifty/Script/Env.pm
-SHA1 79fbed4c7b6e3b85cd78afbc69bd990f45f97a01 lib/Jifty/Script/FastCGI.pm
+SHA1 1c590d75775560691e63bb62de2462d49d1c9815 lib/Jifty/Script/FastCGI.pm
 SHA1 e25a259fc9fd3183a7981236f428f54ac4c4f49e lib/Jifty/Script/Help.pm
 SHA1 29d16baf7dbf778f6082f8852723b6ef82842ef0 lib/Jifty/Script/ModPerl2.pm
 SHA1 ae7d6a97f55a111e10cbc127d3174c48dc896afc lib/Jifty/Script/Model.pm
-SHA1 77c221bb4b7162b2fc95c841ceeea04194afb264 lib/Jifty/Script/Plugin.pm
-SHA1 3365d06f92ffe40175f2c9d7fa4175c48cd42ed9 lib/Jifty/Script/Po.pm
-SHA1 cf7d62a4634d32342e5b4581ceb1dc8c78439943 lib/Jifty/Script/Schema.pm
-SHA1 50e6a73afeb945145576c079d14d3707d13b4344 lib/Jifty/Script/Server.pm
-SHA1 48a8889b44c0450ccb2d27cbb0b7c7f3ae26a426 lib/Jifty/Server.pm
+SHA1 4fae01f79baf125856a69265fa9f4caeb5f858bf lib/Jifty/Script/Plugin.pm
+SHA1 163f0b9e8e01f4f4c7edf96fdbe1a27aee29b785 lib/Jifty/Script/Po.pm
+SHA1 c12c0206c00f40dbd511d71834d2499f73e0850b lib/Jifty/Script/Schema.pm
+SHA1 e18ad09e9078c0a9782c588398cd38541f966b5d lib/Jifty/Script/Server.pm
+SHA1 4918c842d52a7ab4de9d60da95a47a31d04d4631 lib/Jifty/Server.pm
 SHA1 b49ae221b107519b3019b3f5e5ab5b7e8c6b4332 lib/Jifty/Server/Fork.pm
-SHA1 9a8c2f2cdc88fc00baaafaa72bc3413e086c0448 lib/Jifty/Server/Prefork.pm
+SHA1 d151b0cb6c1f9b63a2671a7211c30f965b12289e lib/Jifty/Server/Prefork.pm
 SHA1 e7453a3cda290e60d5432e60e53bec8b92a91772 lib/Jifty/Server/Prefork/NetServer.pm
-SHA1 4c51d2df15281788b74d080488b0c5959cd95a1a lib/Jifty/Subs.pm
-SHA1 b3d122eed10c4ac3cba561913a42220c2f5ee085 lib/Jifty/Subs/Render.pm
-SHA1 e15bde68ba67b141ae1565be066f1e1f171f63ec lib/Jifty/Test.pm
-SHA1 0d6d53b5084bb89baea771d3e9d602a195255e37 lib/Jifty/Test/WWW/Mechanize.pm
+SHA1 3e117a5866ca55825f4f292e95af01f98fe67edc lib/Jifty/Subs.pm
+SHA1 7b8eed10682b0e357b2049fa479e7d85bd4b1b58 lib/Jifty/Subs/Render.pm
+SHA1 565ba8120759b462ed66c4ce376ac21547d5f96f lib/Jifty/Test.pm
+SHA1 db34b059e165273d1474a858604abed5d73ef551 lib/Jifty/Test/WWW/Declare.pm
+SHA1 654f9c629de22884beefacf1194e6a89c94402c5 lib/Jifty/Test/WWW/Mechanize.pm
+SHA1 5011ef6d08ca86adb2f14bae1474df7ed94a50e8 lib/Jifty/Test/WWW/Selenium.pm
 SHA1 47c5840fafd56473a0e1a9228f169d3813317c13 lib/Jifty/TestServer.pm
-SHA1 a0ad0b8d54a5ed0edbbac99f6c06a9438a4046fa lib/Jifty/Upgrade.pm
-SHA1 f3957ac04ad92737d68b2a63f068679b4bacafed lib/Jifty/Upgrade/Internal.pm
-SHA1 d42ff4531e36438208cd8242d15f7d210928ff6b lib/Jifty/Util.pm
-SHA1 05af258e63bbb8ea166e845b8028546b858fcecb lib/Jifty/View/Declare.pm
-SHA1 dd7596d3d4cc8df03c074a44a0cd0813a5cf0a89 lib/Jifty/View/Declare/BaseClass.pm
-SHA1 912220dc62ae0d21d78ed499626a235a14db09a6 lib/Jifty/View/Declare/CoreTemplates.pm
-SHA1 88c62ed42916e58a344e1d09e9a0019436e34cb6 lib/Jifty/View/Declare/Handler.pm
-SHA1 bba1390f47745aa843a72565fbdf1adce3afd241 lib/Jifty/View/Declare/Helpers.pm
-SHA1 1df8f813bdc979dd182a455e9953254abcd247ed lib/Jifty/View/Mason/Handler.pm
-SHA1 2e97fd90420ade1dcdc6d5b815a352cd83c23671 lib/Jifty/View/Static/Handler.pm
-SHA1 94172b92971ea2a5fcdc3f11ebcc94a8274688c6 lib/Jifty/Web.pm
-SHA1 5dbb5f45d0ff0e0ddfabe91d6152c1aa507dbc9b lib/Jifty/Web/Form.pm
-SHA1 f073670eb2f206f99bd1f1377740965cd5da5ac1 lib/Jifty/Web/Form/Clickable.pm
-SHA1 d77f222ad036fe8b39715c85fab009b7a283e1b9 lib/Jifty/Web/Form/Element.pm
-SHA1 a823ab5fae50fa6222c4d3143779d2288e1cc3da lib/Jifty/Web/Form/Field.pm
-SHA1 bb9dc120af472abe3a35f89c772728c75ed8a3c2 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 a4787c85a11c091f8560d1960edce1aaeeced2c9 lib/Jifty/Web/Menu.pm
-SHA1 e912f7c224380aaf79c6cece6219670a973fe85e lib/Jifty/Web/PageRegion.pm
-SHA1 0fb24e6615e89b7f3b2b0af7457c4aaa61a48ad9 lib/Jifty/Web/Session.pm
+SHA1 5c0db87837a6a1311ec85b03f06623468afe8098 lib/Jifty/Upgrade.pm
+SHA1 fa642baf010edb939aa7682ae68b07c8a69b06b6 lib/Jifty/Upgrade/Internal.pm
+SHA1 a5f6803f9cb942696c2f875b0e6161b439c36a52 lib/Jifty/Util.pm
+SHA1 45b78364045808eeb2c895760646d910ae06c709 lib/Jifty/View.pm
+SHA1 5da88edb702a7ad194a13656838fff0d1459760c lib/Jifty/View/Declare.pm
+SHA1 f646c99eb386ca9ecb58e0f2184c2b153a547aaf lib/Jifty/View/Declare/BaseClass.pm
+SHA1 b9b0894f722dd9eca9107425c79434909cb6bc06 lib/Jifty/View/Declare/CRUD.pm
+SHA1 d2b2fa668602ba5a46b604bc4d340afb408fabd6 lib/Jifty/View/Declare/Compile.pm
+SHA1 a15bc046d04afb7f7985d65f9498d24b3bb6fdbd lib/Jifty/View/Declare/CoreTemplates.pm
+SHA1 98a230a110f1a4021dc6bd8affaa67fb608c68cf lib/Jifty/View/Declare/Handler.pm
+SHA1 149f7757d07f7ead613781ceaf1dcd17c2d1913c lib/Jifty/View/Declare/Helpers.pm
+SHA1 a7687bce27c03cd6f8a6928fbd03b291326933ed lib/Jifty/View/Declare/Page.pm
+SHA1 dd83643c2642a05950f05af9c7ebd5886504fc60 lib/Jifty/View/Mason/Handler.pm
+SHA1 baaa9dc12e0a3f7f4fa737c0842bc542bdad1e72 lib/Jifty/View/Static/Handler.pm
+SHA1 aedf4742499f18d7589f44f57d16a7bd8c5d2ac2 lib/Jifty/Web.pm
+SHA1 bf7ec95d6ce6c0ed7abd0d89b24d7521344a8ff3 lib/Jifty/Web/Form.pm
+SHA1 9ca1418c5fd7e539cc666cf7ab57285e49c7d0e4 lib/Jifty/Web/Form/Clickable.pm
+SHA1 47534929a065db0a5eaff83bfe3abc44292acc5d lib/Jifty/Web/Form/Element.pm
+SHA1 b3a69a7f5439e70b1104b015fda293c61eab62ab lib/Jifty/Web/Form/Field.pm
+SHA1 03322439e64fbaca08164b19de0d24cf0e5fdc82 lib/Jifty/Web/Form/Field/Button.pm
+SHA1 1f5e05a6cc7483112a86f9863752da7742797242 lib/Jifty/Web/Form/Field/Checkbox.pm
+SHA1 9ecf88f8cf1af8e08bc5cdcdd8357da794cca82f lib/Jifty/Web/Form/Field/Collection.pm
+SHA1 8ca1e7b6637720d0eb65c5391d52c573ad2510c7 lib/Jifty/Web/Form/Field/Combobox.pm
+SHA1 a57b8a2908b9642320f07c91fab31d8eab63c97c lib/Jifty/Web/Form/Field/Date.pm
+SHA1 d8fdc993320b9e81926181e028b5fc688ec874fe lib/Jifty/Web/Form/Field/Hidden.pm
+SHA1 4db6a90ae2668ba11eb9e8633f949ee45e56cfad lib/Jifty/Web/Form/Field/InlineButton.pm
+SHA1 1b98474c61f1065ffe76b5a9f8356e4a10afc202 lib/Jifty/Web/Form/Field/Password.pm
+SHA1 ceddfc29b6b45228497a0dcd33754fed669b6491 lib/Jifty/Web/Form/Field/Radio.pm
+SHA1 bbac35dafb24e72fa91643f6353796e5d856258e lib/Jifty/Web/Form/Field/ResetButton.pm
+SHA1 1957d01fa20adf8e9ee30bd44c180653c886bc11 lib/Jifty/Web/Form/Field/Select.pm
+SHA1 04507f0d805d9e8e4ef4da4d7a919f5e7d7986fe lib/Jifty/Web/Form/Field/Text.pm
+SHA1 b287156ff11abaf1a09739b1372d240ff1c86171 lib/Jifty/Web/Form/Field/Textarea.pm
+SHA1 20eb63da765651816b5c5c7c03bd8f8f8ca107f3 lib/Jifty/Web/Form/Field/Unrendered.pm
+SHA1 3e19292cf95afaf08e811cd9814a33d69ddfddaa lib/Jifty/Web/Form/Field/Upload.pm
+SHA1 40ca8f6a48095dbacbe8fe2c1cc7e97466a7339f lib/Jifty/Web/Form/Link.pm
+SHA1 0a5c4adca9b3af4680a919c58ff146f70c6d1918 lib/Jifty/Web/Menu.pm
+SHA1 d407e3f85e158fa3fe86295d63fb602c4a20fae0 lib/Jifty/Web/PageRegion.pm
+SHA1 46deb18cbea37dcbbb14581965a34147d21ba091 lib/Jifty/Web/Session.pm
 SHA1 f655fb9734715ebf51fb5e9b554c02b9e4e2ac61 lib/Jifty/Web/Session/ClientSide.pm
 SHA1 d7a8f92ddbc614904ad6aa53d818b0bd5df03317 lib/Jifty/Web/Session/None.pm
 SHA1 c4de1ef964243aae5ab90d0a5a6dd9c213eb9f80 lib/Jifty/YAML.pm
@@ -369,6 +457,47 @@
 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 2ed0376180340aeda1ddd9a726d31cc4280db0d5 plugins/CodePress/Makefile.PL
+SHA1 38d2e59ce6c94c5d1decabf2264917ad036c8d40 plugins/CodePress/doc/index.html
+SHA1 6e0db78e2f82a523d83d82cf2ffad63ed6f7d755 plugins/CodePress/lib/Jifty/Plugin/CodePress.pm
+SHA1 a96dea1ba0989dce13dc9415a612ffc5166714a1 plugins/CodePress/lib/Jifty/Plugin/CodePress/Textarea.pm
+SHA1 c7eb60ddfb4cd7b0fa88527e73e34ca3ed9a2434 plugins/CodePress/share/web/static/codepress/codepress.css
+SHA1 6962d574546a57cb39fde034e87d341d9ee492d1 plugins/CodePress/share/web/static/codepress/codepress.html
+SHA1 19b105de2058da5842e19aaf9d14119c4475138d plugins/CodePress/share/web/static/codepress/engines/gecko.js
+SHA1 da39a3ee5e6b4b0d3255bfef95601890afd80709 plugins/CodePress/share/web/static/codepress/engines/khtml.js
+SHA1 f107593f521e20ee5fe2709776c22df619efcaab plugins/CodePress/share/web/static/codepress/engines/msie.js
+SHA1 da39a3ee5e6b4b0d3255bfef95601890afd80709 plugins/CodePress/share/web/static/codepress/engines/older.js
+SHA1 824049771826d5625e8765db3fb29f11f8439efc plugins/CodePress/share/web/static/codepress/engines/opera.js
+SHA1 22239de595dc22e1caaa4429c3d4aaa6e15eccb2 plugins/CodePress/share/web/static/codepress/images/line-numbers.png
+SHA1 f492c5f1b248c1a7c8c3bc84d948dde0aae68792 plugins/CodePress/share/web/static/codepress/languages/asp.css
+SHA1 656c5a2c9f050ea4b4f42a1ba0f367d8dc321df4 plugins/CodePress/share/web/static/codepress/languages/asp.js
+SHA1 2b1759f99c2c10353a50f46f63e651987c007419 plugins/CodePress/share/web/static/codepress/languages/csharp.css
+SHA1 dd57b69a0377402507a3fe73d8e50c67f4d94f7b plugins/CodePress/share/web/static/codepress/languages/csharp.js
+SHA1 5e85a186e21ecd82e578d0e7e5cd26755ad77158 plugins/CodePress/share/web/static/codepress/languages/css.css
+SHA1 d8878e88f00ab8d82428ad9d6d56040c006d27f0 plugins/CodePress/share/web/static/codepress/languages/css.js
+SHA1 44397c6e641c882ec925a022581262643b3dc43e plugins/CodePress/share/web/static/codepress/languages/generic.css
+SHA1 9c35fa9bb382d0ebd365b075c220069bd79b22e8 plugins/CodePress/share/web/static/codepress/languages/generic.js
+SHA1 7814ad55305aebc5483b2539dff19381b8b67ae2 plugins/CodePress/share/web/static/codepress/languages/html.css
+SHA1 aac42e2528b262acb3d56857376fb22dec63cf89 plugins/CodePress/share/web/static/codepress/languages/html.js
+SHA1 0a610cd843509b9c40d0d05a0c2fb1f9e99a4aac plugins/CodePress/share/web/static/codepress/languages/java.css
+SHA1 c599822d1d0aa3ae0c7390a827cee42fa91bb3dc plugins/CodePress/share/web/static/codepress/languages/java.js
+SHA1 725ae27fea59ca091aa981ec11c02d7452b18ea0 plugins/CodePress/share/web/static/codepress/languages/javascript.css
+SHA1 20321858b5799df9243db63a9eec6f03abe6a397 plugins/CodePress/share/web/static/codepress/languages/javascript.js
+SHA1 63fe45cca0dcbb3dc632943592e89583e1ba3813 plugins/CodePress/share/web/static/codepress/languages/perl.css
+SHA1 b1497e9b1dc1e7b7ca8796a4f9f1223ed65a5cc8 plugins/CodePress/share/web/static/codepress/languages/perl.js
+SHA1 34c98822164d98af1ae6ef815548b63d7877fdf4 plugins/CodePress/share/web/static/codepress/languages/php.css
+SHA1 c029478dbc0936846b0d69fdf6b6b29bbdf2cb4f plugins/CodePress/share/web/static/codepress/languages/php.js
+SHA1 755c78f665d09c0a2d8fd9d7ebf63100c9f62e60 plugins/CodePress/share/web/static/codepress/languages/ruby.css
+SHA1 53f6aea84f0c94598fd4b8c674b65ce8288f4ade plugins/CodePress/share/web/static/codepress/languages/ruby.js
+SHA1 7a591cd02dbfab9e03321bf0f9e4d459926295a3 plugins/CodePress/share/web/static/codepress/languages/sql.css
+SHA1 9a1f54826b54352a95160700aba2ee8a1934a327 plugins/CodePress/share/web/static/codepress/languages/sql.js
+SHA1 a2421625dc6194a618fe6b572364964c7dbb43b8 plugins/CodePress/share/web/static/codepress/languages/text.css
+SHA1 01e7bc291730313b5d9f484b297431c174117601 plugins/CodePress/share/web/static/codepress/languages/text.js
+SHA1 3eccb9fce27f1818f83f93ca6d85ed03936df283 plugins/CodePress/share/web/static/codepress/languages/vbscript.css
+SHA1 656c5a2c9f050ea4b4f42a1ba0f367d8dc321df4 plugins/CodePress/share/web/static/codepress/languages/vbscript.js
+SHA1 faeb676264a045387456be98af7a9ef2de69567d plugins/CodePress/share/web/static/codepress/languages/xsl.css
+SHA1 e7857c042a70c8a9c030b45c8fe863589bd98e6a plugins/CodePress/share/web/static/codepress/languages/xsl.js
+SHA1 27a0c400c74e2122b95c664756e58a40cedb3166 plugins/CodePress/share/web/static/js/codepress.js
 SHA1 90adb765b74e3b0d115b33a4a6da8ed38a6eb179 plugins/DumpDispatcher/Makefile.PL
 SHA1 94f0d477bc64ce6e88e0023cccc228c4f5ed1cf9 plugins/DumpDispatcher/lib/Jifty/Plugin/DumpDispatcher.pm
 SHA1 e8ce16205eccb1b99224ca81d3a3496163a98864 plugins/EditInPlace/Makefile.PL
@@ -777,6 +906,71 @@
 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 aeac1e62204c91f58488b74d12d75b3ca28cead0 plugins/WikiToolbar/Makefile.PL
+SHA1 48fbd12e106b3fa2114e90fff8bdbc3de3c261d1 plugins/WikiToolbar/lib/Jifty/Plugin/WikiToolbar.pm
+SHA1 88104d8fcb9e7fd1ba81bad8d8e6cf93c3827847 plugins/WikiToolbar/lib/Jifty/Plugin/WikiToolbar/Dispatcher.pm
+SHA1 f9b74f969588298d457af7bb46a74ba1636b4801 plugins/WikiToolbar/lib/Jifty/Plugin/WikiToolbar/Textarea.pm
+SHA1 1fdd77bdbf866caefaf9d8f9f6e5e1cf29d639a1 plugins/WikiToolbar/share/web/static/img/wt/bold.png
+SHA1 54fcf1718afb4ca0c7e24b44f67856c38caff15d plugins/WikiToolbar/share/web/static/img/wt/box.png
+SHA1 1b7a3e5108d829d5a9dc0ee7350066badfa01eab plugins/WikiToolbar/share/web/static/img/wt/center.png
+SHA1 d02e825f393333ec6655c095f82e7484cd5e9f19 plugins/WikiToolbar/share/web/static/img/wt/code.png
+SHA1 038ab8bc024b618fbb87990813fe3a47217b3fdd plugins/WikiToolbar/share/web/static/img/wt/code1.png
+SHA1 fbd780f9176dd6e299e44dcfb49ef5e3a2b393c5 plugins/WikiToolbar/share/web/static/img/wt/empty.png
+SHA1 db3408de528f1ab6f1f2bf38ee77c52f79b60024 plugins/WikiToolbar/share/web/static/img/wt/h1.png
+SHA1 183f5d8c54fb43c7dbf40281de849e3f46b385bc plugins/WikiToolbar/share/web/static/img/wt/h2.png
+SHA1 bbf4470ea8c14136f3033567977881e11f7c9e07 plugins/WikiToolbar/share/web/static/img/wt/h3.png
+SHA1 7a7bf4f6df668952b8df33e132fa573a4ed7cbeb plugins/WikiToolbar/share/web/static/img/wt/italic.png
+SHA1 2dd20159f7517ce39265d8569755c931ab6ddca0 plugins/WikiToolbar/share/web/static/img/wt/link.png
+SHA1 9162eefd6325a864189ac229a8514e2a13dd7262 plugins/WikiToolbar/share/web/static/img/wt/linkextern.png
+SHA1 bc0a304bfa26300d9d3870438f0a21149e39924a plugins/WikiToolbar/share/web/static/img/wt/new.png
+SHA1 86794a87fb0b89b0e95065737140365b94710edf plugins/WikiToolbar/share/web/static/img/wt/ol.png
+SHA1 28f24810f5f6123c909ab7be1f4033dffefd0bbd plugins/WikiToolbar/share/web/static/img/wt/red.png
+SHA1 d87b63236979bfd1d9094c831e6842161097bb88 plugins/WikiToolbar/share/web/static/img/wt/strike.png
+SHA1 7f54e4bd50db116b1a91fc3268f47b2ce4911e29 plugins/WikiToolbar/share/web/static/img/wt/tab.png
+SHA1 5766651228cc6e938d68e82208b35996e241d4d3 plugins/WikiToolbar/share/web/static/img/wt/ul.png
+SHA1 ba133230de9ab17b7365957344465c14ee068e3e plugins/WikiToolbar/share/web/static/img/wt/underline.png
+SHA1 d4fc5b73db20a2b5af07b88e773a4a89e5ccbed7 plugins/WikiToolbar/share/web/static/js/wikitoolbar.js
+SHA1 9045eec8bc1248e7c04d68c837e87b3b6c69675a plugins/WyzzEditor/Makefile.PL
+SHA1 872e602b1ee6a11df81ab1b5edf24e2e9e5b9726 plugins/WyzzEditor/lib/Jifty/Plugin/WyzzEditor.pm
+SHA1 700a8acec334a474187a27fd8c9cd3e429890057 plugins/WyzzEditor/lib/Jifty/Plugin/WyzzEditor/Dispatcher.pm
+SHA1 a97e181eb39dbc1900eef6210a154fc79f0626c0 plugins/WyzzEditor/lib/Jifty/Plugin/WyzzEditor/Textarea.pm
+SHA1 8b6ff3a311cade7b594dd7deebba38a1e4f400b5 plugins/WyzzEditor/share/web/static/img/wyzzicons/backcolor.gif
+SHA1 52e105c0de232a5c2d95afaed21f43bf3f2161bf plugins/WyzzEditor/share/web/static/img/wyzzicons/bold.gif
+SHA1 323dd5d3802c728a624158ca1308004eb91d971c plugins/WyzzEditor/share/web/static/img/wyzzicons/close.gif
+SHA1 c88d95981f5180e7a3badfd6cc5611d20f1094fe plugins/WyzzEditor/share/web/static/img/wyzzicons/copy.gif
+SHA1 a4ac35105313a6e7b246116ca102d2ff603e9512 plugins/WyzzEditor/share/web/static/img/wyzzicons/cut.gif
+SHA1 f041a77044e7b4776aacdc658e38b644885371c2 plugins/WyzzEditor/share/web/static/img/wyzzicons/downsize.gif
+SHA1 67d2f7531a634678753f8a17a6924d163084caa5 plugins/WyzzEditor/share/web/static/img/wyzzicons/font.gif
+SHA1 b63dc2e6baab55e7444ebb278879f9ef91c2b9f2 plugins/WyzzEditor/share/web/static/img/wyzzicons/forecolor.gif
+SHA1 8280ae0c421ef11e6e99453a3943dcd7a073b626 plugins/WyzzEditor/share/web/static/img/wyzzicons/headers.gif
+SHA1 8003b8df35f418545e15326089212165ff85f6c8 plugins/WyzzEditor/share/web/static/img/wyzzicons/help.gif
+SHA1 e6d7775c1aed02120a99df446b555090a19c084d plugins/WyzzEditor/share/web/static/img/wyzzicons/htmlmode.gif
+SHA1 3b66037dd4a67d823cb9ea835e36b8349063ad2d plugins/WyzzEditor/share/web/static/img/wyzzicons/indent.gif
+SHA1 e08c48670f43f22dab83d78f1cb21b5a4325546b plugins/WyzzEditor/share/web/static/img/wyzzicons/inserthorizontalrule.gif
+SHA1 fcacd66649042d124d5a4311ecc491a18e350c8e plugins/WyzzEditor/share/web/static/img/wyzzicons/insertimage.gif
+SHA1 213b8f49e6c0e7109405def6196400eed5b05541 plugins/WyzzEditor/share/web/static/img/wyzzicons/insertorderedlist.gif
+SHA1 dcb14acf325509438e33148be83b622d478d776f plugins/WyzzEditor/share/web/static/img/wyzzicons/insertunorderedlist.gif
+SHA1 acc97c4ae38011e8db2985fa94d32631b173b3b0 plugins/WyzzEditor/share/web/static/img/wyzzicons/italic.gif
+SHA1 ca29431a02f3f7cb7016227bc42c5672cc24e9fb plugins/WyzzEditor/share/web/static/img/wyzzicons/justifycenter.gif
+SHA1 8c938cbb16e20e6baa386eacb9840bc68ce178af plugins/WyzzEditor/share/web/static/img/wyzzicons/justifyfull.gif
+SHA1 61a5f6cdc71a124c4558efe7876ec97280e7f37f plugins/WyzzEditor/share/web/static/img/wyzzicons/justifyleft.gif
+SHA1 c7f88506634da1875c7a6740f7567409ce21e424 plugins/WyzzEditor/share/web/static/img/wyzzicons/justifyright.gif
+SHA1 54ba9c02f347f743ad59d6877f0bdc10d5e4f126 plugins/WyzzEditor/share/web/static/img/wyzzicons/link.gif
+SHA1 7a48fb13191180a1f2401c5ee0dea6be3d2debb6 plugins/WyzzEditor/share/web/static/img/wyzzicons/outdent.gif
+SHA1 a309300447d1c07ae7619b15d854d5da227fe3fd plugins/WyzzEditor/share/web/static/img/wyzzicons/paste.gif
+SHA1 45e82296d618eccbc2a52a7d701f4cb2e15c048c plugins/WyzzEditor/share/web/static/img/wyzzicons/redo.gif
+SHA1 1ec3c72e003ece9a94330226e8cfcb4045abe34c plugins/WyzzEditor/share/web/static/img/wyzzicons/removeformat.gif
+SHA1 9c3b4a60c04c3dcd9ae57842cdf8aaffc3c0e1a0 plugins/WyzzEditor/share/web/static/img/wyzzicons/specialchar.gif
+SHA1 8b14f5d5042a3e76ea2d4890a4c25b40c1a33601 plugins/WyzzEditor/share/web/static/img/wyzzicons/strikethrough.gif
+SHA1 6893a2c29708f16d78c3bb4203cb01f798b7ee30 plugins/WyzzEditor/share/web/static/img/wyzzicons/subscript.gif
+SHA1 ec1a5511417a92a75ca70db9aa88b3bf44bb4dfa plugins/WyzzEditor/share/web/static/img/wyzzicons/superscript.gif
+SHA1 27d7ef1137a8462874bb6eec252ade1d6bec3c90 plugins/WyzzEditor/share/web/static/img/wyzzicons/underline.gif
+SHA1 971e4261232546b0928f1fd1cba31b551e423647 plugins/WyzzEditor/share/web/static/img/wyzzicons/undo.gif
+SHA1 7f8010a1b83ae6e1cac35f75c3abfef2c2a888f9 plugins/WyzzEditor/share/web/static/img/wyzzicons/upsize.gif
+SHA1 8003b8df35f418545e15326089212165ff85f6c8 plugins/WyzzEditor/share/web/static/img/wyzzicons/wyzzicon.gif
+SHA1 d3bc3c8c1a4fabef38cf028833655f21d8368e15 plugins/WyzzEditor/share/web/static/js/wyzz.js
+SHA1 16292c20a0374de33680f727ca1cf9516016e35e plugins/WyzzEditor/share/web/static/wyzzstyles/editarea.css
+SHA1 bf8fbe3c41e7943ec2cf4be2ce6cec05314ee968 plugins/WyzzEditor/share/web/static/wyzzstyles/style.css
 SHA1 2c5d29ea2981cc759cc5f5b27cf7c536d4ea9384 share/dtd/xhtml-lat1.ent
 SHA1 62229286e03ec35c922c8a7f9f3bb68412a78a55 share/dtd/xhtml-special.ent
 SHA1 e7749f99989ec8a9608f6cfbd41a5e5dddc18aec share/dtd/xhtml-symbol.ent
@@ -790,20 +984,54 @@
 SHA1 ed176f653007f8a08e88355bfd68e1b0fc645723 share/plugins/Jifty/Plugin/AdminUI/web/templates/__jifty/admin/fragments/list/search
 SHA1 a118ac016c645629ed720d9626b36266089eee58 share/plugins/Jifty/Plugin/AdminUI/web/templates/__jifty/admin/fragments/list/update
 SHA1 9d339e7acebf3e929b15c1d8b4642ecbddf7f4b6 share/plugins/Jifty/Plugin/AdminUI/web/templates/__jifty/admin/fragments/list/view
-SHA1 559940ee5c2e5b56f3a438f377ea110018e20b18 share/plugins/Jifty/Plugin/AdminUI/web/templates/__jifty/admin/index.html
+SHA1 9c6ec4bcfacc9091533b50eea7b01fe3dd81883c share/plugins/Jifty/Plugin/AdminUI/web/templates/__jifty/admin/index.html
 SHA1 99ce3e69e2201664a1363ca2535dcc51a64a1f92 share/plugins/Jifty/Plugin/AdminUI/web/templates/__jifty/admin/model/dhandler
+SHA1 07b8ea847efe863f179013a7d23087488eee03bf share/plugins/Jifty/Plugin/AutoReference/web/static/js/autoreference.js
+SHA1 0fc7fc9888d41402a0c079cb124769bd044a9f2d share/plugins/Jifty/Plugin/Chart/web/static/css/simple_bars.css
+SHA1 5a544d4b79ad280727d6a3a24a549606c69b239c share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts.swf
+SHA1 172c35bdf32dd3e7c31782a1959c7080778eb33e share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/arno.swf
+SHA1 5bc8dfd3f8aec0f79fe40f5e8fec3ddb20bb5220 share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/arst.swf
+SHA1 7e586bf286140bf5807496836859c5f53de70fd3 share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/brfl.swf
+SHA1 19efe2d338f3d885d59968ba784d24e75013d5c4 share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/brno.swf
+SHA1 d000df0ff2c890deb81884384933805749fe3c5c share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/brst.swf
+SHA1 b00586781067517c472deea06334c0d066fa0e07 share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/cl3d.swf
+SHA1 d2efde2393d3e79b2ea23482a4adfbf0e0367812 share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/clfl.swf
+SHA1 51d5ad57465b8b38dc5bdb951ab413dc73e7d331 share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/clno.swf
+SHA1 eb5570a1c10d94d9baf6ea450b5fad52f19bbfa4 share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/clp3.swf
+SHA1 f3b0595db54960e9db62c69062cb6d5604046c5f share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/cls3.swf
+SHA1 6a3a5ef6406b3aa83b64e78849f916d83129e82e share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/clst.swf
+SHA1 47a1ebff64d7a63f107aea9251282f9da2c1cdec share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/cnno.swf
+SHA1 6cb99ac1b9b6e53011cd2e0fd10cd09bd4c9ac4b share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/lnno.swf
+SHA1 4a8bf970847bdc6693fde1d7fed317e6d8a7f623 share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/mxno.swf
+SHA1 85aba8c388834b2a634407d4f8d95365a6582c87 share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/pi3d.swf
+SHA1 21eeaf60cdcf8d8a851a70663a9876a6f2e7e0c4 share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/pino.swf
+SHA1 3c3058e7d0e5a8d35f8aea4078c24343f1bbcf1f share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/pono.swf
+SHA1 a1c41ce6aebca3f0807508f38684d5f112eb6900 share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/scno.swf
+SHA1 b2e5faf9283ed95c29bc154751b681e1ae82ed10 share/plugins/Jifty/Plugin/Chart/web/static/js/MochiKit/MochiKit.js
+SHA1 6f05da1941ddb410b89f26ea4ea4e72c235ceaed share/plugins/Jifty/Plugin/Chart/web/static/js/MochiKit/__package__.js
+SHA1 3c46aa6eb3074e8b7997b7eb836728cd154bf3b3 share/plugins/Jifty/Plugin/Chart/web/static/js/PlotKit/PlotKit_Packed-20060807-custom.js
+SHA1 3222ee358056a954472dc6f55a0617464ec8875a share/plugins/Jifty/Plugin/Chart/web/static/js/PlotKit/excanvas.js
+SHA1 016570edda3b0aa661ebbddcdaff2acdad4274a9 share/plugins/Jifty/Plugin/Chart/web/static/js/chart_img_behaviour.js
+SHA1 49fde8a24c0345ea74800f1e99bbd62567b51988 share/plugins/Jifty/Plugin/Chart/web/static/js/mochikit.noexport.js
+SHA1 9ffed1b5658c22190f2124fa2e6bd344ee74a1e0 share/plugins/Jifty/Plugin/Chart/web/static/js/simple_bars.js
+SHA1 15bd576afbe4cc9d44623cc8feb9b379fbe2a603 share/plugins/Jifty/Plugin/GoogleMap/web/static/css/google_map.css
+SHA1 1178d664f92ecfdd4635363fed6b53ef843cb593 share/plugins/Jifty/Plugin/GoogleMap/web/static/js/google_map.js
+SHA1 8cf05fe74c914a1c3aaa50a1a21984e630ef2acc share/plugins/Jifty/Plugin/I18N/web/static/js/loc.js
+SHA1 e1c9252b3e60673e4fa1bb1648cb18cd33139535 share/plugins/Jifty/Plugin/JQuery/web/static/js/jquery.js
+SHA1 e9813f935b17859c91b651d992cae6c90776ad14 share/plugins/Jifty/Plugin/JQuery/web/static/js/noConflict.js
 SHA1 7efa4f24d875a19035963c3955f2fc4e613b8cfe share/plugins/Jifty/Plugin/OnlineDocs/web/templates/__jifty/online_docs/autohandler
 SHA1 608c393b35a8884b4d32a4c855bbcc73478dc849 share/plugins/Jifty/Plugin/OnlineDocs/web/templates/__jifty/online_docs/content.html
 SHA1 58a4059cc383f792108986d2386a5e7edc81363f share/plugins/Jifty/Plugin/OnlineDocs/web/templates/__jifty/online_docs/index.html
 SHA1 37555dd5c3acfbaecfc28416e0fa21b0aa6e1d77 share/plugins/Jifty/Plugin/OnlineDocs/web/templates/__jifty/online_docs/toc.html
-SHA1 0da91e5da11aa851363d08a8f9aeade85f72aeb7 share/po/en.po
+SHA1 2996fff4ab04768018cfc0f617c18757e1847a7b share/po/en.po
 SHA1 63a333ff328933df4082e868ba9115b3c0fa24bd share/po/fr.po
-SHA1 88408a7350d0599fd4e7cd7f709fd918b55b6b27 share/po/ja.po
-SHA1 94151e35cbd50a82e09f33718dad3f15fdb0416f share/po/zh_cn.po
-SHA1 623dbb738a8db1e79de6127fc3af2c918d2c93e6 share/po/zh_tw.po
+SHA1 ed8f6b98f97d0e65d7d34a67afbd01ed463bf551 share/po/ja.po
+SHA1 12b260c8de9d985a8968254021cde5c7d56156b3 share/po/ru.po
+SHA1 d3f30c5bc2a7ef0fc0ea8acaaa75f03563eb16d9 share/po/zh_cn.po
+SHA1 71d8302cdf676b5c34ac46d6bf670c1b4735bcd6 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 1a534de383a840e027c1fc9d75acdb44746e3023 share/web/static/css/autocomplete.css
 SHA1 5f569fc23eb815ee6f6d086aa6df87f6c38952d3 share/web/static/css/autohandler
 SHA1 98bd4a5fc5cdcf84794d85da8e6a91f13df875cc share/web/static/css/base.css
 SHA1 669f07ab36d148383013733d8230d49e7e7b25ec share/web/static/css/calendar.css
@@ -814,7 +1042,7 @@
 SHA1 c7eede0c22f68e4417748bb0903b48195648f4c0 share/web/static/css/keybindings.css
 SHA1 a67bd8704c0c8e8866e01de8a98feb9788b853bd share/web/static/css/main.css
 SHA1 903069dae3de35d6a3226b8272ff317b8eebd58c share/web/static/css/nav.css
-SHA1 45861338bc40888737738521a44d4adf286b1204 share/web/static/css/notices.css
+SHA1 19b3b2e382f0c7e9619021c2e68da41a6bfc0057 share/web/static/css/notices.css
 SHA1 6af7922df30a9bcbba91de135280f35020c3de75 share/web/static/css/yui/calendar/calendar.css
 SHA1 c38a3f0ee9c3177b3b57c8a12259583937596252 share/web/static/css/yui/menu/map.gif
 SHA1 454d5a1fc8a75cfdfda8da84fcdb3ad61bc28ecf share/web/static/css/yui/menu/menu.css
@@ -855,7 +1083,7 @@
 SHA1 716b6f8326bd5cd05c31e521351197b5038e052e share/web/static/js/app_behaviour.js
 SHA1 2b28f63068ff486e7fd7937f725f47e886d01831 share/web/static/js/behaviour.js
 SHA1 47cd1a7e3c41b4032cc1712f12b8564cdac12d37 share/web/static/js/bps_util.js
-SHA1 c5fe967d89bc082af093d1ea4eaa2ef3cbfa4e79 share/web/static/js/calendar.js
+SHA1 7d750c5322985e968873c084fe8a4942a70708ba 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
@@ -867,7 +1095,7 @@
 SHA1 4553f3cb184b09228ed4362898e9d30200a2a585 share/web/static/js/formatDate.js
 SHA1 a1d2c6292d656c275383b97aad6ca913b8a1b031 share/web/static/js/halo.js
 SHA1 08b20563e958e72c3e8a221d91614c412bdd068b share/web/static/js/iepngfix.htc
-SHA1 8160deda9af812be79ea1f8a30d8cf8d67055777 share/web/static/js/jifty.js
+SHA1 b0da2c62db377608f91b5ed075811399eb44da75 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 1a4ccf6b5d376984d91c439e1642bd2b7fb11115 share/web/static/js/jifty_utils.js
@@ -879,27 +1107,29 @@
 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 7a09ac75f9140b7faf2f3b0e97493c78997798f6 share/web/static/js/key_bindings.js
+SHA1 7b049d5c904724df588f31fb104dee9b33ab03a7 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 136d82eaaca78f577292ee5df7886675cf897022 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 4f45c2546592411e46e149a73d347e176d0c7ad6 share/web/static/js/yui/calendar.js
+SHA1 9a0f2a5192257ee847450894c9fc21cb1dbfdcc1 share/web/static/js/template_declare.js
+SHA1 c29a9ef089e604a15f8cd269d7d99b2c9bb408e5 share/web/static/js/yui/calendar.js
 SHA1 4d8516305e94d483884ebdfca238d5ea51d0998c share/web/static/js/yui/container.js
 SHA1 dcffe77f6896444b0d71fcb1e8be0ad559256a3e share/web/static/js/yui/dom.js
 SHA1 7b3e21d1cab9fd5da64911838295429d16415797 share/web/static/js/yui/element-beta.js
 SHA1 96f4c1ab0670c62a11e1be261dce68f33064bd67 share/web/static/js/yui/event.js
 SHA1 bfe29fd94ccc2d6b6c008245b3a4ce3f8ed651f2 share/web/static/js/yui/menu.js
+SHA1 1f8b765e6b904e5540e7175f37723570077953e7 share/web/static/js/yui/oom_select.patch
 SHA1 ae99956060eb7a5a7b8b0856cd1013a3ef5b0189 share/web/static/js/yui/tabview.js
 SHA1 d246ffa31cb6ef0218be0cbec5fd0ba8438ef6cc share/web/static/js/yui/yahoo.js
 SHA1 05359f0e32b4bebd965707788228704167689d3c share/web/templates/=/subs
-SHA1 bdaeeff0c2522a8509d4da45be703d1e8cf5c96c share/web/templates/__jifty/autocomplete.xml
+SHA1 fc608ff66778543d016d7082d57549403bbadf47 share/web/templates/__jifty/autocomplete.xml
 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
@@ -908,24 +1138,24 @@
 SHA1 78e1e0af483fa3bdac2de4e5a9d82486d67b8537 share/web/templates/__jifty/error/error.css
 SHA1 6834dbbfe494846bb3a70f1f8d6735c4f8c1b326 share/web/templates/__jifty/error/mason_internal_error
 SHA1 36736d4597f9a4259b84618f49122a3e2c2790a0 share/web/templates/__jifty/halo
-SHA1 d56aea6b4ec690cc9078873da2f93bcbb88d2921 share/web/templates/__jifty/validator.xml
+SHA1 85a5bd5f190d3c5210c2f532b44d334c507da340 share/web/templates/__jifty/validator.xml
 SHA1 8ec2abf3fb43be8217f7a0af4054cf40f7e1e47e share/web/templates/__jifty/webservices/json
-SHA1 bbe1d4af8ab26a9e7d528d819bb271b591172f3c share/web/templates/__jifty/webservices/xml
+SHA1 1ee9cb1bdf246dc8cf37621ba4676decb5339830 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 9a63bfc72a36bc01d3d480d24d695ca5a3fe9169 share/web/templates/_elements/nav
 SHA1 b74c8647eb89e1e189d3cefbbfae51da608edcff share/web/templates/_elements/page_nav
-SHA1 c255d8da7c3eb68fc66f9f7c041c92b247489fcc share/web/templates/_elements/sidebar
+SHA1 63218c27ce4ad809c66c391556f2830cfa447d18 share/web/templates/_elements/sidebar
 SHA1 bc8991085ddd0c664bb57a1ad4a79e9bca1807ea share/web/templates/_elements/wrapper
-SHA1 35adfc3fd3cbf1c2c3310dd2c2a7eb7317495ced share/web/templates/autohandler
+SHA1 3fe248a1e0f6c8cf60ba6c593f2729d18fb9650b 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 c118e782947f715afec7b5cffa4ebc413e990c12 share/web/transform_templates
 SHA1 7f9dae91a9bfc2743eec1d7aaf78e16fc9f1baba t/00-load.t
-SHA1 1e1a7b63b3ea8d2712214eb28b5fe69aae76da1d t/01-dependencies.t
+SHA1 4d2af70e8612e39f60cee6b9477d5da99d15fdd6 t/01-dependencies.t
 SHA1 777e40e7d3c591c21883d0ed880df8712dd50ff3 t/01-test-mechanize.t
 SHA1 ade22974f54a6e0991e14be587be0c9797e72ed5 t/01-test-web.t
 SHA1 95fe956e7bae756a7bc25e65a6761ee0c64981e7 t/01-version_checks.t
@@ -941,9 +1171,11 @@
 SHA1 38ab90a10fc0cbbbfc3205e543312950302f37a2 t/08-client.t
 SHA1 ea9587b57587f6b9b5e02e3d30b96807f7b62200 t/09-url.t
 SHA1 7ca6fdf97c470ec1360425d3121690b9a4e93790 t/10-i18n.t
+SHA1 4d22cf9380a78774b7671eff0f55521abccfd7ab t/10-utf8.t
 SHA1 d571f6fae9d1a33060fda8c89951492a02b1af01 t/11-config-files.t
 SHA1 bd4520e6f2bfdabc6dba2d27e0cb6d33453f82e7 t/12-param-schema.t
-SHA1 59c44900b1cb957d262f96363ceff21b46e0d598 t/99-pod-coverage.t
+SHA1 d4b169132cc2aaaa2ff9c38b2e7009fc38918567 t/13-sessions.t
+SHA1 cf5b3950070fda63ba1b497f7d89dd6c36ae9c93 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
@@ -962,16 +1194,63 @@
 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 08b841f2fe1ad83f70fb4541a10ecc83d4a65939 t/DateTime.t
 SHA1 29a9d0b63d274805fe33c3123faf1a5f871a0e24 t/Jifty.pm
 SHA1 a7dc1f376cac630ea28d2965e561469deb951cc7 t/Mapper/bin/jifty
-SHA1 b381fb60f53d16f8846f7011b8f8a862eaa834c0 t/Mapper/lib/Mapper/Action/CrossBridge.pm
+SHA1 632a449d9f14deda4a0a4115b5bb4df80826305c 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 545c0ef898ffb413b2e837ac294a2c46916ed04d 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 cdc883e2e3ff2997c1efc01f688638f8d983edd9 t/Mapper/t/02-api.t
+SHA1 6187a88b827fc446b8aa3fc2a352ec71e33f416f t/Mapper/t/02-api.t
+SHA1 0b51950a8666a1a41838b3c16205fe97750f860f t/TestApp-Plugin-AppPluginHasModels/Makefile.PL
+SHA1 c1ff9ff7f2a88bc4306b3866b6b80fb9aa8e8423 t/TestApp-Plugin-AppPluginHasModels/bin/jifty
+SHA1 79e1b3ea60bc54ec9ad0a000d6f2f849d12f847f t/TestApp-Plugin-AppPluginHasModels/etc/config.yml
+SHA1 d5223018628abf42b6f7c3eb8ca1e1c006ab5bf1 t/TestApp-Plugin-AppPluginHasModels/lib/TestApp/Plugin/AppPluginHasModels/Plugin/MyAppPlugin.pm
+SHA1 ccbcf7e598398c80a0b687174f64ade7c1e57909 t/TestApp-Plugin-AppPluginHasModels/lib/TestApp/Plugin/AppPluginHasModels/Plugin/MyAppPlugin/Model/Color.pm
+SHA1 0fdc9d3c5ee9636b20eeae64f46bdcef713c2397 t/TestApp-Plugin-AppPluginHasModels/t/plugin-model.t
+SHA1 c590849c33f8e87e8c34ec7dbe4ba384ba6880b7 t/TestApp-Plugin-Chart/Makefile.PL
+SHA1 c1ff9ff7f2a88bc4306b3866b6b80fb9aa8e8423 t/TestApp-Plugin-Chart/bin/jifty
+SHA1 e4f269ce7e959f03ba40afe933ce34269e081894 t/TestApp-Plugin-Chart/etc/config.yml
+SHA1 94c39dbef547367bb6e6da1b9223cf1608ed748e t/TestApp-Plugin-Chart/lib/TestApp/Plugin/Chart/View.pm
+SHA1 ff856d51a878c5ff67f828c67088af5ca9248591 t/TestApp-Plugin-Chart/t/chart.t
+SHA1 8912276067f1707ad2a05677bd54c2013bc49ff1 t/TestApp-Plugin-Chart/t/gd_graph.t
+SHA1 711894dd5ab6923eb725582e7cd739c2ca5c403e t/TestApp-Plugin-CompressedCSSandJS/Makefile.PL
+SHA1 c1ff9ff7f2a88bc4306b3866b6b80fb9aa8e8423 t/TestApp-Plugin-CompressedCSSandJS/bin/jifty
+SHA1 b51eb68ba760a5f48db573d44eb7588a57f02a57 t/TestApp-Plugin-CompressedCSSandJS/etc/config.yml
+SHA1 7056be7bdbb7d6eefdcf5581e60c48b5b588e567 t/TestApp-Plugin-CompressedCSSandJS/t/css.t
+SHA1 711894dd5ab6923eb725582e7cd739c2ca5c403e t/TestApp-Plugin-JQuery/Makefile.PL
+SHA1 c1ff9ff7f2a88bc4306b3866b6b80fb9aa8e8423 t/TestApp-Plugin-JQuery/bin/jifty
+SHA1 f2b2073c99fd411f2b43db5cbcad29c1d6eafc74 t/TestApp-Plugin-JQuery/etc/config.yml
+SHA1 93b414ca2f8f633893a5bc460c136754042772ac t/TestApp-Plugin-JQuery/t/jquery.t
+SHA1 543e899febfce9d8b9c644d557ef3af98f6ad89e t/TestApp-Plugin-News/Makefile.PL
+SHA1 f7f44f9a7337def0c97f981073e3ed970851d9ae t/TestApp-Plugin-News/bin/jifty
+SHA1 47529b82ece48a5943eac7c1ab8b2de5c94f8795 t/TestApp-Plugin-News/etc/config.yml
+SHA1 f69a79e81e98c9c6a99c74cedd9b060b6618970a t/TestApp-Plugin-News/lib/TestApp/Plugin/News/Model/News.pm
+SHA1 53c30add7fff4ebeedf1b6b2ee8474c3bc34efd6 t/TestApp-Plugin-News/lib/TestApp/Plugin/News/View.pm
+SHA1 e30ec0746a5a0681129d963a55b36930c7f0b0c3 t/TestApp-Plugin-OAuth/Makefile.PL
+SHA1 c1ff9ff7f2a88bc4306b3866b6b80fb9aa8e8423 t/TestApp-Plugin-OAuth/bin/jifty
+SHA1 42e1e283dabedf44d2deae64ee46bab06c7f8643 t/TestApp-Plugin-OAuth/etc/config.yml
+SHA1 dc7624e59325207bbfc5205b6f46a840993b511a t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Dispatcher.pm
+SHA1 8a86bdcafd192866d1b713c2d8f5a3d7bf4ac5d4 t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Model/User.pm
+SHA1 027ce32b06ae148269ae68aa6c54d8ca3cef6d05 t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Test.pm
+SHA1 7a121b004811014f5674d55da450995680b23052 t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/View.pm
+SHA1 fceb03fe7a34fdff50a5c808364cec21e2a5cadf t/TestApp-Plugin-OAuth/t/00-test-setup.t
+SHA1 fdd04ef4b775843c808e4947805daab4eb213a86 t/TestApp-Plugin-OAuth/t/01-basic.t
+SHA1 1e4022b12b882130ecfca27d61a75e0ccfe7fca8 t/TestApp-Plugin-OAuth/t/02-request-token.t
+SHA1 2b118556b4d823f1e9a88eb501332e2ab0bda461 t/TestApp-Plugin-OAuth/t/03-authorize.t
+SHA1 72aadc20063aac4bb46d0f30617de4f0ece62772 t/TestApp-Plugin-OAuth/t/04-access-token.t
+SHA1 6f007457f45f1281cfa173562097ab8586a9fb52 t/TestApp-Plugin-OAuth/t/05-protected-resource.t
+SHA1 e38a2d7034e474f2efb95efdb01afdbb1c675d7d t/TestApp-Plugin-OAuth/t/id_rsa
+SHA1 73fd51a00ea7b52b0feffbc2e11d8fefc918b814 t/TestApp-Plugin-OAuth/t/id_rsa.pub
+SHA1 726df49c955a3ed0fe0a511f462b14408b3e34a1 t/TestApp-Plugin-OnClick/Makefile.PL
+SHA1 c1ff9ff7f2a88bc4306b3866b6b80fb9aa8e8423 t/TestApp-Plugin-OnClick/bin/jifty
+SHA1 750bc31fc1747ca292d3f2d6b82600eac85ee7ef t/TestApp-Plugin-OnClick/etc/config.yml
+SHA1 09c2f8647e14e49e922b955c194102070597c2d1 t/TestApp-Plugin-OnClick/share/web/templates/content.html
+SHA1 358cb997ccc4f7db96e5a3bb710a25b1affd8823 t/TestApp-Plugin-OnClick/share/web/templates/content1.html
+SHA1 dfe47fe40442840e7b738c7587a55c8324bff55d t/TestApp-Plugin-OnClick/share/web/templates/onclick.html
+SHA1 2a6feee9c6f26290b11f5293702dc38f87e86139 t/TestApp-Plugin-OnClick/t/onclick.t
 SHA1 5151dae3d7ac5f80dcfaf39fdeea0157af85f189 t/TestApp-Plugin-PasswordAuth/Makefile.PL
 SHA1 f7f44f9a7337def0c97f981073e3ed970851d9ae t/TestApp-Plugin-PasswordAuth/bin/jifty
 SHA1 2ad861771e8cc20e90a5820cb4d3d0837a4fc047 t/TestApp-Plugin-PasswordAuth/etc/config.yml
@@ -984,6 +1263,7 @@
 SHA1 7513161b6944b864a0d06f74ce230907e2064cf7 t/TestApp-Plugin-PasswordAuth/t/00-model-User.t
 SHA1 cd358dc97050649f35034d8363590798a543799d t/TestApp-Plugin-PasswordAuth/t/01-tokengen.t
 SHA1 fda1f40fc6869572331ab18da6b57679b847d3f0 t/TestApp-Plugin-PasswordAuth/t/11-current_user.t
+SHA1 19873166c5da3e6a652135891e82f523794e91d9 t/TestApp-Plugin-PasswordAuth/t/12-i18n.t
 SHA1 e39787081ffb2783de44db32157c6f3bf1631047 t/TestApp-Plugin-PasswordAuth/t/12-signup.t
 SHA1 a7dc1f376cac630ea28d2965e561469deb951cc7 t/TestApp-Plugin-REST/bin/jifty
 SHA1 4762d5e154fcbeb0b188a1ecb90c4997403c9d24 t/TestApp-Plugin-REST/etc/config.yml
@@ -995,21 +1275,33 @@
 SHA1 3b7b51b4428dcbf0b9b1d55c39fd139a3ee4868a t/TestApp-Plugin-REST/t/00-prototype.t
 SHA1 b6c65e7f47ff136c7d370b85cf01f27537dd81ff t/TestApp-Plugin-REST/t/01-config.t
 SHA1 daa99f581a0b42976ce7ae4fe8c3f4d79d799827 t/TestApp-Plugin-REST/t/02-basic-use.t
+SHA1 711894dd5ab6923eb725582e7cd739c2ca5c403e t/TestApp-Plugin-SinglePage/Makefile.PL
+SHA1 c1ff9ff7f2a88bc4306b3866b6b80fb9aa8e8423 t/TestApp-Plugin-SinglePage/bin/jifty
+SHA1 92ac1f7352e3e68ec39ef71e2c829f2cddec59af t/TestApp-Plugin-SinglePage/etc/config.yml
+SHA1 65259a1e0c8a8c45b36800da0d34d3f370f1f0c3 t/TestApp-Plugin-SinglePage/lib/TestApp/Plugin/SinglePage/Model/User.pm
+SHA1 238ee5edab29f5d6ac7d79be59c524c801028b87 t/TestApp-Plugin-SinglePage/lib/TestApp/Plugin/SinglePage/View.pm
 SHA1 a7dc1f376cac630ea28d2965e561469deb951cc7 t/TestApp/bin/jifty
+SHA1 9de42ecbfd32964a392a2b1dbe39c077d8145737 t/TestApp/etc/config.yml
+SHA1 66da809653f3152522ce5760f0894a86624b2394 t/TestApp/etc/site_config.yml
 SHA1 f9a9321b803e4f248ecdd86fd71613164c01bd86 t/TestApp/lib/TestApp/Action/DoSomething.pm
 SHA1 f93118ca17be86a7c171ee47864d7149baf7344c t/TestApp/lib/TestApp/Action/DoSomethingElse.pm
+SHA1 05114360ab9678da24bbc0ad68562756d6300681 t/TestApp/lib/TestApp/Action/SayHi.pm
 SHA1 6e27f855429d18181d6c5a0de6b83492fa5c6219 t/TestApp/lib/TestApp/CurrentUser.pm
-SHA1 7e5dfbfaf1fc9c867e3bb3471597e9d744218949 t/TestApp/lib/TestApp/Dispatcher.pm
-SHA1 5e1331569dbfafb56c4bbd26e0e2ad58e2f3b3cd t/TestApp/lib/TestApp/Model/User.pm
+SHA1 0ff8efa1a274a4e0db2256a7231886ab27f3eb18 t/TestApp/lib/TestApp/Dispatcher.pm
+SHA1 01cc97136094c4dcaa0b00f7b89ea679f35e752d t/TestApp/lib/TestApp/Model/OtherThingy.pm
+SHA1 60e2eed33da6b06ea32506cc17229f8ed3c4d482 t/TestApp/lib/TestApp/Model/Thingy.pm
+SHA1 24b88bd57bed05b8ef19edd5ac42773f934b8d23 t/TestApp/lib/TestApp/Model/User.pm
 SHA1 b2d3474949dae7c171157e8697dbb208ef3805f8 t/TestApp/lib/TestApp/Upgrade.pm
-SHA1 0807d39d56ad1c603d5fda36c26f92f32709feb0 t/TestApp/lib/TestApp/View.pm
-SHA1 1a738a666e1bd83a6f6791d069738d0a90e724d4 t/TestApp/lib/TestApp/View/base.pm
-SHA1 0838ac6deac43ed66fb6bfb358051b716e12e7a8 t/TestApp/lib/TestApp/View/instance.pm
+SHA1 0ebc732808b445f31c4dd54dec81d52329026bbf t/TestApp/lib/TestApp/View.pm
+SHA1 f8be83226541599e4543484128fd209dfc664ff6 t/TestApp/lib/TestApp/View/base.pm
+SHA1 e376d22f0db953b5c9ede1e4e07660fe94390bf5 t/TestApp/lib/TestApp/View/instance.pm
 SHA1 67f41db40d62b81d71cb60c542695e0d7e6d393d t/TestApp/share/web/static/images/pony.jpg
+SHA1 7ba52ae3cd4e86402ece8b2ffa6ac314daf132b9 t/TestApp/share/web/templates/_elements/wrapper
 SHA1 5a47fc9c85527214f6eaf2c8f8dd84bfb80fef61 t/TestApp/share/web/templates/concrete.html
 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 fb9265ecb04b222ba615e53d0d316ab3e88aab8e t/TestApp/share/web/templates/dispatch/protocol
 SHA1 44795ddb863c9c32c05678bf8288c9816e3366a9 t/TestApp/share/web/templates/dosomethingelse
 SHA1 f5870c2fb3222b86d97f14bdf8155821c887987b t/TestApp/share/web/templates/editform
 SHA1 9cea9df8bd6b4644b100259b9290f5e52f874d0d t/TestApp/share/web/templates/index.html
@@ -1017,43 +1309,57 @@
 SHA1 70e9bbbe8ad7df2db3352073ec840a93c4d6d6fb t/TestApp/share/web/templates/path_test/foo/index.html
 SHA1 4e9416993c5e56de1f9688061648ec6a164b1734 t/TestApp/share/web/templates/path_test/in_both
 SHA1 0aa10de36e1c4b34d3e00ca954d10f2c532a1e25 t/TestApp/share/web/templates/path_test/mason_only
-SHA1 1f95e8e0f677fd2571dfcf6e66688b52f0e22184 t/TestApp/share/web/templates/redirected
 SHA1 55438c7d68b465f08c9774aacacbc2aa62cced8f t/TestApp/share/web/templates/regions/list
 SHA1 cc7e1174609f5ae92b441c4ecf7a4734cf5a9436 t/TestApp/share/web/templates/regions/long
 SHA1 f7b091879df762cccacaf827fbadc1ad4b6294b1 t/TestApp/share/web/templates/regions/short
 SHA1 9a50bb56338896f9cd50b7098a9e28397ad28a34 t/TestApp/share/web/templates/somedir/dhandler
-SHA1 350ab77f7c18d826ed91eaf251f3f80ebd9605ba t/TestApp/t/00-model-User.t
+SHA1 06ee53664c6d4dffb42190c5157dd682607f0818 t/TestApp/t/00-model-User.t
 SHA1 3b7b51b4428dcbf0b9b1d55c39fd139a3ee4868a t/TestApp/t/00-prototype.t
 SHA1 94a1fe86cc34fbdce9087f30a69a859063ca8714 t/TestApp/t/01-config.t
-SHA1 840b50a023f9bc45b2345e0c664949d55d056b15 t/TestApp/t/02-dispatch-show-rule-in-wrong-ruleset.t
+SHA1 77466bc82d8f48bb7890bcf7900749b73b2a9f97 t/TestApp/t/02-dispatch-http.t
+SHA1 cd484f47abbb3834c91420dcf1fa7e0e39f24dda t/TestApp/t/02-dispatch-https.t
+SHA1 1b767d139947568b87bb8f198ce96716a3a2aece t/TestApp/t/02-dispatch-show-rule-in-wrong-ruleset.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 af0857e7c15356be76792cb17ac5843416563ae4 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 3c28d88a25d4f7ed0edf2450d7b4ab7577f04dbe t/TestApp/t/08-notifications.t
+SHA1 3e648a4acabeb7a137f28a01698b8c1853a65aa1 t/TestApp/t/09-redirect.t
 SHA1 55ba141d6c73a6dfa7ccccb6cb9f32253ed8decb t/TestApp/t/10-compress.t
-SHA1 18667b3bdcbe7b9dd2715e16c8bbffe51c28548f t/TestApp/t/11-current_user.t
+SHA1 1bbc046a6c6115b67087149fe5c7358c6bfa7ab9 t/TestApp/t/11-current_user.t
 SHA1 019605c6e627bf65ee3d2ed1b8d2b34fc8e10853 t/TestApp/t/12-search.t
 SHA1 1c80b396277bf78134c2aa3de9e0f375e7efef75 t/TestApp/t/13-page-regions.t
-SHA1 b57b4cf6832d53a3777fe65e56f9ae076f474b0a t/TestApp/t/14-template-paths.t
-SHA1 601af3bb2265d698a458d2f02ca76b1a4f076fe7 t/TestApp/t/15-template-subclass.t
+SHA1 b2e93a99969307d1cec54016f6f23c3f15c66af4 t/TestApp/t/14-template-paths.t
+SHA1 af490a8e49140d63e12feb96a8544bc1ea4fe925 t/TestApp/t/15-template-subclass.t
+SHA1 10286b19e32167d07ad59c927dcbe46159edb184 t/TestApp/t/16-template-region.t
+SHA1 e4f33eb967231751ce043a1be3cdbf526671127c t/TestApp/t/17-template-region-internal-redirect.t
+SHA1 338a0f857d73bbf76ca6490e1dc276813767e603 t/TestApp/t/18-test-www-declare.t
+SHA1 23c8686edc138faa7faa1db3eb8c460dd26cd191 t/TestApp/t/19-rightsfrom.t
+SHA1 5108b2617b04b1f903534f8cad9c4f307bbe8337 t/TestApp/t/before_access.t
 SHA1 69401ad0579fa743f087731536229d2806dd1d6a t/TestApp/t/config-Cachable
-SHA1 710c4b0faaea46f90a7b071e5396541a59ed0ff1 t/TestApp/t/config-Record
+SHA1 576e73eedda16d331b018da82b36d01510060422 t/TestApp/t/config-Record
+SHA1 eb2cf455fc2123ee3320f9fdcbc5a68fc1b8286f t/TestApp/t/config/01-basic.t
+SHA1 7ce5f003d1cda10b6cfcec2db107f08d9389339c t/TestApp/t/config/02-individual.t
+SHA1 9bd0b9d64c8bb22183beba7dd091c8f60f1718ee t/TestApp/t/config/02-individual.t-config.yml
+SHA1 91443737eab5ac3f4f51d1925917acd3b190fd3d t/TestApp/t/config/03-nosubtest.t
+SHA1 b9e3bfb1b277c9c61890cc292d711e61f5d5f1e8 t/TestApp/t/config/test_config.yml
+SHA1 e0d433ff3707e84d370b9b3a67306c66275ebe41 t/TestApp/t/crud.t
 SHA1 6c16e68284cc30d81eba755c6986675690b78f77 t/TestApp/t/i18n-standalone.t
 SHA1 30274351a6eb9342daef843ffb8a2aafee38afb4 t/TestApp/t/instance_id.t
 SHA1 ee548850452b377e08f36a9269c1b8f7911bdb2d t/TestApp/t/regex_meta_in_path_info.t
+SHA1 0270868131aad30c20ef0be68a43393c088cc902 t/TestApp/t/test_config.yml
 SHA1 f502e4937629f7525cf90cf982cadd29bc60ed5a t/TestApp/t/upgrade.t
-SHA1 aa8319f0acdd5c8e8c60b34d360cff834029e570 t/TestApp/testapp
-SHA1 c8fb21f31b593627b38129ee9dd41eaf9c556ced t/lib/Jifty/SubTest.pm
+SHA1 a415b718785f367c0c7c4e8f72b33613f5dbce44 t/TestApp/t/use_mason_wrapper.t
+SHA1 ca61102870f9c092374d7d74c19174ce601d80e7 t/clientside/td.t
+SHA1 5c0b3c689031ace0031a3520cca976b852a622ed t/lib/Jifty/SubTest.pm
 -----BEGIN PGP SIGNATURE-----
-Version: GnuPG v1.4.3 (Darwin)
+Version: GnuPG v1.4.7 (Darwin)
 
-iD8DBQFGLAc7Ei9d9xCOQEYRAhCwAJ4lMvEguYMYUKn5vsKqG/5r5NzLeACfdTbM
-ErSkhd0sSOnf65Oqb8gHWzg=
-=cCbP
+iD8DBQFHPgg6sxfQtHhyRPoRAjyoAJ9YjgyyqaikziPGyud9BovlehSwUgCfYaUJ
+BKDsT/pMy7OqTHN7i7Uulf0=
+=oOHW
 -----END PGP SIGNATURE-----

Added: jifty/branches/js-refactor/contrib/jifty_completion.sh
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/contrib/jifty_completion.sh	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,55 @@
+#-*- mode: shell-script;-*-
+#
+# bash complition script for jifty 
+# put this file into /etc/bash_completion.d/
+
+have jifty &&
+_jifty()
+{
+    local cur
+    cur=${COMP_WORDS[COMP_CWORD]}
+
+    local jifty_commands="action adopt app console deps env fastcgi help modperl2 model plugin po schema server"
+    if (($COMP_CWORD == 1)); then
+        COMPREPLY=( $( compgen -W "$jifty_commands" -- $cur ) )
+        return 0
+    fi
+
+    local opts_schema="--create-database --drop-database --help --ignore-reserved-words --man --print --setup"
+    local opts_server="--port"
+    local opts_action="--name --force"
+    local opts_model="--name --force"
+    local opts_op="--js --dir --language"
+    local opts_adopt="--ls"
+
+    case "${COMP_WORDS[1]}" in 
+        schema)
+            COMPREPLY=( $( compgen -W "$opts_schema" -- $cur ) )
+            return 0
+        ;;
+        server)
+            COMPREPLY=( $( compgen -W "$opts_server" -- $cur ) )
+            return 0
+        ;;
+        action)
+            COMPREPLY=( $( compgen -W "$opts_action" -- $cur ) )
+            return 0
+        ;;
+        model)
+            COMPREPLY=( $( compgen -W "$opts_model" -- $cur ) )
+            return 0
+        ;;
+        op)
+            COMPREPLY=( $( compgen -W "$opts_op" -- $cur ) )
+            return 0
+        ;;
+        adopt)
+            COMPREPLY=( $( compgen -W "$opts_adopt" -- $cur ) )
+            return 0
+        ;;
+        *)
+        ;;
+    esac
+}
+
+[ "$have" ] && complete -F _jifty -o default jifty

Modified: jifty/branches/js-refactor/debian/changelog
==============================================================================
--- jifty/branches/js-refactor/debian/changelog	(original)
+++ jifty/branches/js-refactor/debian/changelog	Thu Nov 29 12:25:16 2007
@@ -1,3 +1,9 @@
+jifty (0.70820-1) unstable; urgency=low
+
+  * svn snaphsot
+
+ -- AGOSTINI Yves <agostini at univ-metz.fr>  Tue, 21 Aug 2007 11:17:06 +0200
+
 jifty (0.70416-1) unstable; urgency=low
 
   * New cpan release

Modified: jifty/branches/js-refactor/debian/control
==============================================================================
--- jifty/branches/js-refactor/debian/control	(original)
+++ jifty/branches/js-refactor/debian/control	Thu Nov 29 12:25:16 2007
@@ -57,10 +57,11 @@
  libdbd-sqlite3-perl, libdata-page-perl, libossp-uuid-perl,
  libdatetime-perl, libdatetime-format-builder-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-messageid-perl, libemail-mime-perl, libemail-mime-encodings-perl, libemail-mime-perl, libemail-mime-encodings-perl, libemail-mime-contenttype-perl, libemail-simple-perl, libemail-mime-modifier-perl, libemail-mime-creator-perl, libemail-mime-createhtml-perl,
+ libemail-localdelivery-perl, 
+ libemail-send-perl (>> 1.99_01), 
  libemail-simple-creator-perl, libexporter-lite-perl,
+ libextutils-command-perl, 
  libfile-find-rule-perl, libfile-mmagic-perl,
  libfile-sharedir-perl (>> 0.04), libhtml-parser-perl,
  libhtml-lint-perl, libhtml-mason-perl (>> 1.31), 
@@ -72,9 +73,11 @@
  libmodule-corelist-perl, libmodule-refresh-perl,
  libmodule-scandeps-perl, libobject-declare-perl (>> 0.22),
  libparams-validate-perl, libscalar-defer-perl (>> 0.10),
+ libpadwalker-perl,
  libstring-koremutake-perl, libsql-reservedwords-perl,
- libtemplate-declare-perl (>> 0.21), 
- libtest-base-perl, libuniversal-require-perl, liburi-perl,
+ libtemplate-declare-perl (>> 0.26), 
+ libtest-base-perl, libtest-log4perl-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),
@@ -86,7 +89,8 @@
  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
+ libjifty-plugin-editinplace-perl, libjifty-plugin-login-perl,
+ libimage-info-perl, libchart-perl, libgd-gd2-perl, libgd-graph-perl
 Description: Jifty perl libraries
  Yet another web framework.
  .

Modified: jifty/branches/js-refactor/examples/HelloKitty/lib/HelloKitty/View.pm
==============================================================================
--- jifty/branches/js-refactor/examples/HelloKitty/lib/HelloKitty/View.pm	(original)
+++ jifty/branches/js-refactor/examples/HelloKitty/lib/HelloKitty/View.pm	Thu Nov 29 12:25:16 2007
@@ -2,9 +2,8 @@
 use strict;
 
 package HelloKitty::View;
-use base qw/Jifty::View::Declare::Templates/;
+use Jifty::View::Declare -base;
 use Template::Declare::Tags;
-use Jifty::View::Declare::Templates;
 
 template foo => sub {
     html {

Added: jifty/branches/js-refactor/examples/ShrinkURL/Makefile.PL
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/examples/ShrinkURL/Makefile.PL	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,8 @@
+use inc::Module::Install;
+
+name        'ShrinkURL';
+version     '0.01';
+requires    'Jifty' => '0.70824';
+requires    'Number::RecordLocator';
+
+WriteAll;

Added: jifty/branches/js-refactor/examples/ShrinkURL/bin/jifty
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/examples/ShrinkURL/bin/jifty	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,11 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+use File::Basename qw(dirname); 
+use UNIVERSAL::require;
+
+use Jifty;
+use Jifty::Script;
+
+local $SIG{INT} = sub { warn "Stopped\n"; exit; };
+Jifty::Script->dispatch();

Added: jifty/branches/js-refactor/examples/ShrinkURL/etc/config.yml
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/examples/ShrinkURL/etc/config.yml	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,68 @@
+--- 
+framework: 
+  SkipAccessControl: 1
+  AdminMode: 1
+  ApplicationClass: ShrinkURL
+  ApplicationName: ShrinkURL
+  ApplicationUUID: CA394F5C-7E9E-11DC-8297-EA4406D64C5E
+  ConfigFileVersion: 2
+  Database: 
+    CheckSchema: 1
+    Database: shrinkurl
+    Driver: SQLite
+    Host: localhost
+    Password: ''
+    RecordBaseClass: Jifty::DBI::Record::Cachable
+    User: ''
+    Version: 0.0.1
+  DevelMode: 1
+  L10N: 
+    PoDir: share/po
+  LogLevel: DEBUG
+  Mailer: Sendmail
+  MailerArgs: []
+
+  Plugins: 
+    - 
+      LetMe: {}
+
+    - 
+      SkeletonApp: {}
+
+    - 
+      REST: {}
+
+    - 
+      Halo: {}
+
+    - 
+      ErrorTemplates: {}
+
+    - 
+      OnlineDocs: {}
+
+    - 
+      CompressedCSSandJS: {}
+
+    - 
+      AdminUI: {}
+
+  PubSub: 
+    Backend: Memcached
+    Enable: ~
+  SkipAccessControl: 0
+  TemplateClass: ShrinkURL::View
+  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/js-refactor/examples/ShrinkURL/lib/ShrinkURL/Action/CreateShrunkenURL.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/examples/ShrinkURL/lib/ShrinkURL/Action/CreateShrunkenURL.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,43 @@
+#!/usr/bin/env perl
+package ShrinkURL::Action::CreateShrunkenURL;
+use strict;
+use warnings;
+
+use base qw/Jifty::Action::Record::Create/;
+sub record_class { 'ShrinkURL::Model::ShrunkenURL' }
+
+# have we already shrunk this URL? if so, no need to do it again!
+sub take_action {
+    my $self = shift;
+    my $url = $self->argument_value('url');
+
+    my $shrunkenurl = ShrinkURL::Model::ShrunkenURL->new;
+    $shrunkenurl->load_by_cols(url => $url);
+
+    if ($shrunkenurl->id) {
+
+        # for the benefit of report_success
+        $self->record($shrunkenurl);
+
+        # for the benefit of the template that displays new shrunken URLs
+        # this is called in a superclass which we bypass
+        $self->result->content(id => $shrunkenurl->id);
+
+        # this too is called in a superclass
+        $self->report_success;
+
+        # Create actions return object's ID
+        return $shrunkenurl->id;
+    }
+
+    return $self->SUPER::take_action(@_);
+}
+
+# display a nice little message for the user
+sub report_success {
+    my $self = shift;
+    $self->result->message(_("URL shrunked to %1", $self->record->shrunken));
+}
+
+1;
+

Added: jifty/branches/js-refactor/examples/ShrinkURL/lib/ShrinkURL/Dispatcher.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/examples/ShrinkURL/lib/ShrinkURL/Dispatcher.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,26 @@
+#!/usr/bin/env perl
+package ShrinkURL::Dispatcher;
+use strict;
+use warnings;
+use Jifty::Dispatcher -base;
+
+# visiting / will let users create new shrunken URLs
+on '/' => show 'shrink';
+
+# any other URL (that has no path separator) is potentially a shrunken URL
+on '*' => run {
+    my $url = $1;
+
+    my $shrunkenurl = ShrinkURL::Model::ShrunkenURL->new;
+    $shrunkenurl->load_by_shrunken($url);
+
+    if ($shrunkenurl->id) {
+        redirect($shrunkenurl->url);
+    }
+
+    # if there's no valid URL, just let the person create a new one :)
+    redirect('/');
+};
+
+1;
+

Added: jifty/branches/js-refactor/examples/ShrinkURL/lib/ShrinkURL/Model/ShrunkenURL.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/examples/ShrinkURL/lib/ShrinkURL/Model/ShrunkenURL.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,43 @@
+#!/usr/bin/env perl
+package ShrinkURL::Model::ShrunkenURL;
+use strict;
+use warnings;
+use Number::RecordLocator;
+my $generator = Number::RecordLocator->new;
+
+use Jifty::DBI::Schema;
+use Jifty::Record schema {
+    column url =>
+        is distinct,
+        is varchar(1000);
+};
+
+# shrunken URL is just an encoding of ID
+sub shrunken {
+    my $self = shift;
+    Jifty->web->url(path => $generator->encode($self->id));
+}
+
+# helper function so we can easily change the internal representation of
+# shrunken URLs if we desire
+sub load_by_shrunken {
+    my $self = shift;
+    my $shrunken = shift;
+    my $id = $generator->decode($shrunken);
+
+    return $self->load($id);
+}
+
+# prepend http:// if the scheme is not already there
+sub canonicalize_url {
+    my $self = shift;
+    my $url = shift;
+
+    $url = "http://$url"
+        unless $url =~ m{^\w+://};
+
+    return $url;
+}
+
+1;
+

Added: jifty/branches/js-refactor/examples/ShrinkURL/lib/ShrinkURL/View.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/examples/ShrinkURL/lib/ShrinkURL/View.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,60 @@
+#!/usr/bin/env perl
+package ShrinkURL::View;
+use strict;
+use warnings;
+use Jifty::View::Declare -base;
+
+template 'shrink' => page {
+
+    # render the "shrink a URL" widget, which we can put on any page of the app
+    render_region(
+        name => 'new_shrink',
+        path => '/misc/new_shrink',
+    );
+
+    # render an empty region that we push results onto
+    render_region(
+        name => 'new_shrinks',
+    );
+};
+
+template '/misc/new_shrink' => sub {
+    my $action = new_action(class => 'CreateShrunkenURL');
+    form {
+        Jifty->web->form->register_action($action);
+        render_action($action => ['url']);
+
+        form_submit(
+            submit  => $action,
+            label   => _('Shrink it!'),
+
+            onclick => [
+                { submit => $action },
+                {
+                    # prepend this result onto the empty region above
+                    region => 'new_shrinks',
+                    prepend => '/misc/shrunk_region',
+                    args => {
+                        id => { result_of => $action, name => 'id' },
+                    },
+                },
+            ],
+        );
+    };
+};
+
+template '/misc/shrunk_region' => sub {
+    my $id = get 'id';
+    my $shrunken = ShrinkURL::Model::ShrunkenURL->new;
+    $shrunken->load($id);
+
+    if ($shrunken->id) {
+        div {
+            strong { a { attr { href => $shrunken->shrunken } $shrunken->shrunken  } };
+            outs _(" is now a shortcut for %1.", $shrunken->url);
+        }
+    }
+};
+
+1;
+

Modified: jifty/branches/js-refactor/lib/Jifty.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty.pm	Thu Nov 29 12:25:16 2007
@@ -11,7 +11,7 @@
     require Time::Local;
 
     # Declare early to make sure Jifty::Record::schema_version works
-    $Jifty::VERSION = '0.70422';
+    $Jifty::VERSION = '0.71116';
 }
 
 =head1 NAME
@@ -100,7 +100,7 @@
 use base qw/Jifty::Object/;
 use Jifty::Everything;
 
-use vars qw/$HANDLE $CONFIG $LOGGER $HANDLER $API $CLASS_LOADER $PUB_SUB @PLUGINS/;
+use vars qw/$HANDLE $CONFIG $LOGGER $HANDLER $API $CLASS_LOADER $PUB_SUB $WEB @PLUGINS/;
 
 =head1 METHODS
 
@@ -143,13 +143,14 @@
 sub new {
     my $ignored_class = shift;
 
+    # Setup the defaults
     my %args = (
         no_handle        => 0,
+        pre_init         => 0,
         logger_component => undef,
         @_
     );
 
-
     # Add the appliation's library path
     push @INC, File::Spec->catdir(Jifty::Util->app_root, "lib");
 
@@ -158,30 +159,44 @@
     # which is likely Record::Cachable or Record::Memcached
     @Jifty::Record::ISA = grep { $_ ne 'Jifty::DBI::Record' } @Jifty::Record::ISA;
 
+    # Configure the base class used by Jifty models
     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';
 
+    # Logger turn on
     Jifty->logger( Jifty::Logger->new( $args{'logger_component'} ) );
 
     # Set up plugins
     my @plugins;
     my @plugins_to_load = @{Jifty->config->framework('Plugins')};
     my $app_plugin = Jifty->app_class('Plugin');
+    # we are pushing prereq to plugin, hence the 3-part for.
     for (my $i = 0; my $plugin = $plugins_to_load[$i]; $i++) {
-        my $plugin_name = (keys %{$plugin})[0];
+        # Prepare to learn the plugin class name
+        my ($plugin_name) = keys %{$plugin};
         my $class;
+
+        # Is the plugin name a fully-qualified class name?
         if ($plugin_name =~ /^(?:Jifty::Plugin|$app_plugin)::/) {
             # app-specific plugins use fully qualified names, Jifty plugins may
             $class = $plugin_name; 
         }
+
         # otherwise, assume it's a short name, qualify it
         else {
             $class = "Jifty::Plugin::".$plugin_name;
         }
-        my %options = %{ $plugin->{ $plugin_name } };
+
+        # Load the plugin options
+        my %options = (%{ $plugin->{ $plugin_name } },
+                        _pre_init => $args{'pre_init'} );
+
+        # Load the plugin code
         Jifty::Util->require($class);
         Jifty::ClassLoader->new(base => $class)->require;
+
+        # Initialize the plugin and mark the prerequisites for loading too
         my $plugin_obj = $class->new(%options);
         push @plugins, $plugin_obj;
         foreach my $name ($plugin_obj->prereq_plugins) {
@@ -190,6 +205,7 @@
         }
     }
 
+    # All plugins loaded, save them for later reference
     Jifty->plugins(@plugins);
 
     # Now that we have the config set up and loaded plugins,
@@ -201,9 +217,11 @@
         base => Jifty->app_class,
     );
 
+    # Save the class loader for later reference
     Jifty->class_loader($class_loader);
     $class_loader->require;
 
+    # Configure the request handler and action API handler
     Jifty->handler(Jifty::Handler->new());
     Jifty->api(Jifty::API->new());
 
@@ -217,9 +235,9 @@
     # application specific for startup
     my $app = Jifty->app_class;
     
+    # Run the App::start() method if it exists for app-specific initialization
     $app->start()
         if $app->can('start');
-    
 }
 
 # Explicitly destroy the classloader; if this happens during global
@@ -322,8 +340,7 @@
 =cut
 
 sub web {
-    $HTML::Mason::Commands::JiftyWeb ||= Jifty::Web->new();
-    return $HTML::Mason::Commands::JiftyWeb;
+    return $Jifty::WEB ||= Jifty::Web->new();
 }
 
 =head2 subs
@@ -392,7 +409,9 @@
 sub find_plugin {
     my $self = shift;
     my $name = shift;
-    return grep { $_->isa($name) } Jifty->plugins;
+
+    my @plugins = grep { $_->isa($name) } Jifty->plugins;
+    return wantarray ? @plugins : $plugins[0];
 }
 
 =head2 class_loader
@@ -419,6 +438,13 @@
 
 Defaults to false. If true, Jifty won't try to set up a database handle
 
+=item pre_init
+
+Defaults to false. If true, plugins are notificed that this is a
+pre-init, any trigger registration in C<init()> should not happen
+during this stage.  Note that model mixins's register_triggers is
+unrelated to this.
+
 =back
 
 
@@ -431,15 +457,20 @@
     my $self = shift;
     my %args = (no_handle =>0,
                 @_);
+
+    # Don't setup the database connection if we're told not to
     unless ( $args{'no_handle'}
         or Jifty->config->framework('SkipDatabase')
         or not Jifty->config->framework('Database') )
     {
 
+        # Load the app's database handle and connect
         my $handle_class = Jifty->app_class("Handle");
         Jifty::Util->require( $handle_class );
         Jifty->handle( $handle_class->new );
         Jifty->handle->connect();
+
+        # Make sure the app version matches the database version
         Jifty->handle->check_schema_version()
             unless $args{'no_version_check'};
     }

Modified: jifty/branches/js-refactor/lib/Jifty/API.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/API.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/API.pm	Thu Nov 29 12:25:16 2007
@@ -57,8 +57,10 @@
     my $class = shift;
     my $self  = bless {}, $class;
 
+    # Setup the basic allow/deny rules
     $self->reset;
 
+    # Find all the actions for the API reference (available at _actions)
     Jifty::Module::Pluggable->import(
         search_path => [
             Jifty->app_class("Action"),
@@ -85,12 +87,15 @@
     my $self   = shift;
     my $action = shift;
 
+    # Get the application class name
     my $base_path = Jifty->app_class;
 
+    # Return the class now if it's already fully qualified
     return $action
         if ($action =~ /^Jifty::/
         or $action =~ /^\Q$base_path\E::/);
 
+    # Otherwise qualify it
     return $base_path . "::Action::" . $action;
 }
 
@@ -109,6 +114,7 @@
     # Set up defaults
     my $app_actions = Jifty->app_class("Action");
 
+    # These are the default action limits
     $self->action_limits(
         [   { deny => 1, restriction => qr/.*/ },
             {   allow       => 1,
@@ -180,6 +186,7 @@
     my $polarity     = shift;
     my @restrictions = @_;
 
+    # Check the sanity of the polarity
     die "Polarity must be 'allow' or 'deny'"
         unless $polarity eq "allow"
         or $polarity     eq "deny";

Modified: jifty/branches/js-refactor/lib/Jifty/Action.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Action.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Action.pm	Thu Nov 29 12:25:16 2007
@@ -113,22 +113,33 @@
         current_user => undef,
         @_);
 
+    # Setup current user according to parameter or pickup the actual
     if ($args{'current_user'}) {
         $self->current_user($args{current_user});
     } else {
         $self->_get_current_user();
     }
 
-
+    # If given a moniker, validate/sanitize it
     if ( $args{'moniker'} ) {
+
+        # XXX Should this be pickier about sanitized monikers?
+
+        # Monkiers must not contain semi-colons
         if ( $args{'moniker'} =~ /[\;]/ ) {
+
+            # Replace the semis with underscores and warn
             $args{'moniker'} =~ s/[\;]/_/g;
             $self->log->warn(
                 "Moniker @{[$args{'moniker'}]} contains invalid characters. It should not contain any ';' characters. "
                     . "It has been autocorrected, but you should correct your code"
             );
         }
+
+        # Monikers must not start with a digit
         if ( $args{'moniker'} =~ /^\d/ ) {
+
+            # Stick "fixup-" to the front and warn
             $args{'moniker'} = "fixup-" . $args{'moniker'};
             $self->log->warn(
                 "Moniker @{[$args{'moniker'}]} contains invalid characters. It can not begin with a digit. "
@@ -138,28 +149,29 @@
         }
     }
 
-
+    # Setup the moniker and run order
     $self->moniker($args{'moniker'} || $self->_generate_moniker);
     $self->order($args{'order'});
 
+    # Fetch any arguments from a passed in request
     my $action_in_request = Jifty->web->request->action( $self->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;
     }
 
+    # Setup the argument values with the new_action arguments taking precedent
     $self->argument_values( { %{ $args{'request_arguments' } }, %{ $args{'arguments'} } } );
 
-    # Keep track of whether arguments came from the request, or were
-    # programmatically set elsewhere
+    # Track how an argument was set, again new_action args taking precedent
     $self->values_from_request({});
     $self->values_from_request->{$_} = 1 for keys %{ $args{'request_arguments' } };
     $self->values_from_request->{$_} = 0 for keys %{ $args{'arguments' } };
     
+    # Place this actions result in the response result if already processed
     $self->result(Jifty->web->response->result($self->moniker) || Jifty::Result->new);
     $self->result->action_class(ref($self));
 
+    # Remember stickiness
     $self->sticky_on_success($args{sticky_on_success});
     $self->sticky_on_failure($args{sticky_on_failure});
 
@@ -182,24 +194,46 @@
 sub _generate_moniker {
     my $self = shift;
 
+    # We use Digest::MD5 to generate the moniker
     use Digest::MD5 qw(md5_hex);
+
+    # Use information from the call stack as the data for the digest 
     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
+    # Generate the digest that forms the basis of the auto-moniker
     my $digest = md5_hex("@stack");
+
+    # Increment the per-request moniker digest counter, for the case of looped action generation
     # 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();
+    # Multiple things happening here that need to be noted:
+    #  
+    #  1. We have a per-request moniker digest counter, which handles the
+    #     highly unlikely circumstance that the same digest were hit twice
+    #     within the same request.
+    #
+    #  2. We should always have a stash, but sometimes we don't. (Specifically,
+    #     some Hiveminder tests create actions outside of a Jifty::Web, which
+    #     don't.) In that case, add more random data at the end and cross our
+    #     fingers that we don't hit that one in a billion (or actually one in a
+    #     significantly larger than a billion odds here).
+
+    # Create a serial number that prevents collisions within a request
+    my $serial = Jifty->handler->stash 
+        ? ++(Jifty->handler->stash->{monikers}{$digest}) 
+        : rand();
+
+    # Build the actual moniker from digest + serial
     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
@@ -254,6 +288,8 @@
 sub run {
     my $self = shift;
     $self->log->debug("Running action ".ref($self) . " " .$self->moniker);
+
+    # We've already had a validation failure. STOP!
     unless ($self->result->success) {
         $self->log->debug("Not taking action, as it doesn't validate");
 
@@ -270,16 +306,20 @@
 
         return;
     }
+
+    # Made it past validation, continue...
     $self->log->debug("Taking action ".ref($self) . " " .$self->moniker);
+
+    # Take the action (user-defined)
     my $ret = $self->take_action;
     $self->log->debug("Result: ".(defined $ret ? $ret : "(undef)"));
     
+    # Perform post action clean-up (user-defined)
     $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>.
@@ -307,7 +347,6 @@
 
 sub check_authorization { 1; }
 
-
 =head2 setup
 
 C<setup> is expected to return a true value, or
@@ -319,7 +358,6 @@
 
 sub setup { 1; }
 
-
 =head2 take_action
 
 Do whatever the action is supposed to do.  This and
@@ -334,7 +372,6 @@
 
 sub take_action { 1; }
 
-
 =head2 cleanup
 
 Perform any action-specific cleanup.  By default, does nothing.
@@ -360,14 +397,16 @@
     my $self = shift;
     my $arg = shift;
 
+    # Not only get it, but set it
     if(@_) {
         $self->values_from_request->{$arg} = 0;
         $self->argument_values->{$arg} = shift;
     }
+
+    # Get it
     return $self->argument_values->{$arg};
 }
 
-
 =head2 has_argument ARGUMENT
 
 Returns true if the action has been provided with an value for the
@@ -383,7 +422,6 @@
     return exists $self->argument_values->{$arg};
 }
 
-
 =head2 form_field ARGUMENT
 
 Returns a L<Jifty::Web::Form::Field> object for this argument.  If
@@ -392,14 +430,15 @@
 
 =cut
 
-
 sub form_field {
     my $self = shift;
     my $arg_name = shift;
 
+    # Determine whether we want reads or write on this field
     my $mode = $self->arguments->{$arg_name}{'render_mode'};
     $mode = 'update' unless $mode && $mode eq 'read';
 
+    # Return the widget
     $self->_form_widget( argument => $arg_name,
                          render_mode => $mode,
                          @_);
@@ -418,6 +457,8 @@
 sub form_value {
     my $self = shift;
     my $arg_name = shift;
+
+    # Return the widget, but in read mode
     $self->_form_widget( argument => $arg_name,
                          render_mode => 'read',
                          @_);
@@ -431,17 +472,17 @@
                  render_mode => 'update',
                  @_);
 
+    # Setup the field name
     my $field = $args{'argument'};
-    
     my $arg_name = $field. '!!' .$args{'render_mode'};
 
+    # This particular field hasn't been added to the form yet
     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)) {
+        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;
         }
@@ -449,7 +490,9 @@
         # $sticky can be overrided per-parameter
         $sticky = $field_info->{sticky} if defined $field_info->{sticky};
 
+        # It is in fact a form field for this action
         if ($field_info) {
+            
             # form_fields overrides stickiness of what the user last entered.
             my $default_value;
             $default_value = $field_info->{'default_value'}
@@ -457,6 +500,7 @@
             $default_value = $self->argument_value($field)
               if $self->has_argument($field) && !$self->values_from_request->{$field};
 
+            # Add the form field to the cache
             $self->{_private_form_fields_hash}{$arg_name}
                 = Jifty::Web::Form::Field->new(
                 %$field_info,
@@ -469,14 +513,22 @@
                 %args
                 );
 
-        }    # else $field remains undef
+        }    
+
+        # The field name is not known by this action
         else {
             Jifty->log->warn("$arg_name isn't a valid field for $self");
         }
-    } elsif ( $args{render_as} ) {
+    } 
+    
+    # It has been cached, but render_as is explicitly set
+    elsif ( $args{render_as} ) {
+
+        # Rebless the form control as something else
         bless $self->{_private_form_fields_hash}{$arg_name},
           "Jifty::Web::Form::Field::$args{render_as}";
     }
+
     return $self->{_private_form_fields_hash}{$arg_name};
 }
 
@@ -490,6 +542,8 @@
 sub hidden {
     my $self = shift;
     my ($arg, $value, @other) = @_;
+
+    # Return the control as a hidden widget
     $self->form_field( $arg, render_as => 'hidden', default_value => $value, @other);
 }
 
@@ -519,16 +573,16 @@
 
 sub register {
     my $self = shift;
+
+    # Add information about the action to the form
     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! );
 
-
-
+    # Add all the default values as hidden fields to the form
     my %args = %{$self->arguments};
-
     while ( my ( $name, $info ) = each %args ) {
         next unless $info->{'constructor'};
         Jifty::Web::Form::Field->new(
@@ -540,6 +594,7 @@
             render_as     => 'Hidden'
         )->render();
     }
+
     return '';
 }
 
@@ -553,6 +608,7 @@
 sub render_errors {
     my $self = shift;
     
+    # Render the span that contians errors
     if (defined $self->result->error) {
         # XXX TODO FIXME escape?
         Jifty->web->out( '<div class="form_errors">'
@@ -583,11 +639,17 @@
                  register  => 0,
                  @_);
 
+    # The user has asked to register the action while we're at it
     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 } ) {
+    } 
+    
+    # Not registered yet, so we need to place registration in the button itself
+    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;
@@ -595,9 +657,12 @@
             = $self->argument_value($_) || $arguments->{$_}->{'default_value'}
             for grep { $arguments->{$_}{constructor} } keys %{ $arguments };
     }
+
+    # Add whatever additional arguments they've requested to the button
     $args{parameters}{$self->form_field_name($_)} = $args{arguments}{$_}
       for keys %{$args{arguments}};
 
+    # Render us a button
     Jifty->web->link(%args);
 }
 
@@ -614,16 +679,18 @@
 sub return {
     my $self = shift;
     my %args = (@_);
+
+    # Fetch the current continuation or build a new one
     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};
 
+    # Render a button that will call the continuation
     $self->button( call => $continuation, %args );
 }
 
-
 =head1 NAMING METHODS
 
 These methods return the names of HTML form elements related to this
@@ -641,7 +708,7 @@
     return 'J:A-' . (defined $self->order ? $self->order . "-" : "") .$self->moniker;
 }
 
-
+# prefixes a fieldname with a given prefix and follows it with the moniker
 sub _prefix_field {
     my $self = shift;
     my ($field_name, $prefix) = @_;
@@ -723,7 +790,6 @@
   return 'canonicalization_note-' . $self->form_field_name($field_name);
 }
 
-
 =head1 VALIDATION METHODS
 
 =head2 argument_names
@@ -733,7 +799,6 @@
 
 =cut
 
-
 sub argument_names {
     my $self      = shift;
     my %arguments = %{ $self->arguments };
@@ -746,7 +811,6 @@
     );
 }
 
-
 =head2 _canonicalize_arguments
 
 Canonicalizes each of the L<arguments|Jifty::Manual::Glossary/arguments> that
@@ -763,6 +827,7 @@
 sub _canonicalize_arguments {
     my $self   = shift;
 
+    # For each, canonicalize them all
     $self->_canonicalize_argument($_)
       for $self->argument_names;
 }
@@ -793,6 +858,7 @@
     my $self  = shift;
     my $field = shift;
 
+    # Setup some variables
     my $field_info = $self->arguments->{$field};
     my $value = $self->argument_value($field);
     my $default_method = 'canonicalize_' . $field;
@@ -800,21 +866,32 @@
     # XXX TODO: Do we really want to skip undef values?
     return unless defined $value;
 
+    # Do we have a valid canonicalizer for this field?
     if ( $field_info->{canonicalizer}
-        and defined &{ $field_info->{canonicalizer} } )
-    {
+        and defined &{ $field_info->{canonicalizer} } ) {
+        
+        # Run it, sucka
         $value = $field_info->{canonicalizer}->( $self, $value );
-    } elsif ( $self->can($default_method) ) {
+    } 
+    
+    # How about a method named canonicalize_$field?
+    elsif ( $self->can($default_method) ) {
+
+        # Run that, foo'
         $value = $self->$default_method( $value );
-    } elsif (   defined( $field_info->{render_as} )
+    } 
+    
+    # Or is it a date?
+    elsif (   defined( $field_info->{render_as} )
              && lc( $field_info->{render_as} ) eq 'date') {
+
+        # Use the default date canonicalizer, Mr. T!
         $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>.
@@ -842,10 +919,10 @@
 sub _validate_arguments {
     my $self   = shift;
     
+    # Validate each argument
     $self->_validate_argument($_)
       for $self->argument_names;
 
-
     return $self->result->success;
 }
 
@@ -867,15 +944,19 @@
     my $self  = shift;
     my $field = shift;
 
+    # Do nothing if we don't have a field name
     return unless $field;
     
     $self->log->debug(" validating argument $field");
 
+    # Do nothing if we don't know what that field is
     my $field_info = $self->arguments->{$field};
     return unless $field_info;
 
+    # Grab the current value
     my $value = $self->argument_value($field);
     
+    # When it isn't even given, check if it's mandatory and whine about it
     if ( !defined $value || !length $value ) {
         if ( $field_info->{mandatory} ) {
             return $self->validation_error( $field => _("You need to fill in this field") );
@@ -886,9 +967,9 @@
     # XXX TODO this should be a validate_valid_values sub
     if ( $value && $field_info->{valid_values} ) {
 
+        # If you're not on the list, you can't come to the party
         unless ( grep $_->{'value'} eq $value,
-            @{ $self->valid_values($field) } )
-        {
+            @{ $self->valid_values($field) } ) {
 
             return $self->validation_error(
                 $field => _("That doesn't look like a correct value") );
@@ -897,6 +978,7 @@
    # ... but still check through a validator function even if it's in the list
     }
 
+    # the validator method name is validate_$field
     my $default_validator = 'validate_' . $field;
 
     # Finally, fall back to running a validator sub
@@ -906,6 +988,7 @@
         return $field_info->{validator}->( $self, $value );
     }
 
+    # Check to see if it's the validate_$field method instead and use that
     elsif ( $self->can($default_validator) ) {
         return $self->$default_validator( $value );
     }
@@ -944,17 +1027,22 @@
     my $field_info = $self->arguments->{$field};
     my $value = $self->argument_value($field);
 
+    # the method is autocomplete_$field
     my $default_autocomplete = 'autocomplete_' . $field;
 
+    # If it's defined on the field, use that autocompleter
     if ( $field_info->{autocompleter}  )
     {
         return $field_info->{autocompleter}->( $self, $value );
     }
 
+    # If it's a method on the class, use that autocompleter
     elsif ( $self->can($default_autocomplete) ) {
         return $self->$default_autocomplete( $value );
     }
 
+    # Otherwise, return zip-zero-notta
+    return();
 }
 
 =head2 valid_values ARGUMENT
@@ -1001,18 +1089,32 @@
     my $field = shift;
     my $type = shift;
 
+    # Check for $type_values (valid_values or available_values)
     my $vv_orig = $self->arguments->{$field}{$type .'_values'};
     local $@;
+
+    # Try making that into a list or just return it
     my @values = eval { @$vv_orig } or return $vv_orig;
 
+    # This is a final return list
     my $vv = [];
 
+    # For each value in the *_values list
     for my $v (@values) {
+
+        # If it's a hash, it may be a collection spec or a display/value
         if ( ref $v eq 'HASH' ) {
+
+            # Check for a collection spec
             if ( $v->{'collection'} ) {
+
+                # Load the display_from/value_from paramters
                 my $disp = $v->{'display_from'};
                 my $val  = $v->{'value_from'};
+
                 # XXX TODO: wrap this in an eval?
+
+                # Fetch all the record from the given collection and keep'em
                 push @$vv, map {
                     {
                         display => ( $_->$disp() || '' ),
@@ -1021,12 +1123,16 @@
                 } grep {$_->check_read_rights} @{ $v->{'collection'}->items_array_ref };
 
             }
+
+            # Otherwise, push on the display/value hash
             else {
 
                 # assume it's already display/value
                 push @$vv, $v;
             }
         }
+
+        # Otherwise, treat plain string both display and value
         else {
 
             # just a string

Modified: jifty/branches/js-refactor/lib/Jifty/Action/Autocomplete.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Action/Autocomplete.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Action/Autocomplete.pm	Thu Nov 29 12:25:16 2007
@@ -59,12 +59,15 @@
 sub take_action {
     my $self = shift;
 
+    # Load the arguments
     my $moniker = $self->argument_value('moniker');
     my $argument = $self->argument_value('argument');
 
+    # Load the action associated with the moniker
     my $request_action = Jifty->web->request->action($moniker);
     my $action = Jifty->web->new_action_from_request($request_action);
 
+    # Call the autocompleter for that action and argument and set the result
     my @completions = $action->_autocomplete_argument($argument);
     $self->result->content->{completions} = \@completions;
 

Modified: jifty/branches/js-refactor/lib/Jifty/Action/Record.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Action/Record.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Action/Record.pm	Thu Nov 29 12:25:16 2007
@@ -69,26 +69,31 @@
     );
     my $self = $class->SUPER::new(%args);
 
-
+    # Load the associated record class just in case it hasn't been already
     my $record_class = $self->record_class;
     Jifty::Util->require($record_class);
 
+    # Die if we were given a record that wasn't a record
     if (ref $args{'record'} && !$args{'record'}->isa($record_class)) {
         Carp::confess($args{'record'}." isn't a $record_class");
     }
 
-
-
-    # Set up record
+    # If the record class is a record, use that
     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) ) {
+    } 
+
+    # Otherwise, try to use the record passed to the constructor
+    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 {
+    } 
+    
+    # Otherwise, try to use the arguments to load the record
+    else {
 
         # We could leave out the explicit current user, but it'd have
         # a slight negative performance implications
@@ -101,6 +106,7 @@
         }
         $self->record->load_by_primary_keys(%given_pks) if %given_pks;
     }
+
     return $self;
 }
 
@@ -132,193 +138,274 @@
 sub arguments {
     my $self = shift;
 
+    # Don't do this twice, it's too expensive
     return $self->_cached_arguments if $self->_cached_arguments;
 
-        my $field_info = {};
+    # Get ready to rumble
+    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;
+
+        # The field is a column object, adjust to that
+        if ( ref $field ) {
+            $column = $field;
+            $field  = $column->name;
+        } 
+        
+        # Otherwise, we need to load the column info
+        else {
+            # Load teh column object and the record's current value
+            $column = $self->record->column($field);
+            my $current_value = $self->record->$field;
+
+            # If the current value is actually a pointer to
+            # another object, turn it into an ID
+            $current_value = $current_value->id
+                if blessed($current_value)
+                and $current_value->isa('Jifty::Record');
 
-        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' );
-                    }
-                };
+            # The record's current value becomes the widget's default value
+            $info->{default_value} = $current_value if $self->record->id;
+        }
 
-                $field_info->{ $field . "_confirm" } = {
-                    render_as => 'Password',
-                    virtual => '1',
-                    validator => $same,
-                    sort_order => ($column->sort_order +.01),
-                    mandatory => 0
-                };
-            }
+        # 
+        #  if($field =~ /^(.*)_id$/ && $self->record->column($1)) {
+        #    $column = $self->record->column($1);
+        #}
+
+    #########
+
+        # Canonicalize the render_as setting for the column
+        my $render_as = $column->render_as;
+        $render_as = defined $render_as ? lc($render_as) : '';
+
+        # Use a select box if we have a list of valid values
+        if ( defined (my $valid_values = $column->valid_values)) {
+            $info->{valid_values} = $valid_values;
+            $info->{render_as}    = 'Select';
+        } 
+        
+        # Use a checkbox for boolean fields
+        elsif ( defined $column->type && $column->type =~ /^bool/i ) {
+            $info->{render_as} = 'Checkbox';
+        } 
+        
+        # Add an additional _confirm field for passwords
+        elsif ( $render_as eq 'password' ) {
+
+            # Add a validator to make sure both fields match
+            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' );
+                }
+            };
 
-            elsif ( defined (my $refers_to = $column->refers_to) ) {
-                if ( UNIVERSAL::isa( $refers_to, 'Jifty::Record' ) ) {
+            # Add the extra confirmation field
+            $field_info->{ $field . "_confirm" } = {
+                render_as => 'Password',
+                virtual => '1',
+                validator => $same,
+                sort_order => ($column->sort_order +.01),
+                mandatory => 0
+            };
+        }
 
-                    my $collection = Jifty::Collection->new(
-                        record_class => $refers_to,
-                        current_user => $self->record->current_user
-                    );
-                    $collection->unlimit;
+        # Handle the X-to-one references
+        elsif ( defined (my $refers_to = $column->refers_to) ) {
 
-                    my $method = $refers_to->_brief_description();
+            # Render as a select box unless they override
+            if ( UNIVERSAL::isa( $refers_to, 'Jifty::Record' ) ) {
+                $info->{render_as} = $render_as || 'Select';
+            }
 
-                    $info->{valid_values} = [
-                        {   display_from => $refers_to->can($method) ? $method : "id",
-                            value_from => 'id',
-                            collection => $collection
-                        }
-                    ];
+            # If it's a select box, setup the available values
+            if ( UNIVERSAL::isa( $refers_to, 'Jifty::Record' ) && $info->{render_as} eq 'Select' ) {
 
-                    $info->{render_as} = 'Select';
-                } else {
-                    # No need to generate arguments for
-                    # JDBI::Collections, as we can't do anything
-                    # useful with them yet, anyways.
-
-                    # However, if the column comes with a
-                    # "render_as", we can assume that the app
-                    # developer know what he/she is doing.
-                    # So we just render it as whatever specified.
+                # Get an unlimited collection
+                my $collection = Jifty::Collection->new(
+                    record_class => $refers_to,
+                    current_user => $self->record->current_user,
+                );
+                $collection->unlimit;
+
+                # Fetch the _brief_description() method
+                my $method = $refers_to->_brief_description();
+
+                # FIXME: we should get value_from with the actualy refered by key
+
+                # Setup the list of valid values
+                $info->{valid_values} = [
+                    {   display_from => $refers_to->can($method) ? $method : "id",
+                        value_from => 'id',
+                        collection => $collection
+                    }
+                ];
+                unshift @{ $info->{valid_values} }, {
+                    display => _('no value'),
+                    value   => '',
+                } unless $column->mandatory;
+            } 
+            
+            # If the reference is X-to-many instead, skip it
+            else {
+                # No need to generate arguments for
+                # JDBI::Collections, as we can't do anything
+                # useful with them yet, anyways.
+
+                # However, if the column comes with a
+                # "render_as", we can assume that the app
+                # developer know what he/she is doing.
+                # So we just render it as whatever specified.
 
-                    next unless $render_as;
-                }
+                next unless $render_as;
             }
+        }
 
-	    #########
+    #########
 
+        # Figure out what the action's validation method would for this field
+        my $validate_method = "validate_" . $field;
 
-            # 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"))
-                            )
+        # Build up a validator sub if the column implements validation
+        # and we're not overriding it at the action level
+        if ( $column->validator and not $self->can($validate_method) ) {
+            $info->{ajax_validates} = 1;
+            $info->{validator} = sub {
+                my $self  = shift;
+                my $value = shift;
+
+                # Check the column's validator
+                my ( $is_valid, $message )
+                    = &{ $column->validator }( $self->record, $value );
+
+                # The validator reported valid, return OK
+                if ($is_valid) {
+                    return $self->validation_ok($field);
+                } 
+                
+                # Bad stuff, report an error
+                else {
+                    unless ($message) {
+                        $self->log->error(
+                            qq{Schema validator for $field didn't explain why the value '$value' is invalid}
                         );
                     }
-                };
-            }
-            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
+                    return (
+                        $self->validation_error(
+                            $field => ($message || _("That doesn't look right, but I don't know why"))
+                        )
                     );
+                }
+            };
+        }
 
-                    $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;
-                };
-            }
+        # What would the autocomplete method be for this column in the record
+        my $autocomplete_method = "autocomplete_" . $field;
 
-            if ( $self->record->has_canonicalizer_for_column($field) ) {
-                $info->{'ajax_canonicalizes'} = 1;
-                $info->{'canonicalizer'} ||= sub {
-                    my ( $self, $value ) = @_;
-                    return $self->record->run_canonicalization_for_column(column => $field, value => $value);
-                };
-            } elsif ( $render_as eq 'date')
-            {
-                $info->{'ajax_canonicalizes'} = 1;
-            }
+        # Set the autocompleter if the record has one
+        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 );
+            };
+        }
 
-            # 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 container)) {
+        # The column requests an automagically generated autocompleter, which
+        # is baed upon the values available in the field
+        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
+                );
+
+                # Return the first 20 matches...
+                $collection->unlimit;
+                $collection->rows_per_page(20);
+
+                # ...that start with the value typed...
+                if (length $value) {
+                    $collection->limit(
+                        column   => $field, 
+                        value    => $value, 
+                        operator => 'STARTSWITH', 
+                        entry_aggregator => 'AND'
+                    );
+                }
 
-                if ( defined (my $val = $column->$_) ) {
-                    $info->{$_} = $val;
+                # ...but are not NULL...
+                $collection->limit(
+                    column => $field, 
+                    value => 'NULL', 
+                    operator => 'IS NOT', 
+                    entry_aggregator => 'AND'
+                );
+
+                # ...and are not empty.
+                $collection->limit(
+                    column => $field, 
+                    value => '', 
+                    operator => '!=', 
+                    entry_aggregator => 'AND'
+                );
+
+                # Optimize the query a little bit
+                $collection->columns('id', $field);
+                $collection->order_by(column => $field);
+                $collection->group_by(column => $field);
+
+                # Set up the list of choices to return
+                my @choices;
+                while (my $record = $collection->next) {
+                    push @choices, $record->$field;
                 }
+                return @choices;
+            };
+        }
+
+        # Add a canonicalizer for the column if the record provides one
+        if ( $self->record->has_canonicalizer_for_column($field) ) {
+            $info->{'ajax_canonicalizes'} = 1;
+            $info->{'canonicalizer'} ||= sub {
+                my ( $self, $value ) = @_;
+                return $self->record->run_canonicalization_for_column(column => $field, value => $value);
+            };
+        } 
+        
+        # Otherwise, if it's a date, we have a built-in canonicalizer for that
+        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 container)) {
+
+            if ( defined (my $val = $column->$_) ) {
+                $info->{$_} = $val;
             }
-            $field_info->{$field} = $info;
         }
+        $field_info->{$field} = $info;
+    }
 
+    # After all that, use the schema { ... } params for the final bits
     if ($self->can('PARAMS')) {
+
         # User-defined declarative schema fields can override default ones here
         my $params = $self->PARAMS;
 
@@ -333,9 +420,13 @@
             }
         }
 
+        # Cache the result of merging the Jifty::Action::Record and schema
+        # parameters
         use Jifty::Param::Schema ();
         $self->_cached_arguments(Jifty::Param::Schema::merge_params($field_info, $params));
     }
+
+    # No schema { ... } block, so just use what we generated
     else {
         $self->_cached_arguments($field_info);
     }
@@ -372,6 +463,7 @@
 sub _setup_event_before_action {
     my $self = shift;
 
+    # Setup the information regarding the event for later publishing
     my $event_info = {};
     $event_info->{as_hash_before} = $self->record->as_hash;
     $event_info->{record_id} = $self->record->id;
@@ -385,10 +477,19 @@
 sub _setup_event_after_action {
     my $self = shift;
     my $event_info = shift;
+
+    unless (defined $event_info->{record_id}) {
+        $event_info->{record_id} = $self->record->id;
+        $event_info->{record_class} = ref($self->record);
+        $event_info->{action_class} = ref($self);
+    }
+
+    # Add a few more bits about the result
     $event_info->{result} = $self->result;    
     $event_info->{timestamp} = time(); 
     $event_info->{as_hash_after} = $self->record->as_hash;
 
+    # Publish the event
     my $event_class = $event_info->{'record_class'};
     $event_class =~ s/::Model::/::Event::Model::/g;
     Jifty::Util->require($event_class);

Modified: jifty/branches/js-refactor/lib/Jifty/Action/Record/Create.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Action/Record/Create.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Action/Record/Create.pm	Thu Nov 29 12:25:16 2007
@@ -30,6 +30,7 @@
 sub arguments {
     my $self = shift;
     
+    # Add default values to the arguments configured by Jifty::Action::Record
     my $args = $self->SUPER::arguments;
     for my $arg (keys %{$args}) {
         my $column = $self->record->column($arg) or next;
@@ -56,13 +57,18 @@
     my $self   = shift;
     my $record = $self->record;
 
+    # Build the event to be fired later
     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
+
+    # Iterate through all that are set, except for the virtual ones
     for (grep { defined $self->argument_value($_) && !$self->arguments->{$_}->{virtual} } $self->argument_names) {
+
+        # Prepare the hash to pass to create for each argument
         $values{$_} = $self->argument_value($_);
+
+        # Handle file uploads
         if (ref $values{$_} eq "Fh") { # CGI.pm's "lightweight filehandle class"
             local $/;
             my $fh = $values{$_};
@@ -70,24 +76,31 @@
             $values{$_} = scalar <$fh>;
         }
     }
+
+    # Attempt creating the record
     my $id;
     my $msg = $record->create(%values);
-    # Handle errors?
-    if (ref($msg)) { # If it's a Class::ReturnValue
+
+    # Convert Class::ReturnValue to an id and message
+    if (ref($msg)) {
         ($id,$msg) = $msg->as_array;
     }
 
+    # If ID is 0/undef, the record didn't create, so we fail
     if (! $record->id ) {
         $self->log->warn(_("Create of %1 failed: %2", ref($record), $msg));
         $self->result->error($msg || _("An error occurred.  Try again later"));
     }
 
+    # No errors! Report success
     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) ;
+
+    # Publish the event, noting success or failure
+    $self->_setup_event_after_action($event_info);
 
     return ($self->record->id);
 }
@@ -105,5 +118,15 @@
     $self->result->message(_("Created"))
 }
 
+=head1 SEE ALSO
+
+L<Jifty::Action::Record>, L<Jifty::Record>
+
+=head1 LICENSE
+
+Jifty is Copyright 2005-2007 Best Practical Solutions, LLC.
+Jifty is distributed under the same terms as Perl itself.
+
+=cut
 
 1;

Modified: jifty/branches/js-refactor/lib/Jifty/Action/Record/Delete.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Action/Record/Delete.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Action/Record/Delete.pm	Thu Nov 29 12:25:16 2007
@@ -33,6 +33,7 @@
     my $self = shift;
     my $arguments = {};
 
+    # Mark the primary key for use in the constructor and not rendered
     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
@@ -52,12 +53,17 @@
 sub take_action {
     my $self = shift;
 
+    # Setup the event info for later publishing
     my $event_info = $self->_setup_event_before_action();
 
+    # Delete the record and return an error if delete fails
     my ( $val, $msg ) = $self->record->delete;
     $self->result->error($msg) if not $val and $msg;
 
+    # Otherwise, we seem to have succeeded, report that
     $self->report_success if not $self->result->failure;
+
+    # Publish the event
     $self->_setup_event_after_action($event_info);
 
     return 1;
@@ -76,4 +82,15 @@
     $self->result->message(_("Deleted"))
 }
 
+=head1 SEE ALSO
+
+L<Jifty::Action::Record>, L<Jifty::Record>
+
+=head1 LICENSE
+
+Jifty is Copyright 2005-2007 Best Practical Solutions, LLC.
+Jifty is distributed under the same terms as Perl itself.
+
+=cut
+
 1;

Modified: jifty/branches/js-refactor/lib/Jifty/Action/Record/Search.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Action/Record/Search.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Action/Record/Search.pm	Thu Nov 29 12:25:16 2007
@@ -51,27 +51,31 @@
 
 sub arguments {
     my $self = shift;
+
+    # The args processing here is involved, so only calculate them once
     return $self->_cached_arguments if $self->_cached_arguments;
     
+    # Iterate through all the arguments setup by Jifty::Action::Record
     my $args = $self->SUPER::arguments;
     for my $field (keys %$args) {
         
+        # Figure out what information we know about the field
         my $info = $args->{$field};
-
         my $column = $self->record->column($field);
-        # First, modify the ``exact match'' search field (same name as
-        # the original argument)
 
+        # We don't care about validation and mandatories on search
         delete $info->{validator};
         delete $info->{mandatory};
 
-        if($info->{valid_values}) {
+        # If the column has a set of valid values, deal with those
+        if ($info->{valid_values}) {
             my $valid_values = $info->{valid_values};
 
+            # Canonicalize the 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
+            # For radio display, display an "any" label (empty looks weird)
             if (lc $info->{render_as} eq 'radio') {
                 if (@$valid_values > 1) {
                     unshift @$valid_values, { display => _("(any)"), value => '' };
@@ -82,53 +86,98 @@
                     $info->{default_value} ||= $valid_values->[0];
                 }
             }
+
+            # If not radio, add a blank options
             else {
                 unshift @$valid_values, "";
             }
         }
 
+        # You can't search passwords, so remove the fields
         if(lc $info->{'render_as'} eq 'password') {
             delete $args->{$field};
             next;
         }
 
+        # Warn if we have a search field without an actual column
         warn "No column for: $field" unless($column);
         
+        # Drop out X-to-many columns from the search
         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));
 
+        # Setup the field label for the comparison operator selection
         my $label = $info->{label} || $field;
+
+        # Add the "X is not" operator
         $args->{"${field}_not"} = { %$info, label => _("%1 is not", $label) };
+
+        # The operators available depend on the type
         my $type = lc($column->type);
+
+        # Add operators available for text fields
         if($type =~ /(?:text|char)/) {
+
+            # Show a text entry box (rather than a textarea)
             $info->{render_as} = 'text';
+
+            # Add the "X contains" operator
             $args->{"${field}_contains"} = { %$info, label => _("%1 contains", $label) };
+
+            # Add the "X lacks" operator (i.e., opposite of "X contains")
             $args->{"${field}_lacks"} = { %$info, label => _("%1 lacks", $label) };
-        } elsif($type =~ /(?:date|time)/) {
+        } 
+        
+        # Handle date, datetime, time, and timestamp fields
+        elsif($type =~ /(?:date|time)/) {
+
+            # Add the "X after" date/time operation
             $args->{"${field}_after"} = { %$info, label => _("%1 after", $label) };
+
+            # Add the "X before" date/time operation
             $args->{"${field}_before"} = { %$info, label => _("%1 before", $label) };
+
+            # Add the "X since" date/time operation
             $args->{"${field}_since"} = { %$info, label => _("%1 since", $label) };
+
+            # Add the "X until" date/time operation
             $args->{"${field}_until"} = { %$info, label => _("%1 until", $label) };
-        } elsif(    $type =~ /(?:int|float|double|decimal|numeric)/
+        } 
+        
+        # Handle number fields
+        elsif(    $type =~ /(?:int|float|double|decimal|numeric)/
                 && !$column->refers_to) {
+
+            # Add the "X greater than" operation
             $args->{"${field}_gt"} = { %$info, label => _("%1 greater than", $label) };
+
+            # Add the "X less than" operation
             $args->{"${field}_lt"} = { %$info, label => _("%1 less than", $label) };
+
+            # Add the "X greater than or equal to" operation
             $args->{"${field}_ge"} = { %$info, label => _("%1 greater or equal to", $label) };
+
+            # Add the "X less than or equal to" operation
             $args->{"${field}_le"} = { %$info, label => _("%1 less or equal to", $label) };
+
+            # Add the "X is whatever the heck I say it is" operation
             $args->{"${field}_dwim"} = { %$info, hints => _('!=>< allowed') };
         }
     }
 
+    # Add generic contains/lacks search boxes for all fields
     $args->{contains} = { type => 'text', label => _('Any field contains') };
     $args->{lacks} = { type => 'text', label => _('No field contains') };
 
+    # Cache the results so we don't have to do THAT again
     return $self->_cached_arguments($args);
 }
 
@@ -145,30 +194,43 @@
 sub take_action {
     my $self = shift;
 
+    # Create a generic collection for our record class
     my $collection = Jifty::Collection->new(
         record_class => $self->record_class,
         current_user => $self->record->current_user
     );
 
+    # Start with an unlimited collection
     $collection->unlimit;
 
+    # For each field, process the limits
     for my $field (grep {$self->has_argument($_)} $self->argument_names) {
+
+        # We process contains last, skip it here
         next if $field eq 'contains';
+
+        # Get the value set on the field
         my $value = $self->argument_value($field);
         
+        # Load the column this field belongs to
         my $column = $self->record->column($field);
         my $op = undef;
         
+        # A comparison or substring search rather than an exact match?
         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*$/;
 
+            # Decode the field_op name
             if ($field =~ m{^(.*)_([[:alpha:]]+)$}) {
                 $field = $1;
                 $op = $2;
+
+                # Convert each operator into limit operators
                 if($op eq 'not') {
                     $op = '!=';
                 } elsif($op eq 'contains') {
@@ -193,41 +255,60 @@
                         $op = '=' if $op eq '==';
                     }
                 }
-            } else {
+            } 
+            
+            # Doesn't look like a field_op, skip it
+            else {
                 next;
             }
         }
         
-        if(defined($value)) {
-            next if $value =~ /^\s*$/;
+        # Now, add the limit if we have a value set
+        if (defined($value)) {
+            next if $value =~ /^\s*$/; # skip blank values!
            
+            # Allow != and NOT LIKE to match NULL columns
             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 => 'OR', 
+                    case_sensitive => 0,
+                );
+                $collection->limit( 
+                    column   => $field, 
+                    value    => 'NULL', 
+                    operator => 'IS',
+                );
+            } 
             
-            $collection->limit(
-                column   => $field,
-                value    => $value,
-                operator => $op || "=",
-                entry_aggregator => 'AND',
-                $op ? (case_sensitive => 0) : (),
-               );
-
+            # For any others, just the facts please
+            else { 
+                $collection->limit(
+                    column   => $field,
+                    value    => $value,
+                    operator => $op || "=",
+                    entry_aggregator => 'AND',
+                    $op ? (case_sensitive => 0) : (),
+                );
             } 
+        } 
 
-
-        } else {
+        # The value is not defined at all, so expect a NULL
+        else {
             $collection->limit(
                 column   => $field,
                 value    => 'NULL',
                 operator => 'IS'
-               );
+            );
         }
     }
 
+    # Handle the general contains last
     if($self->has_argument('contains')) {
+
+        # See if any column contains the text described
         my $any = $self->argument_value('contains');
         for my $col ($self->record->columns) {
             if($col->type =~ /(?:text|varchar)/) {
@@ -240,6 +321,7 @@
         }
     }
 
+    # Add the limited collection to the results
     $self->result->content(search => $collection);
     $self->result->success;
 }
@@ -248,6 +330,11 @@
 
 L<Jifty::Action::Record>, L<Jifty::Collection>
 
+=head1 LICENSE
+
+Jifty is Copyright 2005-2007 Best Practical Solutions, LLC.
+Jifty is distributed under the same terms as Perl itself.
+
 =cut
 
 1;

Modified: jifty/branches/js-refactor/lib/Jifty/Action/Record/Update.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Action/Record/Update.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Action/Record/Update.pm	Thu Nov 29 12:25:16 2007
@@ -18,6 +18,8 @@
 
 use base qw/Jifty::Action::Record/;
 
+use Scalar::Util qw/ blessed /;
+
 =head1 METHODS
 
 =head2 arguments
@@ -33,12 +35,14 @@
     my $self = shift;
     my $arguments = $self->SUPER::arguments(@_);
 
+    # Mark read-only columns for read-only display
     for my $column ( $self->record->columns ) {
         if ( not $column->writable and $column->readable ) {
             $arguments->{$column->name}{'render_mode'} = 'read';
         }
     }
 
+    # Add the primary keys to constructors and make them mandatory
     for my $pk (@{ $self->record->_primary_keys }) {
         $arguments->{$pk}{'constructor'} = 1;
         $arguments->{$pk}{'mandatory'} = 1;
@@ -64,6 +68,7 @@
 sub _validate_arguments {
     my $self = shift;
 
+    # Only validate the arguments given
     $self->_validate_argument($_) for grep {
         $self->has_argument($_)
             or $self->arguments->{$_}->{constructor}
@@ -84,12 +89,16 @@
     my $self = shift;
     my $changed = 0;
 
+    # Prepare the event for later publishing
     my $event_info = $self->_setup_event_before_action();
 
+    # Iterate through all the possible arguments
     for my $field ( $self->argument_names ) {
+
         # Skip values that weren't submitted
         next unless $self->has_argument($field);
 
+        # Load the column object for the field
         my $column = $self->record->column($field);
 
         # Skip nonexistent fields
@@ -108,6 +117,7 @@
         next if lc $self->arguments->{$field}{render_as} eq "upload"
           and (not defined $value or not ref $value);
 
+        # Handle file uploads
         if (ref $value eq "Fh") { # CGI.pm's "lightweight filehandle class"
             local $/;
             binmode $value;
@@ -117,7 +127,7 @@
         # 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' );
+        $old = $old->id if blessed($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
@@ -127,17 +137,21 @@
         next if (  (not defined $old or not length $old)
                     and ( not defined $value or not length $value ));
 
+        # Calculate the name of the setter and set; asplode on failure
         my $setter = "set_$field";
         my ( $val, $msg ) = $self->record->$setter( $value );
         $self->result->field_error($field, $msg)
           if not $val and $msg;
 
+        # Remember that we changed something (if we did)
         $changed = 1 if $val;
     }
 
+    # Report success if there's a change and no error, otherwise say nah-thing
     $self->report_success
       if $changed and not $self->result->failure;
 
+    # Publish the update event
     $self->_setup_event_after_action($event_info);
 
     return 1;
@@ -156,4 +170,15 @@
     $self->result->message(_("Updated"))
 }
 
+=head1 SEE ALSO
+
+L<Jifty::Action::Record>, L<Jifty::Record>
+
+=head1 LICENSE
+
+Jifty is Copyright 2005-2007 Best Practical Solutions, LLC.
+Jifty is distributed under the same terms as Perl itself.
+
+=cut
+
 1;

Modified: jifty/branches/js-refactor/lib/Jifty/Action/Redirect.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Action/Redirect.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Action/Redirect.pm	Thu Nov 29 12:25:16 2007
@@ -5,11 +5,26 @@
 
 Jifty::Action::Redirect - Redirect the browser
 
+=head1 SYNOPSIS
+
+  Jifty->web->new_action(
+      class => 'Redirect',
+      arguments => {
+          url => '/my/other/page',
+      },
+  )->run;
+
+=head1 DESCRIPTION
+
+Given a URL, this action forces Jifty to perform a redirect to thast URL after processing the rest of the request.
+
 =cut
 
 package Jifty::Action::Redirect;
 use base qw/Jifty::Action/;
 
+=head1 METHODS
+
 =head2 new
 
 By default, redirect actions happen as late as possible in the run
@@ -35,10 +50,9 @@
 =cut
 
 sub arguments {
-        {
-            url => { constructor => 1 },
-        }
-
+    {
+        url => { constructor => 1 },
+    }
 }
 
 =head2 take_action
@@ -52,15 +66,32 @@
 
 sub take_action {
     my $self = shift;
+
+    # Return now if the URL is not set
     return 1 unless ($self->argument_value('url'));
+
+    # Return now if the response is already sent (i.e., too late to redirect)
     return 0 unless Jifty->web->response->success;
 
+    # Find the URL to redirect to
     my $page = $self->argument_value('url');
 
+    # Set the next page and force the redirect
     Jifty->web->next_page($page);
     Jifty->web->force_redirect(1);
     return 1;
 }
 
+=head1 SEE ALSO
+
+L<Jifty::Action>, L<Jifty::Web/next_page>, L<Jity::Web/force_redirect>
+
+=head1 LICENSE
+
+Jifty is Copyright 2005-2007 Best Practical Solutions, LLC.
+Jifty is distributed under the same terms as Perl itself.
+
+=cut
+
 1;
 

Modified: jifty/branches/js-refactor/lib/Jifty/Bootstrap.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Bootstrap.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Bootstrap.pm	Thu Nov 29 12:25:16 2007
@@ -1,6 +1,10 @@
 use warnings;
 use strict;
 
+package Jifty::Bootstrap;
+
+use base qw/Jifty::Object/;
+
 =head1 NAME
 
 Jifty::Bootstrap - Insert initial data into your database
@@ -12,7 +16,6 @@
 your application is first installed.
 
 =head1 EXAMPLE
-
  
  package MyApp::Bootstrap;
  use base 'Jifty::Bootstrap';
@@ -23,13 +26,6 @@
      $modelclass->create( name => 'Widget');
  }; 
  
- 
-=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
@@ -42,5 +38,15 @@
     1;
 }
 
+=head1 SEE ALSO
+
+L<Jifty::Upgrade>, L<Jifty::Script::Schema>
+
+=head1 LICENSE
+
+Jifty is Copyright 2005-2006 Best Practical Solutions, LLC.
+Jifty is distributed under the same terms as Perl itself.
+
+=cut
 
 1;

Modified: jifty/branches/js-refactor/lib/Jifty/ClassLoader.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/ClassLoader.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/ClassLoader.pm	Thu Nov 29 12:25:16 2007
@@ -23,18 +23,22 @@
 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
+application's or a plugin's base path; all of the classes under this will be
 automatically loaded.
 
+L<Jifty::ClassLoader> objects are singletons per C<base>. If you call C<new> and a class loader for the given base has already been initialized, this will return a reference to that object rather than creating a new one.
+
 =cut
 
 sub new {
     my $class = shift;
     my %args = @_;
 
+    # Check to make sure this classloader hasn't been built yet and stop if so
     my @exist = grep {ref $_ eq $class and $_->{base} eq $args{base}} @INC;
     return $exist[0] if @exist;
 
+    # It's a new one, build it
     my $self = bless {%args}, $class;
     push @INC, $self;
     return $self;
@@ -44,7 +48,9 @@
 
 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:
+auto-generated the class loader. 
+
+Here the "I<Application>" indicates the name of the application the class loader is being applied to. However, this really just refers to the C<base> argument passed to the constructor, so it could refer to a plugin class or just about anything else.
 
 =over
 
@@ -53,55 +59,83 @@
 An empty application base class is created that doen't provide any
 methods or inherit from anything.
 
-=item I<Application>::Record
+=item I<Application>::Action
 
-An empty class that descends from L<Jifty::Record> is created.
+An empty class that descends from L<Jifty::Action>.
 
-=item I<Application>::Event
+=item I<Application>::Action::I<[Verb]>I<[Something]>
 
-An empty class that descends from L<Jifty::Event> is created.
+If I<Application>::Model::I<Something> is a valid model class and I<Verb> is one of "Create", "Search", "Update", or "Delete", then it creates a subclass of I<Application>::Action::Record::I<Verb>
+
+=item I<Application>::Action::I<Something>
+
+The class loader will search for a plugin I<Plugin> such that I<Plugin>::Action::I<Something> exists. It will then create an empty class named I<Application>::Action::I<Something> that descends from I<Plugin>::Action::I<Something>.
+
+This means that a plugin may be written to allow the application to override the default implementation used by the plugin as long as the plugin uses the application version of the class.
+
+=item I<Application>::Action::Record::I<Something>
+
+An empty class that descends from the matching Jifty class, Jifty::Action::Record::I<Something>. This is generally used to build application-specific descendants of L<Jifty::Action::Record::Create>, L<Jifty::Action::Record::Search>, L<Jifty::Action::Record::Update>, or L<Jifty::Action::Record::Delete>.
+
+=item I<Application>::Bootstrap
+
+An empty class that descends from L<Jifty::Bootstrap>.
 
 =item I<Application>::Collection
 
 An empty class that descends from L<Jifty::Collection> is created.
 
-=item I<Application>::Notification
+=item I<Application>::CurrentUser
 
-An empty class that descends from L<Jifty::Notification>.
+An empty class that descends from L<Jifty::CurrentUser>.
 
 =item I<Application>::Dispatcher
 
 An empty class that descends from L<Jifty::Dispatcher>.
 
-=item I<Application>::Handle
+=item I<Application>::Event
 
-An empty class that descends from L<Jifty::Handle> is created.
+An empty class that descends from L<Jifty::Event> is created.
 
-=item I<Application>::Bootstrap
+=item I<Application>::Event::Model
 
-An empty class that descends from L<Jifty::Bootstrap>.
+An empty class that descents from L<Jifty::Event::Model> is created.
 
-=item I<Application>::Upgrade
+=item I<Application>::Event::Model::I<Something>
 
-An empty class that descends from L<Jifty::Upgrade>.
+If I<Application>::Model::I<Something> is a valid model class, then it creates an empty descendant of I<Application>::Event::Model with the C<record_class> set to I<Application>::Model::I<Something>.
 
-=item I<Application>::CurrentUser
+=item I<Application>::Handle
 
-An empty class that descends from L<Jifty::CurrentUser>.
+An empty class that descends from L<Jifty::Handle> is created.
 
-=item I<Application>::Model::I<Anything>Collection
+=item I<Application>::Model::I<Something>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 or Search)I<Anything>
+=item I<Application>::Notification
 
-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>, L<Jifty::Action::Record::Delete> or
-L<Jifty::Action::Record::Search> whose I<record_class> is
-C<I<Application>::Model::I<Something>>.
+An empty class that descends from L<Jifty::Notification>.
+
+=item I<Application>::Notification::I<Something>
+
+The class loader will search for a plugin I<Plugin> such that I<Plugin>::Notification::I<Something> exists. It will then create an empty class named I<Application>::Notification::I<Something> that descends from I<Plugin>::Notification::I<Something>.
+
+This allows an application to customize the email notification sent out by a plugin as long as the plugin defers to the application version of the class.
+
+=item I<Application>::Record
+
+An empty class that descends from L<Jifty::Record> is created.
+
+=item I<Application>::Upgrade
+
+An empty class that descends from L<Jifty::Upgrade>.
+
+=item I<Application>::View
+
+An empty class that descends from L<Jifty::View::Declare>.
 
 =back
 
@@ -181,39 +215,62 @@
         Jifty::Util->_require( module => $modelclass, quiet => 1);
 
         # Don't generate the action unless it really is a model
-        return undef unless eval { $modelclass->isa('Jifty::Record') };
+        if ( eval { $modelclass->isa('Jifty::Record') } ) {
 
-        return $self->return_class(
+            return $self->return_class(
                   "package $module;\n"
                 . "use base qw/$base\::Action::Record::$1/;\n"
                 . "sub record_class { '$modelclass' };\n"
                 . "sub _autogenerated { 1 };\n"
             );
+        }
 
     }
 
-    # This is if, not elsif because we might have $base::Action::Deleteblah
-    # that matches that last elsif clause but loses on the eval.
+    # This is a little hard to grok, so pay attention. This next if checks to
+    # see if the requested class belongs to an application (i.e., this class
+    # loader does not belong to a plugin). If so, it will attempt to create an
+    # application override of a plugin class, if the plugin provides the same
+    # type of notification or action.
+    #
+    # This allows the application to customize what happens on a plugin action
+    # or customize the email notification sent by a plugin. 
+    #
+    # However, this depends on the plugin being well-behaved and always using
+    # the application version of actions and notifications rather than trying
+    # to use the plugin class directly.
+    #
+    # Of course, if the class loader finds such a case, then the application
+    # has not chosen to override it and we're generating the empty stub to take
+    # it's place.
+
+    # N.B. This is if and not elsif on purpose. If the class name requested is
+    # App::Action::(Create|Update|Search|Delete)Thing, but there is no such
+    # model as App::Model::Thing, we may be trying to create a sub-class of
+    # Plugin::Action::(Create|Update|Search|Delete)Thing for
+    # Plugin::Model::Thing instead.
+    
+    # Requesting an application override of a plugin action or notification?
     if ( $module =~ /^(?:$base)::(Action|Notification)::(.*)$/x and not grep {$_ eq $base} map {ref} Jifty->plugins ) {
         my $type = $1;
         my $item = $2;
-        # If we don't have the action in our own app, let's try the plugins
-        # the app has loaded.
+
+        # Find a plugin with a matching action or notification
         foreach my $plugin (map {ref} Jifty->plugins) {
             next if ($plugin eq $base);
             my $class = $plugin."::".$type."::".$item;
-            if (Jifty::Util->try_to_require($class) ) {
-        return $self->return_class(
-                  "package $module;\n"
-                . "use base qw/$class/;\n"
-                . "sub _autogenerated { 1 };\n"
-            );
-
 
+            # Found it! Generate the empty stub.
+            if (Jifty::Util->try_to_require($class) ) {
+                return $self->return_class(
+                        "package $module;\n"
+                        . "use base qw/$class/;\n"
+                        . "sub _autogenerated { 1 };\n"
+                    );
             }
         }
-
     }
+
     # Didn't find a match
     return undef;
 }
@@ -229,11 +286,12 @@
     my $self = shift;
     my $content = shift;
 
+    # ALWAYS use warnings; use strict!!!
     $content = "use warnings; use strict; ". $content  . "\n1;";
 
+    # Magically turn the text into a file handle
     open my $fh, '<', \$content;
     return $fh;
-
 }
 
 =head2 require
@@ -247,40 +305,55 @@
 
 sub require {
     my $self = shift;
-
     my $base = $self->{base};
+
+    # XXX It would be nice to have a comment here or somewhere in here
+    # indicating when it's possible for a class loader to be missing it's base.
+    # This is a consistent check in the class loader, but I don't know of an
+    # example where this would be the case. -- Sterling
+
     # if we don't even have an application class, this trick will not work
     return unless ($base);
+
+    # Always require the base and the base current user first
     Jifty::Util->require($base);
     Jifty::Util->require($base."::CurrentUser");
 
-    my %models;
-
-
+    # Use Module::Pluggable to help locate our models, actions, notifications,
+    # and events
     Jifty::Module::Pluggable->import(
         # $base goes last so we pull in the view class AFTER the model classes
         search_path => [map { $base . "::" . $_ } ('Model', 'Action', 'Notification', 'Event')],
-        require => 1,
+        require => 0,
         except  => qr/\.#/,
         inner   => 0
     );
-    $models{$_} = 1 for grep {/^($base)::Model::(.*)$/ and not /Collection$/} $self->plugins;
+    
+    # Construct the list of models for the application for later reference
+    my %models;
+    for ($self->plugins) {
+        Jifty::Util->require($_);  
+    }
+    $models{$_} = 1 for grep {/^($base)::Model::(.*)$/ and not /Collection(?:$||\:\:)/} $self->plugins;
     $self->models(sort keys %models);
+
+    # Load all those models and model-related actions, notifications, and events
     for my $full ($self->models) {
         $self->_require_model_related_classes($full);
     }
-
+    $_->finalize_triggers for grep {$_->can('finalize_triggers')} $self->models;
 }
 
+# This class helps Jifty::ClassLoader::require() load each model, the model's
+# collection and the model's create, update, delete, and search actions.
 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/;
-
+    my($short) = $full =~ /::Model::(\w*)/;
+    Jifty::Util->require($full . "Collection");
+    Jifty::Util->require($base . "::Action::" . $_ . $short)
+        for qw/Create Update Delete Search/;
 }
 
 
@@ -299,6 +372,10 @@
 
 =cut
 
+# XXX TODO FIXME Holy crap! This is in the trunk! See the virtual-models branch
+# of Jifty if you really want to see this in action (unless it's finally been
+# merged intot he trunk), which isn't the case as of August 13, 2007. 
+# -- Sterling
 sub require_classes_from_database {
     my $self = shift;
     my @instantiated;
@@ -321,8 +398,8 @@
 
 sub require_views {
     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."::View");
@@ -339,9 +416,13 @@
 
 sub models {
     my $self = shift;
+
+    # If we have args, update the list of models
     if (@_) {
         $self->{models} = ref($_[0]) ? $_[0] : \@_;
     }
+
+    # DWIM: return an array if they want a list, return an arrayref otherwise
     wantarray ? @{ $self->{models} ||= [] } : $self->{models};
 }
 
@@ -364,17 +445,17 @@
 
 # Use of uninitialized value in require at /tmp/7730 line 9 during global destruction.
 
-
 sub DESTROY {
     my $self = shift;
     @INC = grep {defined $_ and $_ ne $self} @INC;
 }
 
-=head1 Writing your own classes
+=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.
+If you require more functionality than is provided by the classes created by
+ClassLoader (which you'll almost certainly need to do if you want an
+application that does more than display a pretty Pony) 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:
@@ -389,6 +470,15 @@
  package MyApp::Model::UserCollection;
  use base 'MyApp::Collection';
 
+=head1 SEE ALSO
+
+L<Jifty> and just about every other class that this provides an empty override for.
+
+=head1 LICENSE
+
+Jifty is Copyright 2005-2007 Best Practical Solutions, LLC.
+Jifty is distributed under the same terms as Perl itself.
+
 =cut
 
 1;

Modified: jifty/branches/js-refactor/lib/Jifty/Client.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Client.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Client.pm	Thu Nov 29 12:25:16 2007
@@ -13,10 +13,9 @@
 use List::Util qw(first);
 use Carp;
 
-
 =head1 NAME
 
-Jifty::Client --- Subclass of L<WWW::Mechanize> with extra Jifty features
+Jifty::Client - Subclass of L<WWW::Mechanize> with extra Jifty features
 
 =head1 DESCRIPTION
 
@@ -54,18 +53,25 @@
   my $action = Jifty->api->qualify(shift);
   my %args = @_;
 
+  # Search through all the inputs of all the forms
   for my $f ($self->forms) {
   INPUT: 
     for my $input ($f->inputs) {
+
+      # Look for the matching action
       if ($input->type eq "hidden" and $input->name =~ /^J:A-(?:\d+-)?(.*)/ and $input->value eq $action) {
 
+        # We have a potential moniker
         my $moniker = $1;
 
+        # Make sure that this action actually has the field values we're
+        # looking for, if not keep looking
         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};
         }
 
+        # It does! Return it...
         return $1;
       }
     }
@@ -87,17 +93,18 @@
     my $moniker = shift;
     my %args = @_;
 
+    # Load the form object containing the given moniker or quit
     my $action_form = $self->action_form($moniker, keys %args);
     return unless $action_form;
 
+    # For each field name given, set the field's value
     for my $arg (keys %args) {
         my $input = $action_form->find_input("J:A:F-$arg-$moniker");
-        unless ($input) {
-            return;
-        } 
+        return unless $input;
         $input->value($args{$arg});
     } 
 
+    # Return the form in case they want to do something with it
     return $action_form;
 }
 
@@ -116,14 +123,18 @@
     my @fields = @_;
     Carp::confess("No moniker") unless $moniker;
 
+    # Go through all the forms looking for the moniker
     my $i;
     for my $form ($self->forms) {
         no warnings 'uninitialized';
 
+        # Keep looking unless the right kind of input is found
         $i++;
         next unless first {   $_->name =~ /J:A-(?:\d+-)?$moniker/
                            && $_->type eq "hidden" }
                         $form->inputs;
+
+        # Keep looking if the suggested field's don't match up
         next if grep {not $form->find_input("J:A:F-$_-$moniker")} @fields;
 
         $self->form_number($i); #select it, for $mech->submit etc
@@ -144,9 +155,11 @@
     my $moniker = shift;
     my $field = shift;
 
+    # Find the form containing the moniker requested
     my $action_form = $self->action_form($moniker, $field);
     return unless $action_form;
     
+    # Find the input containing the field requested and fetch the value
     my $input = $action_form->find_input("J:A:F-$field-$moniker");
     return unless $input;
     return $input->value;
@@ -168,10 +181,11 @@
     my $class = shift;
     my %args = @_;
 
-
+    # Setup the URL of the request we're about to make
     my $uri = $self->uri->clone;
     $uri->path("__jifty/webservices/yaml");
 
+    # Setup the action request we're going to send
     my $request = HTTP::Request->new(
         POST => $uri,
         [ 'Content-Type' => 'text/x-yaml' ],
@@ -187,6 +201,8 @@
             }
         )
     );
+
+    # Fire off the request, evaluate the result, and return it
     my $result = $self->request( $request );
     my $content = eval { Jifty::YAML::Load($result->content)->{action} } || undef;
     $self->back;
@@ -205,9 +221,11 @@
     my $path = shift;
     my %args = @_;
 
+    # Setup the URL we're going to use
     my $uri = $self->uri->clone;
     $uri->path("__jifty/webservices/xml");
 
+    # Setup the request we're going to use
     my $request = HTTP::Request->new(
         POST => $uri,
         [ 'Content-Type' => 'text/x-yaml' ],
@@ -223,6 +241,8 @@
             }
         )
     );
+
+    # Fire the request, evaluate the result, and return it
     my $result = $self->request( $request );
     use XML::Simple;
     my $content = eval { XML::Simple::XMLin($result->content, SuppressEmpty => '')->{fragment}{content} } || '';
@@ -254,7 +274,7 @@
 
 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.
+field can't be found, return undef).
 
 =cut
 
@@ -263,10 +283,11 @@
     my $moniker = shift;
     my $field = shift;
 
+    # Setup the XPath processor and the ID we're looking for
     my $xp = XML::XPath->new( xml => $self->content );
-
     my $id = "errors-J:A:F-$field-$moniker";
 
+    # Search for the span containing that error
     my $nodeset = $xp->findnodes(qq{//span[\@id = "$id"]});
     return unless $nodeset->size == 1;
     
@@ -295,8 +316,10 @@
 sub session {
     my $self = shift;
 
+    # We don't have a session!
     return undef unless $self->cookie_jar->as_string =~ /JIFTY_SID_\d+=([^;]+)/;
 
+    # Load the data stored in the session cookie
     my $session = Jifty::Web::Session->new;
     $session->load($1);
     return $session;
@@ -312,12 +335,15 @@
 sub continuation {
     my $self = shift;
 
+    # If we don't have a session, we don't have a continuation
     my $session = $self->session;
     return undef unless $session;
     
+    # Look for the continuation info in the URL
     my $id = shift;
     ($id) = $self->uri =~ /J:(?:C|CALL|RETURN)=([^&;]+)/ unless $id;
 
+    # Return information about the continuation
     return $session->get_continuation($id);
 }
 
@@ -330,9 +356,11 @@
 sub current_user {
     my $self = shift;
 
+    # We don't have a current user if we don't have a session
     my $session = $self->session;
     return undef unless $session;
 
+    # Fetch information about user from the session
     my $id = $session->get('user_id');
     my $object = Jifty->app_class("CurrentUser")->new();
     my $user = $session->get('user_ref')->new( current_user => $object );
@@ -342,4 +370,15 @@
     return $object;
 }
 
+=head1 SEE ALSO
+
+L<Jifty::Test::WWW::Mechanize>
+
+=head1 LICENSE
+
+Jifty is Copyright 2005-2007 Best Practical Solutions, LLC.
+Jifty is distributed under the same terms as Perl itself.
+
+=cut
+
 1;

Modified: jifty/branches/js-refactor/lib/Jifty/Collection.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Collection.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Collection.pm	Thu Nov 29 12:25:16 2007
@@ -3,6 +3,9 @@
 
 package Jifty::Collection;
 
+use base qw/Jifty::Object Jifty::DBI::Collection Class::Accessor::Fast/;
+use Data::Page;
+
 =head1 NAME
 
 Jifty::Collection - Collection of Jifty::Record objects
@@ -30,65 +33,93 @@
 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.
+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
+If your results from the query are 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.
+C<results_are_readable => 1>.  This causes check_read_rights to bypass
+normal current_user_can checks.
 
 =cut
 
 __PACKAGE__->mk_accessors(qw(pager results_are_readable));
 
+=head2 as_search_action PARAMHASH
+
+Returns the L<Jifty::Action::Record::Search> action for the model
+associated with this collection.
+
+The PARAMHASH allows you to add additional parameters to pass to
+L<Jifty::Web/new_action>.
+
+=cut
+
+sub as_search_action {
+    my $self = shift;
+    return $self->record_class->as_search_action(@_);
+}
+
 =head2 add_record
 
-Only add records to the collection that we can read
+If L</results_are_readable> is false, only add records to the
+collection that we can read (by checking
+L<Jifty::Record/check_read_rights>). Otherwise, make sure all records
+added are readable.
 
 =cut
 
 sub add_record {
     my $self = shift;
-    my($record) = (@_);
+    my ($record) = (@_);
 
+    # If results_are_readable is set, guarantee that they are
     $record->_is_readable(1)
         if $self->results_are_readable;
 
+    # Only add a record if results_are_readable or the user has read rights
     $self->SUPER::add_record($record)
         if $self->results_are_readable || $record->check_read_rights;
 }
 
+# Overrides the _init method of Jifty::DBI::Collection and is called by new.
+# This does the following:
+#
+#  - Sets up the current user
+#  - Sets up the record class, if given as an argument
+#  - Sets up results_are_readable, if given as an argument
+#  - Sets up the table used for storage
+#
 sub _init {
     my $self = shift;
-
     my %args = (
-        record_class => undef,
-        current_user => undef,
+        record_class         => undef,
+        current_user         => undef,
         results_are_readable => undef,
         @_
     );
 
+    # Setup the current user, record class, results_are_readable
     $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) {
+    $self->record_class( $args{record_class} ) if defined $args{record_class};
+    $self->results_are_readable( $args{results_are_readable} );
+
+    # Bad stuff, we really need one of these
+    unless ( $self->current_user ) {
         Carp::confess("Collection created without a current user");
     }
-    $self->table($self->new_item->table());
+
+    # Setup the table and call the super-implementation
+    $self->table( $self->new_item->table() );
     $self->SUPER::_init(%args);
 }
 
@@ -100,22 +131,28 @@
 
 sub implicit_clauses {
     my $self = shift;
-    $self->order_by( column => 'id',order => 'asc');
+    $self->order_by( column => 'id', order => 'asc' );
+}
+
+sub _new_record_args {
+    my $self = shift;
+    return ( current_user => $self->current_user );
 }
 
-=head2 new_item
+sub _new_collection_args {
+    my $self = shift;
+    return ( current_user => $self->current_user );
+}
 
-Overrides L<Jifty::DBI::Collection>'s new_item to pass in the current
-user.
+=head1 SEE ALSO
 
-=cut
+L<Jifty::DBI::Collection>, L<Jifty::Object>, L<Jifty::Record>
 
-sub new_item {
-    my $self = shift;
-    my $class =$self->record_class();
+=head1 LICENSE
 
-    # 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);
-}
+Jifty is Copyright 2005-2007 Best Practical Solutions, LLC.
+Jifty is distributed under the same terms as Perl itself.
+
+=cut
 
 1;

Modified: jifty/branches/js-refactor/lib/Jifty/Config.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Config.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Config.pm	Thu Nov 29 12:25:16 2007
@@ -5,10 +5,18 @@
 
 =head1 NAME
 
-Jifty::Config -- wrap a jifty configuration file
+Jifty::Config - the configuration handler for Jifty
+
+=head1 SYNOPSIS
+
+  my $app_name = Jifty->config->framework('ApplicationName');
+  my $frobber  = Jifty->config->app('PreferredFrobnicator');
 
 =head1 DESCRIPTION
 
+This class is automatically loaded during Jifty startup. It contains the configuration information loaded from the F<config.yml> file (generally stored in the F<etc> directory of your application, but see L</load> for the details). This configuration file is stored in L<YAML> format.
+
+This configuration file contains two major sections named "C<framework>" and "C<application>". The framework section contains Jifty-specific configuration options and the application section contains whatever configuration options you want to use with your application. (I.e., if there's any configuration information your application needs to know at startup, this is a good place to put it.)
 
 =cut
 
@@ -31,6 +39,12 @@
 
 =head2 new PARAMHASH
 
+In general, you never need to call this, just use:
+
+  Jifty->config
+
+in your application.
+
 This class method instantiates a new C<Jifty::Config> object. This
 object deals with configuration files.  
 
@@ -44,7 +58,6 @@
 
 =back
 
-
 =cut
 
 sub new {
@@ -54,18 +67,19 @@
              );
     my $self  = {};
     bless $self, $proto;
+
+    # Setup the initially empty stash
     $self->stash( {} );
 
+    # Load from file unless they tell us not to
     $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
+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
@@ -82,35 +96,37 @@
 the framework's C<TestConfig> or the C<JIFTY_TEST_CONFIG> environment
 variable.
 
+Note that the test config may be drawn from several files if you use
+L<Jifty::Test>. See the documentation of L<Jifty::Test::load_test_configs>.
+
 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.
+those in the application configuration file. (See L</WHY SO MANY FILES> for a deeper search for truth on this matter.)
 
 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.
+After we have the config file, we call the coderef in C<$Jifty::Config::postload>, if it exists. This last bit is generally used by the test harness to do a little extra work.
 
-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.)
+B<SPECIAL PER-VALUE PROCESSING:> If a value begins and ends with "%" (e.g.,
+"%bin/foo%"), converts it with C<Jifty::Util/absolute_path> to an absolute path.
+This is typically unnecessary, but helpful for configuration variables such as C<MailerArgs> that only sometimes specify files.
 
 =cut
 
 sub load {
     my $self = shift;
 
+    # Add the default configuration file locations to the stash
     $self->stash( Hash::Merge::merge( $self->_default_config_files, $self->stash ));
 
+    # Calculate the location of the application etc/config.yml
     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
+    # Start by loading application configuration file
     if ( -f $file and -r $file ) {
         $app = $self->load_file($file);
         $app = Hash::Merge::merge( $self->stash, $app );
@@ -118,32 +134,40 @@
         # Load the $app so we know where to find the vendor config file
         $self->stash($app);
     }
+
+    # Load the vendor configuration file
     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
+    # Merge the app config with vendor config, vendor taking precedent
     my $config = Hash::Merge::merge( $self->stash, $vendor );
     $self->stash($config);
 
-
+    # Load the site configuration file
     my $site = $self->load_file(
         Jifty::Util->absolute_path(
+            # Note: $ENV{'JIFTY_SITE_CONFIG'} is already considered
+            #       in ->_default_config_files(), but we || here again
+            #       in case someone overrided _default_config_files().
             $self->framework('SiteConfig') || $ENV{'JIFTY_SITE_CONFIG'}
         )
     );
 
+    # Merge the app, vendor, and site config, site taking precedent
     $config = Hash::Merge::merge( $self->stash, $site );
     $self->stash($config);
 
+    # Load the test configuration file
     my $test = $self->load_file(
         Jifty::Util->absolute_path(
             $self->framework('TestConfig') || $ENV{'JIFTY_TEST_CONFIG'}
         )
     );
+
+    # Merge the app, vendor, site and test config, test taking precedent
     $config = Hash::Merge::merge( $self->stash, $test );
     $self->stash($config);
 
@@ -155,9 +179,9 @@
     # getting stuck in a default config file for an app
     $self->stash( Hash::Merge::merge( $self->defaults, $self->stash));
 
+    # Bring old configurations up to current expectations
     $self->stash($self->update_config($self->stash));
 
-
     # Finally, check for global postload hooks (these are used by the
     # test harness)
     $self->$Jifty::Config::postload()
@@ -190,6 +214,7 @@
     $self->_get( 'application', $var );
 }
 
+# A teeny helper for framework and app
 sub _get {
     my $self    = shift;
     my $section = shift;
@@ -198,18 +223,19 @@
     return  $self->stash->{$section}->{$var} 
 }
 
-
+# Sets up the initial location of the site configuration file
 sub _default_config_files {
     my $self = shift;
     my $config  = {
         framework => {
-            SiteConfig => 'etc/site_config.yml'
+            SiteConfig => (
+                $ENV{JIFTY_SITE_CONFIG} || 'etc/site_config.yml'
+            )
         }
     };
     return $self->_expand_relative_paths($config);
 }
 
-
 =head2 guess
 
 Attempts to guess (and return) a configuration hash based solely
@@ -222,27 +248,37 @@
 sub guess {
     my $self = shift;
 
-    # Walk around a potential loop by calling guess to get the app name
+    # First try at guessing the app name...
     my $app_name;
+
+    # Was it passed to this method?
     if (@_) {
         $app_name = shift;
-    } elsif ($self->stash->{framework}->{ApplicationName}) {
+    }
+   
+    # Is it already in the stash?
+    elsif ($self->stash->{framework}->{ApplicationName}) {
         $app_name =  $self->stash->{framework}->{ApplicationName};
-    } else {
+    } 
+    
+    # Finally, just guess from the application root
+    else {
         $app_name =  Jifty::Util->default_app_name;
     }
 
+    # Setup the application class name based on the application 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;
 
+    # Build up the guessed configuration
     my $guess = {
         framework => {
             AdminMode        => 1,
             DevelMode        => 1,
-	    SkipAccessControl => 0,
+            SkipAccessControl => 0,
             ApplicationClass => $app_class,
             TemplateClass    => $app_class."::View",
             ApplicationName  => $app_name,
@@ -251,8 +287,9 @@
             PubSub           => {
                 Enable => undef,
                 Backend => 'Memcached',
-                    },
+            },
             Database         => {
+                AutoUpgrade => 1,
                 Database =>  $db_name,
                 Driver   => "SQLite",
                 Host     => "localhost",
@@ -285,14 +322,16 @@
         },
     };
 
+    # Make sure to handle any %path% values we may have guessed
     return $self->_expand_relative_paths($guess);
-
 }
 
 
 =head2 initial_config
 
-Returns a default guessed config for a new application
+Returns a default guessed config for a new application.
+
+See L<Jifty::Script::App>.
 
 =cut
 
@@ -315,8 +354,6 @@
     return $guess;
 }
 
-
-
 =head2 update_config  $CONFIG
 
 Takes an application's configuration as a hashref.  Right now, it just sets up
@@ -338,7 +375,12 @@
             { OnlineDocs            => {}, },
             { CompressedCSSandJS    => {}, },
             { AdminUI               => {}, },
-
+        )
+    }
+    if ( $config->{'framework'}->{'ConfigFileVersion'} <3) {
+        # These are the plugins which old apps expect because their
+        # features used to be in the core.
+        unshift (@{$config->{'framework'}->{'Plugins'}}, 
             # JS libs which are now turning into plugins
             { CSSBrowserSelectorJS  => {}, },
             { FormatDateJS          => {}, },
@@ -351,8 +393,6 @@
     return $config;
 }
 
-
-
 =head2 defaults
 
 We have a couple default values that shouldn't be included in the
@@ -387,7 +427,6 @@
 =cut
 
 sub load_file {
-
     my $self = shift;
     my $file = shift;
 
@@ -396,6 +435,7 @@
     my $hashref = Jifty::YAML::LoadFile($file)
         or die "I couldn't load config file $file: $!";
 
+    # Make sure %path% values are made absolute
     $hashref = $self->_expand_relative_paths($hashref);
     return $hashref;
 }
@@ -405,17 +445,28 @@
 sub _expand_relative_paths {
     my $self  = shift;
     my $datum = shift;
+
+    # Recurse through each value in an array
     if ( ref $datum eq 'ARRAY' ) {
         return [ map { $self->_expand_relative_paths($_) } @$datum ];
-    } elsif ( ref $datum eq 'HASH' ) {
+    } 
+    
+    # Recurse through each value in a hash
+    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 ) {
+    } 
+    
+    # Do nothing with other kinds of references
+    elsif ( ref $datum ) {
         return $datum;
-    } else {
+    } 
+    
+    # Check scalars for %path% and convert the enclosed value to an abspath
+    else {
         if ( defined $datum and $datum =~ /^%(.+)%$/ ) {
             $datum = Jifty::Util->absolute_path($1);
         }
@@ -423,10 +474,41 @@
     }
 }
 
+=head1 WHY SO MANY FILES
+
+The Jifty configuration can be loaded from many locations. This breakdown allows for configuration files to be layered on top of each other for advanced deployments.
+
+This section hopes to explain the intended purpose of each configuration file.
+
+=head1 APPLICATION
+
+The first configuration file loaded is the application configuration. This file provides the basis for the rest of the configuration loaded. The purpose of this file is for storing the primary application-specific configuration and defaults.
+
+This can be used as the sole configuration file on a simple deployment. In a complex environment, however, this file may be considered read-only to be overridden by other files, allowing the later files to customize the configuration at each level.
+
+=head1 VENDOR
+
+The vendor configuration file is loaded and overrides settings in the application configuration. This is an intermediate level in the configuration. It overrides any defaults specified in the application configuration, but is itself overridden by the site configuration.
+
+This provides an additional layer of abstraction for truly complicated deployments. A developer may provide a particular Jifty application (such as the Wifty wiki available from Best Practical Solutions) for download. A system administrator may have a standard set of configuration overrides to use on several different deployments that can be set using the vendor configuration, which can then be further overridden by each deployment using a site configuration. Several installations of the application might even share the vendor configuration file.
+
+=head2 SITE
+
+The site configuration allows for specific overrides of the application and vendor configuration. For example, a particular Jifty application might define all the application defaults in the application configuration file. Then, each administrator that has downloaded that appliation and is installing it locally might customize the configuration for a particular deployment using this configuration file, while leaving the application defaults intact (and, thus, still available for later reference). This can even override the vendor file containing a standard set of overrides.
+
+=head1 SEE ALSO
+
+L<Jifty>
+
 =head1 AUTHOR
 
 Various folks at BestPractical Solutions, LLC.
 
+=head1 LICENSE
+
+Jifty is Copyright 2005-2007 Best Practical Solutions, LLC.
+Jifty is distributed under the same terms as Perl itself.
+
 =cut
 
 1;

Modified: jifty/branches/js-refactor/lib/Jifty/Continuation.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Continuation.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Continuation.pm	Thu Nov 29 12:25:16 2007
@@ -9,10 +9,27 @@
 
 =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.
+In programming, a continuation is a construct that allows you to freeze the current state of a program and then recover that state later by calling the continuation. For example, you could save a continuation when throwing an exception to save the state, an exception handler could resolve the problem that caused the exception, and then call the continuation to resume execution at the point where the exception was thrown now that the problem has been solved.
+
+In Jifty, continuations are used to save the state of a request (and sometimes the response). Continuations can be used in situations such as these:
+
+=over
+
+=item 1.
+
+A user visits a page that requires login to view. The dispatcher saves a continuation and then sends the user off to the login page. Once the user logs in successfully, the login action can call the continuation to return the user back to the original page.
+
+=item 2. 
+
+A blogging application might have a "Edit" link on each post to allow the editor to jump to the change page. If this link includes a saved continuation, then the "Save" button could trigger that continuation to be called to return the user back to the original page where they clicked "Edit". This way, it could return the user to the view page, or a list page, or an administrative view depending on which page the user started the edit process from.
+
+=item 3.
+
+If you have a wizard for editing some information in your application, but entering some data may require jumping to another page you can save a continuation to allow the user to return after editing. If that page also requires a jump to yet another page, you can save another continuation. Since continuations save a stack of previous continuations, you can return twice to get back to the wizard.
+
+=back
+
+C<Jifty::Continuation> handles the details of saving this information for later recovery. When a continuation is saved, the current request and response are saved to the database in the current user's session. When a continuation is called, the current request and response become those that were saved in the continuation. A continuation can be called at any point in the same session.
 
 Continuations store a L<Jifty::Request> object and the
 L<Jifty::Response> object for the request.  They can also store
@@ -24,7 +41,7 @@
 
 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
+The rest of the request is saved, its execution is to be continued at a
 later time.
 
 Continuations are run after any actions have run.  When a continuation
@@ -158,15 +175,16 @@
 
     Jifty->log->debug("Redirect to @{[$self->request->path]} via continuation");
     if (Jifty->web->request->argument('_webservice_redirect')) {
-	# for continuation - perform internal redirect under webservices.
+        # for continuation - perform internal redirect under webservices.
         Jifty->web->webservices_redirect($self->request->path);
-	return;
+        return;
     }
+
     # 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\-_.!~*'()/?&;+]};
+        if $self->request->path =~ m{[^A-Za-z0-9\-_.!~*'()/?&;+]};
 
     # Clone our request
     my $request = $self->request->clone;
@@ -175,6 +193,7 @@
     $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
@@ -191,7 +210,7 @@
                                         code => $self->code,
                                        );
     $next->request->continuation(Jifty->web->session->get_continuation($next->parent))
-      if defined $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);
@@ -241,4 +260,15 @@
 
 }
 
+=head1 SEE ALSO
+
+L<Jifty::Manual::Continuations>
+
+=head1 LICENSE
+
+Jifty is Copyright 2005-2007 Best Practical Solutions, LLC.
+Jifty is distributed under the same terms as Perl itself.
+
+=cut
+
 1;

Modified: jifty/branches/js-refactor/lib/Jifty/CurrentUser.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/CurrentUser.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/CurrentUser.pm	Thu Nov 29 12:25:16 2007
@@ -8,7 +8,6 @@
 
 __PACKAGE__->mk_accessors(qw(is_superuser is_bootstrap_user));
 
-
 =head1 NAME
 
 Jifty::CurrentUser - Base class and basic implementation of current user object
@@ -28,8 +27,6 @@
 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
@@ -44,24 +41,49 @@
     my $self  = {};
     bless $self, (ref $class || $class);
     my %args = (@_);
+
+    # Make this user a bootstrap user if in bootstrap mode
     if ( delete $args{'_bootstrap'} ) { $self->is_bootstrap_user(1); }
+
+    # Call _init for app-specific initialization
     $self->_init(%args);
+
     return $self;
 }
 
+=head2 _init
 
-sub _init {
+Applications should override this method to provide any application-specific user loading code. The built-in
+
+If you do nothing, code similar to this will be called by _init.
+
+	sub _init {
 	    my $self = shift;
 	    my %args = (@_);
 	
-            if (keys %args and UNIVERSAL::can(Jifty->app_class('Model', 'User'), 'new')  ) {
-                
+        if (keys %args and UNIVERSAL::can(Jifty->app_class('Model', 'User'), 'new')) {
 	        $self->user_object(Jifty->app_class('Model', 'User')->new(current_user => $self));
 	        $self->user_object->load_by_cols(%args);
 	    }
         return 1;
-}
+	}
+
+That is, it will attempt to load the columns given in the model named C<App::Model::User> (where I<App> is the name of your application class). If your notion of a user object isn't a typical Jifty model or named something else, you will definitely need to override this method. If you need to perform any additional initialization for user objects, you may want to override this as well.
 	
+=cut
+
+sub _init {
+    my $self = shift;
+    my %args = (@_);
+
+    # Duck-typing to check to for a user class
+    if (keys %args and UNIVERSAL::can(Jifty->app_class('Model', 'User'), 'new')  ) {
+        $self->user_object(Jifty->app_class('Model', 'User')->new(current_user => $self));
+        $self->user_object->load_by_cols(%args);
+    }
+
+    return 1;
+}
 
 =head2 superuser
 
@@ -73,7 +95,11 @@
 sub superuser {
     my $class = shift;
     $class = ref( $class ) if ref $class;
+
+    # Create the current user object
     my $self = $class->new();
+
+    # Make it superuser and send it out
     $self->is_superuser(1);
     return $self;
 }
@@ -81,35 +107,23 @@
 =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
+user. Generally, you're expected to set and load it in the L</_init> method
 in your L<Jifty::CurrentUser> subclass.
 
-If you do nothing, code similar to this will be called by _init.
-
-	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);
-	    }
-	}
-	
-
 =cut
 
 sub user_object {
     my $self = shift;
     return $self->{'user_object'} unless @_;
-
     $self->{'user_object'} = shift;
-    # protect ourself from circular refereces
+
+    # protect ourself from circular refereces to prevent memory leaks
     if ( $self->{'user_object'}{'_current_user'} == $self ) {
         Scalar::Util::weaken( $self->{'user_object'}{'_current_user'} )
             unless Scalar::Util::isweak( $self->{'user_object'}{'_current_user'} );
         $self->{'user_object'}{'_resurrect_current_user'} = 1;
     }
+
     return $self->{'user_object'};
 }
 
@@ -122,9 +136,14 @@
 
 sub id {
     my $self = shift;
+
+    # Make sure we have a user object before trying to ID it
     if ($self->user_object) {
         return ($self->user_object->id());
-    } else {
+    } 
+    
+    # No user object, return a null ID
+    else {
         return '0';
     }
 
@@ -137,7 +156,7 @@
 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>
+rather than another C<Jifty::CurrentUser> object.
 
 =cut
 
@@ -225,7 +244,6 @@
 
 =cut
 
-
 =head2 current_user_can ACTION
 
 For a current user object, the current user can always C<read>, but
@@ -233,7 +251,7 @@
 
 =cut
 
-
+# XXX Is this actually used?
 sub current_user_can {
     my $self = shift;
     my $action = shift;
@@ -241,4 +259,15 @@
     return (0);
 }
 
+=head1 SEE ALSO
+
+L<Jifty::Object>, L<Jifty::Plugin::User>
+
+=head1 LICENSE
+
+Jifty is Copyright 2005-2007 Best Practical Solutions, LLC.
+Jifty is distributed under the same terms as Perl itself.
+
+=cut
+
 1;

Modified: jifty/branches/js-refactor/lib/Jifty/DateTime.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/DateTime.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/DateTime.pm	Thu Nov 29 12:25:16 2007
@@ -7,6 +7,18 @@
 
 Jifty::DateTime - a DateTime subclass that knows about Jifty users
 
+=head1 SYNOPSIS
+
+  use Jifty::DateTime;
+  
+  # Get the current date and time
+  my $dt = Jifty::DateTime->now;
+  
+  # Print out the pretty date (i.e., today, tomorrow, yesterday, or 2007-09-11)
+  Jifty->web->out( $dt->friendly_date );
+
+  # Better date parsing
+  my $dt_from_human = Jifty::DateTime->new_from_string("next Saturday");
 
 =head1 DESCRIPTION
 
@@ -14,6 +26,8 @@
 without timezone. This class loads and parses dates and sets them
 into the proper timezone.
 
+To use this DateTime class to it's fullest ability, you'll need to add a C<time_zone> method to your application's user object class. This is the class returned by L<Jifty::CurrentUser/user_object>. It must return a value valid for using as an argument to L<DateTime>'s C<set_time_zone()> method.
+
 =cut
 
 BEGIN {
@@ -27,12 +41,12 @@
 
 use base qw(Jifty::Object DateTime);
 
-
 =head2 new ARGS
 
-See L<DateTime/new>.  After calling that method, set this object's
+See L<DateTime/new>. If we get what appears to be a date, then we
+keep this in the floating datetime. Otherwise, set this object's
 timezone to the current user's time zone, if the current user has a
-method called C<time_zone>.
+method called C<time_zone>.  
 
 =cut
 
@@ -41,31 +55,115 @@
     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 );
+    # XXX What if they really mean midnight offset by time zone?
+
+    #     this behavior is (sadly!) consistent with
+    #     DateTime->truncate(to => 'day') and Jifty::DateTime::new_from_string
+    #     suggestions for improvement are very welcome
+
+    # Do not bother with time zones unless time is used, we assume that
+    # 00:00:00 implies that no time is used
+    if ($self->hour || $self->minute || $self->second) {
+
+        # Unless the user has explicitly said they want a floating time,
+        # we want to convert to the end-user's timezone. If we ignore
+        # $args{time_zone}, then DateTime::from_epoch will get very confused
+        if (!$args{time_zone} and my $tz = $self->current_user_has_timezone) {
+
+            # XXX: we do this because of the floating timezone
+            $self->set_time_zone("UTC");
+
+            $self->set_time_zone( $tz );
+        }
+    }
+
+    # No time, just use the floating time zone
+    else {
+        $self->set_time_zone("floating");
     }
+
+    return $self;
+}
+
+=head2 now ARGS
+
+See L<DateTime/now>. If a time_zone argument is passed in, then this method
+is effectively a no-op.
+
+OTHERWISE this will always set this object's timezone to the current user's
+timezone (or UTC if that's not available). Without this, DateTime's C<now> will
+set the timezone to UTC always (by passing C<< time_zone => 'UTC' >> to
+C<Jifty::DateTime::new>. We want Jifty::DateTime to always reflect the current
+user's timezone (unless otherwise requested, of course).
+
+=cut
+
+sub now {
+    my $class = shift;
+    my %args  = @_;
+    my $self  = $class->SUPER::now(%args);
+
+    $self->set_current_user_timezone()
+        unless $args{time_zone};
+
+    return $self;
+}
+
+=head2 from_epoch ARGS
+
+See L<DateTime/from_epoch> and L<Jifty::DateTime/now>.
+
+=cut
+
+sub from_epoch {
+    my $class = shift;
+    my %args  = @_;
+    my $self  = $class->SUPER::from_epoch(%args);
+
+    $self->set_current_user_timezone()
+        unless $args{time_zone};
+
     return $self;
 }
 
 =head2 current_user_has_timezone
 
-Return timezone if the current user has it
+Return timezone if the current user has one. This is determined by checking to see if the current user has a user object. If it has a user object, then it checks to see if that user object has a C<time_zone> method and uses that to determine the value.
 
 =cut
 
 sub current_user_has_timezone {
     my $self = shift;
     $self->_get_current_user();
-    return unless $self->current_user->can('user_object');
+
+    # Can't continue if we have no notion of a user_object
+    $self->current_user->can('user_object') or return;
+
+    # Can't continue unless the user object is defined
     my $user_obj = $self->current_user->user_object or return;
+
+    # Check for a time_zone method and then use it if it exists
     my $f = $user_obj->can('time_zone') or return;
     return $f->($user_obj);
 }
 
+=head2 set_current_user_timezone [DEFAULT_TZ]
+
+Set this Jifty::DateTime's timezone to the current user's timezone. If that's
+not available, then use the passed in DEFAULT_TZ (or GMT if not passed in).
+Returns the Jifty::DateTime object itself.
+
+=cut
+
+sub set_current_user_timezone {
+    my $self    = shift;
+    my $default = shift || 'GMT';
+    my $tz = $self->current_user_has_timezone || $default;
+
+    $self->set_time_zone($tz);
+    return $self;
+}
+
 =head2 new_from_string STRING
 
 Take some user defined string like "tomorrow" and turn it into a
@@ -73,6 +171,8 @@
 it in the floating timezone, otherwise, set it to the current user's
 timezone.
 
+As of this writing, this uses L<Date::Manip> along with some internal hacks to alter the way L<Date::Manip> normally interprets week day names. This may change in the future.
+
 =cut
 
 sub new_from_string {
@@ -80,6 +180,7 @@
     my $string = shift;
     my $now;
 
+    # Hack to use Date::Manip to flexibly scan dates from strings
     {
         # Date::Manip interprets days of the week (eg, ''monday'') as
         # days within the *current* week. Detect these and prepend
@@ -95,14 +196,75 @@
         Date::Manip::Date_Init("TZ=GMT");
         $now = Date::Manip::UnixDate( $string, "%o" );
     }
+
+    # Stop here if Date::Manip couldn't figure it out
     return undef unless $now;
+
+    # Build a DateTime object from the Date::Manip value and setup the TZ
     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;
 }
 
+=head2 friendly_date
+
+Returns the date given by this C<Jifty::DateTime> object. It will display "today"
+for today, "tomorrow" for tomorrow, or "yesterday" for yesterday. Any other date
+will be displayed in ymd format.
+
+=cut
+
+sub friendly_date {
+    my $self = shift;
+    my $ymd = $self->ymd;
+
+    # Use the current user's time zone on the date
+    my $tz = $self->current_user_has_timezone || $self->time_zone;
+    my $rel = DateTime->now( time_zone => $tz );
+
+    # Is it today?
+    if ($ymd eq $rel->ymd) {
+        return "today";
+    }
+
+    # Is it yesterday?
+    my $yesterday = $rel->clone->subtract(days => 1);
+    if ($ymd eq $yesterday->ymd) {
+        return "yesterday";
+    }
+
+    # Is it tomorrow?
+    my $tomorrow = $rel->clone->add(days => 1);
+    if ($ymd eq $tomorrow->ymd) {
+        return "tomorrow";
+    }
+    
+    # None of the above, just spit out the date
+    return $ymd;
+}
+
+=head1 WHY?
+
+There are other ways to do some of these things and some of the decisions here may seem arbitrary, particularly if you read the code. They are.
+
+These things are valuable to applications built by Best Practical Solutions, so it's here. If you disagree with the policy or need to do it differently, then you probably need to implement something yourself using a DateTime::Format::* class or your own code. 
+
+Parts may be cleaned up and the API cleared up a bit more in the future.
+
+=head1 SEE ALSO
+
+L<DateTime>, L<DateTime::TimeZone>, L<Jifty::CurrentUser>
+
+=head1 LICENSE
+
+Jifty is Copyright 2005-2007 Best Practical Solutions, LLC.
+Jifty is distributed under the same terms as Perl itself.
+
+=cut
+
 1;

Modified: jifty/branches/js-refactor/lib/Jifty/Dispatcher.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Dispatcher.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Dispatcher.pm	Thu Nov 29 12:25:16 2007
@@ -422,6 +422,7 @@
     my $stage = shift;
     my $pkg   = ref($self) || $self;
     no strict 'refs';
+    no warnings 'once';
     @{ $pkg . '::RULES_' . $stage };
 }
 

Modified: jifty/branches/js-refactor/lib/Jifty/Everything.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Everything.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Everything.pm	Thu Nov 29 12:25:16 2007
@@ -7,11 +7,19 @@
 
 Jifty::Everything - Load all of the important Jifty modules at once.
 
+=head1 DESCRIPTION
+
+This package is loaded very early in the processof loading Jifty to bring in all of the wonderful goodies that make up Jifty. If you use L<JIfty>:
+
+  use Jifty;
+
+you use this package, so you should not need to use it yourself in most circumstances.
+
 =cut
 
 use Cwd ();
 BEGIN {
-    # Cwd::cwd() insists doing `pwd`, which is a few hundres of shell
+    # Cwd::cwd() insists doing `pwd`, which is a few hundreds of shell
     # outs just in the BEGIN time for Module::Pluggable to load things.
     if ($^O ne 'MSWin32') {
         require POSIX;
@@ -78,9 +86,18 @@
 use Jifty::Subs::Render ();
 
 use Jifty::Module::Pluggable;
-Jifty::Module::Pluggable->import(search_path => ['Jifty::Web::Form::Field'],
-                          require     => 1,
-                          except      => qr/\.#/);
-__PACKAGE__->plugins;
+#Jifty::Module::Pluggable->import(search_path => ['Jifty::Web::Form::Field'], require     => 1, except      => qr/\.#/);
+#__PACKAGE__->plugins;
+
+=head1 SEE ALSO
+
+L<Jifty>
+
+=head1 LICENSE
+
+Jifty is Copyright 2005-2007 Best Practical Solutions, LLC.
+Jifty is distributed under the same terms as Perl itself.
+
+=cut
 
 1;

Modified: jifty/branches/js-refactor/lib/Jifty/Filter/DateTime.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Filter/DateTime.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Filter/DateTime.pm	Thu Nov 29 12:25:16 2007
@@ -6,6 +6,15 @@
 Jifty::Filter::DateTime -- A Jifty::DBI filter to work with
                           Jifty::DateTime objects
 
+=head1 SYNOPSIS
+
+   # use it with Jifty::DBI::Filter::Date or J::D::F::DateTime
+    column created =>
+      type is 'timestamp',
+      filters are qw( Jifty::Filter::DateTime Jifty::DBI::Filter::DateTime),
+      label is 'Created',
+      is immutable;
+
 =head1 DESCRIPTION
 
 Jifty::Filter::DateTime promotes DateTime objects to Jifty::DateTime
@@ -48,6 +57,10 @@
         $args{$_} = $$value_ref->$_ if(defined($$value_ref->$_));
     }
 
+    # the floating timezone indicates a date, so we don't want to set any
+    # other timezone on it
+    $args{time_zone} = 'floating' if $$value_ref->time_zone =~ /floating/i;
+
     my $dt = Jifty::DateTime->new(%args);
 
     $$value_ref = $dt;
@@ -58,6 +71,11 @@
 L<Jifty::DBI::Filter::Date>, L<Jifty::DBI::Filter::DateTime>,
 L<Jifty::DateTime>
 
+=head1 LICENSE
+
+Jifty is Copyright 2005-2007 Best Practical Solutions, LLC.
+Jifty is distributed under the same terms as Perl itself.
+
 =cut
 
 1;

Modified: jifty/branches/js-refactor/lib/Jifty/Handle.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Handle.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Handle.pm	Thu Nov 29 12:25:16 2007
@@ -91,7 +91,7 @@
 
     my %lc_db_config;
     # Skip the non-dsn keys, but not anything else
-    for (grep {!/^checkschema|version|recordbaseclass|attributes$/i} keys %db_config) {
+    for (grep {!/^autoupgrade|checkschema|version|forwardcompatible|recordbaseclass|attributes$/i} keys %db_config) {
         $lc_db_config{lc($_)} = $db_config{$_};
     }
     $self->SUPER::connect( %lc_db_config , %args);
@@ -114,7 +114,9 @@
 =cut
 
 sub check_schema_version {
+    my $self = shift;
     require Jifty::Model::Metadata;
+            my $autoup = delete Jifty->config->framework('Database')->{'AutoUpgrade'};
 
     # Application db version check
     {
@@ -145,10 +147,20 @@
             . "\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);
+        unless (version->new($appv) == version->new($dbv)) {
+            # if app version is older than db version, but we are still compatible
+            my $compat = delete Jifty->config->framework('Database')->{'ForwardCompatible'} || $appv;
+            if (version->new($appv) > version->new($dbv) || version->new($compat) < version->new($dbv)) {
+            warn "Application schema version in database ($dbv) doesn't match application schema version ($appv)\n";
+            if( $autoup 
+            ) {
+                warn "Automatically upgrading your database to match the current application schema";
+                $self->_upgrade_schema();
+            } else {
+                 die "Please run `bin/jifty schema --setup` to upgrade the database.\n";
+             }
+            }
+        }
     }
 
     # Jifty db version check
@@ -161,10 +173,15 @@
             = 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;
+            unless ( $appv == $dbv ) {
+           warn "Internal jifty schema version in database ($dbv) doesn't match running jifty version ($appv)\n";
+            if( $autoup) {
+                warn "Automatically upgrading your database to match the current Jifty schema\n";
+                $self->_upgrade_schema;
+            } else {
+        die "Please run `bin/jifty schema --setup` to upgrade the database.\n"
+            }
+        };
     }
 
 }
@@ -184,9 +201,10 @@
     my $mode = shift || 'execute';
     my $database = $self->canonical_database_name;
     my $driver   = Jifty->config->framework('Database')->{'Driver'};
-    my $query = "CREATE DATABASE $database;\n";
+    my $query = "CREATE DATABASE $database";
+    $query .= " TEMPLATE template0" if $driver =~ /Pg/;
     if ( $mode eq 'print') {
-        print $query;
+        print "$query;\n";
     } elsif ( $driver !~ /SQLite/ ) {
         $self->simple_query($query);
     }
@@ -214,10 +232,21 @@
         $self->disconnect if $^O eq 'MSWin32';
         unlink($database);
     } else {
+        local $SIG{__WARN__} =
+          sub { warn $_[0] unless $_[0] =~ /exist|couldn't execute/i };
         $self->simple_query("DROP DATABASE $database");
     }
 }
 
+sub _upgrade_schema {
+    my $self = shift;
+
+    my $hack = {};
+    require Jifty::Script::Schema;
+    bless $hack, "Jifty::Script::Schema";
+    $hack->run_upgrades;
+
+}
 
 =head1 AUTHOR
 

Modified: jifty/branches/js-refactor/lib/Jifty/Handler.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Handler.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Handler.pm	Thu Nov 29 12:25:16 2007
@@ -27,6 +27,7 @@
 use base qw/Class::Accessor::Fast/;
 use Module::Refresh ();
 use Jifty::View::Declare::Handler ();
+use Class::Trigger;
 
 BEGIN {
     # Creating a new CGI object breaks FastCGI in all sorts of painful
@@ -191,35 +192,44 @@
         @_
     );
 
-    if ( Jifty->config->framework('DevelMode') ) {
-        Module::Refresh->refresh;
-        Jifty::I18N->refresh;
-    }
+    $self->call_trigger('before_request', $args{cgi});
 
-    Jifty::I18N->get_language_handle;
+    # this is scoped deeper because we want to make sure everything is cleaned
+    # up for the LeakDetector plugin. I tried putting the triggers in the
+    # method (Jifty::Server::handle_request) that calls this, but Jifty::Server
+    # isn't being loaded in time
+    {
+        # Build a new stash for the life of this request
+        $self->stash( {} );
+        local $Jifty::WEB = Jifty::Web->new();
+
+        if ( Jifty->config->framework('DevelMode') ) {
+            Module::Refresh->refresh;
+            Jifty::I18N->refresh;
+        }
 
-    $self->cgi( $args{cgi} );
-    $self->apache( HTML::Mason::FakeApache->new( cgi => $self->cgi ) );
+        $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;
-    for ( Jifty->plugins ) {
-        $_->new_request;
-    }
-    Jifty->log->debug( "Received request for " . Jifty->web->request->path );
-    Jifty->web->setup_session;
+        Jifty->web->request( Jifty::Request->new()->fill( $self->cgi ) );
+        Jifty->web->response( Jifty::Response->new );
 
-    # Return from the continuation if need be
-    Jifty->web->request->return_from_continuation;
-    Jifty->web->session->set_cookie;
-    $self->dispatcher->handle_request();
-    $self->cleanup_request();
+        Jifty->api->reset;
+        for ( Jifty->plugins ) {
+            $_->new_request;
+        }
+        Jifty->log->info( $self->apache->method . " request for " . Jifty->web->request->path  );
+        Jifty->web->setup_session;
+
+        Jifty::I18N->get_language_handle;
+
+        # Return from the continuation if need be
+        Jifty->web->request->return_from_continuation;
+        $self->dispatcher->handle_request();
+        $self->cleanup_request();
+    }
 
+    $self->call_trigger('after_request', $args{cgi});
 }
 
 =head2 cleanup_request

Modified: jifty/branches/js-refactor/lib/Jifty/I18N.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/I18N.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/I18N.pm	Thu Nov 29 12:25:16 2007
@@ -26,7 +26,7 @@
 
 =head2 C<_>
 
-This module provides a method named C<_>, which allows you to quickly and easily include localized strings in your application. The first argument is the string to translated. If that string contains placeholders, the remaining arguments are used to replace the placeholders. The placeholders in the form of "%1" where the number is the number of the argument used to replace it:
+This module provides a method named C<_>, which allows you to quickly and easily include localized strings in your application. The first argument is the string to be translated. If that string contains placeholders, the remaining arguments are used to replace the placeholders. The placeholders in the form of "%1" where the number is the number of the argument used to replace it:
 
   _('Welcome %1 to the %2', 'Bob', 'World');
 
@@ -71,6 +71,25 @@
     my $lang = Jifty->config->framework('L10N')->{'Lang'};
     $lang = [defined $lang ? $lang : ()] unless ref($lang) eq 'ARRAY';
 
+    # Allow hard-coded allowed-languages in the config file
+    my $allowed_lang = Jifty->config->framework('L10N')->{'AllowedLang'};
+    $allowed_lang = [defined $allowed_lang ? $allowed_lang : ()] unless ref($allowed_lang) eq 'ARRAY';
+
+    if (@$allowed_lang) {
+        my $allowed_regex = join '|', map {
+            my $it = $_;
+            $it =~ tr<-A-Z><_a-z>; # lc, and turn - to _
+            $it =~ tr<_a-z0-9><>cd;  # remove all but a-z0-9_
+            $it;
+        } @$allowed_lang;
+
+        foreach my $lang ($self->available_languages) {
+            # "AllowedLang: zh" should let both zh_tw and zh_cn survive,
+            # so we just check ^ but not $.
+            $lang =~ /^$allowed_regex/ or delete $Jifty::I18N::{$lang.'::'};
+        }
+    }
+
     my $lh = $class->get_handle(@$lang);
 
     $DynamicLH = \$lh unless @$lang; 
@@ -106,6 +125,16 @@
     return $self;
 }
 
+=head2 available_languages
+
+Return an array of available languages
+
+=cut
+
+sub available_languages {
+    return map { /^(\w+)::/ ? $1 : () } sort keys %Jifty::I18N::;
+}
+
 =head2 _get_file_patterns
 
 Get list of patterns for all PO files in the project.
@@ -133,13 +162,29 @@
 
 =head2 get_language_handle
 
-Get the lanauge language for this request.
+Get the language handle for this request.
 
 =cut
 
 sub get_language_handle {
+    # XXX: subrequest should not need to get_handle again.
     my $self = shift;
-    $$DynamicLH = $self->get_handle() if $DynamicLH;
+    my $lang = Jifty->web->session->get('jifty_lang');
+    $$DynamicLH = $self->get_handle($lang ? $lang : ()) if $DynamicLH;
+}
+
+=head2 get_current_language
+
+Get the current language for this request, formatted as a Locale::Maketext
+subclass string (i.e., C<zh_tw> instead of C<zh-TW>).
+
+=cut
+
+sub get_current_language {
+    return unless $DynamicLH;
+
+    my ($lang) = ref($$DynamicLH) =~ m/::(\w+)$/;
+    return $lang;
 }
 
 =head2 refresh
@@ -183,9 +228,15 @@
     my $class = shift;
     my $string = shift;
     my $content_type = shift;
+    my $charset;
 
-    $content_type = Email::MIME::ContentType::parse_content_type($content_type) if $content_type;
-    my $charset = $content_type->{attributes}->{charset} if $content_type;
+    # Don't bother parsing the Content-Type header unless it mentions "charset".
+    # This is to avoid the "Unquoted / not allowed in Content-Type" warnings when
+    # the Base64-encoded MIME boundary string contains "/".
+    if ($content_type and $content_type =~ /charset/i) {
+        $content_type = Email::MIME::ContentType::parse_content_type($content_type);
+        $charset = $content_type->{attributes}->{charset};
+    }
 
     # XXX TODO Is this the right thing? Maybe we should just return
     # the string as-is.

Modified: jifty/branches/js-refactor/lib/Jifty/JSON.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/JSON.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/JSON.pm	Thu Nov 29 12:25:16 2007
@@ -7,6 +7,14 @@
 
 Jifty::JSON -- Wrapper around L<JSON>
 
+=head1 SYNOPSIS
+
+  use Jifty::JSON;
+
+  # Even though you might be using JSON::Syck, use the original JSON names
+  my $obj  = jsonToObj(q! { 'x': 1, 'y': 2, 'z': 3 } !);
+  my $json = objToJson($obj);
+
 =head1 DESCRIPTION
 
 Provides a wrapper around the L<JSON> library.
@@ -26,14 +34,21 @@
 =cut
 
 BEGIN {
+    # Errors that happen here, stay here.
     local $@;
+
+    # We're hacking, so tell the nannies to leave for a minute
     no strict 'refs';
     no warnings 'once';
+
+    # If a good version of JSON::Syck is available use that...
     if (eval { require JSON::Syck; JSON::Syck->VERSION(0.05) }) {
         *jsonToObj = *_jsonToObj_syck;
         *objToJson = *_objToJson_syck;
         $JSON::Syck::ImplicitUnicode = 1;
     }
+
+    # Bummer, fallback to the pure Perl implementation
     else {
         require JSON;
         *jsonToObj = *_jsonToObj_pp;
@@ -74,7 +89,7 @@
     local $JSON::Syck::SingleQuote = $args->{singlequote};
     local $JSON::Syck::ImplicitUnicode = 1;
 	my $json = JSON::Syck::Dump($obj);
-	if (! $args->{singlequte}) {
+	if (! $args->{singlequote}) {
 		$json =~ s/\n\n\n/\\n/gs;	# fix syck bug
 		$json =~ s/\n/\\n/gs;		# just to be safe
 		$json =~ s/\r/\\r/gs;
@@ -113,4 +128,11 @@
     return JSON::objToJson($obj, $args);
 }
 
+=head1 LICENSE
+
+Jifty is Copyright 2005-2006 Best Practical Solutions, LLC.
+Jifty is distributed under the same terms as Perl itself.
+
+=cut
+
 1;

Added: jifty/branches/js-refactor/lib/Jifty/Manual.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/lib/Jifty/Manual.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,89 @@
+=head1 NAME
+
+Jifty::Manual - Jifty documentation
+
+=head1 DESCRIPTION
+
+This manual is divided into many separate pages, each covering a specific topic
+thoroughly.
+
+=over 4
+
+=item L<Jifty::Manual::AccessControl>
+
+Implementing access controls in your Jifty app
+
+=item L<Jifty::Manual::Actions>
+
+All about actions and how to use them
+
+=item L<Jifty::Manual::Continuations>
+
+Continuations made easy. Easier..
+
+=item L<Jifty::Manual::Cookbook>
+
+Chunk-sized pearls of wisdom for accomplishing common tasks
+
+=item L<Jifty::Manual::Deploying>
+
+How to go from C<jifty server> to a robust web-server and database environment
+
+=item L<Jifty::Manual::FAQ>
+
+Frequently asked questions, for some value of frequent
+
+=item L<Jifty::Manual::Glossary>
+
+Common terms that you might want to learn
+
+=item L<Jifty::Manual::Models>
+
+All about models -- the creepy crawlies that live in your database
+
+=item L<Jifty::Manual::ObjectModel>
+
+How all the bits of Jifty hang together
+
+=item L<Jifty::Manual::PageRegions>
+
+You too can have sexy AJAX!
+
+=item L<Jifty::Manual::RequestHandling>
+
+The life cycle of a page load
+
+=item L<Jifty::Manual::Style>
+
+Style guide for the Jifty project itself
+
+=item L<Jifty::Manual::Tutorial>
+
+Your first stop on this tour
+
+=item L<Jifty::Manual::TutorialRest>
+
+Using the REST API in your application
+
+=item L<Jifty::Manual::Tutorial_de>
+
+The tutorial in German
+
+=item L<Jifty::Manual::Tutorial_ja>
+
+The tutorial in Japanese
+
+=item L<Jifty::Manual::Upgrading>
+
+How to change your application's database
+
+=item L<Jifty::Manual::UsingCSSandJS>
+
+How to use custom CSS and JavaScript in your application
+
+=back
+
+=cut
+
+1;
+

Modified: jifty/branches/js-refactor/lib/Jifty/Manual/Glossary.pod
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Manual/Glossary.pod	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Manual/Glossary.pod	Thu Nov 29 12:25:16 2007
@@ -141,6 +141,10 @@
 success or failure, and further detail about why or how.  It can also contain
 arbitrary "content".  See L<Jifty::Result>.
 
+=item return
+
+After performing a L</tangent> a user may perform a L</return> to invoke the L</continuation> created by the original tangent.
+
 =item schema
 
 The B<schema> for a L</model> represents the structure of the data stored in an individual item in that model's L</collection>. See L<Jifty::DBI::Schema>.
@@ -176,6 +180,10 @@
 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 tangent
+
+A tangent is a link or redirect that causes Jifty to save the current state into a L</continuation> for a later L</return>. This operation is handy for situations when you want to jump to another page or form, but return to this page when the user is done with the "tangent" page. Because of the use of continuations, this can be especially handy because a user could go on multiple tangents and eventually return to the start.
+
 =item upgrade
 
 Generally, your Jifty application will change over time. Most of these changes will be simple changes to behavior or appearance. When changes are made to the L</schema> structure of your database, you may need to perform an B<upgrade>. An B<upgrade> is the process by which you rename columns, initialize data, or otherwise update information that needs to be updated when a change to the database needs to be made. See L<Jifty::Upgrade>, L<Jifty::Manual::Upgrading>, and L<Jifty::Script::Schema>.

Modified: jifty/branches/js-refactor/lib/Jifty/Model/Session.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Model/Session.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Model/Session.pm	Thu Nov 29 12:25:16 2007
@@ -18,15 +18,15 @@
 use Jifty::DBI::Schema;
 use Jifty::Record schema {
 
-column session_id => type is 'varchar(32)';
-column data_key => type is 'text';
+column session_id => type is 'varchar(32)', is case_sensitive, is indexed;
+column data_key => type is 'text', is case_sensitive;
 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)';
+column key_type => type is 'varchar(32)', is case_sensitive;
 };
 
 use base qw( Jifty::Record );

Modified: jifty/branches/js-refactor/lib/Jifty/Module/Pluggable.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Module/Pluggable.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Module/Pluggable.pm	Thu Nov 29 12:25:16 2007
@@ -52,18 +52,9 @@
     # On success, it expects you to return undef.
 
     return if $module =~/\.#/;
-
-    local $UNIVERSAL::require::ERROR;
-
-    no warnings; # This is lexical and turns off exactly one warning below -- "Can't locate package in @ISA".
-                 # (for some reason, "no warnings 'syntax'" does not work as advertised here.)
-                 # Note that it does _not_ turn off warnings triggered in the $module itself.
-    if ((not $module->require()) &&  ( $UNIVERSAL::require::ERROR !~ /^Can't locate/)) {
-        die $UNIVERSAL::require::ERROR;
-    }
-         # We'd prefer to use Jifty::Util->require() here, but it spews crazy warnings
-
-    return $UNIVERSAL::require::ERROR;
+    eval { Jifty::Util->require($module); };
+    my $err = $@;
+    return $err;
 }
 
 1;

Modified: jifty/branches/js-refactor/lib/Jifty/Notification.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Notification.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Notification.pm	Thu Nov 29 12:25:16 2007
@@ -19,7 +19,7 @@
 =head1 USAGE
 
 It is recommended that you subclass L<Jifty::Notification> and
-override C<body>, C<html-body>, C<subject>, C<recipients>, and C<from>
+override C<body>, C<html_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.
@@ -94,7 +94,7 @@
     my $self       = shift;
     my @recipients = $self->recipients;
     my $to         = join( ', ',
-        map { ( $_->can('email') ? $_->email : $_ ) } grep {$_} @recipients );
+        map { ( ref $_ && $_->can('email') ? $_->email : $_ ) } grep {$_} @recipients );
     $self->log->debug("Sending a ".ref($self)." to $to"); 
     return unless ($to);
     my $message = "";

Modified: jifty/branches/js-refactor/lib/Jifty/Param/Schema.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Param/Schema.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Param/Schema.pm	Thu Nov 29 12:25:16 2007
@@ -172,10 +172,30 @@
 =cut
 
 sub merge_params {
+    # We pull this deref and re-ref trick to un-bless any
+    # Jifty::Params which might exist; Hash::Merge pre-0.10 merged
+    # objects and hahrefs with no complaint, but 0.10 doesn't.  This
+    # is a horrible, horrible hack, and will hopeflly be able to be
+    # backed out if and when Hash::Merge reverts to the old behavior.
+    my @types;
+    for my $m (@_) {
+        my @t;
+        for (keys %{$m}) {
+            push @t, ref $m->{$_};
+            bless $m->{$_}, "HASH";
+        }
+        push @types, \@t;
+    }
     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 );
+    for my $m (@_) {
+        my @t = @{shift @types};
+        for (keys %{$m}) {
+            bless $m->{$_}, shift @t;
+        }
+    }
     return $rv;
 }
 

Modified: jifty/branches/js-refactor/lib/Jifty/Plugin.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Plugin.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin.pm	Thu Nov 29 12:25:16 2007
@@ -3,7 +3,8 @@
 
 package Jifty::Plugin;
 use File::ShareDir 'module_dir';
-
+use base 'Class::Accessor::Fast';
+__PACKAGE__->mk_accessors('_pre_init');
 
 =head1 NAME
 
@@ -51,14 +52,12 @@
 
 sub new {
     my $class = shift;
-    
+    my $self = $class->SUPER::new( { @_ });
+
     # Get a classloader set up
-    Jifty::ClassLoader->new(base => $class)->require;
     Jifty::Util->require($class->dispatcher);
 
     # XXX TODO: Add .po path
-
-    my $self = bless {} => $class;
     $self->init(@_);
     return $self;
 }
@@ -92,6 +91,7 @@
 sub _calculate_share {
     my $self = shift;
     my $class = ref($self);
+
     unless ( $self->{share} ) {
         local $@
             ; # We're just avoiding File::ShareDir's failure behaviour of dying

Modified: jifty/branches/js-refactor/lib/Jifty/Plugin/ActorMetadata/Mixin/Model/ActorMetadata.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Plugin/ActorMetadata/Mixin/Model/ActorMetadata.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/ActorMetadata/Mixin/Model/ActorMetadata.pm	Thu Nov 29 12:25:16 2007
@@ -5,7 +5,7 @@
 use Jifty::DBI::Schema;
 use base 'Jifty::DBI::Record::Plugin';
 
-our @EXPORT = qw(current_user_can);
+our @EXPORT = qw(current_user_can current_user_is_owner);
 
 =head1 NAME
 
@@ -119,10 +119,25 @@
     }
 
     if ($action eq 'update' or $action eq 'delete') {
-        return undef unless ($self->current_user and $self->current_user->id eq $self->created_by->id);
+        return undef unless $self->current_user_is_owner;
     }
 
     return 1;
 }
 
+=head2 current_user_is_owner
+
+=cut
+
+sub current_user_is_owner {
+    my $self = shift;
+
+    my $created_by = $self->__value('created_by');
+    return unless $self->current_user && $created_by;
+
+    return unless $self->current_user->id;
+
+    return $self->current_user->id == $created_by;
+}
+
 1;

Added: jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/CAS.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/CAS.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,82 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Authentication::CAS;
+use base qw/Jifty::Plugin/;
+use Authen::CAS::Client;
+
+=head1 NAME
+
+Jifty::Plugin::Authentication::CAS
+
+=head1 DESCRIPTION
+
+This may be combined with the L<Jifty::Plugin::User> plugin to provide user authentication using JA-SIG CAS protocol to your application.
+
+https is managed with Crypt::SSLeay
+
+=head1 CONFIG
+
+ in etc/config.yml
+
+  Plugins: 
+    - Authentication::CAS: 
+       CASUrl: https://auth.univ-metz.fr/cas
+       CASDomain: univ-metz.fr                  # optional: create email if login at domain is valid
+
+
+=head1 METHODS
+
+=head2 prereq_plugins
+
+This plugin depends on the L<User|Jifty::Plugin::User> plugin.
+
+=cut
+
+
+sub prereq_plugins {
+    return ('User');
+}
+
+
+my ($CAS,$domain);
+
+=head2 init
+
+load config 
+
+=cut
+
+sub init {
+    my $self = shift;
+    my %args = @_;
+
+    $CAS = Authen::CAS::Client->new ( $args{'CASUrl'} );
+    $domain = $args{'CASDomain'} || "" ;
+};
+
+
+sub CAS {
+    return $CAS;
+};
+
+sub domain {
+    return $domain;
+};
+
+=head1 TODO
+
+add a ldap config to get more attributes
+
+=head1 SEE ALSO
+
+L<Jifty::Manual::AccessControl>, L<Jifty::Plugin::User>, L<Authen::CAS::Client>
+
+=head1 LICENSE
+
+Jifty is Copyright 2005-2007 Best Practical Solutions, LLC.
+Jifty is distributed under the same terms as Perl itself.
+
+=cut
+
+1;

Added: jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/CAS/Action/CASLogin.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/CAS/Action/CASLogin.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,145 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Jifty::Plugin::Authentication::CAS::Action::CASLogin
+
+=cut
+
+package Jifty::Plugin::Authentication::CAS::Action::CASLogin;
+use base qw/Jifty::Action/;
+
+
+=head2 arguments
+
+Return the ticket form field
+
+=cut
+
+sub arguments {
+    return (
+        {
+            ticket => {
+                label          => 'cas ticket',
+                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;
+
+    if ( $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 ($plugin)  = Jifty->find_plugin('Jifty::Plugin::Authentication::CAS');
+
+#    my $service_url = ($ENV{SERVER_PORT} == 443)?'https://':'http://'.
+#    	$ENV{HTTP_HOST}.'/caslogin';
+    
+    my $service_url = Jifty->web->url.'/caslogin';
+
+    if (! $ticket) {
+        my $login_url = $plugin->CAS->login_url( $service_url );
+        Jifty->web->_redirect($login_url);
+        return 1;
+      }
+
+    my $r = $plugin->CAS->service_validate($service_url,$ticket);
+    my $username;
+    if ($r->is_success) {
+        $username = $r->user();
+    }
+    else {
+      Jifty->log->info("CAS error: $ticket $username");
+      return;
+    };
+     
+    # Load up the user
+    my $current_user = Jifty->app_class('CurrentUser');
+    my $user = $current_user->new( cas_id => $username );
+
+    # Autocreate the user if necessary
+    if ( not $user->id ) {
+        my $action = Jifty->web->new_action(
+            class           => 'CreateUser',
+            current_user    => $current_user->superuser,
+            arguments       => {
+                cas_id => $username
+            }
+        );
+        $action->run;
+
+        if ( not $action->result->success ) {
+            # Should this be less "friendly"?
+            $self->result->error(_("Sorry, something weird happened (we couldn't create a user for you).  Try again later."));
+            return;
+        }
+
+        $user = $current_user->new( cas_id => $username );
+    }
+
+    my $u = $user->user_object;
+
+    my ($name,$email);
+    #TODO add a ldap conf to find name and email
+    $email = $username.'@'.$plugin->domain() if ($plugin->domain());
+    $u->__set( column => 'name', value => $username ) if (!$u->name);
+
+    # Update, just in case
+    $u->__set( column => 'name', value => $name ) if ($name);
+    $u->__set( column => 'email', value => $email ) if ($email);
+ 
+    # Actually do the signin thing.
+    Jifty->web->current_user( $user );
+    Jifty->web->session->set_cookie;
+
+    # Success!
+    $self->report_success;
+
+    return 1;
+};
+
+=head2 report_success
+
+=cut
+
+sub report_success {
+    my $self = shift;
+    $self->result->message(_("Hi %1!", Jifty->web->current_user->user_object->name ));
+};
+
+
+1;

Added: jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/CAS/Action/CASLogout.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/CAS/Action/CASLogout.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,35 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Jifty::Plugin::Authentication::CAS::Action::CASLogout
+
+=cut
+
+package Jifty::Plugin::Authentication::CAS::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/js-refactor/lib/Jifty/Plugin/Authentication/CAS/Dispatcher.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/CAS/Dispatcher.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,56 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Authentication::CAS::Dispatcher;
+use Jifty::Dispatcher -base;
+
+# Put any plugin-specific dispatcher rules here.
+
+before '/caslogin' => run {
+ if (get('ticket')) {
+    # verify 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 => "/" ) );
+
+};
+
+
+on '/caslogin' => run {
+
+   Jifty->web->new_action(
+       moniker => 'casloginbox',
+       class   => 'CASLogin',
+       arguments => { ticket => get('ticket') }
+       )->run;
+
+    if(Jifty->web->request->continuation) {
+        Jifty->web->request->continuation->call;
+     } else {
+           redirect '/';
+     }
+};
+
+# Log out
+before '/caslogout' => run {
+    Jifty->web->request->add_action(
+        class   => 'CASLogout',
+        moniker => 'caslogout',
+    );
+};
+
+on '/caslogout' => run {
+   redirect '/';
+};
+
+1;

Added: jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/CAS/Mixin/Model/User.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/CAS/Mixin/Model/User.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,35 @@
+package Jifty::Plugin::Authentication::CAS::Mixin::Model::User;
+use strict;
+use warnings;
+use Jifty::DBI::Schema;
+use base 'Jifty::DBI::Record::Plugin';
+
+=head1 NAME
+
+Jifty::Plugin::Authentication::CAS::Mixin::Model::User;
+
+=head1 DESCRIPTION
+
+L<Jifty::Plugin::Authentication::CAS> mixin for the User model.  Provides an 'cas_id' column.
+
+=cut
+
+our @EXPORT = qw(has_alternative_auth);
+
+use Jifty::Plugin::Authentication::CAS::Record schema {
+
+column cas_id =>
+  type is 'text',
+  label is 'CAS ID',
+  is distinct,
+  is immutable;
+
+};
+
+=head2 has_alternative_auth
+
+=cut
+
+sub has_alternative_auth { 1 }
+
+1;

Added: jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/Ldap.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/Ldap.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,117 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Authentication::Ldap;
+use base qw/Jifty::Plugin/;
+
+=head1 NAME
+
+Jifty::Plugin::Authentication::Ldap - ldap authentication plugin
+
+=head1 DESCRIPTION
+
+B<CAUTION:> This plugin is experimental.
+
+This may be combined with the L<Jifty::Plugin::User> plugin to provide user accounts and ldap password authentication to your application.
+
+in etc/config.yml
+
+  Plugins: 
+    - Login: {}
+    - Authentication::Ldap: 
+       LDAPhost: ldap.univ.fr           # ldap server
+       LDAPbase: ou=people,dc=.....     # base ldap
+       LDAPName: displayname            # name to be displayed (cn givenname)
+       LDAPMail: mailLocalAddress       # email used optionnal
+       LDAPuid: uid                     # optional
+
+
+
+=head2 METHODS
+
+=head2 prereq_plugins
+
+This plugin depends on the L<User|Jifty::Plugin::User> plugin.
+
+=cut
+
+
+sub prereq_plugins {
+    return ('User');
+}
+
+use Net::LDAP;
+
+
+my ($LDAP, %params);
+
+=head2 init
+
+read etc/config.yml
+
+=cut
+
+sub init {
+    my $self = shift;
+    my %args = @_;
+
+    $params{'Hostname'} = $args{LDAPhost};
+    $params{'base'} = $args{LDAPbase};
+    $params{'uid'} = $args{LDAPuid} || "uid";
+    $params{'email'} = $args{LDAPMail} || "";
+    $params{'name'} = $args{LDAPName} || "cn";
+    $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'};
+}
+
+sub email {
+    return $params{'email'};
+};
+
+sub name {
+    return $params{'name'};
+};
+
+
+
+sub get_infos {
+    my ($self,$user) = @_;
+
+    my $result = $self->LDAP()->search (
+            base   => $self->base(),
+            filter => '(uid= '.$user.')',
+            attrs  =>  [$self->name(),$self->email()],
+            sizelimit => 1
+             );
+    my ($ret) = $result->entries;
+    my $name = $ret->get_value($self->name());
+    my $email = $ret->get_value($self->email());
+
+    return ({ name => $name, email => $email });
+};
+
+
+
+=head1 SEE ALSO
+
+L<Jifty::Manual::AccessControl>, L<Jifty::Plugin::User>, L<Net::LDAP>
+
+=head1 LICENSE
+
+Jifty is Copyright 2005-2007 Best Practical Solutions, LLC.
+Jifty is distributed under the same terms as Perl itself.
+
+=cut
+
+1;

Added: jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/Ldap/Action/LDAPLogin.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/Ldap/Action/LDAPLogin.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,135 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Jifty::Plugin::Authentication::Ldap::Action::LDAPLogin;
+
+=cut
+
+package Jifty::Plugin::Authentication::Ldap::Action::LDAPLogin;
+use base qw/Jifty::Action/;
+
+
+=head1 ARGUMENTS
+
+Return the login form field
+
+=cut
+
+use Jifty::Param::Schema;
+use Jifty::Action schema {
+    param ldap_id => 
+        label is _('Login'),
+        is mandatory;
+#        is ajax_validates;
+    param password =>
+        type is 'password',
+        label is _('Password'),
+        is mandatory;
+};
+
+=head2 validate_name NAME
+
+For ajax_validates.
+Makes sure that the name submitted is a legal login.
+
+
+=cut
+
+sub validate_ldap_id {
+    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('ldap_id');
+    my ($plugin)  = Jifty->find_plugin('Jifty::Plugin::Authentication::Ldap');
+    my $dn = $plugin->uid().'='.$username.','.
+        $plugin->base();
+
+
+    # Bind on ldap
+    my $msg = $plugin->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;
+    }
+
+    # Load up the user
+    my $current_user = Jifty->app_class('CurrentUser');
+    my $user = $current_user->new( ldap_id => $username );
+
+    # Autocreate the user if necessary
+    if ( not $user->id ) {
+        my $action = Jifty->web->new_action(
+            class           => 'CreateUser',
+            current_user    => $current_user->superuser,
+            arguments       => {
+                ldap_id => $username
+            }
+        );
+        $action->run;
+
+        if ( not $action->result->success ) {
+            # Should this be less "friendly"?
+            $self->result->error(_("Sorry, something weird happened (we couldn't create a user for you).  Try again later."));
+            return;
+        }
+
+        $user = $current_user->new( ldap_id => $username );
+    }
+
+    my $infos =  $plugin->get_infos($username);
+    my $name = $infos->{name};
+    my $email = $infos->{email};
+    my $u = $user->user_object;
+
+    # Update, just in case
+    $u->__set( column => 'name', value => $name );
+    $u->__set( column => 'email', value => $email );
+
+
+    # Login!
+    Jifty->web->current_user( $user );
+    Jifty->web->session->set_cookie;
+
+    # Success!
+    $self->report_success;
+
+    return 1;
+};
+
+=head2 report_success
+
+=cut
+
+sub report_success {
+    my $self = shift;
+    $self->result->message(_("Hi %1!", Jifty->web->current_user->user_object->name ));
+};
+
+
+1;

Added: jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/Ldap/Action/LDAPLogout.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/Ldap/Action/LDAPLogout.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,35 @@
+use warnings;
+use strict;
+
+=head1 NAME
+
+Jifty::Plugin::Authentication::Ldap::Action::LDAPLogout;
+
+=cut
+
+package Jifty::Plugin::Authentication::Ldap::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/js-refactor/lib/Jifty/Plugin/Authentication/Ldap/Dispatcher.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/Ldap/Dispatcher.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,32 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Authentication::Ldap::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/js-refactor/lib/Jifty/Plugin/Authentication/Ldap/Mixin/Model/User.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/Ldap/Mixin/Model/User.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,36 @@
+package Jifty::Plugin::Authentication::Ldap::Mixin::Model::User;
+use strict;
+use warnings;
+use Jifty::DBI::Schema;
+use base 'Jifty::DBI::Record::Plugin';
+use URI;
+
+=head1 NAME
+
+Jifty::Plugin::Authentication::Ldap::Mixin::Model::User;
+
+=head1 DESCRIPTION
+
+L<Jifty::Plugin::Authentication::Ldap> mixin for the User model.  Provides an 'ldap_id' column.
+
+=cut
+
+our @EXPORT = qw(has_alternative_auth);
+
+use Jifty::Plugin::Authentication::Ldap::Record schema {
+
+column ldap_id =>
+  type is 'text',
+  label is 'Ldap ID',
+  is distinct,
+  is immutable;
+
+};
+
+=head2 has_alternative_auth
+
+=cut
+
+sub has_alternative_auth { 1 }
+
+1;

Added: jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/Ldap/View.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/Ldap/View.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,50 @@
+use utf8;
+use warnings;
+use strict;
+
+=head1 NAME Jifty::Plugin::Authentication::Ldap::View
+
+This provides the templates for the pages and forms used by the ldap authentication plugin.
+
+=cut
+
+package Jifty::Plugin::Authentication::Ldap::View;
+use Jifty::View::Declare -base;
+
+{ no warnings 'redefine';
+sub page (&;$) {
+    no strict 'refs';
+    BEGIN {Jifty::Util->require(Jifty->app_class('View'))};
+    Jifty->app_class('View')->can('page')->(@_);
+}
+}
+
+template ldaplogin => page { title => _('Login!') } content {
+    show('/ldaplogin_widget');
+};
+
+
+template ldaplogin_widget => sub {
+#    title is _("Login with your Ldap account") 
+
+    my ( $action, $next ) = get( 'action', 'next' );
+    $action ||= new_action( class => 'LDAPLogin' );
+    $next ||= Jifty::Continuation->new(
+        request => Jifty::Request->new( path => "/" ) );
+    unless ( Jifty->web->current_user->id ) {
+        h3  { _('Login with your ldap account') };
+        div {
+            attr { id => 'jifty-login' };
+            Jifty->web->form->start( call => $next );
+            render_param( $action, 'ldap_id', focus => 1 );
+            render_param( $action, 'password' );
+            form_return( label => _(q{Login}), submit => $action );
+            Jifty->web->form->end();
+        };
+    } else {
+        outs( _("You're already logged in.") );
+    }
+};
+
+
+1;

Modified: jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/Password/Action/SendPasswordReminder.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/Password/Action/SendPasswordReminder.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/Password/Action/SendPasswordReminder.pm	Thu Nov 29 12:25:16 2007
@@ -97,3 +97,4 @@
 }
 
 1;
+

Modified: jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/Password/Mixin/Model/User.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/Password/Mixin/Model/User.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/Authentication/Password/Mixin/Model/User.pm	Thu Nov 29 12:25:16 2007
@@ -153,8 +153,6 @@
     $self->regenerate_auth_token;
     if ( $self->id and $self->email and not $self->email_confirmed ) {
         Jifty->app_class('Notification','ConfirmEmail')->new( to => $self )->send;
-    } else {
-        warn  $self->id . " " .$self->email;
     }
 }
 

Added: jifty/branches/js-refactor/lib/Jifty/Plugin/AutoReference.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/AutoReference.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,140 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::AutoReference;
+use base qw/ Jifty::Plugin /;
+
+use Jifty::Plugin::AutoReference::Widget;
+
+=head1 NAME
+
+Jifty::Plugin::AutoReference - a plugin to provide a special reference completer
+
+=head1 SYNOPSIS
+
+Add this to your F<config.yml>:
+
+  Plugins:
+   - AutoReference: {}
+
+and then this to your models:
+
+  use MyApp::Record schema {
+      column employer =>
+          references MyApp::Model::Company,
+          label is 'Employer',
+          is AutoReference,
+          ;
+  };
+
+=head1 DESCRIPTION
+
+Provides a special autocompletion widget for reference columns. See L<Jifty::Plugin::AutoReference::Widget>.
+
+=head1 METHODS
+
+=head2 init
+
+Adds the F<autoreference.js> file to the JavaScript files to send to the browser.
+
+=cut
+
+sub init {
+    Jifty->web->add_javascript(qw/ autoreference.js /);
+}
+
+sub _auto_reference_autocompleter {
+    my ($column, $from) = @_;
+
+    my $reference = $column->refers_to;
+    my $field     = $column->by || 'id';
+
+    my $brief     = $reference->can('_brief_description') ? 
+                        $reference->_brief_description : 'name';
+
+    return sub {
+        my $self  = shift;
+        my $value = shift;
+
+        my $collection = Jifty::Collection->new(
+            record_class => $reference,
+            current_user => $self->current_user,
+        );
+
+        $collection->unlimit;
+
+        if (length $value) {
+            $collection->limit(
+                column   => $brief,
+                value    => $value,
+                operator => 'MATCHES',
+                entry_aggregator => 'AND',
+            );
+        }
+
+        $collection->limit(
+            column   => $brief,
+            value    => 'NULL',
+            operator => 'IS NOT',
+            entry_aggregator => 'AND',
+        );
+
+        $collection->limit(
+            column   => $brief,
+            value    => '',
+            operator => '!=',
+            entry_aggregator => 'AND',
+        );
+
+        $collection->columns('id', $brief);
+        $collection->order_by(column => $brief);
+
+        my @choices;
+        if (!length $value && !$column->mandatory) {
+            $collection->rows_per_page(9);
+            push @choices, { label => _('- none -'), value => '' };
+        }
+        
+        else {
+            $collection->rows_per_page(10);
+        }
+
+        while (my $record = $collection->next) {
+            push @choices, { 
+                label => $record->brief_description.' [id:'.$record->id.']', 
+                value => $record->id,
+            };
+        }
+
+        return @choices;
+    };
+}
+
+sub _auto_reference {
+    my ($column, $from) = @_;
+
+    my $name = $column->name;
+
+    no strict 'refs';
+    *{$from.'::autocomplete_'.$name} 
+        = _auto_reference_autocompleter($column, $from);
+}
+
+use Jifty::DBI::Schema;
+Jifty::DBI::Schema->register_types(
+    AutoReference =>
+        sub { _init_handler is \&_auto_reference, render_as 'Jifty::Plugin::AutoReference::Widget' } );
+
+=head1 AUTHORS
+
+Andrew Sterling Hanenkamp C<< <hanenkamp at cpan.org> >>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007 Boomer Consulting, Inc. All Rights Reserved.
+
+This program is free software and may be modified and distributed under the same terms as Perl itself.
+
+=cut
+
+1;

Added: jifty/branches/js-refactor/lib/Jifty/Plugin/AutoReference/Widget.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/AutoReference/Widget.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,157 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::AutoReference::Widget;
+use base qw/ Jifty::Web::Form::Field /;
+
+=head1 NAME
+
+Jifty::Plugin::AutoReference::Widget - an autocomplete widget for references
+
+=head1 SYNOPSIS
+
+  use MyApp::Record schema {
+      column employer =>
+          references MyApp::Model::Company,
+          label is 'Employer',
+          is AutoReference,
+          ;
+  };
+
+=head1 DESCRIPTION
+
+Provides a special autocomplete widget that can be useful when there are too many items for a Select box to be practical.
+
+B<WARNING:> As of this writing, it should be noted that this widget does not degrade gracefully. If you need a widget that operates properly even when JavaScript is unavailable, this widget won't do that job at this time.
+
+=head1 METHODS
+
+=head2 render
+
+Overrides the field renderer to force autocompletion to be turned on.
+
+=cut
+
+sub render {
+    my $self = shift;
+
+    $self->autocompleter(1);
+    return $self->SUPER::render(@_);
+}
+
+sub _record_description {
+    my $self = shift;
+
+    my $value = $self->default_value;
+
+    my $name      = $self->name;
+    my $column    = $self->action->record->column($name);
+
+    # XXX Does this need more advanced handling?
+    return unless $column; # happens in SearchXXX
+
+    my $reference = $column->refers_to;
+    my $brief     = $reference->can('_brief_description') ?
+                        $reference->_brief_description : 'name';
+
+    my $record = $self->action->record->$name;
+    if ($record and $record->id) {
+        return $record->$brief . ' [id:'. $record->id . ']';
+    }
+    else {
+        return;
+    }
+}
+
+sub _switch_current_value_temporarily(&$) {
+    my $code = shift;
+    my $self = shift;
+    
+    my $description = $self->_record_description;
+
+    if ($self->sticky_value and $self->sticky) {
+        my $old_value = $self->sticky_value;
+        $self->sticky_value($description);
+        $code->();
+        $self->sticky_value($old_value);
+    }
+
+    else {
+        my $old_value = $self->default_value;
+        $self->default_value($description);
+        $code->();
+        $self->default_value($old_value);
+    }
+}
+
+=head2 render_widget
+
+Overrides the widget renderer to draw both a hidden field that stores the actual referenced ID and a text field that is autocompleted using the records brief description.
+
+=cut
+
+sub render_widget {
+    my $self = shift;
+
+    # Render the shown autocomplete field first
+    my $input_name = $self->input_name;
+    $self->input_name($input_name.'-display');
+    my $element_id = $self->element_id;
+    $self->_element_id($element_id.'-display');
+    my $class = $self->class;
+    $self->class(join ' ', ($class||''), 'text');
+    _switch_current_value_temporarily {
+        $self->SUPER::render_widget(@_);
+    } $self;
+    $self->input_name($input_name);
+    $self->_element_id($element_id);
+    $self->class($class);
+
+    # Render the hidden value field second
+    $self->type('hidden');
+    $self->SUPER::render_widget(@_);
+    $self->type('text');
+
+    return '';
+}
+
+=head2 render_value
+
+Overrides the value renderer to show the brief description of the referenced record.
+
+=cut
+
+sub render_value {
+    my $self = shift;
+
+    _switch_current_value_temporarily {
+        $self->SUPER::render_value(@_);
+    } $self;
+
+    return '';
+}
+
+=head2 autocomplete_javascript
+
+Overrides the JavaScript autocompletion widget to use C<Jifty.Plugin.AutoReference> instead of the regular C<Jifty.Autocompleter>.
+
+=cut
+
+sub autocomplete_javascript {
+    my $self = shift;
+    return qq{new Jifty.Plugin.AutoReference('@{[$self->element_id]}-display','@{[$self->element_id]}','@{[$self->element_id]}-autocomplete')};
+}
+
+=head1 AUTHORS
+
+Andrew Sterling Hanenkamp C<< <hanenkamp at cpan.org> >>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007 Boomer Consulting, Inc. All Rights Reserved.
+
+This program is free software and may be modified and distributed under the same terms as Perl itself.
+
+=cut
+
+1;

Modified: jifty/branches/js-refactor/lib/Jifty/Plugin/Chart.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Plugin/Chart.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/Chart.pm	Thu Nov 29 12:25:16 2007
@@ -5,6 +5,9 @@
 use base qw/ Jifty::Plugin Class::Accessor::Fast /;
 
 use Jifty::Plugin::Chart::Web;
+use Scalar::Util qw/ blessed /;
+
+__PACKAGE__->mk_accessors(qw/ renderer renderers plugin_args /);
 
 =head1 NAME
 
@@ -46,35 +49,112 @@
 
 =head1 CONFIGURATION
 
-The plugin takes a single configuration option called C<renderer>. This may be set to a chart renderer class, which is just an implementation of L<Jifty::Plugin::Chart::Renderer>. The default, L<Jifty::Plugin::Chart::Renderer::Chart>, uses L<Chart> to render charts as PNG files which are then included in your pages for you.
-
 Here is an example configuration for F<config.yml>:
 
   Plugins:
     - Chart:
-        renderer: Jifty::Plugin::Chart::Renderer::Chart
+        DefaultRenderer: PlotKit
+        PreloadRenderers:
+         - XMLSWF
+         - SimpleBars
+         - App::Renderer::Custom
 
-=cut
+The available options are:
+
+=over
+
+=item DefaultRenderer
+
+This is the name of the class to use as the default renderer. L<Jifty::Plugin::Chart::Renderer::Chart> is the current default, but that could change in the future. It's recommended that you set this to your preference.
+
+=item PreloadRenderers
 
-__PACKAGE__->mk_accessors(qw/ renderer /);
+This is a list of other render classes to load during initialization. If they are not loaded during initialization some renderers may not work correctly the first time they are run because they are not able to inform Jifty of the CSS or JS files they need before that part of the page is already rendered. If you use the "renderer" option of L<Jifty::Plugin::Chart::Web/chart>, then you should make sure any value you use is set here in the configuration to make sure it works properly.
+
+=back
+
+=head1 METHODS
+
+=head2 init
+
+Adds the L<Jifty::Plugin::Chart::Web/chart> method to L<Jifty::Web>.
+
+=cut
 
 sub init {
     my $self = shift;
     my %args = (
-        renderer => __PACKAGE__.'::Renderer::Chart',
+        DefaultRenderer => 'Chart',
         @_,
     );
 
-    eval "use $args{renderer}";
-    warn $@ if $@;
-    $self->renderer( $args{renderer} );
+    # Save the arguments for use in init_renderer() later
+    $self->plugin_args( \%args );
+
+    # Deprecating the old form
+    if (defined $args{renderer}) {
+        warn 'DEPRECATED: renderer argument to Chart plugin is deprecated.'
+            .' Use DefaultRenderer instead.';
+        $args{DefaultRenderer} = delete $args{renderer};
+    }
+
+    # Create the empty renderers list
+    $self->renderers({});
+
+    # Pre-load any renderers they plan to use
+    if (defined $args{PreloadRenderers}) {
+        $args{PreloadRenderers} = [ $args{PreloadRenderers} ]
+            unless ref $args{PreloadRenderers};
+
+        for my $renderer (@{ $args{PreloadRenderers} }) {
+            $self->init_renderer( $renderer );
+        }
+    }
+
+    # Load the default renderer
+    $self->renderer( $self->init_renderer( $args{DefaultRenderer}, %args ) );
 
     push @Jifty::Web::ISA, 'Jifty::Plugin::Chart::Web';
 }
 
-sub render {
-    my $self = shift;
-    $self->renderer->render(@_);
+=head2 init_renderer
+
+  my $renderer = $chart_plugin->init_renderer($renderer_class)
+
+This is a helper method that is used by the API to initialize the renderer class. This is handled automatically so you probably shouldn't use this.
+
+=cut
+
+sub init_renderer {
+    my ( $self, $renderer_class ) = ( shift, shift );
+
+    # If it's already an object, just return that
+    if ( blessed($renderer_class)
+            and $renderer_class->isa(__PACKAGE__.'::Renderer') ) {
+        return $renderer_class;
+    }
+
+    # Prepend Jifty::Plugin::Chart::Renderer:: if we think we need to
+    if ( $renderer_class !~ /::/ ) {
+        $renderer_class = __PACKAGE__.'::Renderer::'.$renderer_class;
+    }
+
+    # Check to see if we already loaded this one
+    my $renderer = $self->renderers->{ $renderer_class };
+    return $renderer if defined $renderer;
+
+    # Tell perl to load the class
+    $renderer_class->require
+        or warn $@;
+
+    # Initialize the renderer
+    $renderer = $renderer_class->new( %{ $self->plugin_args } );
+
+    # Remember it
+    $self->renderers->{ $renderer_class } = $renderer;
+
+    # Return it!
+    return $renderer;
 }
 
 =head1 SEE ALSO
@@ -83,7 +163,7 @@
 
 =head1 AUTHOR
 
-Andrew Sterling Hanenkamp E<< <andrew.hanenkamp at boomer.com> >>
+Andrew Sterling Hanenkamp C<< <andrew.hanenkamp at boomer.com> >>
 
 =head1 COPYRIGHT AND LICENSE
 

Modified: jifty/branches/js-refactor/lib/Jifty/Plugin/Chart/Dispatcher.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Plugin/Chart/Dispatcher.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/Chart/Dispatcher.pm	Thu Nov 29 12:25:16 2007
@@ -10,27 +10,59 @@
 
 Jifty::Plugin::Chart::Dispatcher - Dispatcher for the chart API plugin
 
+=cut
+
+my %classes = (
+    chart       => 'Chart::$TYPE',
+    gd_graph    => 'GD::Graph::$TYPE',
+    xmlswf      => 'XML::Simple',
+);
+
 =head1 RULES
 
-=head2 chart/*
+=head2 chart/*/*
 
-Grabs the chart configuration stored in the key indicated in C<$1> and unpacks it using L<YAML>. It then passes it to the L<Jifty::Plugin::Chart::View/chart> template.
+Grabs the chart configuration stored in the key indicated in C<$1> and unpacks it using L<YAML>. It then passes it to the correct L<Jifty::Plugin::Chart::View> template.
 
 =cut
 
-on 'chart/*' => run {
+on 'chart/*/*' => run {
+    my $renderer = $1;
+
+    # No renderer?  Act like a 404.
+    last_rule if not defined $classes{$renderer};
+
     # Create a session ID to lookup the chart configuration
-    my $session_id = 'chart_' . $1;
+    my $session_id = 'chart_' . $2;
 
     # Unpack the data and then clear it from the session
     my $args = Jifty::YAML::Load( Jifty->web->session->get( $session_id ) );
-    Jifty->web->session->remove( $session_id );
+
+    # XXX if there are a lot of charts, this could asplode
+    #Jifty->web->session->remove( $session_id );
 
     # No data? Act like a 404
     last_rule unless defined $args;
 
+    # Request might override width/height:
+    $args->{width}  = get 'width'  if get 'width';
+    $args->{height} = get 'height' if get 'height';
+
+    # XXX TODO Is there a better way to guess the pixel heights when using CSS
+    # heights initially?
+
+    # Remove 'px' from width/height and set to 400/300 if not in pixels
+    ($args->{width}  =~ s/px$//) or ($args->{width}  = 400);
+    ($args->{height} =~ s/px$//) or ($args->{height} = 300);
+
+    # No zeroes! Ba Ba Blacksheep.
+    $args->{width}  ||= 400;
+    $args->{height} ||= 300;
+
+    my $class = $classes{$renderer};
+    
     # Use the "type" to determine which class to use
-    my $class = 'Chart::' . $args->{type};
+    $class =~ s/\$TYPE/$args->{type}/g;
 
     # Load that class or die if it does not exist
     $class->require;
@@ -40,7 +72,7 @@
 
     # Send them on to chart the chart
     set 'args' => $args;
-    show 'chart';
+    show "chart/$renderer";
 };
 
 =head1 SEE ALSO

Modified: jifty/branches/js-refactor/lib/Jifty/Plugin/Chart/Renderer.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Plugin/Chart/Renderer.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/Chart/Renderer.pm	Thu Nov 29 12:25:16 2007
@@ -20,15 +20,15 @@
   package MyApp::Renderer;
   use base qw/ Jifty::Plugin::Chart::Renderer /;
 
+  sub init {
+      my $self = shift;
+
+      # Handle any required initialization, like required CSS, JS, etc.
+  }
+
   sub render {
       my $self = shift;
-      my %args = (
-          type   => 'points',
-          width  => 400,
-          height => 300,
-          data   => [],
-          @_,
-      );
+      my %args = @_;
 
       # Output your chart
       Jifty->web->out( #{ Output your chart here... } );
@@ -41,6 +41,29 @@
 
 Your renderer implementation must subclass this package and implement the following methods:
 
+=head2 new
+
+This is the constructor. Don't override this directly. Instead implement L</init>.
+
+=cut
+
+sub new {
+    my $class = shift;
+    my $self = bless {}, $class;
+    $self->init( @_ );
+    return $self;
+}
+
+=head2 init
+
+  $renderer->init();
+
+This is called by C<new> immediately after constructing the object.  It is passed a param hash from the config file.  Subclasses should implement this method to do any required initialization such as letting Jifty know about required CSS files, JS files, etc.
+
+=cut
+
+sub init {}
+
 =head2 render
 
   Jifty->web->out($renderer->render(%args));
@@ -49,6 +72,10 @@
 
 The C<render> method may either return it's output or print it out using L<Jifty::Web::out>.
 
+=cut
+
+sub render {}
+
 =head1 SEE ALSO
 
 L<Jifty::Plugin::Chart::Web>, L<Jifty::Plugin::Chart::Renderer::Chart>

Modified: jifty/branches/js-refactor/lib/Jifty/Plugin/Chart/Renderer/Chart.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Plugin/Chart/Renderer/Chart.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/Chart/Renderer/Chart.pm	Thu Nov 29 12:25:16 2007
@@ -16,6 +16,16 @@
 
 =head1 METHODS
 
+=head2 init
+
+Adds the F<chart_img_behaviour.js> script to those loaded.
+
+=cut
+
+sub init {
+    Jifty->web->add_javascript('chart_img_behaviour.js');
+}
+
 =head2 render
 
 Implemented the L<Jifty::Plugin::Chart::Renderer/render> method interface.
@@ -26,21 +36,46 @@
     my $self = shift;
     my %args = @_;
 
-    # Turn any subs into values returned
-    for my $key (keys %args) {
-        $args{$key} = $args{$key}->(\%args) if ref $args{$key} eq 'CODE';
-    }
+    # Conversion from generic types to Chart types
+    my %types = (
+        'bars'           => 'Bars',
+        'composite'      => 'Composite', # non-standard
+        'direction'      => 'Direction', # non-standard
+        'errorbars'      => 'ErrorBars', # non-standard
+        'horizontalbars' => 'HorizontalBars',
+        'lines'          => 'Lines',
+        'linespoints'    => 'LinesPoints',
+        'mountain'       => 'Mountain',  # non-standard
+        'pareto'         => 'Pareto',    # non-standard
+        'pie'            => 'Pie',
+        'points'         => 'Points',
+        'split'          => 'Split',     # non-standard
+        'stackedbars'    => 'StackedBars',
+    );
 
-    # Make sure the type is ready to be used as a class name
-    $args{type} = ucfirst lc $args{type};
+    # Make sure the type is ready to be used as a Chart class name
+    $args{type} = $types{ $args{type} } || undef;
 
     # Save the data for retrieval from the session later
     my $chart_id   = Jifty->web->serial;
     my $session_id = 'chart_' . $chart_id;
     Jifty->web->session->set( $session_id => Jifty::YAML::Dump(\%args) );
 
+    # Build up the chart tag
+    my $img;
+    $img  = qq{<img};
+    $img .= qq{ src="/chart/chart/$chart_id"};
+    $img .= qq{ class="@{[ join ' ', @{ $args{class} } ]}"};
+
+    my @styles;
+    push @styles, "width:$args{width}"   if defined $args{width};
+    push @styles, "height:$args{height}" if defined $args{height};
+
+    $img .= qq{ style="@{[ join ';', @styles ]}"} if @styles;
+    $img .= qq{/>};
+    
     # Output the <img> tag and include the chart's configuration key
-    Jifty->web->out(qq{<img src="/chart/$chart_id" width="$args{width}" height="$args{height}"/>});
+    Jifty->web->out($img);
 
     # Make sure we don't return anything that will get output
     return;

Added: jifty/branches/js-refactor/lib/Jifty/Plugin/Chart/Renderer/GD/Graph.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/Chart/Renderer/GD/Graph.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,97 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Chart::Renderer::GD::Graph;
+use base qw/ Jifty::Plugin::Chart::Renderer /;
+
+=head1 NAME
+
+Jifty::Plugin::Chart::Renderer::GD::Graph - A chart renderer using GD::Graph
+
+=head1 SYNOPSIS
+
+In F<config.yml>:
+
+  Plugins:
+    - Chart:
+        renderer: Jifty::Plugin::Chart::Renderer::GD::Graph
+
+=head1 DESCRIPTION
+
+This is a chart renderer that uses L<GD::Graph> to build charts.
+
+=head1 METHODS
+
+=head2 init
+
+Adds the F<chart_img_behaviour.js> script to those loaded.
+
+=cut
+
+sub init {
+    Jifty->web->add_javascript('chart_img_behaviour.js');
+}
+
+=head2 render
+
+Renders an IMG tag referring to the L<GD::Graph> image view.
+
+=cut
+
+sub render {
+    my $self = shift;
+    my %args = @_;
+
+    # GD::Graph types from generic types
+    my %types = (
+        lines          => 'lines',
+        bars           => 'bars',
+        horizontalbars => 'hbars',
+        points         => 'points',
+        linespoints    => 'linespoints', # non-standart
+        area           => 'area',
+        pie            => 'pie',
+        mixed          => 'mixed', # non-standard
+    );
+
+    # Convert the generic type to a GD::Graph type
+    $args{type} = $types{ $args{type} } || undef;
+
+    # Save the data for retrieval from the session later
+    my $chart_id   = Jifty->web->serial;
+    my $session_id = 'chart_' . $chart_id;
+    Jifty->web->session->set( $session_id => Jifty::YAML::Dump(\%args) );
+
+    # Build up the chart tag
+    my $img;
+    $img  = qq{<img};
+    $img .= qq{ src="/chart/gd_graph/$chart_id"};
+    $img .= qq{ class="@{[ join ' ', @{ $args{class} } ]}"};
+
+    my @styles;
+    push @styles, "width:$args{width}"   if defined $args{width};
+    push @styles, "height:$args{height}" if defined $args{height};
+
+    $img .= qq{ style="@{[ join ';', @styles ]}"} if @styles;
+    $img .= qq{/>};
+
+    # Output the <img> tag and include the chart's configuration key
+    Jifty->web->out($img);
+
+    # Make sure we don't return anything that will get output
+    return;
+}
+
+=head1 AUTHOR
+
+Andrew Sterling Hanenkamp C<< <andrew.hanenkamp at boomer.com> >>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007 Boomer Consulting, Inc.
+
+This is free software and may be modified and distributed under the same terms as Perl itself.
+
+=cut
+
+1;

Added: jifty/branches/js-refactor/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/Chart/Renderer/PlotKit.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,149 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Chart::Renderer::PlotKit;
+use base qw/ Jifty::Plugin::Chart::Renderer /;
+
+use Jifty::YAML;
+
+=head1 NAME
+
+Jifty::Plugin::Chart::Renderer::PlotKit - A chart renderer using PlotKit
+
+=head1 DESCRIPTION
+
+This is an alternate chart renderer used by the L<Jifty::Plugin::Chart> plugin. It works by rendering a <div> tag in the HTML output and some JavaScript in a <script> tag.
+
+=head1 METHODS
+
+=head2 init
+
+Adds the various JavaScript files required to use PlotKit.
+
+=cut
+
+sub init {
+    Jifty->web->add_external_javascript(qw(
+        /static/js/mochikit.noexport.js
+        /static/js/MochiKit/MochiKit.js
+    ));
+    Jifty->web->add_javascript(qw(
+        PlotKit/excanvas.js
+        PlotKit/PlotKit_Packed-20060807-custom.js
+    ));
+}
+
+=head2 render
+
+Implemented the L<Jifty::Plugin::Chart::Renderer/render> method interface.
+
+=cut
+
+sub render {
+    my $self = shift;
+    my %args = ( options => {}, @_ );
+
+    # translations from generic type to PlotKit types
+    my %types = (
+        lines          => { type => 'line' },
+        bars           => { type => 'bar', barOrientation => 'vertical' },
+        pie            => { type => 'pie' },
+        horizontalbars => { type => 'bar', barOrientation => 'horizontal' },
+    );
+
+    # save it for error reporting
+    my $orig_type = $args{type};
+
+    # Make sure the type is ready to be used
+    my $options = $types{ $args{type} } || {};
+    $args{type} = delete $options->{type};
+    $args{options}{$_} = $options->{$_} foreach keys %$options;
+
+    # Bad stuff, not a supported type
+    if ( not defined $args{type} ) {
+        Jifty->log->warn("Unsupported chart type: $orig_type!");
+        return;
+    }
+
+    # Kill the "px" unit
+    $args{width} =~ s/px$//;
+    $args{height} =~ s/px$//;
+
+    $self->_transform_data( \%args );
+
+    # Save the data for retrieval from the session later
+    my $chart_id   = 'chart_' . Jifty->web->serial;
+
+    # Output the <canvas> tag and include the chart's JS
+    my $div;
+    $div  = qq{<div id="$chart_id"};
+    $div .= qq{ class="@{[ join ' ', @{ $args{class} } ]}"};
+    $div .= qq{ height="$args{height}"} if $args{height};
+    $div .= qq{ width="$args{width}"}   if $args{width};
+    $div .= qq{></div>};
+
+    Jifty->web->out(<<"    END_OF_HTML");
+$div
+
+<script type="text/javascript">
+var plot = function() {
+    var plotter = PlotKit.EasyPlot(
+        "$args{type}",
+        @{[Jifty::JSON::objToJson( $args{options} )]},
+        \$("$chart_id"),
+        @{[Jifty::JSON::objToJson( $args{data} )]}
+    );
+};
+YAHOO.util.Event.onAvailable( "$chart_id", plot );
+</script>
+    END_OF_HTML
+
+    # Make sure we don't return anything that will get output
+    return;
+}
+
+sub _transform_data {
+    my $self = shift;
+    my $args = shift;
+
+    my @data;
+    my $labels = shift @{ $args->{data} };
+
+    for ( my $i = 0; $i < @$labels; $i++ ) {
+        push @{$args->{options}{xTicks}}, { v => $i, label => $labels->[$i] }
+            if defined $labels->[$i];
+    }
+    
+    for my $dataset ( @{ $args->{data} } ) {
+        my @ds;
+        for ( my $i = 0; $i < @$dataset; $i++ ) {
+            # PlotKit can't deal with undefined values
+            if ( not ref $dataset->[$i] ) {
+                push @ds, [ $i, defined $dataset->[$i] ? $dataset->[$i] : '0' ];
+            } else {
+                push @ds, $dataset->[$i];
+            }
+        }
+        push @data, \@ds;
+    }
+
+    $args->{data} = \@data;
+}
+
+=head1 SEE ALSO
+
+L<Jifty::Plugin::Chart>, L<Jifty::Plugin::Chart::Renderer>
+
+=head1 AUTHOR
+
+Thomas Sibley
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007 Best Practical Solutions, LLC
+
+This is free software and may be modified and distributed under the same terms as Perl itself.
+
+=cut
+
+1;

Added: jifty/branches/js-refactor/lib/Jifty/Plugin/Chart/Renderer/SimpleBars.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/Chart/Renderer/SimpleBars.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,109 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Chart::Renderer::SimpleBars;
+use base qw/ Jifty::Plugin::Chart::Renderer /;
+
+=head1 NAME
+
+Jifty::Plugin::Chart::Renderer::SimpleBars - a simple horizontal bar chart
+
+=head1 DESCRIPTION
+
+This is a simple renderer for charts created both as a dead simple way of rendering horizontal bar charts, which can be a very simple way of rendering data, and as a prototype for some other work I'm thinking of doing with the chart plugin.
+
+=head1 OPTIONS
+
+Of the rendering API, this only uses the first dataset given and ignores any others. It also fails if used for any type other than the only one it supports "horizontalbars".
+
+It takes the following options:
+
+=over
+
+=item summary
+
+To maximize the accessibility of your chart, set this to describe the data. This will set the table's summary attribute.
+
+=back
+
+=head1 STYLING
+
+Please be aware that when using this object, you must add background  color to the application CSS file to see the bars.
+
+  div.simple_bars span.bar {
+      background-color: black;
+  }
+
+=head1 METHODS
+
+=head2 init
+
+Tell Jifty about the CSS and JS files SimpleBars needs.
+
+=cut
+
+sub init {
+    Jifty->web->add_javascript('simple_bars.js');
+    Jifty->web->add_css('simple_bars.css');
+}
+
+=head2 render
+
+Renders a horizontal bar chart. This is done by rendering a table of HTML values, which is then converted to a bar chart by the Javascript added to the response during L</init>.
+
+If JavaScript is not supported by the browser, all the data is presented ina table. They can still read the data, but just not in the most readable form.
+
+=cut
+
+sub render {
+    my $self = shift;
+    my %args = @_;
+
+    # We only handle horizontalbars, fail on all else
+    if ($args{type} ne 'horizontalbars') {
+        die 'Sorry, SimpleBars charts only handle horizontalbars chart types.';
+    }
+
+    # Create a fresh ID for the chart
+    my $chart_id = 'chart_' . Jifty->web->serial;
+
+    # Add the simple_bars class for the JavaScript to find
+    push @{ $args{class} }, 'simple_bars';
+
+    # Build the table
+    my $table;
+    $table  = qq{<table id="$chart_id"};
+    $table .= qq{ class="@{[ join ' ', @{ $args{class} } ]}"};
+    $table .= qq{ summary="$args{summary}"} if $args{summary};
+    $table .= qq{/><tbody>};
+
+    for my $index (0 .. $#{ $args{data}[0] }) {
+        my $label = $args{data}[0][$index];
+        my $point = $args{data}[1][$index];
+
+        $table .= '<tr>';
+        $table .= "<td>@{[ Jifty->web->escape($label) ]}</td>";
+        $table .= "<td>@{[ Jifty->web->escape($point) ]}</td>";
+        $table .= '</tr>';
+    }
+
+    $table .= '</tbody></table>';
+
+    Jifty->web->out($table);
+
+    return;
+}
+
+=head1 AUTHOR
+
+Andrew Sterling Hanenkamp, C<< <andrew.hanenkamp at boomer.com> >>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007 Boomer Consulting, Inc.
+
+This is free software. You may modify and redistribute it under the same terms as Perl itself.
+
+=cut
+
+1

Added: jifty/branches/js-refactor/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/Chart/Renderer/XMLSWF.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,156 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::Chart::Renderer::XMLSWF;
+use base qw/ Jifty::Plugin::Chart::Renderer /;
+
+use Jifty::YAML;
+
+=head1 NAME
+
+Jifty::Plugin::Chart::Renderer::XMLSWF - A chart renderer using XML SWF charts
+
+=head1 DESCRIPTION
+
+This chart renderer uses the XML SWF charting tools to render charts.
+
+  Plugins:
+    - Chart:
+        renderer: XMLSWF
+        license_key: YOUR_OPTIONAL_LICENSE_KEY
+
+=head1 METHODS
+
+=head2 init
+
+Save the license key, if any
+
+=cut
+
+our $LICENSE = "";
+
+sub init {
+    my $self = shift;
+    my %args = ( @_ );
+
+    if ( defined $args{license_key} ) {
+        $LICENSE = $args{license_key};
+    }
+}
+
+=head2 render
+
+Implemented the L<Jifty::Plugin::Chart::Renderer/render> method interface.
+
+=cut
+
+sub render {
+    my $self = shift;
+    my %args = (
+        bgcolor => '#ffffff',
+        wmode   => 'transparent',
+        @_
+    );
+
+    # Conversion from generic types to XML SWF types -- incomplete
+    my %types = (
+        'bars'           => 'column',
+        'stackedbars'    => 'stacked column',
+        'horizontalbars' => 'bar',
+        'lines'          => 'line',
+        'pie'            => '3d pie',
+        'points'         => 'scatter',
+    );
+
+    # Make sure the type is ready to be used
+    $args{type} = $types{ $args{type} } || undef;
+
+    # Kill the "px" unit
+    $args{width} =~ s/px$//;
+    $args{height} =~ s/px$//;
+
+    # Save the data for retrieval from the session later
+    my $chart_id   = Jifty->web->serial;
+    my $session_id = 'chart_' . $chart_id;
+    Jifty->web->session->set( $session_id => Jifty::YAML::Dump(\%args) );
+
+    # Build up the chart tag
+    my $src = '/static/flash/xmlswf/charts.swf?';
+
+    my $query = Jifty->web->query_string(
+                    library_path => '/static/flash/xmlswf/charts_library',
+                    xml_source   => "/chart/xmlswf/$chart_id",
+                    license      => $LICENSE
+                );
+    $query =~ s/;/&/g;
+    $src .= $query;
+
+    my $tags = {
+        embed => {
+            src             => $src,
+            quality         => 'high',
+            bgcolor         => $args{bgcolor},
+            width           => $args{width},
+            height          => $args{height},
+            name            => $session_id,
+            wmode           => $args{wmode},
+            type            => 'application/x-shockwave-flash',
+            swLiveConnect   => 'true',
+            pluginspage     => 'http://www.macromedia.com/go/getflashplayer',
+        },
+        object => {
+            classid     => 'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000',
+            codebase    => 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0',
+            width       => $args{width},
+            height      => $args{height},
+            id          => $session_id,
+            class       => join( ' ', @{$args{class}} ),
+        },
+        params  => {
+            movie   => $src,
+            quality => 'high',
+            bgcolor => $args{bgcolor},
+            wmode   => $args{wmode},
+        },
+    };
+
+    my $html = "<div>\n";
+    $html .= "<object";
+    $html .= qq[ $_="@{[$tags->{object}{$_}]}"]
+        for keys %{ $tags->{object} };
+    $html .= ">\n";
+
+    $html .= qq[<param name="$_" value="@{[$tags->{params}{$_}]}" />\n] # /damn vim
+        for keys %{ $tags->{params} };
+
+    $html .= "<embed";
+    $html .= qq[ $_="@{[$tags->{embed}{$_}]}"]
+        for keys %{ $tags->{embed} };
+    $html .= " />\n";
+    $html .= "</object>\n";
+    $html .= "</div>\n";
+
+    # Output the HTML and include the chart's configuration key
+    Jifty->web->out($html);
+
+    # Make sure we don't return anything that will get output
+    return;
+}
+
+=head1 SEE ALSO
+
+L<Jifty::Plugin::Chart>, L<Jifty::Plugin::Chart::Renderer>
+
+=head1 AUTHOR
+
+Thomas Sibley
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007 Best Practical Solutions, LLC
+
+This is free software and may be modified and distributed under the same terms as Perl itself.
+
+=cut
+
+1;

Modified: jifty/branches/js-refactor/lib/Jifty/Plugin/Chart/View.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Plugin/Chart/View.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/Chart/View.pm	Thu Nov 29 12:25:16 2007
@@ -10,7 +10,7 @@
 
 =head1 TEMPLATES
 
-=head2 chart
+=head2 chart/chart
 
 This shows a chart using L<Chart>. It expects to find the arguments in the C<args> parameter, which is setup for it in L<Jifty::Plugin::Chart::Dispatcher>.
 
@@ -18,17 +18,17 @@
 
 =cut
 
-template 'chart' => sub {
+template 'chart/chart' => sub {
     # Load the arguments
     my $args = get 'args';
 
-
     # Set the output type to the PNG file type
     Jifty->handler->apache->content_type('image/png');
 
     # Render the chart and output the PNG file generated
     eval {
         my $chart = $args->{class}->new( $args->{width}, $args->{height} );
+        $chart->set(%{ $args->{options} }) if $args->{options};
         # XXX scalar_png() is undocumented!!! Might bad to rely upon.
         outs_raw($chart->scalar_png($args->{data}));
     };
@@ -40,6 +40,87 @@
     }
 };
 
+=head2 chart/gd_graph
+
+This shows a chart using L<GD::Graph>. It expects to find the arguments in the C<args> parameter, which is setup for it in L<Jifty::Plugin::Chart::Dispatcher>.
+
+This will output a PNG file unless there is an error building the chart.
+
+=cut
+
+template 'chart/gd_graph' => sub {
+    # Load the arguments
+    my $args = get 'args';
+
+    # Set the output type to the PNG file type
+    Jifty->handler->apache->content_type('image/png');
+
+    # Render the chart and output the PNG file generated
+    eval {
+        my $graph = $args->{class}->new( $args->{width}, $args->{height} );
+        $graph->set(%{ $args->{options} }) if $args->{options};
+        my $gd    = $graph->plot($args->{data})
+            or die $graph->error;
+        outs_raw($gd->png);
+    };
+
+    # Should have thrown an error if bad stuff happened, handle that
+    if ($@) {
+        Jifty->log->error("Failed to render chart: $@");
+        die $@;
+    }
+};
+
+=head2 chart/xmlswf
+
+This shows a chart using XML SWF. It expects to find the arguments in the C<args> parameter, which is setup for it in L<Jifty::Plugin::Chart::Dispatcher>.
+
+This will output an XML source file unless there is an error building the chart.
+
+=cut
+
+template 'chart/xmlswf' => sub {
+    # Load the arguments
+    my $args = get 'args';
+
+    # Set the output type to the XML file type
+    Jifty->handler->apache->content_type('application/xml');
+
+    # The KeyAttr thing is a bloody hack to get ordering right
+    my $xml = $args->{class}->new(
+        RootName => 'chart',
+        KeyAttr  => { row => '+string' }
+    );
+
+    my $labels = shift @{ $args->{data} };
+
+    # Base chart options
+    my %chart = (
+        chart_type       => { content => $args->{type} },
+        axis_category    => { size => '11', color => '808080' },
+        axis_value       => { size => '11', color => '808080' },
+        axis_ticks       => { major_color => '808080' },
+        legend_label     => { size => '11' },
+        chart_value      => { position => 'cursor', size => '11', color => '666666' },
+        %{ $args->{options} },
+        chart_data       => {
+            row => [
+                {
+                    string => [ {}, @$labels ],
+                },
+            ],
+        },
+    );
+
+    for my $i ( 0 .. $#{ $args->{data} } ) {
+        push @{$chart{'chart_data'}{'row'}}, {
+            string => [ $args->{legend}[$i] || {} ],
+            number => $args->{data}[$i],
+        };
+    }
+
+    outs_raw( $xml->XMLout( \%chart ) );
+};
 =head1 SEE ALSO
 
 L<Jifty::Plugin::Chart::Dispatcher>

Modified: jifty/branches/js-refactor/lib/Jifty/Plugin/Chart/Web.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Plugin/Chart/Web.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/Chart/Web.pm	Thu Nov 29 12:25:16 2007
@@ -3,6 +3,8 @@
 
 package Jifty::Plugin::Chart::Web;
 
+use Scalar::Util qw/ looks_like_number /;
+
 =head1 NAME
 
 Jifty::Plugin::Chart::Web - Base class to add to Jifty::Web's ISA
@@ -23,43 +25,51 @@
 
 =item type
 
-This will be one of the following scalar values indicating the kind of chart:
+This will be one of the following scalar values indicating the kind of chart. A given renderer may not support every type listed here. A renderer might support others in addition to these, but if it supports these it should use these names.
 
 =over
 
-=item Points
+=item points
 
 This is the default value. A scatter plot with each dataset represented using differnet dot styles.
 
-=item Lines
+=item lines
 
 A line plot with each dataset presented as separate line.
 
-=item Bars
+=item bars
 
 A bar chart with each dataset set side-by-side.
 
-=item StackedBars
+=item stackedbars
 
 A bar chart with each dataset stacked on top of each other.
 
-=item Pie
+=item pie
 
 A pie chart with a single dataset representing the values for different pieces of the pie.
 
-=item HorizontalBars
+=item horizontalbars
 
 A bar chart turned sideways.
 
+=item area
+
+An area chart uses lines to represent each dataset, but the lines are stacked on top of each other with filled areas underneath.
+
 =back
 
 =item width
 
-The width, in pixels, the chart should take on the page. Defaults to 400.
+This is the width the chart should take when rendered. This may be a number, indicating the width in pixels. It may also be any value that would be appropriate for the C<width> CSS property.
+
+Defaults to C<undef>, which indicates that the chart will take on whatever size the box it is in will be. See L</CSS FOR CHARTS>.
 
 =item height
 
-The height, in pixels, the chart should take on the page. Defaults to 300.
+This is the height the chart should take when rendered. This may be a number, indicating the height in pixels. It may also be any value that would be appropriate for the C<height> CSS property.
+
+Defaults to C<undef>, which indicates that the chart will take on whatever size the box it is in will be. See L</CSS FOR CHARTS>.
 
 =item data
 
@@ -67,20 +77,33 @@
 
 Defaults to no data (i.e., it must be given if anything useful is to happen).
 
+=item class
+
+This allows you to associated an additional class or classes to the element containing the chart. This can be a string containing on or more class names separated by spaces or an array of class names.
+
+=item renderer
+
+This allows you to use a different renderer than the one configured in F<config.yml>. Give the renderer as a class name, which will be initialized for you.
+
+=item options
+
+This is a hash containing additional options to pass to the renderer and are renderer specific. This may include anything that is not otherwise set by one of the other options above.
+
 =back
 
 Here's an example:
 
   <% Jifty->web->chart(
       type   => 'Pie',
-      width  => 400,
-      height => 300,
+      width  => '100%',
+      height => '300px',
       data   => sub {
           [
               [ 2004, 2005, 2006, 2007 ],
               [ 26, 37, 12, 42 ]
           ];
       },
+      class => 'visualizeronimicon',
   ) %>
 
 Be sure to output anything returned by the method (unless it returns undef).
@@ -94,17 +117,54 @@
     # TODO It might be a good idea to make this config.yml-able
     # Setup the defaults
     my %args = (
-        type   => 'points',
-        width  => 400,
-        height => 300,
-        data   => [],
+        renderer => $plugin->renderer,
+        type     => 'points',
+        width    => undef,
+        height   => undef,
+        data     => [],
+        class    => [],
         @_,
     );
 
-    # Call the rendering plugin's render method
-    return $plugin->render(@_);
+    # load the renderer
+    $args{renderer} = $plugin->init_renderer($args{renderer});
+
+    # canonicalize the width/height
+    $args{width}  .= 'px' if looks_like_number($args{width});
+    $args{height} .= 'px' if looks_like_number($args{height});
+
+    # canonicalize the type argument (always lowercase)
+    $args{type} = lc $args{type};
+
+    # canonicalize the class argument
+    if (not ref $args{class}) {
+        $args{class} = defined $args{class} ? [ $args{class} ] : [];
+    }
+
+    # Add the chart class, which is always present
+    push @{ $args{class} }, 'chart';
+
+    # Turn any subs into values returned
+    for my $key (keys %args) {
+        $args{$key} = $args{$key}->(\%args) if ref $args{$key} eq 'CODE';
+    }
+
+    # Call the rendering class' render method
+    return $args{renderer}->render(%args);
 }
 
+=head1 CSS FOR CHARTS
+
+The chart API allows you to build the charts without explicit pixel widths and heights. In fact, you can not specify C<width> and C<height> and perform the styling in your regular CSS stylesheets by using the "chart" class associated with every chart or by using custom classes with the C<class> argument.
+
+See your renderer class documentation for further details.
+
+=head1 JAVASCRIPT FOR CHARTS
+
+Charts typically require JavaScript to render properly. If the client does not have JavaScript available, the chart may not work or could look very bad. 
+
+If you are using one of the image based renderers like L<Jifty::Plugin::Chart::Renderer::Chart>, it is recommended that you stick with pixel widths if you expect clients with limited or no JavaScript support. 
+
 =head1 SEE ALSO
 
 L<Jifty::Plugin::Chart>, L<Jifty::Plugin::Chart::Renderer>

Modified: jifty/branches/js-refactor/lib/Jifty/Plugin/CompressedCSSandJS.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Plugin/CompressedCSSandJS.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/CompressedCSSandJS.pm	Thu Nov 29 12:25:16 2007
@@ -2,10 +2,11 @@
 use warnings;
 
 package Jifty::Plugin::CompressedCSSandJS;
-use base qw/Jifty::Plugin Class::Accessor::Fast/;
+use base 'Jifty::Plugin';
 
-use Digest::MD5 qw(md5_hex);
+use Digest::MD5 'md5_hex';
 use IPC::Run3 'run3';
+use Compress::Zlib ();
 use IO::Handle ();
 
 =head1 NAME
@@ -21,6 +22,9 @@
         js: 1
         css: 1
         jsmin: /path/to/jsmin
+        cdn: 'http://yourcdn.for.static.prefix/'
+        gzip: 1
+
 
 =head1 DESCRIPTION
 
@@ -35,9 +39,24 @@
 Note that you will need to use C<ConfigFileVersion> 2 to be able to
 configure jsmin feature.
 
+The gzip configuration directive, which defaults to enabled, instructs
+Jifty to transparently gzip css and js files as they're served if the client
+indicates it supports that feature.
+
 =cut
 
-__PACKAGE__->mk_accessors(qw(css js jsmin cached_javascript cached_javascript_digest cached_javascript_time ));
+__PACKAGE__->mk_accessors(qw(css js jsmin cdn gzip_enabled
+
+    cached_javascript 
+    cached_javascript_gzip
+    cached_javascript_digest 
+    cached_javascript_time 
+
+    cached_css
+    cached_css_gzip
+    cached_css_time
+    cached_css_digest
+));
 
 =head2 init
 
@@ -49,16 +68,26 @@
 
 sub init {
     my $self = shift;
+    return if $self->_pre_init;
+
     my %opt  = @_;
     $self->css( $opt{css} );
+    $self->gzip_enabled( exists $opt{gzip} ? $opt{gzip} : 1);
     $self->js( $opt{js} );
     $self->jsmin( $opt{jsmin} );
+    $self->cdn( $opt{cdn} || '');
 
     Jifty::Web->add_trigger(
         name      => 'include_javascript',
         callback  => sub { $self->_include_javascript(@_) },
         abortable => 1,
     ) if $self->js_enabled;
+
+    Jifty::Web->add_trigger(
+        name => 'include_css',
+        callback => sub { $self->_include_css(@_) },
+        abortable => 1,
+    ) if $self->css_enabled;
 }
 
 =head2 js_enabled
@@ -83,16 +112,63 @@
     defined $self->css ? $self->css : 1;
 }
 
+=head2 gzip_enabled
+
+Returns whether gzipping is enabled (which it is by default)
+
+=cut
+
 sub _include_javascript {
     my $self = shift;
 
     $self->_generate_javascript;
-    Jifty->web->out( qq[<script type="text/javascript" src="/__jifty/js/]
+    Jifty->web->out( qq[<script type="text/javascript" src="@{[ $self->cdn ]}/__jifty/js/]
             . $self->cached_javascript_digest
             . qq[.js"></script>] );
     return 0;
 }
 
+sub _include_css {
+    my $self = shift;
+    $self->generate_css;
+    Jifty->web->out(
+    qq{<link rel="stylesheet" type="text/css" href="@{[ $self->cdn ]}/__jifty/css/}
+    . $self->cached_css_digest . '.css" />');
+    return 0;
+}
+
+=head3 generate_css 
+
+
+Checks if the compressed CSS is generated, and if it isn't, generates
+and caches it. (In devel mode, it always regenerates it)
+
+=cut
+
+sub generate_css {
+    my $self = shift;
+            
+    if (not defined $self->cached_css_digest or Jifty->config->framework('DevelMode')) {
+        Jifty->log->debug("Generating CSS...");
+
+        my @roots = map { Jifty::Util->absolute_path( $_ ) }
+            Jifty->handler->view('Jifty::View::Static::Handler')->roots;
+    
+        my $css = CSS::Squish->new( roots => \@roots )->concatenate(
+            map { File::Spec->catfile('css', $_ ) }
+                @{ Jifty->web->css_files }
+        );
+
+        $self->cached_css( $css );
+        $self->cached_css_digest( md5_hex( $css ) );
+        $self->cached_css_time( time );
+        $self->cached_css_gzip(Compress::Zlib::memGzip( $css));
+
+    }
+}
+
+
+
 =head3 _generate_javascript
 
 Checks if the compressed JS is generated, and if it isn't, generates
@@ -107,23 +183,12 @@
         or Jifty->config->framework('DevelMode') ) {
         Jifty->log->debug("Generating JS...");
 
-        my @roots = map { Jifty::Util->absolute_path( File::Spec->catdir( $_, 'js' ) ) }
-                        Jifty->handler->view('Jifty::View::Static::Handler')->roots;
-
+        # for the file cascading logic
+        my $static_handler = Jifty->handler->view('Jifty::View::Static::Handler');
         my $js = "";
 
         for my $file ( @{ Jifty::Web->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;
-                }
-            }
+            my $include = $static_handler->file_path( File::Spec->catdir( 'js', $file ) );
 
             if ( defined $include ) {
                 my $fh;
@@ -146,6 +211,7 @@
         $self->cached_javascript($js);
         $self->cached_javascript_digest( md5_hex($js) );
         $self->cached_javascript_time(time);
+        $self->cached_javascript_gzip(Compress::Zlib::memGzip( $js));
     }
 }
 

Modified: jifty/branches/js-refactor/lib/Jifty/Plugin/CompressedCSSandJS/Dispatcher.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Plugin/CompressedCSSandJS/Dispatcher.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/CompressedCSSandJS/Dispatcher.pm	Thu Nov 29 12:25:16 2007
@@ -13,11 +13,11 @@
 which serve out compiled and compressed CSS and Javascript rules.
 
 =cut
-use Compress::Zlib qw();
 use HTTP::Date ();
 
 use Jifty::Dispatcher -base;
 
+
 on '/__jifty/js/*' => run {
     my $arg = $1;
     if ( $arg !~ /^[0-9a-f]{32}\.js$/ ) {
@@ -47,12 +47,13 @@
     # 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.
-    if ( Jifty::View::Static::Handler->client_accepts_gzipped_content ) {
+    if ( $ccjs->gzip_enabled && Jifty::View::Static::Handler->client_accepts_gzipped_content ) {
         Jifty->log->debug("Sending gzipped squished JS");
         Jifty->handler->apache->header_out( "Content-Encoding" => "gzip" );
         Jifty->handler->apache->send_http_header();
         binmode STDOUT;
-        print Compress::Zlib::memGzip( $ccjs->cached_javascript );
+        print $ccjs->cached_javascript_gzip;
+
     } else {
         Jifty->log->debug("Sending squished JS");
         Jifty->handler->apache->send_http_header();
@@ -63,17 +64,18 @@
 
 on '/__jifty/css/*' => run {
     my $arg = $1;
-    if ( $arg !~ /^[0-9a-f]{32}\.css$/ ) {
+    my ($ccjs) = Jifty->find_plugin('Jifty::Plugin::CompressedCSSandJS');
+    if ( $arg !~ /^[0-9a-f]{32}\.css$/ || !$ccjs) {
 
         # This doesn't look like a real request for squished CSS,
         # so redirect to a more failsafe place
         Jifty->web->redirect( "/static/css/" . $arg );
     }
 
-    Jifty->web->generate_css;
+    $ccjs->generate_css;
 
     if ( Jifty->handler->cgi->http('If-Modified-Since')
-        and $arg eq Jifty->web->cached_css_digest . '.css' )
+        and $arg eq $ccjs->cached_css_digest . '.css' )
     {
         Jifty->log->debug("Returning 304 for cached css");
         Jifty->handler->apache->header_out( Status => 304 );
@@ -86,16 +88,16 @@
     # 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.
-    if ( Jifty::View::Static::Handler->client_accepts_gzipped_content ) {
+    if ($ccjs->gzip_enabled && Jifty::View::Static::Handler->client_accepts_gzipped_content ) {
         Jifty->log->debug("Sending gzipped squished CSS");
         Jifty->handler->apache->header_out( "Content-Encoding" => "gzip" );
         Jifty->handler->apache->send_http_header();
         binmode STDOUT;
-        print Compress::Zlib::memGzip( Jifty->web->cached_css );
+        print $ccjs->cached_css_gzip;
     } else {
         Jifty->log->debug("Sending squished CSS");
         Jifty->handler->apache->send_http_header();
-        print Jifty->web->cached_css;
+        print $ccjs->cached_css;
     }
     abort;
 };

Modified: jifty/branches/js-refactor/lib/Jifty/Plugin/Feedback.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Plugin/Feedback.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/Feedback.pm	Thu Nov 29 12:25:16 2007
@@ -2,7 +2,7 @@
 use warnings;
 
 package Jifty::Plugin::Feedback;
-use base qw/Jifty::Plugin Class::Accessor/;
+use base qw/Jifty::Plugin Class::Accessor::Fast/;
 
 # Your plugin goes here.  If takes any configuration or arguments, you
 # probably want to override L<Jifty::Plugin/init>.

Modified: jifty/branches/js-refactor/lib/Jifty/Plugin/Feedback/Action/SendFeedback.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Plugin/Feedback/Action/SendFeedback.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/Feedback/Action/SendFeedback.pm	Thu Nov 29 12:25:16 2007
@@ -40,7 +40,7 @@
 
 =head2 take_action
 
-Send some mail to the hiveminders describing the issue.
+Send a mail to the feedback recipient describing the issue.
 
 =cut
 
@@ -58,13 +58,19 @@
     # Fall back to normal email
     my $mail = Jifty::Notification->new;
     $mail->body($msg);
-    $mail->from(
-        (          Jifty->web->current_user->id
-                && Jifty->web->current_user->user_object->can('email')
-        )
-        ? Jifty->web->current_user->user_object->email()
-        : $plugin->from
-    );
+
+    if (    Jifty->web->current_user->id
+         && Jifty->web->current_user->user_object->can('email') ) {
+
+         my $user = Jifty->web->current_user->user_object;
+         my $CurrentUser = Jifty->app_class('CurrentUser');
+         $user->current_user( $CurrentUser->superuser );
+         $mail->from( $user->email() || $plugin->from );
+    }
+    else {
+        $mail->from( $plugin->from );
+    }
+
     $mail->recipients( $plugin->to );
     $mail->subject( "["
             . Jifty->config->framework('ApplicationName')

Added: jifty/branches/js-refactor/lib/Jifty/Plugin/I18N.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/I18N.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,105 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::I18N;
+use base 'Jifty::Plugin';
+
+=head1 NAME
+
+Jifty::Plugin::I18N
+
+=head1 SYNOPSIS
+
+# In your jifty config.yml under the framework section:
+
+  L10N:
+    PoDir: share/po
+    AllowedLang:
+      - en
+      - zh_tw
+  Plugins:
+    - I18N:
+        js: 1
+
+
+=head1 DESCRIPTION
+
+This plugin provides additional i18n facility to jifty's core i18n
+features, such as compiling l10n lexicon for client side javascript,
+and a language selector action.
+
+You will still need to manually do the following to make client side l10n work:
+
+=over
+
+=item Extract strings from your js files into your po file
+
+  jifty po --dir share/web/static/js
+
+=item Generate js dictionary
+
+  jifty po --js
+
+=back
+
+=head2 init
+
+=cut
+
+__PACKAGE__->mk_accessors(qw(js));
+
+sub init {
+    my $self = shift;
+    return if $self->_pre_init;
+
+    my %opt  = @_;
+    $self->js( $opt{js} );
+
+    Jifty::Web->add_trigger(
+        name      => 'after_include_javascript',
+        callback  => sub { $self->_i18n_js(@_) },
+    ) if $self->js;
+}
+
+sub _i18n_js {
+    my $self = shift;
+
+    my $current_lang = Jifty::I18N->get_current_language || 'en';
+
+    # diagnosis for htf the client is requesting something not in allowed_lang.
+    my $allowed_lang = Jifty->config->framework('L10N')->{'AllowedLang'};
+    $allowed_lang = [defined $allowed_lang ? $allowed_lang : ()] unless ref($allowed_lang) eq 'ARRAY';
+
+    if (@$allowed_lang) {
+        my $allowed_regex = join '|', map {
+            my $it = $_;
+            $it =~ tr<-A-Z><_a-z>; # lc, and turn - to _
+            $it =~ tr<_a-z0-9><>cd;  # remove all but a-z0-9_
+            $it;
+        } @$allowed_lang;
+
+        unless ( $current_lang =~ /^$allowed_regex/) {
+            Jifty->log->error("user is requesting $current_lang which is not allowed");
+            my $headers = Jifty->handler->apache->headers_in;
+            use Data::Dumper;
+            Jifty->log->error(Dumper($headers));
+        }
+    }
+
+    open my $fh, '<:encoding(utf-8)',
+        Jifty::Util->absolute_path(
+        File::Spec->catdir(
+            Jifty->config->framework('Web')->{StaticRoot},
+            "js/dict/$current_lang.json" ))
+        or Jifty->log->error("Can't find dictionary file $current_lang.json: $!");
+
+    local $/;
+    my $inline_dict = <$fh> || '{}';
+    # js l10n init
+    Jifty->web->out(qq{<script type="text/javascript">
+Localization.dict_path = '/static/js/dict';
+Localization.dict = $inline_dict;
+</script>});
+}
+
+1;

Added: jifty/branches/js-refactor/lib/Jifty/Plugin/I18N/Action/SetLang.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/I18N/Action/SetLang.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,47 @@
+package Jifty::Plugin::I18N::Action::SetLang;
+use strict;
+use DateTime::Locale ();
+
+use base 'Class::Data::Inheritable';
+__PACKAGE__->mk_classdata(_available_languages => undef);
+
+use Jifty::Param::Schema;
+use Jifty::Action schema {
+
+param
+    lang => label is _('Language'),
+    render as 'select',
+    default is defer { Jifty::I18N->get_current_language };
+};
+
+sub available_languages {
+    my $class = shift;
+    return $class->_available_languages if $class->_available_languages;
+
+    $class->_available_languages(
+        [   map { {   display => DateTime::Locale->load($_)->native_name,
+                      value   => $_ } } Jifty::I18N->available_languages ] );
+    return $class->_available_languages;
+}
+
+sub arguments {
+    my $self = shift;
+    my $args = $self->SUPER::arguments;
+
+    # XXX: complete_native_name is way too long
+    $args->{lang}->{valid_values} = $self->available_languages;
+
+    return $args;
+}
+
+sub take_action {
+    my $self = shift;
+    my $lang = $self->argument_value('lang');
+    Jifty->web->session->set(jifty_lang => $lang);
+
+    Jifty::I18N->get_language_handle;
+
+    $self->result->message(_("Hi, we speak %1.", DateTime::Locale->load($lang)->native_name));
+}
+
+1;

Added: jifty/branches/js-refactor/lib/Jifty/Plugin/JQuery.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/JQuery.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,61 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::JQuery;
+use base qw/ Jifty::Plugin /;
+
+=head1 NAME
+
+Jifty::Plugin::JQuery - use the jQuery JavaScript library with Jifty
+
+=head1 SYNOPSIS
+
+In your F<etc/config.yml>:
+
+  framework:
+    Plugins:
+      - JQuery: {}
+
+In your JavaScript files, you can then use any jQuery call:
+
+  jQuery("p.surprise").addClass("ohmy").show("slow");
+
+=head1 DESCRIPTION
+
+The jQuery JavaScript library is a small JavaScript library with the intent of providing most of the shortcuts you need it a simple syntax. Since Jifty already uses Prototype, which implements a C<$()> function, this plugin loads jQuery and calls "C<noConflict()>" to prevent it from clobbering Prototype's C<$()> function. Thus, to use jQuery, you have to use the C<jQuery()> method rather than C<$()>.
+
+You may want to use this idiom from the jQuery documentation if you still want to use this idiom (see L<http://docs.jquery.com/Using_jQuery_with_Other_Libraries>): 
+
+  function($){
+     // Use jQuery stuff using $
+     $("div").hide();
+  }(jQuery);
+
+=head1 METHODS
+
+=head2 init
+
+This initializes the plugin, which simply includes the JavaScript necessary to load jQuery and then disable the jQuery implementation of C<$()>.
+  
+=cut
+
+sub init {
+    Jifty->web->add_javascript(qw/
+        jquery.js
+        noConflict.js
+    /);
+}
+
+=head1 SEE ALSO
+
+L<http://jifty.org>, L<http://visualjquery.com>, L<http://simonwillison.net/2007/Aug/15/jquery/>
+
+=head1 COPYRIGHT AND LICENSE
+
+This plugin is Copyright 2007 Boomer Consulting, Inc. It is available for modication and distribution under the same terms as Perl itself.
+
+jQuery is available for use in all personal or commercial projects under both MIT and GPL licenses. This means taht you can choose the license that best suits your project and use it accordingly. See L<http://jifty.com/> for current information on jQuery copyrights and licensing.
+
+=cut
+
+1;

Added: jifty/branches/js-refactor/lib/Jifty/Plugin/LeakTracker.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/LeakTracker.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,138 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::LeakTracker;
+use base qw/Jifty::Plugin Class::Data::Inheritable/;
+use Data::Dumper;
+use Devel::Events::Handler::ObjectTracker;
+use Devel::Events::Generator::Objects;
+use Devel::Size 'total_size';
+
+our $VERSION = 0.01;
+
+__PACKAGE__->mk_accessors(qw(tracker generator));
+our @requests;
+
+my $empty_array = total_size([]);
+
+=head2 init
+
+init installs the triggers needed around each HTTP request
+=cut
+
+sub init {
+    my $self = shift;
+    return if $self->_pre_init;
+
+    Jifty::Handler->add_trigger(
+        before_request => sub { $self->before_request(@_) }
+    );
+
+    Jifty::Handler->add_trigger(
+        after_request  => sub { $self->after_request(@_) }
+    );
+}
+
+=head2 before_request
+
+This trigger sets up Devel::Events to instrument bless and free so it can keep
+track of all the objects created and destroyed in this request
+
+=cut
+
+sub before_request
+{
+    my $self = shift;
+    $self->tracker(Devel::Events::Handler::ObjectTracker->new());
+    $self->generator(
+        Devel::Events::Generator::Objects->new(handler => $self->tracker)
+    );
+
+    $self->generator->enable();
+}
+
+=head2 after_request
+
+This extracts all the data gathered by Devel::Events and puts it into the
+global C<@Jifty::Plugin::LeakTracker::requests> so the LeakTracker dispatcher
+and views can query it to make nice reports
+
+=cut
+
+sub after_request
+{
+    my $self = shift;
+    my $handler = shift;
+    my $cgi = shift;
+
+    $self->generator->disable();
+
+    my $leaked = $self->tracker->live_objects;
+    my @leaks = keys %$leaked;
+
+    # XXX: Devel::Size seems to segfault Jifty at END time
+    my $size = total_size([ @leaks ]) - $empty_array;
+
+    push @requests, {
+        id => 1 + @requests,
+        url => $cgi->url(-absolute=>1,-path_info=>1),
+        size => $size,
+        objects => Dumper($leaked),
+        time => scalar gmtime,
+        leaks => \@leaks,
+    };
+
+    $self->generator(undef);
+    $self->tracker(undef);
+}
+
+=head1 NAME
+
+Jifty::Plugin::LeakTracker
+
+=head1 DESCRIPTION
+
+Memory leak detection and reporting for your Jifty app
+
+=head1 USAGE
+
+Add the following to your site_config.yml
+
+ framework:
+   Plugins:
+     - LeakTracker: {}
+
+This makes the following URLs available:
+
+View the top-level leak report (how much each request has leaked)
+
+    http://your.app/leaks
+
+View the top-level leak report, including zero-leak requests
+
+    http://your.app/leaks/all
+
+View an individual request's detailed leak report (which objects were leaked)
+
+    http://your.app/leaks/3
+
+=head1 WARNING
+
+If you use this in production, be sure to block off 'leaks' from
+non-administrators. The full Data::Dumper output of the objects
+leaked is available, which may of course contain sensitive information.
+
+=head1 SEE ALSO
+
+L<Jifty::Plugin::LeakTracker::View>, L<Jifty::Plugin::LeakTracker::Dispatcher>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007 Best Practical Solutions
+
+This is free software and may be modified and distributed under the same terms as Perl itself.
+
+=cut
+
+1;
+

Added: jifty/branches/js-refactor/lib/Jifty/Plugin/LeakTracker/Dispatcher.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/LeakTracker/Dispatcher.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,41 @@
+package Jifty::Plugin::LeakTracker::Dispatcher;
+use warnings;
+use strict;
+
+use Jifty::Dispatcher -base;
+
+# http://your.app/leaks -- display full leak report
+on 'leaks' => run {
+    set 'skip_zero' => 1;
+    show "leaks/all";
+};
+
+# http://your.app/leaks/all -- full leak report with non-leaked requests
+on 'leaks/all' => run {
+    set 'skip_zero' => 0;
+    show "leaks/all";
+};
+
+# http://your.app/leaks/xxx -- display leak report for request ID xxx
+on 'leaks/#' => run {
+    abort(404) if $1 < 1;
+    my $leak = $Jifty::Plugin::LeakTracker::requests[$1 - 1]
+        or abort(404);
+    set leak => $leak;
+    show "leaks/one";
+};
+
+=head1 SEE ALSO
+
+L<Jifty::Plugin::LeakTracker>, L<Jifty::Plugin::LeakTracker::View>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007 Best Practical Solutions
+
+This is free software and may be modified and distributed under the same terms as Perl itself.
+
+=cut
+
+1;
+

Added: jifty/branches/js-refactor/lib/Jifty/Plugin/LeakTracker/View.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/LeakTracker/View.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,108 @@
+use strict;
+use warnings;
+
+package Jifty::Plugin::LeakTracker::View;
+use Jifty::View::Declare -base;
+use Scalar::Util 'blessed';
+
+=head1 NAME
+
+Jifty::Plugin::LeakTracker::View - Views for memory leak detection
+
+=head1 TEMPLATES
+
+=head2 leaks/chart
+
+This shows a chart using L<Chart>. It expects to find the arguments in the C<args> parameter, which is setup for it in L<Jifty::Plugin::Chart::Dispatcher>.
+
+This will output a PNG file unless there is an error building the chart.
+
+=cut
+
+template 'leaks/all' => sub {
+    my $skip_zero = get 'skip_zero';
+
+    html {
+        body {
+            h1 { "Leaked Objects" }
+            p {
+                if ($skip_zero) {
+                    a { attr { href => "/leaks/all" }
+                        "Show zero-leak requests" }
+                }
+                else {
+                    a { attr { href => "/leaks" }
+                        "Hide zero-leak requests" }
+                }
+            }
+            hr {}
+
+            table {
+                row {
+                    th { "ID" }
+                    th { "Leaks" }
+                    th { "Bytes leaked" }
+                    th { "Time" }
+                    th { "URL" }
+                };
+
+                for (@Jifty::Plugin::LeakTracker::requests)
+                {
+                    next if $skip_zero && @{$_->{leaks}} == 0;
+
+                    row {
+                        cell { a { attr { href => "leaks/$_->{id}" }
+                                   $_->{id} } }
+
+                        cell { scalar @{$_->{leaks}} }
+                        cell { $_->{size} }
+                        cell { $_->{time} }
+                        cell { $_->{url} }
+                    };
+                }
+            }
+        }
+    }
+};
+
+template 'leaks/one' => sub {
+    my $leak = get 'leak';
+
+    html {
+        body {
+            h1 { "Leaks from Request $leak->{id}" }
+            ul {
+                li { "URL: $leak->{url}" }
+                li { "Time: $leak->{time}" }
+                li { "Objects leaked: " . scalar(@{$leak->{leaks}}) }
+                li { "Total memory leaked: $leak->{size} bytes" }
+            }
+            p { a { attr { href => "/leaks" } "Table of Contents" } }
+            hr {}
+            h2 { "Object types leaked:" }
+            ul {
+                my %seen;
+                for (map { blessed $_ } @{ $leak->{leaks} }) {
+                    next if $seen{$_}++;
+                    li { $_ }
+                }
+            }
+            hr {}
+            pre { $leak->{objects} }
+        }
+    }
+};
+
+=head1 SEE ALSO
+
+L<Jifty::Plugin::LeakTracker>, L<Jifty::Plugin::LeakTracker::Dispatcher>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2007 Best Practical Solutions
+
+This is free software and may be modified and distributed under the same terms as Perl itself.
+
+=cut
+
+1;

Added: jifty/branches/js-refactor/lib/Jifty/Plugin/OAuth.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/OAuth.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,158 @@
+package Jifty::Plugin::OAuth;
+use strict;
+use warnings;
+
+use base qw/Jifty::Plugin/;
+
+our $VERSION = 0.01;
+
+=head1 NAME
+
+Jifty::Plugin::OAuth
+
+=head1 DESCRIPTION
+
+A OAuth web services API for your Jifty app.
+
+=head1 WARNING
+
+This plugin is not yet complete. DO NOT USE IT.
+
+=head1 USAGE
+
+Add the following to your site_config.yml
+
+ framework:
+   Plugins:
+     - OAuth: {}
+
+=head1 GLOSSARY
+
+=over 4
+
+=item service provider
+
+A service provider is an application that has users who have private data. This
+plugin enables your Jifty application to be an OAuth service provider.
+
+=item consumer
+
+A consumer is an application that wants to access users' private data. The
+service provider (in this case, this plugin) ensures that this happens securely
+and with users' full approval. Without OAuth (or similar systems), this would
+be accomplished perhaps by the user giving the consumer her login information.
+Obviously not ideal.
+
+This plugin does not yet implement the consumer half of the protocol.
+
+=item request token
+
+A request token is a unique, random string that a user may authorize for a
+consumer.
+
+=item access token
+
+An access token is a unique, random string that a consumer can use to access
+private resources on the authorizing user's behalf. Consumers may only
+receive an access token if they have an authorized request token.
+
+=back
+
+=head1 NOTES
+
+You must provide public access to C</oauth/request_token> and
+C</oauth/access_token>.
+
+You must not allow public access to C</oauth/authorize>. C</oauth/authorize>
+depends on having the user be logged in.
+
+There is currently no way for consumers to add themselves. This might change in
+the future, but it would be a nondefault configuration. Consumers must
+contact you and provide you with the following data:
+
+=over 4
+
+=item consumer_key
+
+An arbitrary string that uniquely identifies a consumer. Preferably something
+random over, say, "Hiveminder".
+
+=item secret
+
+A (preferably random) string that is used to ensure that it's really the
+consumer you're talking to. After the consumer provides this to you, it's never
+sent in plaintext. It is always, however, included in cryptographic signatures.
+
+=item name
+
+A readable name to use in displaying the consumer to users. This is where you'd
+put "Hiveminder".
+
+=item url (optional)
+
+The website of the consumer.
+
+=item rsa_key (optional)
+
+The consumer's public RSA key. This is optional. Without it, they will not be
+able to use the RSA-SHA1 signature method. They can still use HMAC-SHA1 though.
+
+=back
+
+=head1 TECHNICAL DETAILS
+
+OAuth is an open protocol that enables consumers to access users' private data
+in a secure and authorized manner. The way it works is:
+
+=over 4
+
+=item
+
+The consumer establishes a key and a secret with the service provider. This
+step only happens once.
+
+=item
+
+The user is using the consumer's application and decides that she wants to
+use some data that she already has on the service provider's application.
+
+=item
+
+The consumer asks the service provider for a request token. The service
+provider generates one and gives it to the consumer.
+
+=item
+
+The consumer directs the user to the service provider with that request token.
+
+=item
+
+The user logs in and authorizes that request token.
+
+=item
+
+The service provider directs the user back to the consumer.
+
+=item
+
+The consumer asks the service provider to exchange his authorized request token
+for an access token. This access token lets the consumer access resources on
+the user's behalf in a limited way, for a limited amount of time.
+
+=back
+
+By establishing secrets and using signatures and timestamps, this can be done
+in a very secure manner. For example, a replay attack (an eavesdropper repeats
+a request made by a legitimate consumer) is actively defended against.
+
+=head1 SEE ALSO
+
+L<Net::OAuth::Request>, L<http://oauth.net/>
+
+=head1 AUTHOR
+
+Shawn M Moore C<< <sartak at bestpractical.com> >>
+
+=cut
+
+1;

Added: jifty/branches/js-refactor/lib/Jifty/Plugin/OAuth/Action/AuthorizeRequestToken.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/OAuth/Action/AuthorizeRequestToken.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,86 @@
+package Jifty::Plugin::OAuth::Action::AuthorizeRequestToken;
+use warnings;
+use strict;
+use base qw/Jifty::Action/;
+
+=head1 NAME
+
+Jifty::Plugin::OAuth::Action::AuthorizeRequestToken
+
+=cut
+
+use Jifty::Param::Schema;
+use Jifty::Action schema {
+
+    param 'token',
+        render as 'text',
+        max_length is 30,
+        hints are 'The site you just came from should have provided it',
+        ajax validates;
+
+    param 'authorize',
+        valid_values are qw(allow deny);
+
+    param 'callback',
+        render as 'hidden';
+
+};
+
+=head2 validate_token
+
+Make sure we have such a token, and that it is not already authorized
+
+=cut
+
+sub validate_token {
+    my $self = shift;
+    my $token = shift;
+
+    my $request_token = Jifty::Plugin::OAuth::Model::RequestToken->new(current_user => Jifty::CurrentUser->superuser);
+    $request_token->load_by_cols(
+        token => $token,
+        authorized => '',
+    );
+
+    return $self->validation_error(token => "I don't know of that request token.") unless $request_token->id;
+
+    if ($request_token->valid_until < Jifty::DateTime->now(time_zone => 'GMT')) {
+        $request_token->delete();
+        return $self->validation_error(token => "This request token has expired.");
+    }
+
+    return $self->validation_ok('token');
+}
+
+=head2 take_action
+
+Actually authorize or deny this request token
+
+=cut
+
+sub take_action {
+    my $self = shift;
+
+    my $token = Jifty::Plugin::OAuth::Model::RequestToken->new(current_user => Jifty::CurrentUser->superuser);
+    $token->load_by_cols(
+        token => $self->argument_value('token'),
+    );
+
+    $self->result->content(token_obj => $token);
+    $self->result->content(token     => $token->token);
+    $self->result->content(callback  => $self->argument_value('callback'));
+
+    if ($self->argument_value('authorize') eq 'allow') {
+        $token->set_authorized('t');
+        $self->result->message("Allowing " . $token->consumer->name . " to access your stuff.");
+    }
+    else {
+        $token->delete;
+        $self->result->message("Denying " . $token->consumer->name . " the right to access your stuff.");
+    }
+
+    return 1;
+}
+
+1;
+

Added: jifty/branches/js-refactor/lib/Jifty/Plugin/OAuth/Dispatcher.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/OAuth/Dispatcher.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,381 @@
+package Jifty::Plugin::OAuth::Dispatcher;
+use warnings;
+use strict;
+
+use Jifty::Dispatcher -base;
+
+use Net::OAuth::RequestTokenRequest;
+use Net::OAuth::AccessTokenRequest;
+use Net::OAuth::ProtectedResourceRequest;
+use Crypt::OpenSSL::RSA;
+
+on     POST '/oauth/request_token' => \&request_token;
+before GET  '/oauth/authorize'     => \&authorize;
+on     POST '/oauth/authorize'     => \&authorize_post;
+on     POST '/oauth/access_token'  => \&access_token;
+on          '/oauth/authorized'    => run { redirect '/oauth/authorize' };
+
+before '*' => \&try_oauth;
+
+=head2 abortmsg CODE, MSG
+
+Helper function to abort with a debug message. Maybe should be factored into
+the C<abort> procedure?
+
+=cut
+
+sub abortmsg {
+    my ($code, $msg) = @_;
+    Jifty->log->debug($msg) if defined($msg);
+    abort($code) if $code;
+}
+
+=head2 request_token
+
+The consumer wants a request token
+
+=cut
+
+sub request_token {
+    my @params = qw/consumer_key signature_method signature
+                    timestamp nonce version/;
+    set no_abort => 0;
+
+    my %oauth_params  = get_parameters(@params);
+    my $consumer      = get_consumer($oauth_params{consumer_key});
+    my $signature_key = get_signature_key($oauth_params{signature_method}, $consumer);
+    my ($ok, $msg) = $consumer->is_valid_request(@oauth_params{qw/timestamp nonce/});
+    abortmsg(401, $msg) if !$ok;
+
+    # Net::OAuth::Request will die hard if it doesn't get everything it wants
+    my $request = eval { Net::OAuth::RequestTokenRequest->new(
+        request_url     => Jifty->web->url(path => '/oauth/request_token'),
+        request_method  => Jifty->handler->apache->method(),
+        consumer_secret => $consumer->secret,
+        signature_key   => $signature_key,
+
+        map { $_ => $oauth_params{$_} } @params
+    ) };
+
+    abortmsg(400, "Unable to create RequestTokenRequest: $@") if $@ || !defined($request);
+
+    # make sure the signature matches the rest of what the consumer gave us
+    abortmsg(401, "Invalid signature (type: $oauth_params{signature_method}).") unless $request->verify;
+
+    # ok, everything checks out. send them back a request token
+    # at this point, the only things that could go wrong are:
+    # 1) we've already seen this nonce and timestamp. possibly a replay attack,
+    #    so we abort
+    # 2) we tried a bunch of times to create a unique token but failed. abort
+    #    because we don't have any other option
+
+    my $token = Jifty::Plugin::OAuth::Model::RequestToken->new(current_user => Jifty::CurrentUser->superuser);
+
+    ($ok, $msg) = eval {
+        $token->create(consumer => $consumer);
+    };
+
+    abortmsg(401, "Unable to create a Request Token: " . $@ || $msg)
+        if $@ || !$ok;
+
+    $consumer->made_request(@oauth_params{qw/timestamp nonce/});
+    set oauth_response => {
+        oauth_token        => $token->token,
+        oauth_token_secret => $token->secret
+    };
+    show 'oauth/response';
+}
+
+=head2 authorize
+
+The user is authorizing (or denying) a consumer's request token
+
+=cut
+
+sub authorize {
+    my @params = qw/token callback/;
+
+    set no_abort => 1;
+    my %oauth_params = get_parameters(@params);
+
+    set callback => $oauth_params{callback};
+    set consumer => 'Some application';
+    del 'token';
+
+    if ($oauth_params{token}) {
+        my $request_token = Jifty::Plugin::OAuth::Model::RequestToken->new(current_user => Jifty::CurrentUser->superuser);
+        $request_token->load_by_cols(token => $oauth_params{token}, authorized => '');
+
+        if ($request_token->id) {
+            set consumer => $request_token->consumer;
+            set token    => $oauth_params{token};
+        }
+    }
+}
+
+=head2 authorize_post
+
+The user is submitting an AuthorizeRequestToken action
+
+=cut
+
+sub authorize_post {
+    my $result = Jifty->web->response->result("authorize_request_token");
+    unless ($result && $result->success) {
+        redirect '/oauth/authorize';
+    }
+
+    set result => $result;
+    show '/oauth/authorized';
+}
+
+=head2 access_token
+
+The consumer is trying to trade a request token for an access token
+
+=cut
+
+sub access_token {
+    my @params = qw/consumer_key signature_method signature
+                    timestamp nonce token version/;
+    set no_abort => 0;
+
+    my %oauth_params  = get_parameters(@params);
+    my $consumer      = get_consumer($oauth_params{consumer_key});
+    my $signature_key = get_signature_key($oauth_params{signature_method}, $consumer);
+    my ($ok, $msg) = $consumer->is_valid_request(@oauth_params{qw/timestamp nonce/});
+    abortmsg(401, $msg) if !$ok;
+
+    # is the request token they're using still valid?
+    my $request_token = Jifty::Plugin::OAuth::Model::RequestToken->new(current_user => Jifty::CurrentUser->superuser);
+    $request_token->load_by_cols(consumer => $consumer, token => $oauth_params{token});
+
+    abortmsg(401, "No token found for consumer ".$consumer->name." with key $oauth_params{token}") unless $request_token->id;
+
+    ($ok, $msg) = $request_token->can_trade_for_access_token;
+    abortmsg(401, "Cannot trade request token for access token: $msg") if !$ok;
+
+    # Net::OAuth::Request will die hard if it doesn't get everything it wants
+    my $request = eval { Net::OAuth::AccessTokenRequest->new(
+        request_url     => Jifty->web->url(path => '/oauth/access_token'),
+        request_method  => Jifty->handler->apache->method(),
+        consumer_secret => $consumer->secret,
+        token_secret    => $request_token->secret,
+        signature_key   => $signature_key,
+
+        map { $_ => $oauth_params{$_} } @params
+    ) };
+
+    abortmsg(400, "Unable to create AccessTokenRequest: $@") if $@ || !defined($request);
+
+    # make sure the signature matches the rest of what the consumer gave us
+    abortmsg(401, "Invalid signature (type: $oauth_params{signature_method}).") unless $request->verify;
+
+    my $token = Jifty::Plugin::OAuth::Model::AccessToken->new(current_user => Jifty::CurrentUser->superuser);
+
+    ($ok, $msg) = eval {
+        $token->create(consumer => $consumer,
+                       auth_as  => $request_token->authorized_by);
+    };
+
+    abortmsg(401, "Unable to create an Access Token: " . $@ || $msg)
+        if $@ || !defined($token) || !$ok;
+
+    $consumer->made_request(@oauth_params{qw/timestamp nonce/});
+    $request_token->set_used('t');
+
+    set oauth_response => {
+        oauth_token        => $token->token,
+        oauth_token_secret => $token->secret
+    };
+    show 'oauth/response';
+}
+
+=head2 try_oauth
+
+If this is a protected resource request, see if we can authorize the request
+with an access token.
+
+This is dissimilar to the other OAuth requests because if anything fails, you
+just don't set a current_user, and then the rest of the dispatcher rules will
+take care of it. Thus, failure is handled quite differently in this rule.  We
+try to abort as early as possible to make OAuth less of a hit on all requests.
+
+=cut
+
+sub try_oauth
+{
+    my @params = qw/consumer_key signature_method signature
+                    timestamp nonce token version/;
+    set no_abort => 1;
+    my %oauth_params  = get_parameters(@params);
+    for (@params) {
+        abortmsg(undef, "Undefined required parameter: $_"), return if !defined($oauth_params{$_});
+    }
+
+    my $consumer = get_consumer($oauth_params{consumer_key});
+    return if !$consumer->id;
+    abortmsg(undef, "No known consumer with key $oauth_params{consumer_key}"), return unless $consumer->id;
+
+    my $signature_key = get_signature_key($oauth_params{signature_method}, $consumer);
+    if ($signature_key && ref($signature_key) && !defined($$signature_key)) {
+        abortmsg(undef, "Failed to get signature key.");
+        return;
+    }
+
+    my ($ok, $msg) = $consumer->is_valid_request(@oauth_params{qw/timestamp nonce/});
+    abortmsg(undef, $msg), return if !$ok;
+
+    my $access_token = Jifty::Plugin::OAuth::Model::AccessToken->new(current_user => Jifty::CurrentUser->superuser);
+    $access_token->load_by_cols(consumer => $consumer, token => $oauth_params{token});
+
+    abortmsg(undef, "No token found for consumer ".$consumer->name." with key $oauth_params{token}"), return unless $access_token->id;
+
+    ($ok, $msg) = $access_token->is_valid;
+    abortmsg(undef, "Cannot access protected resources with this access token: $msg"), return if !$ok;
+
+    # Net::OAuth::Request will die hard if it doesn't get everything it wants
+    my $request = eval { Net::OAuth::ProtectedResourceRequest->new(
+        request_url     => Jifty->web->url(path => Jifty->web->request->path),
+        request_method  => Jifty->handler->apache->method(),
+        consumer_secret => $consumer->secret,
+        token_secret    => $access_token->secret,
+        signature_key   => $signature_key,
+
+        map { $_ => $oauth_params{$_} } @params
+    ) };
+
+    abortmsg(undef, "Unable to create ProtectedResourceRequest: $@"), return if $@ || !defined($request);
+
+    abortmsg(undef, "Invalid signature (type: $oauth_params{signature_method})."), return unless $request->verify;
+
+    $consumer->made_request(@oauth_params{qw/timestamp nonce/});
+    Jifty->web->current_user(Jifty->app_class('CurrentUser')->new(id => $access_token->auth_as));
+}
+
+=head2 get_consumer CONSUMER KEY
+
+Helper function to load a consumer by consumer key. Will abort if the key
+is unknown.
+
+=cut
+
+sub get_consumer {
+    my $key = shift;
+    my $consumer = Jifty::Plugin::OAuth::Model::Consumer->new(current_user => Jifty::CurrentUser->superuser);
+    $consumer->load_by_cols(consumer_key => $key);
+    abortmsg(401, "No known consumer with key $key") unless $consumer->id || get 'no_abort';
+    return $consumer;
+}
+
+=head2 get_signature_key SIGNATURE METHOD, CONSUMER
+
+Figures out the signature key for this consumer. Will abort if the signature
+method is unsupported, or if the consumer lacks the prerequisites for this
+signature method.
+
+Will return C<undef> if the signature key is consumer independent, as is the
+case for C<PLAINTEXT> and C<HMAC-SHA1>. C<RSA-SHA1> depends on the consumer
+having the C<rsa_key> field.
+
+If the signature method is invalid and no_abort is set, it will return a
+special value of a reference to undef. Yes this sucks but undef already has
+an important meaning.
+
+=cut
+
+{
+    my %valid_signature_methods = map { $_ => 1 }
+                                  qw/PLAINTEXT HMAC-SHA1 RSA-SHA1/;
+    my %key_field = ('RSA-SHA1' => 'rsa_key');
+
+    sub get_signature_key {
+        my ($method, $consumer) = @_;
+        if (!$valid_signature_methods{$method}) {
+            abortmsg(400, "Unsupported signature method requested: $method")
+                unless get 'no_abort';
+            return \undef;
+        }
+
+        my $field = $key_field{$method};
+
+        # this MUST return undef if the signature method requires no prior key
+        return undef if !defined($field);
+
+        my $key = $consumer->$field;
+
+        if (!defined $key) {
+            abortmsg(400, "Consumer does not have necessary field $field required for signature method $method") unless get 'no_abort';
+            return;
+        }
+
+        if ($method eq 'RSA-SHA1') {
+            $key = Crypt::OpenSSL::RSA->new_public_key($key);
+        }
+
+        return $key;
+    }
+}
+
+=head2 get_parameters REQUIRED PARAMETERS
+
+This will retrieve all the request paremeters. This gets parameters besides
+the ones in the OAuth spec, because the signature is based on all such request
+parameters.
+
+Pass in by name all the OAuth-required parameters. Do not include the C<oauth_>
+prefix.
+
+The precedence of parameters, from highest priority to lowest priority, is:
+
+=over 4
+
+=item Authorization header
+
+=item WWW-Authenticate header
+
+=item POST parameters
+
+=item GET parameters (aka URL's query string)
+
+=back
+
+=cut
+
+sub get_parameters {
+    my %p;
+
+    # XXX: Check Authorization header
+    # XXX: Check WWW-Authenticate header
+
+    my %params = Jifty->handler->apache->params();
+    for (@_) {
+        $p{$_} = delete $params{"oauth_$_"}
+            if !defined $p{$_};
+    }
+
+    $p{version} ||= '1.0';
+
+    unless (get 'no_abort') {
+        # check to see if there are any unsupported parameters
+        while (my ($key, undef) = each %params) {
+            abortmsg(400, "Unsupported parameter: $key")
+                if $key =~ /^oauth_/;
+        }
+
+        # check to see if we're missing anything
+        for (@_) {
+            abortmsg(400, "Undefined required parameter: $_")
+                if !defined($p{$_});
+        }
+
+        if ($p{timestamp} && $p{timestamp} !~ /^\d+$/) {
+            abortmsg(400, "Malformed timestamp. Expected positive integer, got $p{timestamp}");
+        }
+    }
+
+    return %p;
+}
+
+1;
+

Added: jifty/branches/js-refactor/lib/Jifty/Plugin/OAuth/Model/AccessToken.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/OAuth/Model/AccessToken.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,69 @@
+#!/usr/bin/env perl
+package Jifty::Plugin::OAuth::Model::AccessToken;
+use strict;
+use warnings;
+
+use base qw( Jifty::Plugin::OAuth::Token Jifty::Record );
+
+# kludge 1: you cannot call Jifty->app_class within schema {}
+# kludge 3: due to the loading order, you can't really do this
+#my $app_user;
+#BEGIN { $app_user = Jifty->app_class('Model', 'User') }
+
+use Jifty::DBI::Schema;
+use Jifty::Record schema {
+
+    # kludge 2: this kind of plugin cannot yet casually refer_to app models
+    column auth_as =>
+        type is 'integer';
+        #refers_to $app_user;
+
+    column valid_until =>
+        type is 'timestamp',
+        filters are 'Jifty::DBI::Filter::DateTime';
+
+    column token =>
+        type is 'varchar',
+        is required;
+
+    column secret =>
+        type is 'varchar',
+        is required;
+
+    column consumer =>
+        refers_to Jifty::Plugin::OAuth::Model::Consumer;
+
+};
+
+=head2 table
+
+AccessTokens are stored in the table C<oauth_access_tokens>.
+
+=cut
+
+sub table {'oauth_access_tokens'}
+
+=head2 is_valid
+
+This neatly encapsulates the "is this access token perfect?" check.
+
+This will return a (boolean, message) pair, with boolean indicating success
+(true means the token is good) and message indicating error (or another
+affirmation of success).
+
+=cut
+
+sub is_valid {
+    my $self = shift;
+
+    return (0, "Access token has no authorizing user")
+        if !$self->auth_as;
+
+    return (0, "Access token expired")
+        if $self->valid_until < DateTime->now;
+
+    return (1, "Request token valid");
+}
+
+1;
+

Added: jifty/branches/js-refactor/lib/Jifty/Plugin/OAuth/Model/Consumer.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/OAuth/Model/Consumer.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,125 @@
+#!/usr/bin/env perl
+package Jifty::Plugin::OAuth::Model::Consumer;
+use strict;
+use warnings;
+
+use base qw( Jifty::Record );
+
+use Jifty::DBI::Schema;
+use Jifty::Record schema {
+
+    # the unique key that identifies a consumer
+    column consumer_key =>
+        type is 'varchar',
+        is distinct,
+        is required;
+
+    # a secret used in signing to verify that we have the real consumer (and
+    # not just someone who got ahold of the key)
+    column secret =>
+        type is 'varchar',
+        is required;
+
+    # the name of the consumer, e.g. Bob's Social Network
+    column name =>
+        type is 'varchar',
+        is required;
+
+    # the url of the consumer, e.g. http://social.bob/
+    column url =>
+        type is 'varchar';
+
+    column rsa_key =>
+        type is 'varchar',
+        hints are 'This is only necessary if you want to support RSA-SHA1 signatures';
+
+    # we use these to make sure we aren't being hit with a replay attack
+    column last_timestamp =>
+        type is 'integer',
+        is required,
+        default is 0;
+
+    column nonces =>
+        type is 'blob',
+        filters are 'Jifty::DBI::Filter::Storable';
+};
+
+=head2 table
+
+Consumers are stored in the table C<oauth_consumers>.
+
+=cut
+
+sub table {'oauth_consumers'}
+
+=head2 before_set_last_timestamp
+
+If the new timestamp is different from the last_timestamp, then clear any
+nonces we've used. Nonces must only be unique for requests of a given
+timestamp.
+
+Note that you should ALWAYS call is_valid_request before updating the
+last_timestamp. You should also verify the signature and make sure the request
+all went through before updating the last_timestamp. Otherwise an attacker
+may be able to create a request with an extraordinarily high timestamp and
+screw up the regular consumer.
+
+=cut
+
+sub before_set_last_timestamp {
+    my $self = shift;
+    my $new_ts = shift;
+
+    # uh oh, looks like sloppy coding..
+    if ($new_ts < $self->last_timestamp) {
+        die "The new timestamp is LESS than the last timestamp. You forgot to call is_valid_request!";
+    }
+
+    # if this is a new timestamp, then flush the nonces
+    if ($new_ts != $self->last_timestamp) {
+        $self->set_nonces( {} );
+    }
+}
+
+=head2 is_valid_request TIMESTAMP, NONCE
+
+This will do some sanity checks (as required for security by the OAuth spec).
+It will make sure that the timestamp is not less than the latest timestamp for
+this consumer. It will also make sure that the nonce hasn't been seen for
+this timestamp (very important).
+
+ALWAYS call this method when handling OAuth requests. EARLY.
+
+=cut
+
+sub is_valid_request {
+    my ($self, $timestamp, $nonce) = @_;
+    return (0, "Timestamp nonincreasing.")
+        if $timestamp < $self->last_timestamp;
+    return 1 if $timestamp > $self->last_timestamp;
+
+    # if this is the same timestamp as the last, we must check that the nonce
+    # is unique across the requests of these timestamps
+    return (0, "Already used this nonce.")
+        if defined $self->nonces->{$nonce};
+
+    return 1;
+}
+
+=head2 made_request TIMESTAMP, NONCE
+
+This method is to be called just before you're done processing an OAuth
+request. Parameters were valid, no errors occurred, everything's generally
+hunky-dory. This updates the C<last_timestamp> of the consumer, and sets the
+nonce as "used" for this new timestamp.
+
+=cut
+
+sub made_request {
+    my ($self, $timestamp, $nonce) = @_;
+    $self->set_last_timestamp($timestamp);
+    $self->set_nonces({ %{$self->nonces}, $nonce => 1 });
+}
+
+1;
+

Added: jifty/branches/js-refactor/lib/Jifty/Plugin/OAuth/Model/RequestToken.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/OAuth/Model/RequestToken.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,100 @@
+#!/usr/bin/env perl
+package Jifty::Plugin::OAuth::Model::RequestToken;
+use strict;
+use warnings;
+
+use base qw( Jifty::Plugin::OAuth::Token Jifty::Record );
+
+# kludge 1: you cannot call Jifty->app_class within schema {}
+# kludge 3: due to the loading order, you can't really do this
+#my $app_user;
+#BEGIN { $app_user = Jifty->app_class('Model', 'User') }
+
+use Jifty::DBI::Schema;
+use Jifty::Record schema {
+
+    column valid_until =>
+        type is 'timestamp',
+        filters are 'Jifty::DBI::Filter::DateTime',
+        is required;
+
+    column authorized =>
+        type is 'boolean',
+        default is '';
+
+    # kludge 2: this kind of plugin cannot yet casually refer_to app models
+    column authorized_by =>
+        type is 'integer';
+        #refers_to $app_user;
+
+    column consumer =>
+        refers_to Jifty::Plugin::OAuth::Model::Consumer,
+        is required;
+
+    # kludge 3: Jifty::DBI + SQLite = poor boolean handling
+    # so the empty string is the false value, 't' is the true value
+    column used =>
+        type is 'boolean',
+        default is '';
+
+    column token =>
+        type is 'varchar',
+        is required;
+
+    column secret =>
+        type is 'varchar',
+        is required;
+
+};
+
+=head2 table
+
+RequestTokens are stored in the table C<oauth_request_tokens>.
+
+=cut
+
+sub table {'oauth_request_tokens'}
+
+=head2 after_set_authorized
+
+This will set the C<authorized_by> to the current user. It will also refresh
+the valid_until to be active for another hour.
+
+=cut
+
+sub after_set_authorized {
+    my $self = shift;
+    $self->set_authorized_by(Jifty->web->current_user->id);
+    $self->set_valid_until(DateTime->now->add(hours => 1));
+}
+
+=head2 can_trade_for_access_token
+
+This neatly encapsulates the "is this request token perfect?" check.
+
+This will return a (boolean, message) pair, with boolean indicating success
+(true means the token is good) and message indicating error (or another
+affirmation of success).
+
+=cut
+
+sub can_trade_for_access_token {
+    my $self = shift;
+
+    return (0, "Request token is not authorized")
+        if !$self->authorized;
+
+    return (0, "Request token does not have an authorizing user")
+        if !$self->authorized_by;
+
+    return (0, "Request token already used")
+        if $self->used;
+
+    return (0, "Request token expired")
+        if $self->valid_until < DateTime->now;
+
+    return (1, "Request token valid");
+}
+
+1;
+

Added: jifty/branches/js-refactor/lib/Jifty/Plugin/OAuth/Token.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/OAuth/Token.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,65 @@
+#!/usr/bin/env perl
+package Jifty::Plugin::OAuth::Token;
+use strict;
+use warnings;
+use Scalar::Util 'blessed';
+
+=head1 DESCRIPTION
+
+This just provides some helper methods for both token classes to use
+
+=cut
+
+=head2 generate_token
+
+This will create a randomly generated 20-character token for use as
+a request or access token. The string is hexadecimal.
+
+This does not check for uniqueness.
+
+=cut
+
+sub generate_token {
+    return join '', map { unpack('H2', chr(int rand 256)) } 1..10;
+}
+
+=head2 before_create
+
+This does some checks and provides some defaults.
+
+It tries a number of times to create a unique C<token> using C<generate_token>.
+If that fails, this method will DIE.
+
+It will also create a secret using C<generate_token>.
+
+Finally, it will create a default C<valid_until> of 1 hour from now.
+
+=cut
+
+sub before_create {
+    my ($self, $attr) = @_;
+
+    # attempt 20 times to create a unique token string
+    for (1..20) {
+        $attr->{token} = generate_token();
+        my $token = $self->new(current_user => Jifty::CurrentUser->superuser);
+        $token->load_by_cols(token => $attr->{token});
+        last if !$token->id;
+        delete $attr->{token};
+    }
+    if (!defined $attr->{token}) {
+        die "Failed 20 times to create a unique token. Giving up.";
+        return;
+    }
+
+    # generate a secret. need not be unique, just hard to guess
+    $attr->{secret} = generate_token();
+
+    # default the lifetime of this token to 1 hour
+    $attr->{valid_until} ||= DateTime->now->add(hours => 1);
+
+    return 1;
+}
+
+1;
+

Added: jifty/branches/js-refactor/lib/Jifty/Plugin/OAuth/View.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/OAuth/View.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,210 @@
+package Jifty::Plugin::OAuth::View;
+use strict;
+use warnings;
+
+use Jifty::View::Declare -base;
+
+=head1 NAME
+
+Jifty::Plugin::OAuth::View - Views for OAuth-ey bits
+
+=cut
+
+=head2 oauth/response
+
+Internal template. Do not use.
+
+It returns OAuth parameters to the consumer in the HTTP response body.
+
+=cut
+
+template 'oauth/response' => sub {
+    my $params = get 'oauth_response';
+    if (ref($params) eq 'HASH') {
+        outs_raw join '&',
+                 map { sprintf '%s=%s',
+                       map { Jifty->web->escape_uri($_) }
+                       $_, $params->{$_}
+                 } keys %$params;
+    }
+};
+
+=head2 oauth
+
+An OAuth description page very much geared towards Consumers, since they'll
+most likely be the only ones visiting yourapp.com/oauth
+
+=cut
+
+template 'oauth' => page {
+    p {
+        b { a { attr { href => "http://oauth.net/" } "OAuth" } };
+        outs " is an open protocol to allow secure authentication to users' private data."
+    }
+
+    p {
+        "This application supports OAuth. If you'd like to access the private resources of users of this site, you must first establish a Consumer Key, Consumer Secret, and, if applicable, RSA public key with us. You can do so by contacting " . (Jifty->config->framework('AdminEmail')||'us') . ".";
+    }
+
+    p {
+        "Once you have a Consumer Key and Consumer Secret, you may begin letting users grant you access to our site. The relevant URLs are:"
+    }
+
+    dl {
+        dt {
+            outs "Request a Request Token";
+            dd { Jifty->web->url(path => '/oauth/request_token') }
+        }
+        dt {
+            outs "Obtain user authorization for a Request Token";
+            dd { Jifty->web->url(path => '/oauth/authorize') }
+        }
+        dt {
+            outs "Exchange a Request Token for an Access Token";
+            dd { Jifty->web->url(path => '/oauth/access_token') }
+        }
+    }
+
+    p {
+        my $restful = 0;
+        for (@{ Jifty->config->framework('Plugins') }) {
+            if (defined $_->{REST}) {
+                $restful = 1;
+                last;
+            }
+        }
+
+        outs "While you have a valid access token, you may browse the site as the user normally does.";
+
+        if ($restful) {
+            outs " You may also use our REST interface. See ";
+            a {
+                attr { href => Jifty->web->url(path => '=/help') }
+                Jifty->web->url(path => '=/help')
+            }
+        }
+    }
+};
+
+=head2 oauth/authorize
+
+This is the page that Users see when authorizing a request token. It renders
+the "insert token here" textbox if the consumer didn't put the request token
+in the GET query, and (always) renders Allow/Deny buttons.
+
+=cut
+
+template 'oauth/authorize' => page { title => 'Someone wants stuff!' }
+content {
+    show '/oauth/help';
+
+    my $authorize = Jifty->web->new_action(
+        moniker => 'authorize_request_token',
+        class   => 'AuthorizeRequestToken',
+    );
+
+    Jifty->web->form->start();
+
+    # if the site put the token in the request, then use it
+    # otherwise, prompt the user for it
+    my %args;
+    my $token = get 'token';
+    if ($token) {
+        $args{token} = $token;
+    }
+    else {
+        $authorize->form_field('token')->render;
+    }
+
+    outs_raw $authorize->hidden(callback => get 'callback');
+
+    outs_raw($authorize->button(
+        label => 'Allow',
+        arguments => { %args, authorize => 'allow' },
+    ));
+
+    outs_raw($authorize->button(
+        label => 'Deny',
+        arguments => { %args, authorize => 'deny' },
+    ));
+
+    Jifty->web->form->end();
+};
+
+=head2 oauth/authorized
+
+Displayed after the user authorizes or denies a request token. Uses a link
+to the callback if provided, otherwise the site's URL.
+
+=cut
+
+template 'oauth/authorized' => page { title => 'XXX' }
+content {
+    my $result    = get 'result';
+    my $callback  = $result->content('callback');
+    my $token     = $result->content('token');
+    my $token_obj = $result->content('token_obj');
+
+    $callback ||= $token_obj->consumer->url;
+
+    if (!$callback) {
+        p { "Oops! " . $token_obj->consumer->name . " didn't tell us how to get you back to their service. If you do find your way back, you'll probably need this token: " . $token };
+    }
+    else {
+        $callback .= ($callback =~ /\?/ ? '&' : '?')
+                  .  'oauth_token='
+                  .  $token;
+        set consumer => $token_obj->consumer;
+
+        p {
+            outs 'To return to ';
+            show '/oauth/consumer';
+            outs ', ';
+            hyperlink(
+                label => 'click here',
+                url   => $callback,
+            );
+            outs '.';
+        };
+    }
+};
+
+=head2 oauth/help
+
+This provides a very, very layman description of OAuth for users
+
+=cut
+
+private template 'oauth/help' => sub {
+    div {
+        p {
+            show '/oauth/consumer';
+            outs ' is trying to access some of your data on this site. If you trust this application, you may grant it access. Note that access is read-only and will expire in one hour.';
+        }
+        p {
+            "If you're at all uncomfortable with the idea of someone rifling through your things, click Deny."
+        }
+    }
+};
+
+=head2 oauth/consumer
+
+Renders the consumer's name, and if available, its URL as a link.
+
+=cut
+
+private template 'oauth/consumer' => sub {
+    my $consumer = (get 'consumer') || 'Some application';
+
+    span {
+        outs ref($consumer) ? $consumer->name : $consumer;
+        if (ref($consumer) && $consumer->url) {
+            outs ' <';
+            a { attr { href => $consumer->url } $consumer->url };
+            outs ' >';
+        }
+    }
+};
+
+1;
+

Modified: jifty/branches/js-refactor/lib/Jifty/Plugin/REST/Dispatcher.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Plugin/REST/Dispatcher.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/REST/Dispatcher.pm	Thu Nov 29 12:25:16 2007
@@ -21,6 +21,7 @@
 
 before POST qr{^ (/=/ .*) ! (DELETE|PUT|GET|POST|OPTIONS|HEAD|TRACE|CONNECT) $}x => run {
     $ENV{REQUEST_METHOD} = $2;
+    $ENV{REST_REWROTE_METHOD} = 1;
     dispatch $1;
 };
 
@@ -97,11 +98,24 @@
 sub stringify {
     # XXX: allow configuration to specify model fields that are to be
     # expanded
-    no warnings 'uninitialized';
-    my @r = map { ref($_) && UNIVERSAL::isa($_, 'Jifty::Record')
-                             ? reference_to_data($_) :
-                  defined $_ ? '' . $_               : undef } @_;
-    return wantarray ? @r : pop @r;
+    my @r;
+
+    for (@_) {
+        if (UNIVERSAL::isa($_, 'Jifty::Record')) {
+            push @r, reference_to_data($_);
+        }
+        elsif (UNIVERSAL::isa($_, 'Jifty::DateTime')) {
+            push @r, _datetime_to_data($_);
+        }
+        elsif (defined $_) {
+            push @r, '' . $_; # force stringification
+        }
+        else {
+            push @r, undef;
+        }
+    }
+
+    return wantarray ? @r : $r[-1];
 }
 
 =head2 reference_to_data
@@ -124,6 +138,7 @@
 
   Jifty::DBI::Collection
   Jifty::DBI::Record
+  Jifty::DateTime
 
 =cut
 
@@ -133,6 +148,7 @@
     my %types = (
         'Jifty::DBI::Collection' => \&_collection_to_data,
         'Jifty::DBI::Record'     => \&_record_to_data,
+        'Jifty::DateTime'        => \&_datetime_to_data,
     );
 
     for my $type ( keys %types ) {
@@ -167,6 +183,46 @@
     return \%data;
 }
 
+sub _datetime_to_data {
+    my $dt = shift;
+
+    # if it looks like just a date, then return just the date portion
+    return $dt->ymd
+        if lc($dt->time_zone->name) eq 'floating'
+        && $dt->hms('') eq '000000';
+
+    # otherwise let stringification take care of it
+    return $dt;
+}
+
+=head2 recurse_object_to_data REF
+
+Takes a reference, and calls C<object_to_data> on it if that is
+meaningful.  If it is an arrayref, or recurses on each element.  If it
+is a hashref, recurses on each value.  Returns the new datastructure.
+
+=cut
+
+sub recurse_object_to_data {
+    my $o = shift;
+    return $o unless ref $o;
+
+    my $updated = object_to_data($o);
+    if ($o ne $updated) {
+        return $updated;
+    } elsif (ref $o eq "ARRAY") {
+        my @a = map {recurse_object_to_data($_)} @{$o};
+        return \@a;
+    } elsif (ref $o eq "HASH") {
+        my %h;
+        $h{$_} = recurse_object_to_data($o->{$_}) for keys %{$o};
+        return \%h;
+    } else {
+        return $o;
+    }
+}
+
+
 =head2 list PREFIX items
 
 Takes a URL prefix and a set of items to render. passes them on.
@@ -693,6 +749,7 @@
     eval { $action->run };
 
     if ($@) {
+        Jifty->log->warn($@);
         abort(500);
     }
 
@@ -718,11 +775,7 @@
     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} );
-    }
+    $out->{content} = recurse_object_to_data($result->content);
     
     outs(undef, $out);
 

Modified: jifty/branches/js-refactor/lib/Jifty/Plugin/SinglePage.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Plugin/SinglePage.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/SinglePage.pm	Thu Nov 29 12:25:16 2007
@@ -2,7 +2,7 @@
 use warnings;
 
 package Jifty::Plugin::SinglePage;
-use base qw/Jifty::Plugin Class::Accessor/;
+use base 'Jifty::Plugin';
 
 __PACKAGE__->mk_accessors(qw(region_name));
 
@@ -22,7 +22,12 @@
 
 sub init {
     my $self = shift;
+    return if $self->_pre_init;
+
     Jifty::Web::Form::Clickable->add_trigger( before_new => _sp_link($self));
+    Jifty::Web::Form::Clickable->add_trigger( name      => 'before_state_variable',
+                                              callback  => _filter_page_region_vars($self),
+                                              abortable => 1 );
     my %opt = @_;
     $self->region_name($opt{region_name} || '__page');
 }
@@ -35,19 +40,36 @@
     push @{$args->{onclick}}, @_ if @_;
 }
 
+sub _filter_page_region_vars {
+    my $self = shift;
+    return sub {
+        my ( $clickable, $key, $value ) = @_;
+        if ($key eq 'region-'.$self->region_name || $key =~ m/^region-\Q$self->{region_name}\E\./) {
+            return 0;
+        }
+        return 1;
+    }
+}
+
 sub _sp_link {
     my $self = shift;
     return sub {
         my ( $clickable, $args ) = @_;
         my $url = $args->{'url'};
-        if ( $url && $url !~ m/^#/ && $url !~ m{^https?://} ) {
+        if ( $url && $url !~ m/^#/ && $url !~ m{^https?://} && $url !~ m{^javascript:} ) {
             $self->_push_onclick($args, {
                 region       => $self->region_name,
                 replace_with => $url,
-                args         => $args->{parameters}});
+                args         => { %{$args->{parameters}}} });
         }
-        elsif (exists $args->{submit}) {
-            $self->_push_onclick($args, { refresh_self => 1, submit => $args->{submit} });
+        elsif (exists $args->{submit} && !$args->{onclick}) {
+	    if ($args->{_form} && $args->{_form}{submit_to}) {
+		my $to = $args->{_form}{submit_to};
+		$self->_push_onclick($args, { beforeclick => qq{return _sp_submit_form(this, event, "$to");} });
+	    }
+	    else {
+		$self->_push_onclick($args, { refresh_self => 1, submit => $args->{submit} });
+	    }
             $args->{as_button} = 1;
         }
         if (my $form = delete $args->{_form}) {

Modified: jifty/branches/js-refactor/lib/Jifty/Plugin/SkeletonApp/Dispatcher.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Plugin/SkeletonApp/Dispatcher.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/SkeletonApp/Dispatcher.pm	Thu Nov 29 12:25:16 2007
@@ -20,7 +20,12 @@
 
 on '**' => run {
     my $top = Jifty->web->navigation;
-    $top->child( Home => url => "/", sort_order => 1, label => _('Home') );
+
+    # Do not override the 'Home' menu item if the app had set it already
+    unless ( $top->child('Home') ) {
+        $top->child( Home => url => "/", sort_order => 1, label => _('Home') );
+    }
+
     if ( Jifty->config->framework('AdminMode') ) {
         $top->child(
             Administration =>
@@ -32,5 +37,10 @@
     return ();
 };
 
+before '**' => run {
+    if (my $lang = Jifty->web->request->arguments->{_jifty_lang}) {
+        Jifty->web->session->set(jifty_lang => $lang);
+    }
+};
 
 1;

Modified: jifty/branches/js-refactor/lib/Jifty/Plugin/SkeletonApp/View.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Plugin/SkeletonApp/View.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/SkeletonApp/View.pm	Thu Nov 29 12:25:16 2007
@@ -60,8 +60,7 @@
         title { _($title) };
         Jifty->web->include_css;
         Jifty->web->include_javascript;
-      };
-
+    };
 };
 
 private template 'heading_in_wrapper' => sub {

Modified: jifty/branches/js-refactor/lib/Jifty/Plugin/TabView/View.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Plugin/TabView/View.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/TabView/View.pm	Thu Nov 29 12:25:16 2007
@@ -19,6 +19,12 @@
   template 'index.html' => page {
      my $self = shift;
      $self->render_tabs('foo', [qw(id)], qw( foo bar_tab ) );
+
+     # more flexible tabs
+     $self->render_tabs('foo', [qw(id)],
+                        { label => 'This is foo 1', path => 'foo', name => 'foo 1', args => { id => 1}},
+                        { label => 'This is foo 2', path => 'foo', name => 'foo 2', defer => 1,  args => { id => 2}});
+
   };
   template 'foo' => sub { ... };
   template 'bar' => sub { ... };
@@ -31,6 +37,15 @@
 
 =cut
 
+sub _tab_path {
+    my ($self, $name) = @_;
+   
+    # If the path is already relative or absolute, don't force-relativeize it
+    my $qualified_path = ($name =~ qr|\.?/|) ? $name : "./$name";
+
+    $self->can('fragment_for') ? $self->fragment_for($name) : $qualified_path;
+}
+
 sub render_tabs {
     my ($self, $divname, $args, @tabs) = @_;
 
@@ -38,35 +53,45 @@
 	var myTabs = new YAHOO.widget.TabView("$divname");
 	</script>'  );
 
+    @tabs = map { ref($_) ? $_
+		      : do {
+			  my $path = $_;
+			  my $defer = $path =~ s/_tab$//;
+			  { path => $path,
+			    defer => $defer };
+			  }
+		  } @tabs;
+
+    $_->{name} ||= $_->{path}, $_->{label} ||= $_->{path} for @tabs;
 
     div { { id is $divname, class is 'yui-navset'}
 	  ul { { class is 'yui-nav'};
 	       my $i = 0;
-	       for (@tabs) {
-		   my $tab = $_;
+	       for my $tab (@tabs) {
 		   li { { class is 'selected' unless $i };
-			hyperlink(url => '#tab'.++$i, label => $tab,
-				  $tab =~ s/_tab$// ? 
+			hyperlink(url => '#tab'.++$i, label => $tab->{label},
+				  $tab->{defer} ?
 				  (onclick =>
-				  { region       => Jifty->web->current_region ? Jifty->web->current_region->qualified_name."-$tab-tab" : "$tab-tab",
-				    replace_with => $self->can('fragment_for') ? $self->fragment_for($tab) : $tab, # XXX: should have higher level function handling mount point
-				    args => { map { $_ => get($_)} @$args },
+				  { region       => Jifty->web->current_region ? Jifty->web->current_region->qualified_name."-$tab->{name}-tab" : "$tab->{path}-tab",
+				    replace_with => _tab_path($self, $tab->{path}), # XXX: should have higher level function handling mount point
+				    args => { (map { $_ => get($_)} @$args ), %{$tab->{args} || {}} },
 				  }) : ()
 				 ) }
 	       }
 	   };
 	  div { {class is 'yui-content' };
-      my $default_shown;
-		for (@tabs) {
-		    div { 
-			if (s/_tab$//) {
-			    render_region(name => $_.'-tab', 
-                          ($default_shown++)? () : ( path => $_, args =>  { map { $_ => get($_)} @$args })
+		my $default_shown;
+		for my $tab (@tabs) {
+		    div {
+			if ($tab->{defer}) {
+			    render_region(name => $tab->{name}.'-tab',
+                          ($default_shown++)? () : ( path => _tab_path($self, $tab->{path}),
+						     force_arguments => { ( map { $_ => get($_)} @$args ), %{$tab->{args} || {}} } )
                           )
 			}
 			else {
-			    die "$self $_" unless $self->has_template($_);
-			    $self->has_template($_)->(); 
+			    show( _tab_path($self, $tab->{path}) );
+			    $default_shown++;
 			}
 		    }
 		}

Modified: jifty/branches/js-refactor/lib/Jifty/Plugin/UUID.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Plugin/UUID.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/UUID.pm	Thu Nov 29 12:25:16 2007
@@ -27,7 +27,7 @@
 use Scalar::Defer;
 my $UUID_GEN = Data::UUID->new();
 
-my $UUID = lazy { $UUID_GEN->create_str() } ;
+my $UUID = defer { $UUID_GEN->create_str() } ;
 sub _uuid {
     my ($column, $from) = @_;
     $column->readable(1);

Modified: jifty/branches/js-refactor/lib/Jifty/Plugin/UUID/Widget.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Plugin/UUID/Widget.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/UUID/Widget.pm	Thu Nov 29 12:25:16 2007
@@ -12,14 +12,15 @@
 
 =head1 METHODS
 
+=head2 accessors
 
-=cut
+Overrides L<Jifty::Web::Form::Field/accessors>.
 
-sub accessors { shift->SUPER::accessors() };
+=cut
 
 =head2 render_widget
 
-Renders form fields as a uuid;
+Renders form fields as a UUID.
 
 =cut
 
@@ -29,7 +30,7 @@
     my $action   = $self->action;
     my $readonly = 1;
     
-        my $name = $self->name;
+    my $name = $self->name;
     if ( $action->record->$name() ) {
         Jifty->web->out(  $action->record->$name() );
     } else { 
@@ -39,5 +40,19 @@
     '';
 }
 
+=head1 SEE ALSO
+
+L<Jifty::Plugin::UUID>, L<Jifty::Web::Form::Field>
+
+=head1 AUTHOR
+
+Jesse Vincent
+
+=head1 LICENSE
+
+Jifty is Copyright 2005-2007 Best Practical Solutions, LLC.
+Jifty is distributed under the same terms as Perl itself.
+
+=cut
 
 1;

Modified: jifty/branches/js-refactor/lib/Jifty/Plugin/Userpic/Dispatcher.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Plugin/Userpic/Dispatcher.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/Userpic/Dispatcher.pm	Thu Nov 29 12:25:16 2007
@@ -1,7 +1,31 @@
+use warnings;
+use strict;
+
 package Jifty::Plugin::Userpic::Dispatcher;
 
+
+=head1 NAME
+
+Jifty::Plugin::Userpic::Dispatcher
+
+=head1 DESCRIPTION
+
+The dispatcher for the Jifty Userpic plugin
+
+=cut
+
 use Jifty::Dispatcher -base;
 
+
+=head1 RULES
+
+=head2 on /=/plugin/userpic/*/#/*
+
+When we're asked for a userpic for /recordclass/id/fieldname,  set it up and call /userpic/image.
+
+=cut
+
+
 on '/=/plugin/userpic/*/#/*' => run {
     my $class = $1;
     my $id = $2;

Modified: jifty/branches/js-refactor/lib/Jifty/Plugin/Userpic/View.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Plugin/Userpic/View.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/Userpic/View.pm	Thu Nov 29 12:25:16 2007
@@ -2,6 +2,25 @@
 
 use Jifty::View::Declare -base;
 
+
+=head1 NAME
+
+Jifty::Plugin::Userpic::View
+
+=head1 DESCRIPTION
+
+A view package for the Userpic plugin.
+
+
+=head1 TEMPLATES
+
+
+=head2 userpic/image
+
+Outputs the image stored in 'field' of 'item'. (Both of those are set by the dispatcher)
+
+=cut
+
 template 'userpic/image' => sub {
     my ($item,$field) = get(qw(item field));
     Jifty->handler->apache->content_type("image/jpeg");

Modified: jifty/branches/js-refactor/lib/Jifty/Plugin/Userpic/Widget.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Plugin/Userpic/Widget.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Plugin/Userpic/Widget.pm	Thu Nov 29 12:25:16 2007
@@ -14,8 +14,6 @@
 
 =cut
 
-sub accessors { shift->SUPER::accessors() };
-
 =head2 render_widget
 
 Renders form fields as googlemap widget.

Modified: jifty/branches/js-refactor/lib/Jifty/Record.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Record.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Record.pm	Thu Nov 29 12:25:16 2007
@@ -16,16 +16,16 @@
 
 =cut
 
-use base qw(Jifty::Object Jifty::DBI::Record Class::Accessor::Fast);
+use base qw( Jifty::Object Jifty::DBI::Record);
+
 __PACKAGE__->mk_accessors('_is_readable');
 
 sub _init {
     my $self = shift;
     my %args = (@_);
     $self->_get_current_user(%args);
-    
-    $self->SUPER::_init(@_);
 
+    $self->SUPER::_init(@_);
 }
 
 =head1 METHODS
@@ -36,8 +36,8 @@
 
 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.
+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:
 
@@ -121,9 +121,8 @@
 
 =cut
 
-sub _primary_key { 'id' }
-sub id { $_[0]->{'values'}->{'id'} }
-
+sub _primary_key {'id'}
+sub id           { $_[0]->{'values'}->{'id'} }
 
 =head2 load_or_create
 
@@ -136,8 +135,8 @@
 sub load_or_create {
     my $class = shift;
     my $self;
-    if (ref($class)) {
-       ($self,$class) = ($class,undef); 
+    if ( ref($class) ) {
+        ( $self, $class ) = ( $class, undef );
     } else {
         $self = $class->new();
     }
@@ -149,23 +148,109 @@
         return $self->create(%args);
     }
 
-    return ($id,$msg);
+    return ( $id, $msg );
+}
+
+=head2 as_create_action PARAMHASH
+
+Returns the L<Jifty::Action::Record::Create> action for this model
+class.
+
+The PARAMHASH allows you to add additional parameters to pass to
+L<Jifty::Web/new_action>.
+
+=cut
+
+sub _action_from_record {
+    my $self  = shift;
+    my $verb  = shift;
+    my $class = ref $self || $self;
+    $class =~ s/::Model::/::Action::$verb/;
+    return $class;
+}
+
+sub as_create_action {
+    my $self         = shift;
+    my $action_class = $self->_action_from_record('Create');
+    return Jifty->web->new_action( class => $action_class, @_ );
+}
+
+=head2 as_update_action PARAMHASH
+
+Returns the L<Jifty::Action::Record::Update> action for this model
+class. The current record is passed to the constructor.
+
+The PARAMHASH allows you to add additional parameters to pass to
+L<Jifty::Web/new_action>.
+
+=cut
+
+sub as_update_action {
+    my $self         = shift;
+    my $action_class = $self->_action_from_record('Update');
+    return Jifty->web->new_action(
+        class  => $action_class,
+        record => $self,
+        @_,
+    );
+}
+
+=head2 as_delete_action PARAMHASH
+
+Returns the L<Jifty::Action::Record::Delete> action for this model
+class. The current record is passed to the constructor.
+
+The PARAMHASH allows you to add additional parameters to pass to
+L<Jifty::Web/new_action>.
+
+=cut
+
+sub as_delete_action {
+    my $self         = shift;
+    my $action_class = $self->_action_from_record('Delete');
+    return Jifty->web->new_action(
+        class  => $action_class,
+        record => $self,
+        @_,
+    );
+}
+
+=head2 as_search_action PARAMHASH
+
+Returns the L<Jifty::Action::Record::Search> action for this model
+class.
+
+The PARAMHASH allows you to add additional parameters to pass to
+L<Jifty::Web/new_action>.
+
+=cut
+
+sub as_search_action {
+    my $self         = shift;
+    my $action_class = $self->_action_from_record('Search');
+    return Jifty->web->new_action(
+        class => $action_class,
+        @_,
+    );
 }
 
 =head2 _guess_table_name
 
-Guesses a table name based on the class's last part. In addition to the work performed in L<Jifty::DBI::Record>, this method also prefixes the table name with the plugin table prefix, if the model belongs to a plugin.
+Guesses a table name based on the class's last part. In addition to
+the work performed in L<Jifty::DBI::Record>, this method also prefixes
+the table name with the plugin table prefix, if the model belongs to a
+plugin.
 
 =cut
 
 sub _guess_table_name {
-    my $self = shift;
+    my $self  = shift;
     my $table = $self->SUPER::_guess_table_name;
 
     # Add plugin table prefix if a plugin model
     my $class = ref($self) ? ref($self) : $self;
     my $app_plugin_root = Jifty->app_class('Plugin');
-    if ($class =~ /^(?:Jifty::Plugin::|$app_plugin_root)/) {
+    if ( $class =~ /^(?:Jifty::Plugin::|$app_plugin_root)/ ) {
 
         # Guess the plugin class name
         my $plugin_class = $class;
@@ -175,13 +260,14 @@
         my ($plugin) = grep { ref $_ eq $plugin_class } Jifty->plugins;
 
         # Add the prefix if found
-        if (defined $plugin) {
+        if ( defined $plugin ) {
             $table = $plugin->table_prefix . $table;
         }
 
         # Uh oh. Warn, but try to keep going.
         else {
-            warn "Model $class looks like a plugin model, but $plugin_class could not be found.";
+            warn
+                "Model $class looks like a plugin model, but $plugin_class could not be found.";
         }
     }
 
@@ -219,7 +305,8 @@
 
 =back
 
-Models wishing to customize authorization checks should override this method. You can do so like this:
+Models wishing to customize authorization checks should override this
+method. You can do so like this:
 
   sub current_user_can {
       my ($self, $right, %args) = @_;
@@ -232,7 +319,9 @@
       return $self->SUPER::current_user_can($right, %args);
   }
 
-If you are sure you don't want your model to fallback using the default implementation, you can replace the last line with whatever fallback policy required.
+If you are sure you don't want your model to fallback using the
+default implementation, you can replace the last line with whatever
+fallback policy required.
 
 =head3 Authorization steps
 
@@ -242,24 +331,25 @@
 
 =item 1.
 
-If the C<SkipAccessControl> setting is set to a true value in the framework configuration section of F<etc/config.yml>, C<current_user_can> always returns true.
+If the C<SkipAccessControl> setting is set to a true value in the
+framework configuration section of F<etc/config.yml>,
+C<current_user_can> always returns true.
 
 =item 2.
 
-The method first attempts to call the C<before_access> hooks to check for any
-allow or denial. See L</The before_access hook>.
+The method first attempts to call the C<before_access> hooks to check
+for any allow or denial. See L</The before_access hook>.
 
 =item 3.
 
 Next, the default implementation returns true if the current user is a
-superuser or a boostrap user.  
+superuser or a boostrap user.
 
 =item 4.
 
 Then, if the model can perform delegation, usually by using
-L<Jifty::RightsFrom>, the access control decision is deferred to another object
-(via the C<delegate_current_user_can>
-subroutine).  
+L<Jifty::RightsFrom>, the access control decision is deferred to
+another object (via the C<delegate_current_user_can> subroutine).
 
 =item 5.
 
@@ -269,7 +359,9 @@
 
 =head3 The before_access hook
 
-This implementation may make use of a trigger called C<before_access> to make the decision. A new handler can be added to the trigger point by calling C<add_handler>:
+This implementation may make use of a trigger called C<before_access>
+to make the decision. A new handler can be added to the trigger point
+by calling C<add_handler>:
 
   $record->add_trigger(
       name => 'before_access',
@@ -277,15 +369,21 @@
       abortable => 1,
   );
 
-The C<before_access> handler will be passed the same arguments that were used to call C<current_user_can>, including the current record object, the operation being checked, and any arguments being passed to the operation.
-
-The C<before_access> handler should return one of three strings: C<'deny'>, C<'allow'>, or C<'ignore'>. The C<current_user_can> implementation reacts as follows to these results:
+The C<before_access> handler will be passed the same arguments that
+were used to call C<current_user_can>, including the current record
+object, the operation being checked, and any arguments being passed to
+the operation.
+
+The C<before_access> handler should return one of three strings:
+C<'deny'>, C<'allow'>, or C<'ignore'>. The C<current_user_can>
+implementation reacts as follows to these results:
 
 =over
 
 =item 1.
 
-If a handler is abortable and aborts by returning a false value (such as C<undef>), C<current_user_can> returns false.
+If a handler is abortable and aborts by returning a false value (such
+as C<undef>), C<current_user_can> returns false.
 
 =item 2.
 
@@ -293,11 +391,14 @@
 
 =item 3.
 
-If any handler returns 'allow' and no handler returns 'deny', C<current_user_can> returns true.
+If any handler returns 'allow' and no handler returns 'deny',
+C<current_user_can> returns true.
 
 =item 4.
 
-In all other cases, the results of the handlers are ignored and C<current_user_can> proceeds to check using superuser, bootstrap, and delegation.
+In all other cases, the results of the handlers are ignored and
+C<current_user_can> proceeds to check using superuser, bootstrap, and
+delegation.
 
 =back
 
@@ -306,38 +407,38 @@
 sub current_user_can {
     my $self  = shift;
     my $right = shift;
-    
+
     # Turn off access control for the whole application
-    if (Jifty->config->framework('SkipAccessControl')) {
-	    return 1;	
+    if ( Jifty->config->framework('SkipAccessControl') ) {
+        return 1;
     }
 
     my $hook_status = $self->call_trigger( before_access => $right, @_ );
 
     # If not aborted...
-    if (defined $hook_status) {
+    if ( defined $hook_status ) {
 
         # Compile the handler results
         my %results;
-        $results{ $_->[0] }++ for (@{ $self->last_trigger_results });
+        $results{ $_->[0] }++ for ( @{ $self->last_trigger_results } );
 
         # Deny always takes precedent
-        if ($results{deny}) {
+        if ( $results{deny} ) {
             return 0;
         }
 
         # Then allow...
-        elsif ($results{allow}) {
+        elsif ( $results{allow} ) {
             return 1;
         }
-       
+
         # Otherwise, no instruction from the handlers, move along...
     }
 
     # Abort! Return false for safety
     else {
         return 0;
-    } 
+    }
 
     if (   $self->current_user->is_bootstrap_user
         or $self->current_user->is_superuser )
@@ -345,12 +446,11 @@
         return (1);
     }
 
-    
-    if ($self->can('delegate_current_user_can')) {
-        return $self->delegate_current_user_can($right, @_); 
+    if ( $self->can('delegate_current_user_can') ) {
+        return $self->delegate_current_user_can( $right, @_ );
     }
 
-    unless ( $self->current_user->isa( 'Jifty::CurrentUser' ) ) {
+    unless ( $self->current_user->isa('Jifty::CurrentUser') ) {
         $self->log->error(
             "Hm. called to authenticate without a currentuser - "
                 . $self->current_user );
@@ -366,8 +466,7 @@
 
 =cut
 
-sub check_create_rights { return shift->current_user_can('create', @_) }
-
+sub check_create_rights { return shift->current_user_can( 'create', @_ ) }
 
 =head2 check_read_rights
 
@@ -389,8 +488,7 @@
 
 =cut
 
-sub check_update_rights { return shift->current_user_can('update', @_) } 
-
+sub check_update_rights { return shift->current_user_can( 'update', @_ ) }
 
 =head2 check_delete_rights
 
@@ -398,24 +496,22 @@
 
 =cut
 
-sub check_delete_rights { return shift->current_user_can('delete', @_) }
-
+sub check_delete_rights { return shift->current_user_can( 'delete', @_ ) }
 
 sub _set {
     my $self = shift;
 
-    unless ($self->check_update_rights(@_)) {
-        return (0, _('Permission denied'));
+    unless ( $self->check_update_rights(@_) ) {
+        return ( 0, _('Permission denied') );
     }
     $self->SUPER::_set(@_);
 }
 
-    
 sub _value {
-    my $self = shift;
+    my $self   = shift;
     my $column = shift;
 
-    unless ($self->check_read_rights( $column => @_ )) {
+    unless ( $self->check_read_rights( $column => @_ ) ) {
         return (undef);
     }
     my $value = $self->SUPER::_value( $column => @_ );
@@ -425,53 +521,34 @@
     $value;
 }
 
+=head2 as_user CurrentUser
 
-=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.
+Returns a copy of this object with the current_user set to the given
+current_user. This is a way to act on behalf of a particular user (perhaps the
+owner of the object)
 
 =cut
 
-sub as_superuser {
+sub as_user {
     my $self = shift;
+    my $user = shift;
 
-    my $clone = $self->new(current_user => $self->current_user->superuser);
-    $clone->load($self->id);
+    my $clone = $self->new( current_user => $user );
+    $clone->load( $self->id );
     return $clone;
 }
 
+=head2 as_superuser
 
-=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.
+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 _collection_value {
+sub as_superuser {
     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;
+    return $self->as_user( $self->current_user->superuser );
 }
 
 =head2 delete PARAMHASH
@@ -482,11 +559,11 @@
 
 sub delete {
     my $self = shift;
-    unless ($self->check_delete_rights(@_)) {
-            Jifty->log->logcluck("Permission denied");
-            return(0, _('Permission denied'));
-        }
-    $self->SUPER::delete(@_); 
+    unless ( $self->check_delete_rights(@_) ) {
+        Jifty->log->logcluck("Permission denied");
+        return ( 0, _('Permission denied') );
+    }
+    $self->SUPER::delete(@_);
 }
 
 =head2 brief_description
@@ -496,48 +573,54 @@
 =cut
 
 sub brief_description {
-    my $self = shift;
+    my $self   = shift;
     my $method = $self->_brief_description;
     return $self->$method;
 }
 
 =head2 _brief_description
 
-When displaying a list of records, Jifty can display a friendly value 
+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.
+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
+=head2 null_reference
 
-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>.
+By default, L<Jifty::DBI::Record> returns C<undef> on non-existant
+related fields; Jifty prefers to get back an object with an undef id.
 
 =cut
 
-sub _to_record {
-    my $self  = shift;
-    my $column_name = shift;
-    my $value = shift;
+sub null_reference { 0 }
+
+=head2 _new_collection_args
+
+Overrides the default arguments which this collection passes to new
+collections, to pass the C<current_user>.
+
+=cut
+
+sub _new_collection_args {
+    my $self = shift;
+    return ( current_user => $self->current_user );
+}
+
+=head2 _new_record_args
 
-    my $column = $self->column($column_name);
-    my $classname = $column->refers_to();
+Overrides the default arguments which this collection passes to new
+records, to pass the C<current_user>.
 
-    return undef unless $classname;
-    return unless $classname->isa( 'Jifty::Record' );
+=cut
 
-    # 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;
+sub _new_record_args {
+    my $self = shift;
+    return ( current_user => $self->current_user );
 }
 
 =head2 cache_key_prefix
@@ -547,7 +630,6 @@
 
 =cut
 
-
 sub cache_key_prefix {
     Jifty->config->framework('Database')->{'Database'};
 }
@@ -559,21 +641,20 @@
 }
 
 =head2 since
- 
-By default, all models exist since C<undef>, the ur-time when the application was created. Please override it for your model class.
- 
-=cut
- 
 
+By default, all models exist since C<undef>, the ur-time when the
+application was created. Please override it for your model 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.
+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;
 
@@ -583,8 +664,9 @@
 
 =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.
+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
 
@@ -617,9 +699,10 @@
     $ret or die "error removing table $self: " . $ret->error_message;
 }
 
-sub _make_schema { 
+sub _make_schema {
     my $class = shift;
 
+    require Jifty::DBI::SchemaGenerator;
     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 );
@@ -630,7 +713,8 @@
 
 =head2 add_column_sql column_name
 
-Returns the SQL statement necessary to add C<column_name> to this class's representation in the database
+Returns the SQL statement necessary to add C<column_name> to this
+class's representation in the database
 
 =cut
 
@@ -639,11 +723,11 @@
     my $column_name = shift;
 
     my $col        = $self->column($column_name);
-    my $definition = $self->_make_schema()->column_definition_sql($self->table => $col->name);
+    my $definition = $self->_make_schema()
+        ->column_definition_sql( $self->table => $col->name );
     return "ALTER TABLE " . $self->table . " ADD COLUMN " . $definition;
 }
 
-
 =head2 add_column_in_db column_name
 
 Executes the SQL code generated by add_column_sql. Dies on failure.
@@ -652,14 +736,19 @@
 
 sub add_column_in_db {
     my $self = shift;
-        my $ret = Jifty->handle->simple_query($self->add_column_sql(@_));
-        $ret or die "error adding column ". $_[0] ." to  $self: " . $ret->error_message;
+    my $ret  = Jifty->handle->simple_query( $self->add_column_sql(@_) );
+    $ret
+        or die "error adding column "
+        . $_[0]
+        . " to  $self: "
+        . $ret->error_message;
 
 }
 
 =head2 drop_column_sql column_name
 
-Returns the SQL statement necessary to remove C<column_name> from this class's representation in the database
+Returns the SQL statement necessary to remove C<column_name> from this
+class's representation in the database
 
 =cut
 
@@ -671,7 +760,6 @@
     return "ALTER TABLE " . $self->table . " DROP COLUMN " . $col->name;
 }
 
-
 =head2 drop_column_in_db column_name
 
 Executes the SQL code generated by drop_column_sql. Dies on failure.
@@ -680,14 +768,20 @@
 
 sub drop_column_in_db {
     my $self = shift;
-        my $ret = Jifty->handle->simple_query($self->drop_column_sql(@_));
-        $ret or die "error dropping column ". $_[0] ." to  $self: " . $ret->error_message;
+    my $ret  = Jifty->handle->simple_query( $self->drop_column_sql(@_) );
+    $ret
+        or die "error dropping column "
+        . $_[0]
+        . " to  $self: "
+        . $ret->error_message;
 
 }
 
 =head2 schema_version
 
-This method is used by L<Jifty::DBI::Record> to determine which schema version is in use. It returns the current database version stored in the configuration.
+This method is used by L<Jifty::DBI::Record> to determine which schema
+version is in use. It returns the current database version stored in
+the configuration.
 
 Jifty's notion of the schema version is currently broken into two:
 
@@ -695,11 +789,14 @@
 
 =item 1.
 
-The Jifty version is the first. In the case of models defined by Jifty itself, these use the version found in C<$Jifty::VERSION>.
+The Jifty version is the first. In the case of models defined by Jifty
+itself, these use the version found in C<$Jifty::VERSION>.
 
 =item 2.
 
-Any model defined by your application use the database version declared in the configuration. In F<etc/config.yml>, this is lcoated at:
+Any model defined by your application use the database version
+declared in the configuration. In F<etc/config.yml>, this is lcoated
+at:
 
   framework:
     Database:
@@ -707,15 +804,17 @@
 
 =back
 
-A model is considered to be defined by Jifty if it the package name starts with "Jifty::". Otherwise, it is assumed to be an application model.
+A model is considered to be defined by Jifty if it the package name
+starts with "Jifty::". Otherwise, it is assumed to be an application
+model.
 
 =cut
 
 sub schema_version {
     my $class = shift;
-    
+
     # Return the Jifty schema version
-    if ($class =~ /^Jifty::Model::/) {
+    if ( $class =~ /^Jifty::Model::/ ) {
         return $Jifty::VERSION;
     }
 
@@ -729,4 +828,3 @@
 }
 
 1;
-

Modified: jifty/branches/js-refactor/lib/Jifty/Request.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Request.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Request.pm	Thu Nov 29 12:25:16 2007
@@ -122,10 +122,10 @@
 
     # 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)});
+        if ($ct =~ m{^text/x-json}) {
+            return $self->from_data_structure(eval{Jifty::JSON::jsonToObj($data)}, $cgi);
+        } elsif ($ct =~ m{^text/x-yaml}) {
+            return $self->from_data_structure(eval{Jifty::YAML::Load($data)}, $cgi);
         }
     }
 
@@ -146,48 +146,71 @@
 sub from_data_structure {
     my $self = shift;
     my $data = shift;
+    my $cgi;
+    $cgi = shift if (@_);
 
-    $self->path(Jifty::Util->canonicalize_path($data->{path} || "/"));
-    $self->just_validating($data->{validating}) if $data->{validating};
+    my $path = $data->{'path'};
+    
+    if ($cgi && ! $path) {
+        $path = URI::Escape::uri_unescape($cgi->path_info);
+        $path =~ s/\?.*//;
+    };
+
+    if (!$path) {
+        $path = '/';
+    }
 
-    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});
+    $self->path( Jifty::Util->canonicalize_path( $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) {
+    my %actions = %{ $data->{actions} || {} };
+    for my $moniker ( keys %actions ) {
+        my $a = $actions{$moniker};
         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};
+        if ( exists $a->{fields} ) {
+            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};
                 }
-            } else {
-                $arguments{$arg} = $a->{fields}{$arg};
             }
+        } elsif ( exists $a->{params} ) {
+            %arguments = %{$a->{params}};
         }
-        $self->add_action(moniker   => $a->{moniker},
-                          class     => $a->{class},
-                          order     => $a->{order},
-                          active    => exists $a->{active} ? $a->{active} : 1,
-                          arguments => \%arguments,
-                         );
+
+        $self->add_action(
+            moniker => $a->{moniker} || $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 %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) {
+    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},
@@ -195,12 +218,15 @@
             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},
-            }));
+        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},
+                    }
+                )
+            );
         }
     }
 

Modified: jifty/branches/js-refactor/lib/Jifty/Request/Mapper.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Request/Mapper.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Request/Mapper.pm	Thu Nov 29 12:25:16 2007
@@ -157,7 +157,7 @@
 
     # Bail unless it's a mapping
     return ( @original )
-        unless $args{destination} =~ /^J:M-(.*)/;
+        unless defined $args{destination} and $args{destination} =~ /^J:M-(.*)/;
 
     my $destination = $1;
 

Modified: jifty/branches/js-refactor/lib/Jifty/RightsFrom.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/RightsFrom.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/RightsFrom.pm	Thu Nov 29 12:25:16 2007
@@ -75,10 +75,10 @@
 
 sub export_curried_sub {
     my %args = (
-        sub_name     => undef,
-        export_to => undef,
-        as           => undef,
-        args        =>  undef,
+        sub_name   => undef,
+        export_to  => undef,
+        as         => undef,
+        args       => undef,
         @_
     );
     no strict 'refs';
@@ -109,6 +109,7 @@
     $right = 'update' if $right ne 'read';
     my $obj;
 
+    $col_name =~ s/_id$//;
     my $column   = $self->column($col_name);
     my $obj_type = $column->refers_to();
 

Modified: jifty/branches/js-refactor/lib/Jifty/Schema.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Schema.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Schema.pm	Thu Nov 29 12:25:16 2007
@@ -293,13 +293,21 @@
 sub connect_to_db_for_management {
     my $handle = Jifty::Handle->new();
 
-    my $driver   = Jifty->config->framework('Database')->{'Driver'};
+    my $driver = Jifty->config->framework('Database')->{'Driver'};
 
     # Everything but the template1 database is assumed
     my %connect_args;
-    $connect_args{'database'} = 'template1' if ( $handle->isa("Jifty::DBI::Handle::Pg") );
-    $connect_args{'database'} = ''          if ( $handle->isa("Jifty::DBI::Handle::mysql") );
-    $handle->connect(%connect_args);
+    $connect_args{'database'} = 'template1'
+        if ( $handle->isa("Jifty::DBI::Handle::Pg") );
+    $connect_args{'database'} = ''
+        if ( $handle->isa("Jifty::DBI::Handle::mysql") );
+    for ( 1 .. 50 ) {
+        my $counter = $_;
+        eval { $handle->connect(%connect_args); };
+        my $err = $@;
+        last if ( !$err || $err =~ /does not exist/i );
+        sleep 1;
+    }
     return $handle;
 }
 

Modified: jifty/branches/js-refactor/lib/Jifty/Script.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Script.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Script.pm	Thu Nov 29 12:25:16 2007
@@ -24,8 +24,7 @@
 sub prepare {
     my $self = shift;
     if ($ARGV[0] =~ /--?h(elp?)/i) {
-        shift @ARGV; #discard the --help
-        unshift @ARGV, 'help';
+        $ARGV[0] = 'help';
     }
     elsif (!@ARGV) {
         if ( my $cmd = $ENV{'JIFTY_COMMAND'} ) {

Modified: jifty/branches/js-refactor/lib/Jifty/Script/App.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Script/App.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Script/App.pm	Thu Nov 29 12:25:16 2007
@@ -52,7 +52,7 @@
 
     # Turn my-app-name into My::App::Name.
 
-    $self->mod_name (join ("::", map { ucfirst } split (/\-/, $self->prefix)));
+    $self->mod_name (join ("::", split (/\-/, $self->prefix)));
     my $dist = $self->mod_name;
     $self->dist_name($self->prefix);
 

Modified: jifty/branches/js-refactor/lib/Jifty/Script/FastCGI.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Script/FastCGI.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Script/FastCGI.pm	Thu Nov 29 12:25:16 2007
@@ -90,11 +90,13 @@
     my $conf = Jifty->config->framework('Web')->{'FastCGI'} || {};
     $self->{maxrequests} ||= $conf->{MaxRequests};
 
+    my $PATH = $ENV{'PATH'} || '/bin:/usr/bin';
+
     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{'PATH'}   = $PATH;
         $ENV{'SHELL'}  = '/bin/sh' if defined $ENV{'SHELL'};
         $ENV{'PATH_INFO'}   = $ENV{'SCRIPT_NAME'}
             if $ENV{'SERVER_SOFTWARE'} =~ /^lighttpd\b/;

Modified: jifty/branches/js-refactor/lib/Jifty/Script/Po.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Script/Po.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Script/Po.pm	Thu Nov 29 12:25:16 2007
@@ -5,6 +5,7 @@
 use base qw(App::CLI::Command Class::Accessor::Fast);
 
 use File::Copy ();
+use File::Path 'mkpath';
 use Jifty::Config ();
 use Jifty::YAML ();
 use Locale::Maketext::Extract ();
@@ -28,14 +29,25 @@
 
 =head2 options
 
-This script only takes one option, C<--language>, which is optional; it is
-the name of a message catalog to create.  
+This script an option, C<--language>, which is optional; it is the
+name of a message catalog to create.
+
+It also takes C<--dir> to specify additional directories to extract
+from.
+
+If C<--js> is given, other options are ignored and the script will
+generate json files for each language under
+F<share/web/static/js/dict> from the current po files.  Before doing
+so, you might want to run C<jifty po> with C<--dir share/web/static/js>
+to include messages from javascript in your po files.
 
 =cut
 
 sub options {
     (
      'l|language=s' => 'language',
+     'dir=s@'       => 'directories',
+     'js'           => 'js',
     )
 }
 
@@ -48,9 +60,46 @@
 
 
 sub run {
-        my $self = shift;
-            Jifty->new(no_handle => 1);
-        $self->update_catalogs;
+    my $self = shift;
+    Jifty->new(no_handle => 1);
+
+    return $self->_js_gen if $self->{js};
+
+    $self->update_catalogs;
+}
+
+sub _js_gen {
+    my $self = shift;
+    my $static_handler = Jifty::View::Static::Handler->new;
+    my $logger =Log::Log4perl->get_logger("main");
+    for my $file ( @{ Jifty::Web->javascript_libs } ) {
+        next if $file =~ m/^ext/;
+        next if $file =~ m/^yui/;
+        next if $file =~ m/^rico/;
+        my $path = $static_handler->file_path( File::Spec->catdir( 'js', $file ) ) or next;
+
+        $logger->info("Extracting messages from '$path'");
+
+        $LMExtract->extract_file( $path );
+    }
+
+    $LMExtract->set_compiled_entries;
+    $LMExtract->compile(USE_GETTEXT_STYLE);
+
+    Jifty::I18N->new;
+    mkpath ['share/web/static/js/dict'];
+    for my $lang (Jifty::I18N->available_languages) {
+        my $file = "share/web/static/js/dict/$lang.json";
+        $logger->info("Generating $file");
+        open my $fh, '>', $file or die "$file: $!";
+
+        no strict 'refs';
+        print $fh
+            Jifty::JSON::objToJson( { map { my $text = ${"Jifty::I18N::".$lang."::Lexicon"}{$_};
+                                            defined $text ? ( $_ => $text ) : () }
+                                      keys %{$LMExtract->lexicon} },
+                                    { singlequote => 1 } );
+    }
 }
 
 =head2 _check_mime_type FILENAME
@@ -124,7 +173,7 @@
 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 @files = File::Find::Rule->file->in( Jifty->config->framework('Web')->{'TemplateRoot'}, 'lib', 'bin', @{ $self->{directories} || [] } );
 
     my $logger =Log::Log4perl->get_logger("main");
     foreach my $file (@files) {

Modified: jifty/branches/js-refactor/lib/Jifty/Script/Schema.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Script/Schema.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Script/Schema.pm	Thu Nov 29 12:25:16 2007
@@ -48,14 +48,28 @@
     if ( $self->{create_all_tables} ) {
         $self->create_all_tables();
     } elsif ( $self->{'setup_tables'} ) {
-        $self->upgrade_jifty_tables();
-        $self->upgrade_application_tables();
-        $self->upgrade_plugin_tables();
+        $self->run_upgrades();
     } else {
         print "Done.\n";
     }
 }
 
+
+=head2 run_upgrades
+
+Take the actions we need in order to bring an existing database up to current.
+
+=cut
+
+sub run_upgrades {
+    my $self = shift;
+        $self->upgrade_jifty_tables();
+        $self->upgrade_application_tables();
+        $self->upgrade_plugin_tables();
+
+}
+
+
 =head2 setup_environment
 
 Sets up a minimal Jifty environment.
@@ -195,7 +209,7 @@
         for my $plugin (Jifty->plugins) {
             my $plugin_bootstrapper = $plugin->bootstrapper;
             Jifty::Util->require($plugin_bootstrapper);
-            $plugin_bootstrapper->run() if $bootstrapper->can('run');
+            $plugin_bootstrapper->run() if $plugin_bootstrapper->can('run');
         }
     };
     die $@ if $@;
@@ -342,14 +356,50 @@
 
     for my $plugin (Jifty->plugins) {
         my $plugin_class = ref $plugin;
-        my $dbv  = version->new( Jifty::Model::Metadata->load($plugin_class . '_version') || '0.0.1' );
+
+        my $dbv  = Jifty::Model::Metadata->load($plugin_class . '_db_version');
         my $appv = version->new( $plugin->version );
 
-        return unless $self->upgrade_tables( $plugin_class, $dbv, $appv, $plugin->upgrade_class );
-        if ( $self->{print} ) {
-            warn "Need to upgrade ${plugin_class}_db_version to $appv here!";
-        } else {
-            Jifty::Model::Metadata->store( $plugin_class . '_db_version' => $appv );
+        # Upgrade this plugin from dbv -> appv
+        if (defined $dbv) {
+            $dbv = version->new( $dbv );
+
+            next unless $self->upgrade_tables( $plugin_class, $dbv, $appv, $plugin->upgrade_class );
+            if ( $self->{print} ) {
+                warn "Need to upgrade ${plugin_class}_db_version to $appv here!";
+            } else {
+                Jifty::Model::Metadata->store( $plugin_class . '_db_version' => $appv );
+            }
+        }
+
+        # Install this plugin
+        else {
+            my $log = Log::Log4perl->get_logger("SchemaTool");
+            $log->info("Generating SQL to set up $plugin_class...");
+            Jifty->handle->begin_transaction;
+
+            # Create the tables
+            $self->create_tables_for_models(
+                grep { $_->isa('Jifty::DBI::Record') and /^\Q$plugin_class\E::Model::/ }
+                     $self->schema->models);
+            
+            # Save the plugin version to the database
+            Jifty::Model::Metadata->store( $plugin_class . '_db_version' => $appv )
+                unless $self->{print};
+
+            # Run the bootstrapper for initial data
+            unless ($self->{print}) {
+                eval {
+                    my $bootstrapper = $plugin->bootstrapper;
+                    Jifty::Util->require($bootstrapper);
+                    $bootstrapper->run if $bootstrapper->can('run');
+                };
+                die $@ if $@;
+            }
+                
+            # Save them records
+            Jifty->handle->commit;
+            $log->info("Set up $plugin_class version $appv");
         }
     }
 }
@@ -402,20 +452,8 @@
         if ($model->can( 'since' ) and defined $model->since and  $appv >= $model->since and $model->since >$dbv ) {
             unshift @{ $UPGRADES{ $model->since } }, $model->printable_table_schema();
         } else {
-            # Go through the columns
+            # Go through the currently-active 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 } }, sub {
-                        my $renamed = $upgradeclass->just_renamed || {};
-
-                        # skip it if this was dropped by a rename
-                        $model->drop_column_in_db($col->name)
-                            unless defined $renamed->{ $model->table }->{'drop'}->{ $col->name };
-                    };
-                }
-
                 # If they're new, add them
                 if ($col->can( 'since' ) and defined $col->since and $appv >= $col->since and $col->since >$dbv ) {
                     unshift @{ $UPGRADES{ $col->since } }, sub {

Modified: jifty/branches/js-refactor/lib/Jifty/Server/Prefork.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Server/Prefork.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Server/Prefork.pm	Thu Nov 29 12:25:16 2007
@@ -18,7 +18,7 @@
 
 =head2 net_server
 
-This module depends on the L<Net::Server::Prefork> module, which is part of
+This module depends on the L<Net::Server::PreFork> module, which is part of
 the L<Net::Server> CPAN distribution.
 
 =cut

Modified: jifty/branches/js-refactor/lib/Jifty/Test.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Test.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Test.pm	Thu Nov 29 12:25:16 2007
@@ -12,6 +12,8 @@
 use File::Path;
 use File::Spec;
 use File::Temp;
+use Hash::Merge;
+use Cwd 'abs_path';
 
 =head1 NAME
 
@@ -67,9 +69,9 @@
 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'
+    my $is_failing = grep {not $_} $tb->summary;
+    no warnings 'uninitialized';
+    $is_failing ||= $tb->has_plan eq 'no_plan'
                       ? 0
                       : $tb->expected_tests < $tb->current_test;
 
@@ -90,7 +92,9 @@
 
 sub is_done {
     my $tb = Jifty::Test->builder;
-    if( ($tb->has_plan || '') eq 'no_plan' ) {
+
+    no warnings 'uninitialized';
+    if( $tb->has_plan eq 'no_plan' ) {
         return $tb->current_test > 0;
     }
     else {
@@ -209,9 +213,7 @@
 	my $booted;
 	if (Jifty->handle && !$@) {
 	    my $baseclass = Jifty->app_class;
-	    my $schema = Jifty::Script::Schema->new;
-	    $schema->prepare_model_classes;
-	    for my $model_class ( grep {/^\Q$baseclass\E::Model::/} $schema->models ) {
+	    for my $model_class ( grep {/^\Q$baseclass\E::Model::/} Jifty::Schema->new->models ) {
 		# We don't want to get the Collections, for example.
 		next unless $model_class->isa('Jifty::DBI::Record');
 		Jifty->handle->simple_query('TRUNCATE '.$model_class->table );
@@ -236,10 +238,9 @@
 	}
     }
 
-    Jifty->new( no_handle => 1 );
+    Jifty->new( no_handle => 1, pre_init => 1 );
 
-    my $schema = Jifty::Script::Schema->new;
-    $schema->{drop_database}     = 1;
+    my $schema = Jifty::Script::Schema->new; $schema->{drop_database}     = 1;
     $schema->{create_database}   = 1;
     $schema->{create_all_tables} = 1;
     $schema->run;
@@ -247,14 +248,88 @@
     Jifty->new();
 }
 
+=head2 load_test_configs FILENAME
+
+This will load all the test config files that apply to FILENAME (default:
+C<$0>, the current test script file). Say you are running the test script
+C</home/bob/MyApp/t/user/12-delete.t>. The files that will be loaded are:
+
+=over 4
+
+=item C</home/bob/MyApp/t/user/12-delete.t-config.yml>
+
+=item C</home/bob/MyApp/t/user/test_config.yml>
+
+=item C</home/bob/MyApp/t/test_config.yml>
+
+=back
+
+..followed by the usual Jifty configuration files (such as
+C<MyApp/etc/config.yml> and C<MyApp/etc/site_config.yml>). The options in a
+more specific test file override the options in a less specific test file.
+
+The options are returned in a single hashref.
+
+=cut
+
+sub load_test_configs {
+    my $class = shift;
+    my ($test_config_file) = @_;
+
+    # Jifty::SubTest uses chdir which screws up $0, so to be nice it also makes
+    # available the cwd was before it uses chdir.
+    my $cwd = $Jifty::SubTest::OrigCwd;
+
+    # get the initial test config file, which is the input . "-config.yml"
+    $test_config_file = $0 if !defined($test_config_file);
+    $test_config_file .= "-config.yml";
+    $test_config_file = File::Spec->rel2abs($test_config_file, $cwd);
+
+    my $test_options = _read_and_merge_config_file($test_config_file, {});
+
+    # get the directory of the input, so we can recurse upwards
+    my ($volume, $directories) = File::Spec->splitpath($test_config_file);
+    my $directory = File::Spec->catdir($volume, $directories);
+
+    my $depth = $ENV{JIFTY_TEST_DEPTH} || 30;
+
+    for (1 .. $depth)
+    {
+        my $file = File::Spec->catfile($directory, "test_config.yml");
+        $test_options = _read_and_merge_config_file($file, $test_options);
+
+        # are we at the app root? if so, then we can stop moving up
+        $directory = abs_path(File::Spec->catdir($directory, File::Spec->updir($directory)));
+        return $test_options
+            if Jifty::Util->is_app_root($directory);
+    }
+
+    Jifty->log->fatal("Stopping looking for test config files after recursing upwards $depth times. Either you have a nonstandard layout or an incredibly deep test hierarchy. If you really do have an incredibly deep test hierarchy, you can set the environment variable JIFTY_TEST_DEPTH to a larger value.");
+
+    return $test_options;
+}
+
+sub _read_and_merge_config_file {
+    my $file = shift;
+    my $config = shift;
+
+    my $file_options = Jifty::Config->load_file($file);
+
+    Hash::Merge::set_behavior('RIGHT_PRECEDENT');
+
+    # merge the new options into what we have so far
+    return Hash::Merge::merge($file_options, $config);
+}
+
 =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.
+between 10000 and 15000. Individual test configurations may override these
+defaults (see C<load_test_configs>).
 
-It is passed the current configuration.
+It is passed the current configuration before any test config is loaded.
 
 You can override this to provide application-specific test
 configuration, e.g:
@@ -268,25 +343,47 @@
         return $hash;
     }
 
+Note that this is deprecated in favor of having real config files in your
+test directory.
+
 =cut
 
 sub test_config {
     my $class = shift;
     my ($config) = @_;
 
-    return {
+    my $defaults = {
         framework => {
             Database => {
-                Database => $config->framework('Database')->{Database} . "test",
+                Database => $config->framework('Database')->{Database} . $class->_testfile_to_dbname(),
             },
             Web => {
                 Port => int(rand(5000) + 10000),
+                DataDir => File::Temp::tempdir('masonXXXXXXXXXX', CLEANUP => 1)
             },
             Mailer => 'Jifty::Test',
             MailerArgs => [],
-            LogLevel => 'WARN'
+            LogLevel => 'WARN',
+            TestMode => 1,
         }
     };
+
+    Hash::Merge::set_behavior('RIGHT_PRECEDENT');
+    return Hash::Merge::merge($defaults, $class->load_test_configs);
+}
+
+
+sub _testfile_to_dbname {
+    if ($ENV{JIFTY_FAST_TEST}) {
+        return 'fasttest';
+    }
+    else {
+    my $dbname = lc($0);
+    $dbname =~ s/\.t$//;
+    $dbname =~ s/[-_\.\/\\]//g;
+    $dbname = substr($dbname,-32,32);	
+    return $dbname;
+    } 
 }
 
 =head2 make_server
@@ -347,7 +444,7 @@
 =cut
 
 sub mailbox {
-    return Jifty::Util->absolute_path("t/mailbox");
+    return Jifty::Util->absolute_path("t/mailbox_" . _testfile_to_dbname());
 }
 
 =head2 setup_mailbox
@@ -512,6 +609,9 @@
         unlink @Test_Files_To_Cleanup;
     }
 
+    # Cleanup the tempdirs
+    File::Temp::cleanup();
+
     # Unlink test file
     unlink $Test->{test_config} if $Test->{test_config};
 }

Added: jifty/branches/js-refactor/lib/Jifty/Test/WWW/Declare.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/lib/Jifty/Test/WWW/Declare.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,72 @@
+#!/usr/bin/env perl
+package Jifty::Test::WWW::Declare;
+use strict;
+use warnings;
+use base qw(Exporter);
+BEGIN { require Jifty::Test; require Test::WWW::Declare }
+
+our @EXPORT = qw($server $URL get);
+
+our $server;
+our $URL;
+
+sub import
+{
+    my $class = shift;
+
+    # examine the plan
+    Test::More->import(@_);
+
+    # set up database and other things
+    Jifty::Test->setup($class);
+
+    # export the DSL-ey functions
+    Test::WWW::Declare->export_to_level(2);
+
+    # export $server, $URL, and whatever else J:T:W:D adds
+    # note that this must come AFTER T:W:D->export because we override some
+    # of its functions
+    __PACKAGE__->export_to_level(1);
+
+    # create a server (which will be automatically exported)
+    $server = Jifty::Test->make_server;
+    $URL = $server->started_ok;
+}
+
+=head1 NAME
+
+Jifty::Test::WWW::Declare - Subclass of L<Test::WWW::Declare> with
+extra Jifty features
+
+=head1 SYNOPSIS
+
+    use Jifty::Test::WWW::Declare tests => 2;
+
+    session user => run {
+        flow 'login page' => check {
+            get 'login';
+            content should match qr{Email:};
+            content should match qr{Password:};
+        };
+    };
+
+=head1 FUNCTIONS
+
+=head2 get PATH
+
+Automatically prepend the server's URL to the PATH. You may avoid this by
+specifying the full URL yourself (it checks for C</^http/>).
+
+=cut
+
+sub get
+{
+    my $url = shift;
+    $url = "$URL/$url"
+        unless $url =~ /^http/;
+
+    Test::WWW::Declare::get($url);
+}
+
+1;
+

Modified: jifty/branches/js-refactor/lib/Jifty/Test/WWW/Mechanize.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Test/WWW/Mechanize.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Test/WWW/Mechanize.pm	Thu Nov 29 12:25:16 2007
@@ -413,11 +413,19 @@
 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(@_);
+    # Test::WWW::Mechanize allows passing in a hashref of arguments, so we should to
+    if  ( ref($_[0]) eq 'HASH') {
+        # if the user is pashing in { text => 'foo' } ...
+
+        @_ = %{$_[0]};
+    } elsif (@_ % 2 ) {
+        # IF the user is passing in text => 'foo' ,"Cicked the right thing"
+        # Remove reason from end if it's there
+        pop @_ ;
+
+    }
+    carp("Couldn't find link") unless $self->follow_link(@_);
     {
         local $Test::Builder::Level = $Test::Builder::Level;
         $Test::Builder::Level++;

Added: jifty/branches/js-refactor/lib/Jifty/Test/WWW/Selenium.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/lib/Jifty/Test/WWW/Selenium.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,132 @@
+package Jifty::Test::WWW::Selenium;
+use strict;
+use warnings;
+
+use base 'Test::WWW::Selenium';
+
+=head1 NAME
+
+Jifty::Test::WWW::Selenium - Subclass of L<Test::WWW::Selenium> with
+extra Jifty integration
+
+=head1 SYNOPSIS
+
+  use Jifty::Test::WWW::Selenium;
+  my $server  = Jifty::Test->make_server;
+  my $sel = Jifty::Test::WWW::Selenium->rc_ok( $server, lang => 'en_US.UTF-8' );
+  my $URL = $server->started_ok;
+
+  $sel->open_ok('/');
+
+=head1 DESCRIPTION
+
+L<Jifty::Test::WWW::Selenium> creates a L<Test::WWW::Selenium> object
+associated with your jifty application to test.  In addition, it
+starts selenium remote control for you, unless C<SELENIUM_RC_SERVER>
+is specified when the test is run.  You might also want to set
+C<SELENIUM_RC_TEST_AGAINST> to your local IP address so
+C<SELENIUM_RC_SERVER> can test against you.  C<SELENIUM_RC_BROWSER>
+tells the rc server what browser to run the tests with.
+
+=head2 rc_ok
+
+When the selenium rc server is started by
+L<Jifty::Test::WWW::Selenium>, the browser's language is default to
+en_US, unless you pass C<lang> param to rc_ok.
+
+=cut
+
+sub rc_ok {
+    my $class = shift;
+    my $server = shift;
+    my %args = @_;
+
+    if ( $args{selenium_rc} ||= $ENV{SELENIUM_RC_SERVER} ) {
+	@args{'host','port'} = split /:/, $args{selenium_rc}, 2;
+    }
+    else {
+	@args{'host','port'} = eval { $class->_start_src(%args) };
+	if ($@) { # Schwern: i want skip_rest
+	    my $why = "No selenium: $@";
+	    my $Tester = Test::Builder->new;
+	    $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);
+	}
+    }
+
+    $args{browser_url} ||= 'http://'.($ENV{SELENIUM_RC_TEST_AGAINST} || $args{test_server} || 'localhost').':'.$server->port;
+
+    $args{browser} ||= $ENV{SELENIUM_RC_BROWSER} || $class->_get_default_browser;
+
+    $SIG{CHLD} = \&_REAPER;
+
+    my $try = 5;
+    my $sel;
+    while ($try--) {
+	$sel = eval { Test::WWW::Selenium->new( %args, auto_stop => 0 ) };
+	last if $sel;
+	Test::More::diag "waiting for selenium rc...";
+	sleep 3;
+    }
+    Test::More::isa_ok($sel, 'Test::WWW::Selenium');
+    return $sel;
+}
+
+sub _REAPER {
+    my $waitedpid = wait;
+    # loathe sysV: it makes us not only reinstate
+    # the handler, but place it after the wait
+    $SIG{CHLD} = \&_REAPER;
+}
+
+sub _get_default_browser {
+    my $class = shift;
+
+    return '*firefox';
+}
+
+my @cleanup;
+
+sub _start_src {
+    my ($self, %args) = @_;
+    eval 'require Alien::SeleniumRC; 1'
+	or die 'requires Alien::SeleniumRC to start selenium-rc.';
+
+    my $pid = fork();
+    die if $pid == -1;
+    if ($pid) {
+	push @cleanup, $pid;
+	return ('localhost', 4444);
+    }
+    else {
+	require POSIX;
+	POSIX::setsid();
+	unless ($ENV{TEST_VERBOSE}) {
+	    close *STDERR;
+	    close *STDOUT;
+	}
+	$ENV{LANG} = $args{lang} || 'en_US.UTF-8';
+	$ENV{PATH} = "$ENV{PATH}:/usr/lib/firefox:/usr/lib/mozilla-firefox";
+	Test::More::diag "start selenium rc [$$]";
+	local $SIG{CHLD} = \&_REAPER;
+	local $SIG{TERM} = sub { exit 0 };
+	Alien::SeleniumRC::start(@{ $args{args} || [] });
+	Test::More::diag "selenium rc [$$] finished.";
+	exit;
+    }
+}
+
+END {
+    kill(15, -$_) for @cleanup;
+}
+
+
+1;

Modified: jifty/branches/js-refactor/lib/Jifty/Util.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Util.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Util.pm	Thu Nov 29 12:25:16 2007
@@ -174,6 +174,25 @@
     return ''; # returning undef causes tons of 'uninitialized...' warnings.
 }
 
+=head2 is_app_root PATH
+
+Returns a boolean indicating whether the path passed in is the same path as
+the app root. Useful if you're recursing up a directory tree and want to
+stop when you've hit the root. It does not attempt to handle symbolic links.
+
+=cut
+
+sub is_app_root
+{
+    my $self = shift;
+    my $path = shift;
+    my $app_root = $self->app_root;
+
+    my $rel = File::Spec->abs2rel( $path, $app_root );
+
+    return $rel eq File::Spec->curdir;
+}
+
 =head2 default_app_name
 
 Returns the default name of the application.  This is the name of the
@@ -237,18 +256,19 @@
 
     return 1 if $self->already_required($class);
 
-    local $UNIVERSAL::require::ERROR = '';
-    my $retval = $class->require;
-    if ($UNIVERSAL::require::ERROR) {
-        my $error = $UNIVERSAL::require::ERROR;
-        $error =~ s/ at .*?\n$//;
-        if ($args{'quiet'} and $error =~ /^Can't locate/) {
+    my $pkg = $class .".pm";
+    $pkg =~ s/::/\//g;
+    my $retval = eval  {CORE::require "$pkg"} ;
+    my $error = $@;
+    if (my $message = $error) { 
+        $message =~ s/ at .*?\n$//;
+        if ($args{'quiet'} and $message =~ /^Can't locate $pkg/) {
             return 0;
         }
-        elsif ( $UNIVERSAL::require::ERROR !~ /^Can't locate/) {
-            die $UNIVERSAL::require::ERROR;
+        elsif ( $error !~ /^Can't locate $pkg/) {
+            die $error;
         } else {
-            Jifty->log->error(sprintf("$error at %s line %d\n", (caller(1))[1,2]));
+            Jifty->log->error(sprintf("$message at %s line %d\n", (caller(1))[1,2]));
             return 0;
         }
     }

Modified: jifty/branches/js-refactor/lib/Jifty/View/Declare.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/View/Declare.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/View/Declare.pm	Thu Nov 29 12:25:16 2007
@@ -3,7 +3,7 @@
 
 use strict;
 use warnings;
-use constant BaseClass => 'Jifty::View::Declare::BaseClass';
+use constant BaseClassName => 'Jifty::View::Declare::BaseClass';
 
 =head1 NAME
 
@@ -23,7 +23,7 @@
 
 L<Template::Declare> is a templating system using a declarative syntax built on top of Perl. This provides a templating language built in a similar style to the dispatcher language in L<Jifty::Dispatcher>, the model language in L<Jifty::DBI::Schema>, and the action language in L<Jifty::Param::Schema>.
 
-To use this view system, you must declare a class named C<MyApp::View> (where I<MyApp> is the name of your Jifyt application). Use this library class to bring in all the details needed to make it work:
+To use this view system, you must declare a class named C<MyApp::View> (where I<MyApp> is the name of your Jifty application). Use this library class to bring in all the details needed to make it work:
 
   package MyApp::View;
   use Jifty::View::Declare -base;
@@ -39,11 +39,11 @@
     ($import and $import eq '-base') or return;
     no strict 'refs';
     my $pkg = caller;
-    Jifty::Util->require(BaseClass);
-    push @{ $pkg . '::ISA' }, BaseClass;
+    Jifty::Util->require(BaseClassName);
+    push @{ $pkg . '::ISA' }, BaseClassName;
 
-    @_ = BaseClass;
-    goto &{BaseClass()->can('import')};
+    @_ = BaseClassName;
+    goto &{BaseClassName()->can('import')};
 }
 
 =head1 SEE ALSO 

Modified: jifty/branches/js-refactor/lib/Jifty/View/Declare/BaseClass.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/View/Declare/BaseClass.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/View/Declare/BaseClass.pm	Thu Nov 29 12:25:16 2007
@@ -33,7 +33,7 @@
     no warnings 'redefine';
     *{ $class . '::wrapper' } = sub {
         my $code = shift;
-        my $args = shift;
+        my $args = shift || {};
         # so in td handler, we made jifty::web->out appends to td
         # buffer, we need it back for here before we call $code.
         # someday we need to finish fixing the output system that is
@@ -63,13 +63,23 @@
 
 use Attribute::Handlers;
 my (%Static, %Action);
-sub Static :ATTR(CODE,BEGIN) {
-    $Static{$_[2]}++;
-}
 
-sub Action :ATTR(CODE,BEGIN) {
-    $Action{$_[2]}++;
-}
+=head2 Static
+
+This function allows a developer to mark a Template::Declare template as static (unchanging), so that the compiled version can be cached on the client side and inserted with javascript
+
+=cut
+
+sub Static :ATTR(CODE,BEGIN) { $Static{$_[2]}++; }
+
+=head2 Action
+
+This function allows a developer to mark a Template::Declare template as an action. clkao owes documentation as to the meaning of this and when it would be acceptable to use it.
+
+=cut
+
+
+sub Action :ATTR(CODE,BEGIN) { $Action{$_[2]}++; }
 
 =head2 client_cacheable
 
@@ -92,6 +102,20 @@
 
 Render a C<Template::Declare> template.
 
+=head1 ATTRIBUTES
+
+=head2 Static
+
+TODO Document this...
+
+This is part of the client-caching system being developed for Perl to allow you to translate templates into JavaScript running on the client.
+
+=head2 Action
+
+TODO Document this...
+
+This is part of the client-caching system being developed for Perl to allow you to translate templates into JavaScript running on the client.
+
 =head1 SEE ALSO
 
 L<Template::Declare>, L<Jifty::View::Declare::Helpers>, L<Jifty::View::Declare>

Modified: jifty/branches/js-refactor/lib/Jifty/View/Declare/CRUD.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/View/Declare/CRUD.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/View/Declare/CRUD.pm	Thu Nov 29 12:25:16 2007
@@ -12,40 +12,112 @@
     $VIEW{$_[2]}++;
 }
 
-
 =head1 NAME
 
 Jifty::View::Declare::CRUD - Provides typical CRUD views to a model
 
+=head1 SYNOPSIS
+
+  package App::View::User;
+  use Jifty::View::Declare -base;
+  use base qw/ Jifty::View::Declare::CRUD /;
+
+  template 'view' => sub {
+      # customize the view
+  };
+
+  1;
+
+  package App::View::Tag;
+  use Jifty::View::Declare -base;
+  use base qw/ Jifty::View::Declare::CRUD /;
+
+  template 'view' => sub {
+      # customize the view
+  };
+
+  1;
+
+  package App::View;
+  use Jifty::View::Declare -base;
+
+  use Jifty::View::Declare::CRUD;
+
+  # If you have customizations, this is a good way...
+  Jifty::View::Declare::CRUD->mount_view('User');
+  Jifty::View::Declare::CRUD->mount_view('Category', 'App::View::Tag', '/tag');
+
+  # Another way to do the above, good for quick and dirty
+  alias Jifty::View::Declare::CRUD under '/admin/blog', {
+      object_type => 'BlogPost',
+  };
+
 =head1 DESCRIPTION
 
-This class provides a set of views that may be used by a model to
-display Create/Read/Update/Delete views using the L<Template::Declare>
-templating language.
+This class provides a set of views that may be used by a model to display
+Create/Read/Update/Delete views using the L<Template::Declare> templating
+language.
+
+Basically, you can use this class to do most (and maybe all) of the work you need to manipulate and view your records.
 
 =head1 METHODS
 
-=cut
+=begin pod_coverage
 
+=head2 CRUDView
+
+=end pod_coverage
 
 =head2 mount_view MODELCASS VIEWCLASS /path
 
+Call this method in your appliation's view class to add the CRUD views you're looking for. Only the first argument is required.
+
+Arguments:
+
+=over
+
+=item MODELCLASS
+
+This is the name of the model that you want to generate the CRUD views for. This is the only required parameter. Leave off the parts of the class name prior to and including the "Model" part. (I.e., C<App::Model::User> should be passed as just C<User>).
+
+=item VIEWCLASS
+
+This is the name of the class that will be generated to hold the CRUD views of your model. If not given, it will be set to: C<App::View::I<MODELCLASS>>. If given, it should be the full name of the view class.
+
+=item /path
+
+This is the path where you can reach the CRUD views for this model in your browser. If not given, this will be set to the model class name in lowercase letters. (I.e., C<User> would be found at C</user> if not passed explicitly).
+
+=back
+
 =cut
 
 sub mount_view {
     my ($class, $model, $vclass, $path) = @_;
     my $caller = caller(0);
+
+    # Sanitize the arguments
     $model = ucfirst($model);
     $vclass ||= $caller.'::'.$model;
     $path ||= '/'.lc($model);
 
+    # Load the view class, alias it, and define its object_type method
     Jifty::Util->require($vclass);
     eval qq{package $caller;
             alias $vclass under '$path'; 1} or die $@;
+
+    # Override object_type
     no strict 'refs';
-    *{$vclass."::object_type"} = sub { $model };
+    my $object_type = $vclass."::object_type";
+
+    # Avoid the override if object_type() is already defined
+    *{$object_type} = sub { $model } unless defined *{$object_type};
 }
 
+# XXX TODO FIXME This is related to the trimclient branch and performs some
+# magic related to that or that was once related to that. This is also related
+# to the CRUDView attribute above. This is a little unfinished, but I'll leave
+# it up to clkao to figure out what needs to happen here.
 sub _dispatch_template {
     my $class = shift;
     my $code  = shift;
@@ -59,9 +131,10 @@
     goto $code;
 }
 
-
 =head2 object_type
 
+This method returns the type of object this CRUD view has been generated for. This is normally the model class parameter that was passed to L</mount_view>.
+
 =cut
 
 sub object_type {
@@ -69,32 +142,87 @@
     return $self->package_variable('object_type') || get('object_type');
 }
 
+=head2 record_class
+
+This is the full name of the model class these CRUD views are for. The default implementation returns:
+
+  Jifty->app_class('Model', $self->object_type);
+
+You will want to override this if (in addition to L</object_type>) if you want to provide CRUD views in a plugin, or from an external model class, or for one of the Jifty built-in models.
+
+=cut
+
+# NB: We don't just create the record class here and return it. Why? Because
+# the mount_view() method is generally called very early in the Jifty
+# lifecycle. As such, Jifty->app_class() might not work yet since it requires
+# the Jifty singleton to be built and the configuration to be loaded. So, this
+# implementation caches teh record class after the first calculation, which
+# should happen during the request dispatch process, which always happens after
+# Jifty is completely initialized.
+sub record_class {
+    my $self = shift;
+
+    # If object_type is set via set, don't cache
+    if (!$self->package_variable('object_type') && get('object_type')) {
+        return Jifty->app_class('Model', $self->object_type);
+    }
+
+    # Otherwise, assume object_type is permanent
+    else {
+        return ($self->package_variable('record_class') 
+            or ($self->package_variable( record_class =>
+                    Jifty->app_class('Model', $self->object_type))));
+    }
+
+}
 
 =head2 fragment_for
 
+This is a helper that returns the path to a given fragment. The only argument is the name of the fragment. It returns a absolute base path to the fragment page.
+
+This will attempt to lookup a method named C<fragment_for_I<FRAGMENT>>, where I<FRAGMENT> is the argument passed. If that method exists, it's result is used as the returned path.
+
+Otherwise, the L</fragment_base_path> is joined to the passed fragment name to create the return value.
+
+If you really want to mess with this, you may need to read the source code of this class.
+
 =cut
 
 sub fragment_for {
     my $self     = shift;
     my $fragment = shift;
 
+    # Check for fragment_for_$fragment and use that if it exists
     if ( my $coderef = $self->can( 'fragment_for_' . $fragment ) ) {
         return $coderef->($self);
     }
 
+    # Otherwise return the fragment_base_path/$fragment
     return $self->package_variable( 'fragment_for_' . $fragment )
         || $self->fragment_base_path . "/" . $fragment;
 }
 
 =head2 fragment_base_path
 
+This is a helper for L</fragment_for>. It looks up the current template using L<Template::Declare::Tags/current_template>, finds it's parent path and then returns that.
+
+If you really want to mess with this, you may need to read the source code of this class.
+
 =cut
 
 sub fragment_base_path {
     my $self = shift;
+
+    # Rip it apart
     my @parts = split('/', current_template());
+
+    # Remove the last element
     pop @parts;
+
+    # Put it back together again
     my $path = join('/', @parts);
+
+    # And serve
     return $path;
 }
 
@@ -107,7 +235,8 @@
 sub _get_record {
     my ( $self, $id ) = @_;
 
-    my $record_class = Jifty->app_class( "Model", $self->object_type );
+    # Load the model, create an empty object, load the object by ID
+    my $record_class = $self->record_class;
     my $record = $record_class->new();
     $record->load($id);
 
@@ -123,48 +252,57 @@
 sub display_columns {
     my $self = shift;
     my $action = shift;
-     return   grep { !( m/_confirm/ || lc $action->arguments->{$_}{render_as} eq 'password' ) } $action->argument_names;
+     return   $action->argument_names;
+     #return   grep { !( m/_confirm/ ||  $action->arguments->{$_}{unreadable} ) } $action->argument_names;
 }
 
 
-=head1 TEMPLATES
+sub edit_columns {
+    my $self = shift; 
+    return $self->display_columns(@_);
+}
 
+sub create_columns {
+    my $self = shift; 
+    return $self->edit_columns(@_);
 
-=cut
+
+}
+
+
+
+=head1 TEMPLATES
 
 =head2 index.html
 
+Contains the master form and page region containing the list of items. This is mainly a wrapper for the L</list> fragment.
 
 =cut
 
-
 template 'index.html' => page {
     my $self = shift;
     title is $self->object_type;
     form {
-            render_region(
-                name     => $self->object_type.'-list',
-                path     => $self->fragment_base_path.'/list');
+        render_region(
+            name     => $self->object_type.'-list',
+            path     => $self->fragment_base_path.'/list');
     }
 
 };
 
- 
-
-
-
 =head2 search
 
-The search view displays a search screen connected to the search action of the module. See L<Jifty::Action::Record::Search>.
+The search fragment displays a search screen connected to the search action of the module. 
+
+See L<Jifty::Action::Record::Search>.
 
 =cut
 
 template 'search' => sub {
     my $self          = shift;
     my ($object_type) = ( $self->object_type );
-    my $search        = Jifty->web->new_action(
-        class             => "Search" . $object_type,
-        moniker           => "search",
+    my $search        = $self->record_class->as_search_action(
+        moniker           => 'search',
         sticky_on_success => 1,
     );
 
@@ -181,35 +319,43 @@
             }
         );
 
-        }
+    }
 };
 
 =head2 view
 
-This template displays the data held by a single model record.
+This fragment displays the data held by a single model record.
 
 =cut
 
 template 'view' => sub :CRUDView {
-    my ($self, $record) = @_;
-    my $update = new_action(
-        class   => 'Update' . $self->object_type,
+    my $self   = shift;
+    my $record = $self->_get_record( get('id') );
+
+    my $update = $record->as_update_action(
         moniker => "update-" . Jifty->web->serial,
-        record  => $record,
     );
 
     div {
         { class is 'crud read item inline' };
         my @fields = $self->display_columns($update);
-        render_action( $update, \@fields, { render_mode => 'read' } );
-
+        foreach my $field (@fields) {
+            div { { class is 'view-argument-'.$field};
+            render_param( $update => $field,  render_mode => 'read'  );
+            }; 
+        }
         show ('./view_item_controls', $record, $update); 
-
-        hr {};
     };
+    hr {};
 
 };
 
+=head2 private template view_item_controls
+
+Used by the view fragment to show the edit link for each record.
+
+=cut
+
 private template view_item_controls  => sub {
     my $self = shift;
     my $record = shift;
@@ -226,11 +372,11 @@
     }
 };
 
-
-
 =head2 update
 
-The update template displays a form for editing the data held within a single model record. See L<Jifty::Action::Record::Update>.
+The update fragment displays a form for editing the data held within a single model record. 
+
+See L<Jifty::Action::Record::Update>.
 
 =cut
 
@@ -238,13 +384,11 @@
     my $self = shift;
     my ( $object_type, $id ) = ( $self->object_type, get('id') );
 
-    my $record_class = Jifty->app_class( "Model", $object_type );
+    my $record_class = $self->record_class;
     my $record = $record_class->new();
     $record->load($id);
-    my $update = new_action(
-        class   => "Update" . $object_type,
+    my $update = $record->as_update_action(
         moniker => "update-" . Jifty->web->serial,
-        record  => $record
     );
 
     div {
@@ -254,11 +398,9 @@
         show('./edit_item_controls', $record, $update);
 
         hr {};
-        }
+    }
 };
 
-
-
 =head2 edit_item_controls $record $action
 
 The controls we should be rendering in the 'edit' region for a given fragment
@@ -273,16 +415,15 @@
     my $object_type = $self->object_type;
     my $id = $record->id;
 
-    my $delete = Jifty->web->form->add_action(
-        class   => 'Delete' . $object_type,
+    my $delete = $record->as_delete_action(
         moniker => 'delete-' . Jifty->web->serial,
-        record  => $record
     );
+    Jifty->web->form->register_action($delete);
 
         div {
             { class is 'crud editlink' };
             hyperlink(
-                label   => "Save",
+                label   => _("Save"),
                 onclick => [
                     { submit => $update },
                     {   replace_with => $self->fragment_for('view'),
@@ -291,7 +432,7 @@
                 ]
             );
             hyperlink(
-                label   => "Cancel",
+                label   => _("Cancel"),
                 onclick => {
                     replace_with => $self->fragment_for('view'),
                     args         => { object_type => $object_type, id => $id }
@@ -301,10 +442,10 @@
             );
             if ( $record->current_user_can('delete') ) {
                 $delete->button(
-                    label   => 'Delete',
+                    label   => _('Delete'),
                     onclick => {
                         submit => $delete,
-                        confirm => 'Really delete?',
+                        confirm => _('Really delete?'),
                         refresh => Jifty->web->current_region->parent,
                     },
                     class => 'delete'
@@ -326,13 +467,14 @@
     my ( $page ) = get(qw(page ));
     my $item_path = get('item_path') || $self->fragment_for("view");
     my $collection =  $self->_current_collection();
+    div { {class is 'crud-'.$self->object_type}; 
 
     show('./search_region');
     show( './paging_top',    $collection, $page );
     show( './list_items',    $collection, $item_path );
     show( './paging_bottom', $collection, $page );
     show( './new_item_region');
-
+    };
 };
 
 =head2 per_page
@@ -344,10 +486,13 @@
 
 sub per_page { 25 }
 
+# This method just does a whole lot of sanitizing to try and get a valid
+# collection out the other end based upon either the current search or an
+# unlimited collection if there is no current search.
 sub _current_collection {
-    my $self =shift; 
+    my $self = shift; 
     my ( $page, $search_collection ) = get(qw(page  search_collection));
-    my $collection_class = Jifty->app_class( "Model", $self->object_type . "Collection" );
+    my $collection_class = $self->record_class->collection_class;
     my $search = $search_collection || ( Jifty->web->response->result('search') ? Jifty->web->response->result('search')->content('search') : undef );
     my $collection;
     if ( $search ) {
@@ -386,7 +531,7 @@
                 args         => { object_type => $object_type }
             },
         ],
-        label => 'Toggle search'
+        label => _('Toggle search'),
     );
 
     outs( $search_region->render );
@@ -398,7 +543,6 @@
 
 =cut
 
-
 private template 'new_item_region' => sub {
     my $self        = shift;
     my $fragment_for_new_item = get('fragment_for_new_item') || $self->fragment_for('new_item');
@@ -408,31 +552,42 @@
         render_region(
             name     => 'new_item',
             path     => $fragment_for_new_item,
-            defaults => { object_type => $object_type },
+            defaults => {
+                        object_type => $object_type },
         );
     }
 };
 
+=head2 no_items_found
 
-=head2 list_items $collection $item_path
+Prints "No items found."
 
-Renders a div of class list with a region per item.
+=cut
 
+private template 'no_items_found' => sub {
+    div {
+        { class is 'no_items' };
+        outs( _("No items found.") );
+    }
+};
 
+=head2 list_items $collection $item_path
 
-=cut
+Renders a div of class list with a region per item.
 
-private template 'no_items_found' => sub { outs(_("No items found.")) };
+=cut
 
 private template 'list_items' => sub {
     my $self        = shift;
     my $collection  = shift;
     my $item_path   = shift;
+    my $callback    = shift;
     my $object_type = $self->object_type;
-    if ( $collection->pager->total_entries == 0 ) {
-        show('no_items_found');
+    if ( $collection->count == 0 ) {
+        show('./no_items_found');
     }
 
+    my $i = 0;
     div {
         { class is 'list' };
         while ( my $item = $collection->next ) {
@@ -441,23 +596,22 @@
                 path     => $item_path,
                 defaults => { id => $item->id, object_type => $object_type }
             );
+            $callback->(++$i) if $callback;
         }
     };
 
 };
 
-
 =head2 paging_top $collection $page_number
 
 Paging for your list, rendered at the top of the list
 
 =cut
 
-
 private template 'paging_top' => sub {
     my $self       = shift;
     my $collection = shift;
-    my $page       = shift;
+    my $page       = shift || 1;
 
     if ( $collection->pager->last_page > 1 ) {
         span {
@@ -506,19 +660,36 @@
 };
 
 
-
-=head2 edit_item $action
+=head2 new_item $action
 
 Renders the action $Action, handing it the array ref returned by L</display_columns>.
 
 =cut
 
+private template 'create_item' => sub {
+    my $self = shift;
+    my $action = shift;
+   foreach my $field ($self->create_columns($action)) {
+            div { { class is 'create-argument-'.$field}
+                render_param($action, $field) ;
+        }
+   }
+};
 
+=head2 edit_item $action
+
+Renders the action $Action, handing it the array ref returned by L</display_columns>.
+
+=cut
 
 private template 'edit_item' => sub {
     my $self = shift;
     my $action = shift;
-    render_action($action, [$self->display_columns($action)]);
+   foreach my $field ($self->edit_columns($action)) {
+            div { { class is 'update-argument-'.$field}
+    render_param($action, $field) ;
+        }
+   }
 };
 
 =head1 new_item
@@ -531,22 +702,28 @@
     my $self = shift;
     my ( $object_type, $id ) = ( $self->object_type, get('id') );
 
-    my $record_class = Jifty->app_class( "Model", $object_type );
-    my $create = Jifty->web->new_action( class => 'Create' . $object_type );
+    my $record_class = $self->record_class;
+    my $create = $record_class->as_create_action;
 
     div {
         { class is 'crud create item inline' };
-        show('./edit_item', $create);
+        show('./create_item', $create);
+        show('./new_item_controls', $create);
+    }
+};
+
+private template 'new_item_controls' => sub {
+        my $self = shift;
+        my $create = shift;
+    my ( $object_type ) = ( $self->object_type);
 
         outs(
             Jifty->web->form->submit(
-                label   => 'Create',
+                label   => _('Create'),
                 onclick => [
                     { submit       => $create },
                     { refresh_self => 1 },
-                    {   element =>
-                            Jifty->web->current_region->parent->get_element(
-                            'div.list'),
+                    {   element => Jifty->web->current_region->parent->get_element( 'div.list'),
                         append => $self->fragment_for('view'),
                         args   => {
                             object_type => $object_type,
@@ -555,9 +732,11 @@
                     },
                 ]
             )
-        );
-        }
-};
+        )
+    };
+
+
+
 
 =head1 SEE ALSO
 

Modified: jifty/branches/js-refactor/lib/Jifty/View/Declare/Compile.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/View/Declare/Compile.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/View/Declare/Compile.pm	Thu Nov 29 12:25:16 2007
@@ -12,14 +12,60 @@
          CVf_METHOD CVf_LOCKED CVf_LVALUE CVf_ASSERTION
 	 PMf_KEEP PMf_GLOBAL PMf_CONTINUE PMf_EVAL PMf_ONCE PMf_SKIPWHITE
 	 PMf_MULTILINE PMf_SINGLELINE PMf_FOLD PMf_EXTENDED);
+
 BEGIN {
     die "You need a custom version of B::Deparse from http://svn.jifty.org/svn/jifty.org/B/"
         unless B::Deparse->can('e_method')
 }
+
+=head1 NAME
+
+Jifty::View::Declare::Compile - Compile Jifty templates into JavaScript
+
+=head1 DESCRIPTION
+
+B<EXPERIMENTAL:> This code is currently under development and experimental. You will need to get the version of the L<B::Deparse> package from the Subversion repository at:
+
+  http://svn.jifty.org/svn/jifty.org/B/
+
+in order to use this class.
+
+This is a subclass of L<B::Deparse> that compiles Perl into JavaScript with the intention of allowing Jifty applications to render L<Template::Declare> templates on the client.
+
+This class does the dirty work of translating a Perl code reference in JavaScript. 
+
+See L<Jifty::Web::PageRegion/client_cache_content>.
+
+=head1 METHODS
+
+=head2 is_scope
+
+See L<B::Deparse>.
+
+=cut
+
 sub is_scope { goto \&B::Deparse::is_scope }
+
+=head2 is_state
+
+See L<B::Deparse>
+
+=cut
+
 sub is_state { goto \&B::Deparse::is_state }
+
+=head2 null
+
+See L<B::Deparse>
+
+=cut
+
 sub null { goto \&B::Deparse::null }
 
+=head2 padname
+
+=cut
+
 sub padname {
     my $self = shift;
     my $targ = shift;
@@ -32,6 +78,10 @@
         map {@{$_||[]}} @CGI::EXPORT_TAGS{qw/:html2 :html3 :html4 :netscape :form/}
 );
 
+=head2 deparse
+
+=cut
+
 sub deparse {
     my $self = shift;
     my $ret = $self->SUPER::deparse(@_);
@@ -39,6 +89,10 @@
     return $ret;
 }
 
+=head2 loop_common
+
+=cut
+
 sub loop_common {
     my $self = shift;
     my($op, $cx, $init) = @_;
@@ -94,6 +148,10 @@
     return $self->SUPER::loop_common(@_);
 }
 
+=head2 maybe_my
+
+=cut
+
 sub maybe_my {
     my $self = shift;
     my($op, $cx, $text) = @_;
@@ -108,6 +166,10 @@
     }
 }
 
+=head2 maybe_parens_func
+
+=cut
+
 sub maybe_parens_func {
     my $self = shift;
     my($func, $text, $cx, $prec) = @_;
@@ -115,6 +177,10 @@
 
 }
 
+=head2 const
+
+=cut
+
 sub const {
     my $self = shift;
     my($sv, $cx) = @_;
@@ -124,10 +190,28 @@
     return $self->SUPER::const(@_);
 }
 
+=head2 pp_undef
+
+=cut
+
 sub pp_undef { 'null' }
+
+=head2 pp_sne
+
+=cut
+
 sub pp_sne { shift->binop(@_, "!=", 14) }
+
+=head2 pp_grepwhile
+
+=cut
+
 sub pp_grepwhile { shift->mapop(@_, "grep") }
 
+=head2 mapop
+
+=cut
+
 sub mapop {
     my $self = shift;
     my($op, $cx, $name) = @_;
@@ -149,12 +233,20 @@
     return "(".join(", ", @exprs).").select(function (\$_) $code)";
 }
 
+=head2 e_anoncode
+
+=cut
+
 sub e_anoncode {
     my ($self, $info) = @_;
     my $text = $self->deparse_sub($info->{code});
     return "function () " . $text;
 }
 
+=head2 e_anonhash
+
+=cut
+
 sub e_anonhash {
     my ($self, $info) = @_;
     my @exprs = @{$info->{exprs}};
@@ -165,6 +257,10 @@
     return '{' . join(", ", @pairs) . '}';
 }
 
+=head2 pp_entersub
+
+=cut
+
 sub pp_entersub {
     my $self = shift;
     my $ret = $self->SUPER::pp_entersub(@_);
@@ -173,6 +269,10 @@
     return $ret;
 }
 
+=head2 e_method
+
+=cut
+
 sub e_method {
     my ($self, $info) = @_;
     my $obj = $info->{object};
@@ -190,6 +290,10 @@
     return $kid . "(" . $args . ")"; # parens mandatory
 }
 
+=head2 walk_linesq
+
+=cut
+
 sub walk_lineseq {
     my ($self, $op, $kids, $callback) = @_;
     my $xcallback = $callback;
@@ -201,10 +305,20 @@
     $self->SUPER::walk_lineseq($op, $kids, $callback);
 }
 
+=head2 compile_to_js
+
+=cut
+
 sub compile_to_js {
     my $class = shift;
     my $code = shift;
     return 'function() '.$class->new->coderef2text($code);
 }
 
+=head1 SEE ALSO
+
+L<B::Deparse>, L<Jifty::Web::PageRegion/client_cache_content>
+
+=cut
+
 1;

Modified: jifty/branches/js-refactor/lib/Jifty/View/Declare/Handler.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/View/Declare/Handler.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/View/Declare/Handler.pm	Thu Nov 29 12:25:16 2007
@@ -3,7 +3,7 @@
 use warnings;
 use strict;
 
-use base qw/Jifty::Object Class::Accessor/;
+use base qw/Jifty::Object Class::Accessor::Fast/;
 use Template::Declare;
 
 __PACKAGE__->mk_accessors(qw/root_class/);
@@ -76,11 +76,12 @@
     };
     
     my $content = Template::Declare::Tags::show_page( $template, Jifty->web->request->arguments );
-    return unless defined $content && length $content;
+    return unless defined $content;
 
     my $r = Jifty->handler->apache;
     $r->content_type || $r->content_type('text/html; charset=utf-8'); # Set up a default
     unless ( Jifty->handler->apache->http_header_sent || Jifty->web->request->is_subrequest ) {
+        Jifty->web->session->set_cookie;
         Jifty->handler->apache->send_http_header;
     }
 

Modified: jifty/branches/js-refactor/lib/Jifty/View/Declare/Helpers.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/View/Declare/Helpers.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/View/Declare/Helpers.pm	Thu Nov 29 12:25:16 2007
@@ -5,7 +5,15 @@
 use Template::Declare::Tags;
 use base qw/Template::Declare Exporter/;
 
-our @EXPORT = ( qw(hyperlink tangent redirect new_action form_submit form_return form_next_page page content wrapper request get set render_param current_user render_action render_region), @Template::Declare::Tags::EXPORT);
+our @EXPORT = (
+    qw(hyperlink tangent redirect new_action
+    form_submit form_return form_next_page page content
+    wrapper request get set render_param current_user
+    render_action render_region),
+    @Template::Declare::Tags::EXPORT,
+    @Template::Declare::Tags::TagSubs,  # Backward compatibility only
+    @Template::Declare::Tags::TAG_SUB_LIST,
+);
 
 =head1 NAME
 
@@ -260,7 +268,7 @@
     return '';
 }
 
-=head2 page 
+=head2 page
 
  template 'foo' => page {{ title is 'Foo' } ... };
 
@@ -287,7 +295,7 @@
         my $wrapper = Jifty->app_class('View')->can('wrapper') || \&wrapper;
         my @metadata = $meta ? $meta->() : ();
         my $metadata = $#metadata == 0 ? $metadata[0] : {@metadata};
-        local *is::title = sub { warn "Can't use 'title is' when mixing mason and TD" };
+        local *is::title = sub { Carp::carp "Can't use 'title is' when mixing mason and TD" };
         $wrapper->( sub { $code->( $self, $metadata ) }, $metadata );
     }
 }
@@ -320,7 +328,9 @@
     # XXX: fallback, this is ugly
     Jifty::Util->require( $page_class );
 
-    my $page = $page_class->new({ content_code => shift });
+    my $content_code = shift;
+    my $meta = shift;
+    my $page = $page_class->new({ content_code => $content_code, _meta => $meta });
 
     my ($spa) = Jifty->find_plugin('Jifty::Plugin::SinglePage');
 

Modified: jifty/branches/js-refactor/lib/Jifty/View/Declare/Page.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/View/Declare/Page.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/View/Declare/Page.pm	Thu Nov 29 12:25:16 2007
@@ -17,7 +17,7 @@
 
 use Jifty::View::Declare::Helpers;
 
-__PACKAGE__->mk_accessors(qw(content_code done_header _title));
+__PACKAGE__->mk_accessors(qw(content_code done_header _title _meta));
 use constant allow_single_page => 1;
 
 =head2 new
@@ -33,6 +33,9 @@
     my ($title) = get_current_attr(qw(title));
     $self->_title($title);
 
+    $self->_title($self->_meta->{title})
+	if $self->_meta && $self->_meta->{title};
+
     return $self;
 }
 
@@ -185,16 +188,18 @@
 =cut
 
 sub render_jifty_page_detritus {
-
+    show('/app_page_footer') if Template::Declare->resolve_template('/app_page_footer' => 1); # the 1 is 'show_private'
     show('/keybindings');
     with( id => "jifty-wait-message", style => "display: none" ),
         div { _('Loading...') };
+    with( id => "jifty-result-popup" ),
+        div { };
 
     # 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 { outs('new Jifty.Subs({}).start();') };
+        script { outs_raw('new Jifty.Subs({}).start();') };
     }
 }
 

Modified: jifty/branches/js-refactor/lib/Jifty/View/Mason/Handler.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/View/Mason/Handler.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/View/Mason/Handler.pm	Thu Nov 29 12:25:16 2007
@@ -98,13 +98,14 @@
         %{ Jifty->config->framework('Web')->{'MasonConfig'} },
     );
 
+    my $root_serial = 0;
     for my $plugin (Jifty->plugins) {
         my $comp_root = $plugin->template_root;
         unless  ( $comp_root and -d $comp_root) {
             next;
         }
         Jifty->log->debug( "Plugin @{[ref($plugin)]} mason component root added: (@{[$comp_root ||'']})");
-        push @{ $config{comp_root} }, [ ref($plugin)."-".Jifty->web->serial => $comp_root ];
+        push @{ $config{comp_root} }, [ ref($plugin)."-". $root_serial++ => $comp_root ];
     }
     push @{$config{comp_root}}, [jifty => Jifty->config->framework('Web')->{'DefaultTemplateRoot'}];
 
@@ -185,10 +186,16 @@
 sub handle_comp {
     my ($self, $comp, $args) = @_;
 
+    Jifty->web->session->set_cookie;
+
     # Set up the global
     my $r = Jifty->handler->apache;
     $self->interp->set_global('$r', $r);
 
+    # XXX FIXME This is a kludge to get use_mason_wrapper to work
+    $self->interp->set_global('$jifty_internal_request', 0);
+    $self->interp->set_global('$jifty_internal_request', 1) if defined $args;
+
     my %args = $args ? %$args : $self->request_args($r);
 
     my @result;

Modified: jifty/branches/js-refactor/lib/Jifty/View/Static/Handler.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/View/Static/Handler.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/View/Static/Handler.pm	Thu Nov 29 12:25:16 2007
@@ -192,8 +192,9 @@
         # 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',
+        'js'   => 'application/x-javascript',
+        'json' => 'application/json; charset=UTF-8',
+        'htc'  => 'text/x-component',
     );
 
     return ($type_override{$1})
@@ -242,6 +243,9 @@
         $apache->header_out( Status => 200 );
         $apache->content_type($mime_type);
         my $now = time();
+     
+        $apache->header_out('Cache-Control' =>  'max-age=259200, public');
+
         $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');  
@@ -250,10 +254,10 @@
         $apache->send_http_header();
 
         if ($compression eq 'gzip') {
-        undef $/;
-        binmode STDOUT;
-        # XXX TODO: Cache this
-        print STDOUT Compress::Zlib::memGzip(<$fh>);
+            local $/;
+            binmode STDOUT;
+            # XXX TODO: Cache this
+            print STDOUT Compress::Zlib::memGzip(<$fh>);
         } else{
             $apache->send_fd($fh);
         }

Modified: jifty/branches/js-refactor/lib/Jifty/Web.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Web.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Web.pm	Thu Nov 29 12:25:16 2007
@@ -9,13 +9,11 @@
 
 =cut
 
-
-
-
 use CGI::Cookie;
 use XML::Writer;
 use CSS::Squish;
 use Digest::MD5 qw(md5_hex);
+use Scalar::Util qw(blessed);
 use Carp qw(carp);
 use base qw/Class::Accessor::Fast Class::Data::Inheritable Jifty::Object/;
 
@@ -49,6 +47,7 @@
     scriptaculous/effects.js
     scriptaculous/controls.js
     template_declare.js
+    loc.js
     jifty.js
     jifty_utils.js
     jifty_subs.js
@@ -64,7 +63,7 @@
     app_behaviour.js
 )]);
 
-use Jifty::DBI::Class::Trigger;
+use Class::Trigger;
 
 =head1 METHODS
 
@@ -643,6 +642,8 @@
     # XXX: move to singlepage plugin
     my ($spa) = Jifty->find_plugin('Jifty::Plugin::SinglePage') or return;
 
+    return if $self->failed_actions;
+
     Jifty->web->request->remove_state_variable( 'region-'.$spa->region_name );
     Jifty->web->request->add_fragment(
         name      => $spa->region_name,
@@ -771,6 +772,10 @@
     # Headers..
     $apache->header_out( Location => $page );
     $apache->header_out( Status => 302 );
+
+    # cookie has to be sent or returning from continuations breaks
+    Jifty->web->session->set_cookie;
+
     $apache->send_http_header();
 
     # Mason abort, or dispatcher abort out of here
@@ -1062,21 +1067,14 @@
 
 sub include_css {
     my $self = shift;
-    my ($ccjs) = Jifty->find_plugin('Jifty::Plugin::CompressedCSSandJS');
-    if ( $ccjs && $ccjs->css_enabled ) {
-        $self->generate_css;
-        $self->out(
-            '<link rel="stylesheet" type="text/css" href="/__jifty/css/'
-            . __PACKAGE__->cached_css_digest . '.css" />'
-        );
-    }
-    else {
-        $self->out(
-            '<link rel="stylesheet" type="text/css" '
-            . 'href="/static/css/main.css" />'
-        );
-    }
-    
+
+    # if there's no trigger, 0 is returned.  if aborted/handled, undef
+    # is returned.
+    defined $self->call_trigger( 'include_css', @_ ) or return '';
+
+    $self->out( '<link rel="stylesheet" type="text/css" '
+            . 'href="/static/css/main.css" />' );
+
     return '';
 }
 
@@ -1094,37 +1092,6 @@
     ]);
 }
 
-=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 @roots = map { Jifty::Util->absolute_path( File::Spec->catdir( $_, 'css' ) ) }
-                        Jifty->handler->view('Jifty::View::Static::Handler')->roots;
-
-        CSS::Squish->roots( @roots );
-        
-        my $css = CSS::Squish->concatenate(
-            map { CSS::Squish->_resolve_file( $_, @roots ) }
-                @{ $self->css_files }
-        );
-
-        __PACKAGE__->cached_css( $css );
-        __PACKAGE__->cached_css_digest( md5_hex( $css ) );
-		__PACKAGE__->cached_css_time( time );
-    }
-}
-
 =head3 include_javascript
 
 Returns a C<< <script> >> tag for the compressed Javascript.
@@ -1169,14 +1136,16 @@
 
     # if there's no trigger, 0 is returned.  if aborted/handled, undef
     # is returned.
-    defined $self->call_trigger('include_javascript', @_) or return '';
-
-    for my $file ( @{ __PACKAGE__->javascript_libs } ) {
-        $self->out(
-            qq[<script type="text/javascript" src="/static/js/$file"></script>\n]
-        );
+    if ( defined $self->call_trigger('include_javascript', @_) ) {
+        for my $file ( @{ __PACKAGE__->javascript_libs } ) {
+            $self->out(
+                       qq[<script type="text/javascript" src="/static/js/$file"></script>\n]
+                      );
+        }
     }
 
+    $self->call_trigger('after_include_javascript', @_);
+
     return '';
 }
 
@@ -1188,10 +1157,7 @@
 
 sub add_javascript {
     my $self = shift;
-    Jifty->web->javascript_libs([
-        @{ Jifty->web->javascript_libs },
-        @_
-    ]);
+    Jifty->web->javascript_libs([ @{ Jifty->web->javascript_libs }, @_ ]);
 }
 
 =head3 add_external_javascript URL1, URL2, ...
@@ -1203,9 +1169,7 @@
 sub add_external_javascript {
     my $self = shift;
     Jifty->web->external_javascript_libs([
-        @{ Jifty->web->external_javascript_libs },
-        @_
-    ]);
+        @{ Jifty->web->external_javascript_libs }, @_ ]);
 }
 
 =head2 STATE VARIABLES

Modified: jifty/branches/js-refactor/lib/Jifty/Web/Form.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Web/Form.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Web/Form.pm	Thu Nov 29 12:25:16 2007
@@ -5,7 +5,7 @@
 
 use base qw/Jifty::Object Class::Accessor::Fast/;
 
-__PACKAGE__->mk_accessors(qw(actions printed_actions name call is_open disable_autocomplete target submit_to));
+__PACKAGE__->mk_accessors(qw(actions printed_actions name call is_open disable_autocomplete target submit_to onsubmit));
 
 =head1 NAME
 
@@ -200,6 +200,7 @@
     $form_start .= qq! name="@{[ $self->name ]}"! if defined $self->name;
     $form_start .= qq! target="@{[ $self->target ]}"! if defined $self->target;
     $form_start .= qq! autocomplete="off"!  if defined $self->disable_autocomplete;
+    $form_start .= qq! onsubmit="! .Jifty->web->escape( $self->onsubmit ). qq!"!  if defined $self->onsubmit;
     $form_start .= qq! enctype="multipart/form-data" >\n!;
     Jifty->web->out($form_start);
 

Modified: jifty/branches/js-refactor/lib/Jifty/Web/Form/Clickable.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Web/Form/Clickable.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Web/Form/Clickable.pm	Thu Nov 29 12:25:16 2007
@@ -3,6 +3,7 @@
 
 package Jifty::Web::Form::Clickable;
 use Class::Trigger;
+use Scalar::Util qw/blessed/;
 
 =head1 NAME
 
@@ -84,9 +85,26 @@
 
 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.
+moniker or, a L<Jifty::Action> or a hashref with the keys 'action' and 'arguments'. 
+An undefined value submits B<all> actions in the form, an empty list 
+reference (the default) submits none.
+
+In the most complex case, you have something like this:
+
+    submit => [
+                  {   action    => $my_action,
+                      arguments => {
+                          name => 'Default McName',
+                          age  => '23'
+                      },
+                  },
+                  $my_other_action,
+                  'some-other-action-moniker'
+              ]
+
+If you specify arguments in the submit block for a button, they will override 
+any values from form fileds submitted by the user.
+
 
 =item preserve_state
 
@@ -166,11 +184,35 @@
     }
 
     if ( $self->{submit} ) {
-        $self->{submit} = [ $self->{submit} ] unless ref $self->{submit} eq "ARRAY";
-        $self->{submit}
-            = [ map { ref $_ ? $_->moniker : $_ } @{ $self->{submit} } ];
+        $self->{submit} = [ $self->{submit} ]
+            unless ref $self->{submit} eq "ARRAY";
+
+        my @submit_temp = ();
+        foreach my $submit ( @{ $self->{submit} } ) {
+
+       # If we have been handed an action moniker to submit, just submit that.
+            if ( !ref($submit) ) { push @submit_temp, $submit }
+
+            # We've been handed a Jifty::Action to submit
+            elsif ( blessed($submit) ) { push @submit_temp, $submit->moniker }
+
+          # We've been handed a hashref which contains an action and arguments
+            else {
+
+           # Add whatever additional arguments they've requested to the button
+                $args{parameters}{ $submit->{'action'}->form_field_name($_) }
+                    = $submit->{arguments}{$_}
+                    for keys %{ $submit->{arguments} };
+
+                # Add the action's moniker to the submit
+                push @submit_temp, $submit->{'action'}->moniker;
+            }
+        }
+
+        @{ $self->{submit} } = @submit_temp;
     }
 
+
     # Anything doing fragment replacement needs to preserve the
     # current state as well
     if ( grep { $self->$_ } $self->handlers or $self->preserve_state ) {
@@ -260,6 +302,9 @@
 
 sub state_variable {
     my $self = shift;
+    defined $self->call_trigger('before_state_variable', @_)
+        or return; # if aborted by trigger
+
     my ( $key, $value, $fallback ) = @_;
     if ( defined $value and length $value ) {
         $self->{state_variable}{"J:V-$key"} = $value;

Modified: jifty/branches/js-refactor/lib/Jifty/Web/Form/Element.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Web/Form/Element.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Web/Form/Element.pm	Thu Nov 29 12:25:16 2007
@@ -2,6 +2,7 @@
 use strict;
 
 package Jifty::Web::Form::Element;
+use Scalar::Util qw/blessed/;
 
 =head1 NAME
 
@@ -13,8 +14,8 @@
 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 name of the javascript event handler, such as C<onclick> or C<onchange>, 
+with a set of arguments.
 
 The format of the arguments passed to C<onclick> (or any similar
 method) is a string, a hash reference, or a reference to an array of
@@ -84,8 +85,14 @@
 
 =item submit => MONIKER
 
-An action, moniker of an action, or array reference to such; these
-actions are submitted when the event is fired.
+A Jifty::Action, Jifty::Action moniker, hashref of 
+    { action => Jifty::Action::Subclass, 
+     arguments => { argument => value, argument2 => value2 }
+
+or an arrayref of them.
+
+These actions are submitted when the event is fired. Any arguments 
+specified will override arguments submitted by form field.
 
 =item disable => BOOLEAN
 
@@ -132,14 +139,25 @@
 
 =head2 handlers
 
-Currently, the only supported event handlers are C<onclick>.
+The following handlers are supported:
+
+onclick onchange ondblclick onmousedown onmouseup onmouseover 
+onmousemove onmouseout onfocus onblur onkeypress onkeydown 
+onkeyup onselect
+
+NOTE: onload, onunload, onsubmit and onreset are not yet supported
+
 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); }
+sub handlers { qw(onclick onchange ondblclick onmousedown onmouseup onmouseover 
+                  onmousemove onmouseout onfocus onblur onkeypress onkeydown 
+                  onkeyup onselect) }
+
+
 
 =head2 accessors
 
@@ -149,8 +167,11 @@
 
 =cut
 
-sub accessors { shift->handlers, qw(class key_binding key_binding_label id label tooltip continuation) }
-__PACKAGE__->mk_accessors(qw(_onclick class key_binding key_binding_label id label tooltip continuation));
+sub accessors { shift->handlers, qw(class title key_binding key_binding_label id label tooltip continuation rel) }
+__PACKAGE__->mk_accessors(qw(_onclick _onchange _ondblclick _onmousedown _onmouseup _onmouseover 
+                             _onmousemove _onmouseout _onfocus _onblur _onkeypress _onkeydown 
+                             _onkeyup _onselect
+                             class title key_binding key_binding_label id label tooltip continuation rel));
 
 =head2 new PARAMHASH OVERRIDE
 
@@ -162,8 +183,10 @@
 sub new {
     my ($class, $args, $override) = @_;
     # force using accessor for onclick init
-    if (my $onclick = delete $args->{onclick}) {
-        $override->{onclick} = $onclick;
+    for my $trigger ( __PACKAGE__->handlers() ) {
+      if (my $triggerArgs = delete $args->{$trigger}) {
+        $override->{$trigger} = $triggerArgs;
+      }
     }
 
     my $self = $class->SUPER::new($args);
@@ -178,13 +201,195 @@
     return $self;
 }
 
+
+ 
 =head2 onclick
 
+The onclick event occurs when the pointing device button is clicked 
+over an element. This attribute may be used with most elements.
+
 =cut
 
 sub onclick {
     my $self = shift;
-    return $self->_onclick unless @_;
+    $self->_onclick($self->_handler_setup('_onclick', @_));
+}
+
+=head2 onchange
+
+The onchange event occurs when a control loses the input focus 
+and its value has been modified since gaining focus. This handler 
+can be used with all form elements.
+
+=cut
+
+sub onchange {
+    my $self = shift;
+    $self->_onchange($self->_handler_setup('_onchange', @_));
+}
+
+
+
+=head2 ondblclick
+
+The ondblclick event occurs when the pointing device button is 
+double clicked over an element.  This handler 
+can be used with all form elements.
+
+=cut
+
+sub ondblclick {
+    my $self = shift;
+    $self->_ondblclick($self->_handler_setup('_ondblclick', @_));
+}
+
+=head2 onmousedown
+
+The onmousedown event occurs when the pointing device button is 
+pressed over an element.  This handler 
+can be used with all form elements.
+
+=cut
+
+sub onmousedown {
+    my $self = shift;
+    $self->_onmousedown($self->_handler_setup('_onmousedown', @_));
+}
+
+=head2 onmouseup
+
+The onmouseup event occurs when the pointing device button is released 
+over an element.  This handler can be used with all form elements.
+
+=cut
+
+sub onmouseup {
+    my $self = shift;
+    $self->_onmouseup($self->_handler_setup('_onmouseup', @_));
+}
+
+=head2 onmouseover
+
+The onmouseover event occurs when the pointing device is moved onto an 
+element.  This handler can be used with all form elements.
+
+=cut
+
+sub onmouseover {
+    my $self = shift;
+    $self->_onmouseover($self->_handler_setup('_onmouseover', @_));
+}
+
+=head2 onmousemove
+
+The onmousemove event occurs when the pointing device is moved while it 
+is over an element.  This handler can be used with all form elements.
+
+=cut
+
+sub onmousemove {
+    my $self = shift;
+    $self->_onmousemove($self->_handler_setup('_onmousemove', @_));
+}
+
+=head2 onmouseout
+
+The onmouseout event occurs when the pointing device is moved away from 
+an element.  This handler can be used with all form elements.
+
+=cut
+
+sub onmouseout {
+    my $self = shift;
+    $self->_onmouseout($self->_handler_setup('_onmouseout', @_));
+}
+
+=head2 onfocus
+
+The onfocus event occurs when an element receives focus either by the 
+pointing device or by tabbing navigation.  This handler 
+can be used with all form elements.
+
+=cut
+
+sub onfocus {
+    my $self = shift;
+    $self->_onfocus($self->_handler_setup('_onfocus', @_));
+}
+
+=head2 onblur
+
+The onblur event occurs when an element loses focus either by the pointing 
+device or by tabbing navigation.  This handler can be used with all 
+form elements.
+
+=cut
+
+sub onblur {
+    my $self = shift;
+    $self->_onblur($self->_handler_setup('_onblur', @_));
+}
+
+=head2 onkeypress
+
+The onkeypress event occurs when a key is pressed and released over an 
+element.  This handler can be used with all form elements.
+
+=cut
+
+sub onkeypress {
+    my $self = shift;
+    $self->_onkeypress($self->_handler_setup('_onkeypress', @_));
+}
+
+=head2 onkeydown
+
+The onkeydown event occurs when a key is pressed down over an element. 
+This handler can be used with all form elements.
+
+=cut
+
+sub onkeydown {
+    my $self = shift;
+    $self->_onkeydown($self->_handler_setup('_onkeydown', @_));
+}
+
+=head2 onkeyup
+
+The onkeyup event occurs when a key is released over an element. 
+This handler can be used with all form elements.
+=cut
+
+sub onkeyup {
+    my $self = shift;
+    $self->_onkeyup($self->_handler_setup('_onkeyup', @_));
+}
+
+=head2 onselect
+
+The onselect event occurs when a user selects some text in a text field. 
+This attribute may be used with the text and textarea fields.
+
+=cut
+
+sub onselect {
+    my $self = shift;
+    $self->_onselect($self->_handler_setup('_onselect', @_));
+}
+
+
+
+=head2 _handler_setup
+
+This method is used by all handlers to normalize all arguments.
+
+=cut
+
+sub _handler_setup {
+    my $self = shift;
+    my $trigger = shift;
+
+    return $self->$trigger unless @_;
 
     my ($arg) = @_;
 
@@ -199,18 +404,34 @@
         # 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} } ];
+
+            my @submit_tmp;
+            foreach my $submit ( @{$hook->{submit}}) {
+                if (!ref($submit)){ 
+                        push @submit_tmp, $submit;
+                    } 
+                elsif(blessed($submit)) {
+                        push @submit_tmp, $submit->moniker;
+
+                } else { # it's a hashref
+                        push @submit_tmp, $submit->{'action'}->moniker;
+                        $hook->{'action_arguments'}->{ $submit->{'action'}->moniker } = $submit->{'arguments'};
+                }
+
+            }
+
+            @{$hook->{submit}} =  @submit_tmp;
         }
 
+        $hook->{args} ||= $hook->{arguments}; # should be able to use 'arguments' and not lose.
+
         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} };
+                $hook->{args}{$key}{$_} = $hook->{args}{$key}{$_}->moniker for grep { ref $hook->{args}{$key}{$_} } keys %{ $hook->{args}{$key} };
             }
         } else {
             $hook->{args} = {};
@@ -218,7 +439,8 @@
 
     }
 
-    $self->_onclick($arg);
+    return $arg;
+
 }
 
 =head2 javascript
@@ -226,11 +448,12 @@
 Returns the javascript necessary to make the events happen.
 
 =cut
-
 sub javascript {
     my $self = shift;
 
     my $response = "";
+
+  HANDLER:
     for my $trigger ( $self->handlers ) {
         my $value = $self->$trigger;
         next unless $value;
@@ -242,9 +465,16 @@
         my $actions = {};    # Maps actions => disable?
         my $confirm;
         my $beforeclick;
-
+        my $action_arguments = {};
         for my $hook (grep {ref $_ eq "HASH"} (@{$value})) {
 
+            if (!($self->handler_allowed($trigger))) {
+                Jifty->log->error("Handler '$trigger' is not supported for field '" . 
+                                  $self->label . 
+                                  "' with class " . ref $self);
+                next HANDLER;
+            }
+
             my %args;
 
             # Submit action
@@ -255,7 +485,11 @@
                 my $disable_form_on_click = exists $hook->{disable} ? $hook->{disable} : 1;
                 # Normalize to 1/0 to pass to JS
                 $disable_form_on_click = $disable_form_on_click ? 1 : 0;
-                $actions->{$_} = $disable_form_on_click for (@{ $hook->{submit} || [] }); 
+                for (@{ $hook->{submit} || [] }) {
+                    $actions->{$_} = $disable_form_on_click;
+                    $action_arguments->{$_} = $hook->{'action_arguments'}->{$_};
+                }
+
             }
 
             $hook->{region} ||= Jifty->web->qualified_region;
@@ -288,7 +522,7 @@
                 if ($region) {
                     @args{qw/mode path region/} = ('Replace', $region->path, $region->qualified_name);
                 } else {
-                    $self->log->debug("Can't find region ".$hook->{refresh});
+                    $self->log->warn("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}})) {
@@ -323,11 +557,25 @@
         }
 
         my $string = join ";", (grep {not ref $_} (ref $value eq "ARRAY" ? @{$value} : ($value)));
-        if (@fragments or (!$actions || %$actions)) {
+        if ( @fragments or ( !$actions || %$actions ) ) {
 
-            my $update = Jifty->web->escape("update( ". Jifty::JSON::objToJson( {actions => $actions, fragments => \@fragments, continuation => $self->continuation }, {singlequote => 1}) .", this );");
-            $string .= 'if(event.ctrlKey||event.metaKey||event.altKey||event.shiftKey) return true; ';
-            $string .= $self->javascript_preempt ? "return $update" : "$update; return true;";
+            my $update = Jifty->web->escape(
+                "Jifty.update( "
+                    . Jifty::JSON::objToJson(
+                    {   actions      => $actions,
+                        action_arguments => $action_arguments,
+                        fragments    => \@fragments,
+                        continuation => $self->continuation
+                    },
+                    { singlequote => 1 }
+                    ) . ", this );"
+            );
+            $string
+                .= 'if(event.ctrlKey||event.metaKey||event.altKey||event.shiftKey) return true; '
+                if ( $trigger eq 'onclick' );
+            $string .= $self->javascript_preempt
+                ? "return $update"
+                : "$update; return true;";
         }
         if ($confirm) {
             $string = Jifty->web->escape("if(!confirm(" . Jifty::JSON::objToJson($confirm, {singlequote => 1}) . ")) { Event.stop(event); return false }") . $string;
@@ -355,6 +603,10 @@
 
 Sets the CSS class that the element will display as
 
+=head2 title
+
+Sets the title that the element will display, e.g. for tooltips
+
 =head2 key_binding
 
 Sets the key binding associated with this element
@@ -415,4 +667,42 @@
     return '';
 }
 
+
+=head2 handler_allowed HANDLER_NAME
+
+Returns 1 if the handler (e.g. onclick) is allowed.  Undef otherwise.
+
+The set defined here represents the typical handlers that are 
+permitted.  Derived classes should override if they stray from the norm.
+
+By default we allow:
+
+onchange onclick ondblclick onmousedown onmouseup onmouseover onmousemove 
+onmouseout onfocus onblur onkeypress onkeydown onkeyup
+
+=cut
+
+sub handler_allowed {
+    my $self = shift;
+    my ($handler) = @_;
+
+    return {onchange => 1, 
+            onclick => 1, 
+            ondblclick => 1, 
+            onmousedown => 1,
+            onmouseup => 1,
+            onmouseover => 1,
+            onmousemove => 1,
+            onmouseout => 1,
+            onfocus => 1,
+            onblur => 1,
+            onkeypress => 1,
+            onkeydown => 1,
+            onkeyup => 1
+           }->{$handler};
+
+}
+ 
+
+
 1;

Modified: jifty/branches/js-refactor/lib/Jifty/Web/Form/Field.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Web/Form/Field.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Web/Form/Field.pm	Thu Nov 29 12:25:16 2007
@@ -487,17 +487,21 @@
     my $field = qq!  <input !;
     $field .= qq! type="@{[ $self->type ]}"!;
     $field .= qq! name="@{[ $self->input_name ]}"! if ($self->input_name);
+    $field .= qq! title="@{[ $self->title ]}"! if ($self->title);
     $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 .= $self->javascript;
     $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

Modified: jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Button.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Button.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Button.pm	Thu Nov 29 12:25:16 2007
@@ -43,6 +43,7 @@
         'type="submit"',
         'name="' . $self->input_name . '" ',
         'value="' . Jifty->web->escape(_($self->label )). '"',
+        ($self->title ? qq( title="@{[$self->title]}") : ''),
         'id="'. Jifty->web->serial . '"',
         ($self->key_binding ? qq( accesskey="@{[$self->key_binding]}") : ''),
         $self->_widget_class('button', ($self->button_as_link ? ("button_as_link") : ())),

Modified: jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Checkbox.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Checkbox.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Checkbox.pm	Thu Nov 29 12:25:16 2007
@@ -34,6 +34,7 @@
 
     $field .= qq!<input!;
     $field .= qq! type="checkbox"!;
+    $field .= qq! title="@{[ $self->title ]}"! if ($self->title);
     $field .= qq! name="@{[ $self->input_name ]}"!;
     $field .= qq! id="@{[ $self->element_id ]}"!;
     $field .= qq! value="@{[$self->value ||1]}"!;
@@ -57,6 +58,7 @@
     my $self  = shift;
     my $field .= qq!<input type="checkbox"!;
     $field .= qq! name="@{[ $self->input_name ]}"!;
+    $field .= qq! title="@{[ $self->title ]}"! if ($self->title);
     $field .= qq! id="@{[ $self->element_id ]}"!;
     $field .= qq! value="@{[$self->value ||1]}"!;
     $field .= $self->_widget_class;

Modified: jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Collection.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Collection.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Collection.pm	Thu Nov 29 12:25:16 2007
@@ -51,6 +51,7 @@
     $id =~ s/\s+/_/;
     my $field = qq! <input type="text" !;
     $field .= qq! name="@{[ $self->input_name ]}"!;
+    $field .= qq! title="@{[ $self->title ]}"! if ($self->title);
     $field .= qq! id="@{[ $id ]}"!;
     $field .= qq! value="0"!;
     $field .= $self->_widget_class;

Modified: jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Combobox.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Combobox.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Combobox.pm	Thu Nov 29 12:25:16 2007
@@ -19,16 +19,20 @@
 
 sub render_widget {
     my $self  = shift;
+    my $title = $self->title ? qq! title="@{[ $self->title ]}"!
+                             : qq! !;
 
 my $field = <<"EOF";
 <nobr>
 <span id="@{[ $self->element_id ]}_Container" class="combobox">
 <input name="@{[ $self->fallback_name ]}" 
        id="@{[ $self->element_id ]}" 
+       $title
        @{[ $self->_widget_class('combo-text')]}
        value="@{[ $self->current_value ]}" 
        type="text" 
        size="30"
+       @{[ $self->javascript ]}
        autocomplete="off" /><span id="@{[ $self->element_id ]}_Button" 
        @{[ $self->_widget_class('combo-button')]}
         ></span></span><span style="display: none"></span><select 
@@ -67,6 +71,7 @@
     '';
 }
 
+
 =head2 render_autocomplete
 
 Never render anything for autocomplete.

Modified: jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Date.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Date.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Date.pm	Thu Nov 29 12:25:16 2007
@@ -22,4 +22,16 @@
     return join(' ', 'date', ($self->SUPER::classes));
 }
 
+
+=head2 handler_allowed HANDLER_NAME
+
+Returns 1 if the handler (e.g. onclick) is allowed.  Undef otherwise.
+
+=cut
+
+sub handler_allowed {
+    undef;
+}
+
+
 1;

Modified: jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Hidden.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Hidden.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Hidden.pm	Thu Nov 29 12:25:16 2007
@@ -30,4 +30,16 @@
     $self->render_widget();
     return '';
 }
+
+=head2 handler_allowed HANDLER_NAME
+
+Returns 1 if the handler (e.g. onclick) is allowed.  Undef otherwise.
+
+=cut
+
+sub handler_allowed {
+    undef;
+}
+
+
 1;

Modified: jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Radio.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Radio.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Radio.pm	Thu Nov 29 12:25:16 2007
@@ -59,11 +59,13 @@
     my $field = qq! <input type="radio" !;
     $field .= qq! name="@{[ $self->input_name ]}"!;
     $field .= qq! id="@{[ $id ]}"!;
+    $field .= qq! title="@{[ $self->title ]}"! if ($self->title);
     $field .= qq! value="@{[ $value ]}"!;
     $field .= $self->_widget_class;
 
     $field .= qq! checked="checked" !
       if defined $self->current_value and $self->current_value eq $value;
+    $field .= $self->javascript;
     $field .= qq! /><label for="@{[ $id ]}"!;
     $field .= $self->_widget_class;
     $field .= qq!>$display</label>\n!;
@@ -73,4 +75,5 @@
     '';
 }
 
+
 1;

Modified: jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/ResetButton.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/ResetButton.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/ResetButton.pm	Thu Nov 29 12:25:16 2007
@@ -24,6 +24,7 @@
         '<input',
         'type="reset"',
         'name="' . $self->input_name . '" ',
+        ($self->title ? 'title="'.$self->title.'" ' : ''),
         'value="' . _($self->label ). '"',
         $self->_widget_class('button', 'reset'),
         $self->javascript,
@@ -35,4 +36,5 @@
 }
 
 
+
 1;

Modified: jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Select.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Select.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Select.pm	Thu Nov 29 12:25:16 2007
@@ -22,7 +22,9 @@
     my $field = qq! <select !;
     $field .= qq! name="@{[ $self->input_name ]}"!;
     $field .= qq! id="@{[ $self->element_id ]}"!;
+    $field .= qq! id="@{[ $self->title ]}"! if ($self->title);
     $field .= $self->_widget_class;
+    $field .= $self->javascript;
     $field .= qq!      >\n!;
     for my $opt (@{ $self->action->available_values($self->name) }) {
         my $display = $opt->{'display'};

Modified: jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Text.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Text.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Text.pm	Thu Nov 29 12:25:16 2007
@@ -25,4 +25,21 @@
     return join(' ', 'text', ($self->SUPER::classes));
 }
 
+
+=head2 handler_allowed HANDLER_NAME
+
+Returns 1 if the handler (e.g. onclick) is allowed.  Undef otherwise.
+
+=cut
+
+sub handler_allowed {
+    my $self = shift;
+    my ($handler) = @_;
+
+    return $self->SUPER::handler_allowed($handler) ||
+           {onselect => 1}->{$handler};
+
+}
+
+
 1;

Modified: jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Textarea.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Textarea.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Textarea.pm	Thu Nov 29 12:25:16 2007
@@ -34,9 +34,11 @@
     $field .= qq!<textarea!;
     $field .= qq! name="@{[ $self->input_name ]}"!;
     $field .= qq! id="@{[ $self->element_id ]}"!;
+    $field .= qq! title="@{[ $self->title ]}"! if ($self->title);
     $field .= qq! rows="@{[$self->rows || 5 ]}"!;
     $field .= qq! cols="@{[$self->cols || 60]}"!;
     $field .= $self->_widget_class;
+    $field .= $self->javascript;
     $field .= qq! >!;
     $field .= Jifty->web->escape($self->current_value) if $self->current_value;
     $field .= qq!</textarea>\n!;
@@ -44,4 +46,20 @@
     '';
 }
 
+=head2 handler_allowed HANDLER_NAME
+
+Returns 1 if the handler (e.g. onclick) is allowed.  Undef otherwise.
+
+=cut
+
+sub handler_allowed {
+    my $self = shift;
+    my ($handler) = @_;
+
+    return $self->SUPER::handler_allowed($handler) ||
+           {onselect => 1}->{$handler};
+
+}
+
+
 1;

Modified: jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Unrendered.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Unrendered.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Unrendered.pm	Thu Nov 29 12:25:16 2007
@@ -24,4 +24,16 @@
     '';
 }
 
+
+=head2 handler_allowed HANDLER_NAME
+
+Returns 1 if the handler (e.g. onclick) is allowed.  Undef otherwise.
+
+=cut
+
+sub handler_allowed {
+    undef;
+}
+
+
 1;

Modified: jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Upload.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Upload.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Web/Form/Field/Upload.pm	Thu Nov 29 12:25:16 2007
@@ -28,11 +28,13 @@
     my $self  = shift;
     my $field = qq!<input type="file" name="@{[ $self->input_name ]}" !;
     $field .= $self->_widget_class();
-        $field .= qq!/>!;
+    $field .= $self->javascript;
+    $field .= qq!/>!;
     Jifty->web->out($field);
     '';
 }
 
+
 =head2 render_value
 
 The 'value', rendered, is empty so that BLOBs and the like don't get

Modified: jifty/branches/js-refactor/lib/Jifty/Web/Form/Link.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Web/Form/Link.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Web/Form/Link.pm	Thu Nov 29 12:25:16 2007
@@ -31,8 +31,8 @@
 
 =cut
 
-sub accessors { shift->SUPER::accessors(), qw(url escape_label tooltip target); }
-__PACKAGE__->mk_accessors(qw(url escape_label tooltip target));
+sub accessors { shift->SUPER::accessors(), qw(url escape_label tooltip target rel); }
+__PACKAGE__->mk_accessors(qw(url escape_label tooltip target rel));
 
 =head2 new PARAMHASH
 
@@ -75,6 +75,7 @@
         tooltip      => undef,
         escape_label => 1,
         class        => '',
+        rel          => '',
         target       => '' }, $args );
 
     return $self;
@@ -86,15 +87,14 @@
 
 =cut
 
-=head2 render
+=head2 as_string
 
-Render the string of the link, including any necessary javascript.
+Returns the string of the link, including any necessary javascript.
 
 =cut
 
-sub render {
+sub as_string {
     my $self = shift;
-
     my $label = $self->label;
     $label = Jifty->web->escape( $label )
         if ( $self->escape_label );
@@ -103,17 +103,39 @@
     $tooltip = Jifty->web->escape( $tooltip )
         if ( defined $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="@{[$tooltip]}"))       if defined $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();
+    my $output = '';
+
+    $output .= (qq(<a));
+    $output .= (qq( id="@{[$self->id]}"))         if $self->id;
+    $output .= (qq( class="@{[$self->class]}"))   if $self->class;
+    $output .= (qq( title="@{[$tooltip]}"))       if defined $tooltip;
+    $output .= (qq( target="@{[$self->target]}")) if $self->target;
+    $output .= (qq( accesskey="@{[$self->key_binding]}")) if $self->key_binding;
+    $output .= (qq( rel="@{[$self->rel]}"))       if $self->rel;
+    $output .= (qq( href="@{[Jifty->web->escape($self->url)]}"));
+    $output .= ( $self->javascript() );
+    $output .= (qq(>$label</a>));
+
+    $output .= (
+        '<script type="text/javascript"><!--' .
+        "\n" .
+        Jifty->web->escape($self->key_binding_javascript).
+        "\n" .
+        "--></script>") if $self->key_binding;
+
+    return $output;
+}
+
+=head2 render
+
+Render the string of the link, including any necessary javascript.
+
+=cut
+
+sub render {
+    my $self = shift;
 
+    Jifty->web->out($self->as_string);
     return ('');
 }
 

Modified: jifty/branches/js-refactor/lib/Jifty/Web/Menu.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Web/Menu.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Web/Menu.pm	Thu Nov 29 12:25:16 2007
@@ -33,6 +33,14 @@
 
 Sets or returns the string that the menu item will be displayed as.
 
+=cut
+
+sub label {
+    my $self = shift;
+    $self->{label} = shift if @_;
+    return $self->{label};
+}
+
 =head2 parent [MENU]
 
 Gets or sets the parent L<Jifty::Web::Menu> of this item; this defaults
@@ -112,14 +120,16 @@
 sub child {
     my $self = shift;
     my $key = shift;
+    my $proto = ref $self || $self;
+
     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,
-                                                        @_
-                                                       });
+        $self->{children}{$key} = $proto->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) {
@@ -231,7 +241,7 @@
     my @kids = $self->children;
     my $id   = Jifty->web->serial;
     Jifty->web->out( qq{<li class="toplevel }
-            . ( $self->active ? 'active' : 'closed' ) . qq{">}
+            . ( $self->active ? 'active' : 'closed' ) .' '.($self->class||"").' '. qq{">}
             . qq{<span class="title">} );
     Jifty->web->out( $self->as_link );
     Jifty->web->out(qq{</span>});
@@ -244,7 +254,7 @@
                 . $id
                 . qq{">} );
         for (@kids) {
-            Jifty->web->out(qq{<li class="submenu }.($_->active ? 'active' : '' ).qq{">});
+            Jifty->web->out(qq{<li class="submenu }.($_->active ? 'active' : '' ).' '. ($_->class || "").qq{">});
 
             # We should be able to get this as a string.
             # Either stringify the link object or output the label

Modified: jifty/branches/js-refactor/lib/Jifty/Web/PageRegion.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Web/PageRegion.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Web/PageRegion.pm	Thu Nov 29 12:25:16 2007
@@ -36,7 +36,7 @@
 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)
+=item arguments (optional) (formerly 'defaults')
 
 Specifies an optional set of parameter defaults.  These should all be
 simple scalars, as they might be passed across HTTP if AJAX is used.
@@ -82,6 +82,9 @@
                 @_
                );
 
+
+    $args{'arguments'} ||= delete $args{'defaults'};
+
     # Name is required
     if (not defined $args{name}) {
         warn "Name is required for page regions.";
@@ -89,16 +92,16 @@
     }
 
     # 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}};
+    if (grep {ref $_} values %{$args{arguments}}) {
+        warn "Reference '$args{arguments}{$_}' passed as default for '$_' to region '$args{name}'"
+          for grep {ref $args{arguments}{$_}} keys %{$args{arguments}};
         return;
     }
 
     $self->name($args{name});
     $self->qualified_name(Jifty->web->qualified_region($self));
     $self->default_path($args{path});
-    $self->default_arguments($args{defaults});
+    $self->default_arguments($args{arguments});
     $self->force_arguments($args{force_arguments});
     $self->force_path($args{force_path});
     $self->arguments({});
@@ -310,13 +313,14 @@
     $subrequest->path( $self->path );
     $subrequest->top_request( Jifty->web->request->top_request );
 
+    my %args;
     if ($self->path =~ m/\?/) {
 	# XXX: this only happens if we are redirect within region AND
 	# with continuation, which is already taken care of by the
 	# clone.
 	my ($path, $arg) = split(/\?/, $self->path, 2);
 	$subrequest->path( $path );
-	my %args = (map { split /=/, $_ } split /&/, $arg);
+	%args = (map { split /=/, $_ } split /&/, $arg);
 	if ($args{'J:C'}) {
 	    $subrequest->continuation($args{'J:C'});
 	}
@@ -327,6 +331,13 @@
     }
     # $subrequest->clear_actions;
     local Jifty->web->{request} = $subrequest;
+    if ($args{'J:RETURN'}) {
+	my $top = Jifty->web->request->top_request;
+	my $cont = Jifty->web->session->get_continuation($args{'J:RETURN'});
+	$cont->return;
+	# need to set this as subrequest again as it's clobbered by the return
+	Jifty->web->request->top_request($top);
+    }
 
     # While we're inside this region, have Mason to tack its response
     # onto a variable and not send headers when it does so
@@ -388,6 +399,8 @@
 
 =head2 client_cacheable
 
+Returns the client cacheable state of the regions path. Returns false if the template has not been marked as client cacheable. Otherwise it returns the string "static" or "action" based uon the cacheable attribute set on the template.
+
 =cut
 
 sub client_cacheable {
@@ -397,7 +410,9 @@
     return Jifty::View::Declare::BaseClass->client_cacheable($self->path);
 }
 
-=head2 client_cacheable
+=head2 client_cache_content
+
+Returns the template as JavaScript code.
 
 =cut
 

Modified: jifty/branches/js-refactor/lib/Jifty/Web/Session.pm
==============================================================================
--- jifty/branches/js-refactor/lib/Jifty/Web/Session.pm	(original)
+++ jifty/branches/js-refactor/lib/Jifty/Web/Session.pm	Thu Nov 29 12:25:16 2007
@@ -5,6 +5,7 @@
 use base qw/Jifty::Object/;
 use CGI::Cookie ();
 use DateTime ();
+use Storable ();
  
 =head1 NAME
 
@@ -73,45 +74,41 @@
 
     $session->create( key_type => "session" ) unless $session->id;
     $self->_session($session);
-    $self->{cache} = undef;
 }
 
-=head2 load_by_kv key => value OR key, default, callback
+=head2 load_by_kv key => value 
 
 Load up the current session from the given (key, value) pair. If no matching
 session could be found, it will create a new session with the key, value set.
 Be sure that what you're loading by is unique. If you're loading a session
 based on, say, a timestamp, then you're asking for trouble.
 
-The second form is used when you have a complex value. Each value for the
-given key is passed to the callback. The callback should return true if
-there's a match. The default value is used to create a new entry when no
-value was chosen.
-
 =cut
 
 sub load_by_kv {
     my $self = shift;
-    my $k = shift;
-    my $v = shift;
-    my $callback = shift || sub { $_[0] eq $v };
+    my $k    = shift;
+    my $v    = shift;
     my $session_id;
 
     # tried doing this with load_by_cols but it never returned any rows
     my $sessions = Jifty::Model::SessionCollection->new;
-    $sessions->limit(column => 'key_type', value => 'key');
-    $sessions->limit(column => 'data_key', value => $k );
+    $sessions->limit( column => 'key_type', value => 'key' );
+    $sessions->limit( column => 'data_key', value => $k );
 
-    while (my $row = $sessions->next) {
-        my $match = $callback->($row->value);
-        if ($match) {
-            $session_id = $row->session_id;
-            last;
-        }
+    # XXX TODO: we store this data in a storable. so we now want to match on the storable version
+    # It would be so nice if Jifty::DBI could do this for us.
+    $Storable::Deparse = 1;
+    my $value = Storable::nfreeze(\$v);
+
+    $sessions->limit( column => 'value' => value => $value );
+
+    while ( my $row = $sessions->next ) {
+        $session_id = $row->session_id;
+        last;
     }
-
     $self->load($session_id);
-    $self->set($k => $v) if !$session_id;
+    $self->set( $k => $v ) if !$session_id;
 }
 
 sub _get_session_id_from_client {
@@ -167,7 +164,7 @@
 
     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,
@@ -175,19 +172,6 @@
             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};
-    }
 
 }
 
@@ -227,8 +211,6 @@
         );
     }
 
-    $self->{cache}{$key_type}{$key} = $value
-      if $self->{cache};
 
 }
 
@@ -303,8 +285,8 @@
     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' );
+    $conts->limit( column => "key_type",   value => "continuation" );
+    $conts->limit( column => "session_id", value => $self->id );
 
     my %continuations;
     $continuations{ $_->key } = $_->value while $_ = $conts->next;

Added: jifty/branches/js-refactor/share/plugins/Jifty/Plugin/AutoReference/web/static/js/autoreference.js
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/share/plugins/Jifty/Plugin/AutoReference/web/static/js/autoreference.js	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,66 @@
+if (!Jifty.Plugin) Jifty.Plugin = {};
+
+Jifty.Plugin.AutoReference = Class.create();
+Object.extend(Object.extend(Jifty.Plugin.AutoReference.prototype, Jifty.Autocompleter.prototype), {
+    initialize: function(field, hiddenField, div) {
+        this.hiddenField = $(hiddenField);
+
+        // Copied from Jifty.Autocompleter.initialize
+        this.field  = $(field);
+        this.action = Form.Element.getAction(this.hiddenField);
+        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,
+            updateElement: this.updateElment,
+            afterUpdateElement: this.afterUpdate
+        });
+    },
+
+    updateElement: function(selection) {
+        var label = Element.collectTextNodes(document.getElementsByClassName('informal', selection)[0]);
+        var value = Element.collectTextNodes(document.getElementsByClassName('hidden_value', selection)[0]);
+
+        this.field.value = label;
+        this.hiddenField.value = value;
+    },
+
+    afterUpdate: function(field, selection) {
+        
+        Form.Element.validate(this.hiddenField);
+    },
+
+    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;
+
+        // Fix up the field to use the real field instead of the hidden one
+        var value = this.field.value.replace(/\s*\[(?:i(?:d(?::(?:\s*(?:\d+\]?)?)?)?)?)?$/, '')
+        request['actions'][this.action.moniker]['fields'][a['fields']['argument']]['value'] = value;
+
+        var options = { postBody: JSON.stringify(request),
+            onComplete: this.onComplete.bind(this),
+            requestHeaders: ['Content-Type', 'text/x-json']
+        };
+
+        new Ajax.Request(this.url,
+                options
+                );
+    }
+
+});

Added: jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/css/simple_bars.css
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/css/simple_bars.css	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,31 @@
+div.simple_bars div.row {
+    position: relative;
+    padding: 3px 1%;
+    width: 100%;
+    overflow: auto;
+}
+
+div.simple_bars div.row span.label {
+    display: block;
+    float: left;
+    width: 30%;
+    overflow: hidden;
+}
+
+div.simple_bars div.row span.barArea {
+    display: block;
+    float: left;
+    width: 60%;
+    overflow: hidden;
+}
+
+div.simple_bars div.row span.barArea span.bar {
+    display: block;
+}
+
+div.simple_bars div.row span.value {
+    display: block;
+    float: left;
+    width: 8%;
+    text-align: right;
+}

Added: jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts.swf
==============================================================================
Binary file. No diff available.

Added: jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/arno.swf
==============================================================================
Binary file. No diff available.

Added: jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/arst.swf
==============================================================================
Binary file. No diff available.

Added: jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/brfl.swf
==============================================================================
Binary file. No diff available.

Added: jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/brno.swf
==============================================================================
Binary file. No diff available.

Added: jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/brst.swf
==============================================================================
Binary file. No diff available.

Added: jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/cl3d.swf
==============================================================================
Binary file. No diff available.

Added: jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/clfl.swf
==============================================================================
Binary file. No diff available.

Added: jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/clno.swf
==============================================================================
Binary file. No diff available.

Added: jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/clp3.swf
==============================================================================
Binary file. No diff available.

Added: jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/cls3.swf
==============================================================================
Binary file. No diff available.

Added: jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/clst.swf
==============================================================================
Binary file. No diff available.

Added: jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/cnno.swf
==============================================================================
Binary file. No diff available.

Added: jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/lnno.swf
==============================================================================
Binary file. No diff available.

Added: jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/mxno.swf
==============================================================================
Binary file. No diff available.

Added: jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/pi3d.swf
==============================================================================
Binary file. No diff available.

Added: jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/pino.swf
==============================================================================
Binary file. No diff available.

Added: jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/pono.swf
==============================================================================
Binary file. No diff available.

Added: jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/flash/xmlswf/charts_library/scno.swf
==============================================================================
Binary file. No diff available.

Added: jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/js/MochiKit/MochiKit.js
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/js/MochiKit/MochiKit.js	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,4802 @@
+/***
+
+    MochiKit.MochiKit 1.3.1 : PACKED VERSION
+
+    THIS FILE IS AUTOMATICALLY GENERATED.  If creating patches, please
+    diff against the source tree, not this file.
+
+    See <http://mochikit.com/> for documentation, downloads, license, etc.
+
+    (c) 2005 Bob Ippolito.  All rights Reserved.
+
+***/
+
+if(typeof (dojo)!="undefined"){
+dojo.provide("MochiKit.Base");
+}
+if(typeof (MochiKit)=="undefined"){
+MochiKit={};
+}
+if(typeof (MochiKit.Base)=="undefined"){
+MochiKit.Base={};
+}
+MochiKit.Base.VERSION="1.3.1";
+MochiKit.Base.NAME="MochiKit.Base";
+MochiKit.Base.update=function(_1,_2){
+if(_1===null){
+_1={};
+}
+for(var i=1;i<arguments.length;i++){
+var o=arguments[i];
+if(typeof (o)!="undefined"&&o!==null){
+for(var k in o){
+_1[k]=o[k];
+}
+}
+}
+return _1;
+};
+MochiKit.Base.update(MochiKit.Base,{__repr__:function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+},toString:function(){
+return this.__repr__();
+},counter:function(n){
+if(arguments.length===0){
+n=1;
+}
+return function(){
+return n++;
+};
+},clone:function(_7){
+var me=arguments.callee;
+if(arguments.length==1){
+me.prototype=_7;
+return new me();
+}
+},flattenArguments:function(_9){
+var res=[];
+var m=MochiKit.Base;
+var _12=m.extend(null,arguments);
+while(_12.length){
+var o=_12.shift();
+if(o&&typeof (o)=="object"&&typeof (o.length)=="number"){
+for(var i=o.length-1;i>=0;i--){
+_12.unshift(o[i]);
+}
+}else{
+res.push(o);
+}
+}
+return res;
+},extend:function(_13,obj,_15){
+if(!_15){
+_15=0;
+}
+if(obj){
+var l=obj.length;
+if(typeof (l)!="number"){
+if(typeof (MochiKit.Iter)!="undefined"){
+obj=MochiKit.Iter.list(obj);
+l=obj.length;
+}else{
+throw new TypeError("Argument not an array-like and MochiKit.Iter not present");
+}
+}
+if(!_13){
+_13=[];
+}
+for(var i=_15;i<l;i++){
+_13.push(obj[i]);
+}
+}
+return _13;
+},updatetree:function(_17,obj){
+if(_17===null){
+_17={};
+}
+for(var i=1;i<arguments.length;i++){
+var o=arguments[i];
+if(typeof (o)!="undefined"&&o!==null){
+for(var k in o){
+var v=o[k];
+if(typeof (_17[k])=="object"&&typeof (v)=="object"){
+arguments.callee(_17[k],v);
+}else{
+_17[k]=v;
+}
+}
+}
+}
+return _17;
+},setdefault:function(_19,obj){
+if(_19===null){
+_19={};
+}
+for(var i=1;i<arguments.length;i++){
+var o=arguments[i];
+for(var k in o){
+if(!(k in _19)){
+_19[k]=o[k];
+}
+}
+}
+return _19;
+},keys:function(obj){
+var _20=[];
+for(var _21 in obj){
+_20.push(_21);
+}
+return _20;
+},items:function(obj){
+var _22=[];
+var e;
+for(var _24 in obj){
+var v;
+try{
+v=obj[_24];
+}
+catch(e){
+continue;
+}
+_22.push([_24,v]);
+}
+return _22;
+},_newNamedError:function(_25,_26,_27){
+_27.prototype=new MochiKit.Base.NamedError(_25.NAME+"."+_26);
+_25[_26]=_27;
+},operator:{truth:function(a){
+return !!a;
+},lognot:function(a){
+return !a;
+},identity:function(a){
+return a;
+},not:function(a){
+return ~a;
+},neg:function(a){
+return -a;
+},add:function(a,b){
+return a+b;
+},sub:function(a,b){
+return a-b;
+},div:function(a,b){
+return a/b;
+},mod:function(a,b){
+return a%b;
+},mul:function(a,b){
+return a*b;
+},and:function(a,b){
+return a&b;
+},or:function(a,b){
+return a|b;
+},xor:function(a,b){
+return a^b;
+},lshift:function(a,b){
+return a<<b;
+},rshift:function(a,b){
+return a>>b;
+},zrshift:function(a,b){
+return a>>>b;
+},eq:function(a,b){
+return a==b;
+},ne:function(a,b){
+return a!=b;
+},gt:function(a,b){
+return a>b;
+},ge:function(a,b){
+return a>=b;
+},lt:function(a,b){
+return a<b;
+},le:function(a,b){
+return a<=b;
+},ceq:function(a,b){
+return MochiKit.Base.compare(a,b)===0;
+},cne:function(a,b){
+return MochiKit.Base.compare(a,b)!==0;
+},cgt:function(a,b){
+return MochiKit.Base.compare(a,b)==1;
+},cge:function(a,b){
+return MochiKit.Base.compare(a,b)!=-1;
+},clt:function(a,b){
+return MochiKit.Base.compare(a,b)==-1;
+},cle:function(a,b){
+return MochiKit.Base.compare(a,b)!=1;
+},logand:function(a,b){
+return a&&b;
+},logor:function(a,b){
+return a||b;
+},contains:function(a,b){
+return b in a;
+}},forwardCall:function(_30){
+return function(){
+return this[_30].apply(this,arguments);
+};
+},itemgetter:function(_31){
+return function(arg){
+return arg[_31];
+};
+},typeMatcher:function(){
+var _33={};
+for(var i=0;i<arguments.length;i++){
+var typ=arguments[i];
+_33[typ]=typ;
+}
+return function(){
+for(var i=0;i<arguments.length;i++){
+if(!(typeof (arguments[i]) in _33)){
+return false;
+}
+}
+return true;
+};
+},isNull:function(){
+for(var i=0;i<arguments.length;i++){
+if(arguments[i]!==null){
+return false;
+}
+}
+return true;
+},isUndefinedOrNull:function(){
+for(var i=0;i<arguments.length;i++){
+var o=arguments[i];
+if(!(typeof (o)=="undefined"||o===null)){
+return false;
+}
+}
+return true;
+},isEmpty:function(obj){
+return !MochiKit.Base.isNotEmpty.apply(this,arguments);
+},isNotEmpty:function(obj){
+for(var i=0;i<arguments.length;i++){
+var o=arguments[i];
+if(!(o&&o.length)){
+return false;
+}
+}
+return true;
+},isArrayLike:function(){
+for(var i=0;i<arguments.length;i++){
+var o=arguments[i];
+var typ=typeof (o);
+if((typ!="object"&&!(typ=="function"&&typeof (o.item)=="function"))||o===null||typeof (o.length)!="number"){
+return false;
+}
+}
+return true;
+},isDateLike:function(){
+for(var i=0;i<arguments.length;i++){
+var o=arguments[i];
+if(typeof (o)!="object"||o===null||typeof (o.getTime)!="function"){
+return false;
+}
+}
+return true;
+},xmap:function(fn){
+if(fn===null){
+return MochiKit.Base.extend(null,arguments,1);
+}
+var _36=[];
+for(var i=1;i<arguments.length;i++){
+_36.push(fn(arguments[i]));
+}
+return _36;
+},map:function(fn,lst){
+var m=MochiKit.Base;
+var itr=MochiKit.Iter;
+var _39=m.isArrayLike;
+if(arguments.length<=2){
+if(!_39(lst)){
+if(itr){
+lst=itr.list(lst);
+if(fn===null){
+return lst;
+}
+}else{
+throw new TypeError("Argument not an array-like and MochiKit.Iter not present");
+}
+}
+if(fn===null){
+return m.extend(null,lst);
+}
+var _40=[];
+for(var i=0;i<lst.length;i++){
+_40.push(fn(lst[i]));
+}
+return _40;
+}else{
+if(fn===null){
+fn=Array;
+}
+var _41=null;
+for(i=1;i<arguments.length;i++){
+if(!_39(arguments[i])){
+if(itr){
+return itr.list(itr.imap.apply(null,arguments));
+}else{
+throw new TypeError("Argument not an array-like and MochiKit.Iter not present");
+}
+}
+var l=arguments[i].length;
+if(_41===null||_41>l){
+_41=l;
+}
+}
+_40=[];
+for(i=0;i<_41;i++){
+var _42=[];
+for(var j=1;j<arguments.length;j++){
+_42.push(arguments[j][i]);
+}
+_40.push(fn.apply(this,_42));
+}
+return _40;
+}
+},xfilter:function(fn){
+var _44=[];
+if(fn===null){
+fn=MochiKit.Base.operator.truth;
+}
+for(var i=1;i<arguments.length;i++){
+var o=arguments[i];
+if(fn(o)){
+_44.push(o);
+}
+}
+return _44;
+},filter:function(fn,lst,_45){
+var _46=[];
+var m=MochiKit.Base;
+if(!m.isArrayLike(lst)){
+if(MochiKit.Iter){
+lst=MochiKit.Iter.list(lst);
+}else{
+throw new TypeError("Argument not an array-like and MochiKit.Iter not present");
+}
+}
+if(fn===null){
+fn=m.operator.truth;
+}
+if(typeof (Array.prototype.filter)=="function"){
+return Array.prototype.filter.call(lst,fn,_45);
+}else{
+if(typeof (_45)=="undefined"||_45===null){
+for(var i=0;i<lst.length;i++){
+var o=lst[i];
+if(fn(o)){
+_46.push(o);
+}
+}
+}else{
+for(i=0;i<lst.length;i++){
+o=lst[i];
+if(fn.call(_45,o)){
+_46.push(o);
+}
+}
+}
+}
+return _46;
+},_wrapDumbFunction:function(_47){
+return function(){
+switch(arguments.length){
+case 0:
+return _47();
+case 1:
+return _47(arguments[0]);
+case 2:
+return _47(arguments[0],arguments[1]);
+case 3:
+return _47(arguments[0],arguments[1],arguments[2]);
+}
+var _48=[];
+for(var i=0;i<arguments.length;i++){
+_48.push("arguments["+i+"]");
+}
+return eval("(func("+_48.join(",")+"))");
+};
+},method:function(_49,_50){
+var m=MochiKit.Base;
+return m.bind.apply(this,m.extend([_50,_49],arguments,2));
+},bind:function(_51,_52){
+if(typeof (_51)=="string"){
+_51=_52[_51];
+}
+var _53=_51.im_func;
+var _54=_51.im_preargs;
+var _55=_51.im_self;
+var m=MochiKit.Base;
+if(typeof (_51)=="function"&&typeof (_51.apply)=="undefined"){
+_51=m._wrapDumbFunction(_51);
+}
+if(typeof (_53)!="function"){
+_53=_51;
+}
+if(typeof (_52)!="undefined"){
+_55=_52;
+}
+if(typeof (_54)=="undefined"){
+_54=[];
+}else{
+_54=_54.slice();
+}
+m.extend(_54,arguments,2);
+var _56=function(){
+var _57=arguments;
+var me=arguments.callee;
+if(me.im_preargs.length>0){
+_57=m.concat(me.im_preargs,_57);
+}
+var _52=me.im_self;
+if(!_52){
+_52=this;
+}
+return me.im_func.apply(_52,_57);
+};
+_56.im_self=_55;
+_56.im_func=_53;
+_56.im_preargs=_54;
+return _56;
+},bindMethods:function(_58){
+var _59=MochiKit.Base.bind;
+for(var k in _58){
+var _60=_58[k];
+if(typeof (_60)=="function"){
+_58[k]=_59(_60,_58);
+}
+}
+},registerComparator:function(_61,_62,_63,_64){
+MochiKit.Base.comparatorRegistry.register(_61,_62,_63,_64);
+},_primitives:{"boolean":true,"string":true,"number":true},compare:function(a,b){
+if(a==b){
+return 0;
+}
+var _65=(typeof (a)=="undefined"||a===null);
+var _66=(typeof (b)=="undefined"||b===null);
+if(_65&&_66){
+return 0;
+}else{
+if(_65){
+return -1;
+}else{
+if(_66){
+return 1;
+}
+}
+}
+var m=MochiKit.Base;
+var _67=m._primitives;
+if(!(typeof (a) in _67&&typeof (b) in _67)){
+try{
+return m.comparatorRegistry.match(a,b);
+}
+catch(e){
+if(e!=m.NotFound){
+throw e;
+}
+}
+}
+if(a<b){
+return -1;
+}else{
+if(a>b){
+return 1;
+}
+}
+var _68=m.repr;
+throw new TypeError(_68(a)+" and "+_68(b)+" can not be compared");
+},compareDateLike:function(a,b){
+return MochiKit.Base.compare(a.getTime(),b.getTime());
+},compareArrayLike:function(a,b){
+var _69=MochiKit.Base.compare;
+var _70=a.length;
+var _71=0;
+if(_70>b.length){
+_71=1;
+_70=b.length;
+}else{
+if(_70<b.length){
+_71=-1;
+}
+}
+for(var i=0;i<_70;i++){
+var cmp=_69(a[i],b[i]);
+if(cmp){
+return cmp;
+}
+}
+return _71;
+},registerRepr:function(_73,_74,_75,_76){
+MochiKit.Base.reprRegistry.register(_73,_74,_75,_76);
+},repr:function(o){
+if(typeof (o)=="undefined"){
+return "undefined";
+}else{
+if(o===null){
+return "null";
+}
+}
+try{
+if(typeof (o.__repr__)=="function"){
+return o.__repr__();
+}else{
+if(typeof (o.repr)=="function"&&o.repr!=arguments.callee){
+return o.repr();
+}
+}
+return MochiKit.Base.reprRegistry.match(o);
+}
+catch(e){
+if(typeof (o.NAME)=="string"&&(o.toString==Function.prototype.toString||o.toString==Object.prototype.toString)){
+return o.NAME;
+}
+}
+try{
+var _77=(o+"");
+}
+catch(e){
+return "["+typeof (o)+"]";
+}
+if(typeof (o)=="function"){
+o=_77.replace(/^\s+/,"");
+var idx=o.indexOf("{");
+if(idx!=-1){
+o=o.substr(0,idx)+"{...}";
+}
+}
+return _77;
+},reprArrayLike:function(o){
+var m=MochiKit.Base;
+return "["+m.map(m.repr,o).join(", ")+"]";
+},reprString:function(o){
+return ("\""+o.replace(/(["\\])/g,"\\$1")+"\"").replace(/[\f]/g,"\\f").replace(/[\b]/g,"\\b").replace(/[\n]/g,"\\n").replace(/[\t]/g,"\\t").replace(/[\r]/g,"\\r");
+},reprNumber:function(o){
+return o+"";
+},registerJSON:function(_79,_80,_81,_82){
+MochiKit.Base.jsonRegistry.register(_79,_80,_81,_82);
+},evalJSON:function(){
+return eval("("+arguments[0]+")");
+},serializeJSON:function(o){
+var _83=typeof (o);
+if(_83=="undefined"){
+return "undefined";
+}else{
+if(_83=="number"||_83=="boolean"){
+return o+"";
+}else{
+if(o===null){
+return "null";
+}
+}
+}
+var m=MochiKit.Base;
+var _84=m.reprString;
+if(_83=="string"){
+return _84(o);
+}
+var me=arguments.callee;
+var _85;
+if(typeof (o.__json__)=="function"){
+_85=o.__json__();
+if(o!==_85){
+return me(_85);
+}
+}
+if(typeof (o.json)=="function"){
+_85=o.json();
+if(o!==_85){
+return me(_85);
+}
+}
+if(_83!="function"&&typeof (o.length)=="number"){
+var res=[];
+for(var i=0;i<o.length;i++){
+var val=me(o[i]);
+if(typeof (val)!="string"){
+val="undefined";
+}
+res.push(val);
+}
+return "["+res.join(", ")+"]";
+}
+try{
+_85=m.jsonRegistry.match(o);
+return me(_85);
+}
+catch(e){
+if(e!=m.NotFound){
+throw e;
+}
+}
+if(_83=="function"){
+return null;
+}
+res=[];
+for(var k in o){
+var _87;
+if(typeof (k)=="number"){
+_87="\""+k+"\"";
+}else{
+if(typeof (k)=="string"){
+_87=_84(k);
+}else{
+continue;
+}
+}
+val=me(o[k]);
+if(typeof (val)!="string"){
+continue;
+}
+res.push(_87+":"+val);
+}
+return "{"+res.join(", ")+"}";
+},objEqual:function(a,b){
+return (MochiKit.Base.compare(a,b)===0);
+},arrayEqual:function(_88,arr){
+if(_88.length!=arr.length){
+return false;
+}
+return (MochiKit.Base.compare(_88,arr)===0);
+},concat:function(){
+var _90=[];
+var _91=MochiKit.Base.extend;
+for(var i=0;i<arguments.length;i++){
+_91(_90,arguments[i]);
+}
+return _90;
+},keyComparator:function(key){
+var m=MochiKit.Base;
+var _93=m.compare;
+if(arguments.length==1){
+return function(a,b){
+return _93(a[key],b[key]);
+};
+}
+var _94=m.extend(null,arguments);
+return function(a,b){
+var _95=0;
+for(var i=0;(_95===0)&&(i<_94.length);i++){
+var key=_94[i];
+_95=_93(a[key],b[key]);
+}
+return _95;
+};
+},reverseKeyComparator:function(key){
+var _96=MochiKit.Base.keyComparator.apply(this,arguments);
+return function(a,b){
+return _96(b,a);
+};
+},partial:function(_97){
+var m=MochiKit.Base;
+return m.bind.apply(this,m.extend([_97,undefined],arguments,1));
+},listMinMax:function(_98,lst){
+if(lst.length===0){
+return null;
+}
+var cur=lst[0];
+var _100=MochiKit.Base.compare;
+for(var i=1;i<lst.length;i++){
+var o=lst[i];
+if(_100(o,cur)==_98){
+cur=o;
+}
+}
+return cur;
+},objMax:function(){
+return MochiKit.Base.listMinMax(1,arguments);
+},objMin:function(){
+return MochiKit.Base.listMinMax(-1,arguments);
+},findIdentical:function(lst,_101,_102,end){
+if(typeof (end)=="undefined"||end===null){
+end=lst.length;
+}
+for(var i=(_102||0);i<end;i++){
+if(lst[i]===_101){
+return i;
+}
+}
+return -1;
+},findValue:function(lst,_104,_105,end){
+if(typeof (end)=="undefined"||end===null){
+end=lst.length;
+}
+var cmp=MochiKit.Base.compare;
+for(var i=(_105||0);i<end;i++){
+if(cmp(lst[i],_104)===0){
+return i;
+}
+}
+return -1;
+},nodeWalk:function(node,_107){
+var _108=[node];
+var _109=MochiKit.Base.extend;
+while(_108.length){
+var res=_107(_108.shift());
+if(res){
+_109(_108,res);
+}
+}
+},nameFunctions:function(_110){
+var base=_110.NAME;
+if(typeof (base)=="undefined"){
+base="";
+}else{
+base=base+".";
+}
+for(var name in _110){
+var o=_110[name];
+if(typeof (o)=="function"&&typeof (o.NAME)=="undefined"){
+try{
+o.NAME=base+name;
+}
+catch(e){
+}
+}
+}
+},queryString:function(_113,_114){
+if(typeof (MochiKit.DOM)!="undefined"&&arguments.length==1&&(typeof (_113)=="string"||(typeof (_113.nodeType)!="undefined"&&_113.nodeType>0))){
+var kv=MochiKit.DOM.formContents(_113);
+_113=kv[0];
+_114=kv[1];
+}else{
+if(arguments.length==1){
+var o=_113;
+_113=[];
+_114=[];
+for(var k in o){
+var v=o[k];
+if(typeof (v)!="function"){
+_113.push(k);
+_114.push(v);
+}
+}
+}
+}
+var rval=[];
+var len=Math.min(_113.length,_114.length);
+var _118=MochiKit.Base.urlEncode;
+for(var i=0;i<len;i++){
+v=_114[i];
+if(typeof (v)!="undefined"&&v!==null){
+rval.push(_118(_113[i])+"="+_118(v));
+}
+}
+return rval.join("&");
+},parseQueryString:function(_119,_120){
+var _121=_119.replace(/\+/g,"%20").split("&");
+var o={};
+var _122;
+if(typeof (decodeURIComponent)!="undefined"){
+_122=decodeURIComponent;
+}else{
+_122=unescape;
+}
+if(_120){
+for(var i=0;i<_121.length;i++){
+var pair=_121[i].split("=");
+var name=_122(pair[0]);
+var arr=o[name];
+if(!(arr instanceof Array)){
+arr=[];
+o[name]=arr;
+}
+arr.push(_122(pair[1]));
+}
+}else{
+for(i=0;i<_121.length;i++){
+pair=_121[i].split("=");
+o[_122(pair[0])]=_122(pair[1]);
+}
+}
+return o;
+}});
+MochiKit.Base.AdapterRegistry=function(){
+this.pairs=[];
+};
+MochiKit.Base.AdapterRegistry.prototype={register:function(name,_124,wrap,_126){
+if(_126){
+this.pairs.unshift([name,_124,wrap]);
+}else{
+this.pairs.push([name,_124,wrap]);
+}
+},match:function(){
+for(var i=0;i<this.pairs.length;i++){
+var pair=this.pairs[i];
+if(pair[1].apply(this,arguments)){
+return pair[2].apply(this,arguments);
+}
+}
+throw MochiKit.Base.NotFound;
+},unregister:function(name){
+for(var i=0;i<this.pairs.length;i++){
+var pair=this.pairs[i];
+if(pair[0]==name){
+this.pairs.splice(i,1);
+return true;
+}
+}
+return false;
+}};
+MochiKit.Base.EXPORT=["counter","clone","extend","update","updatetree","setdefault","keys","items","NamedError","operator","forwardCall","itemgetter","typeMatcher","isCallable","isUndefined","isUndefinedOrNull","isNull","isEmpty","isNotEmpty","isArrayLike","isDateLike","xmap","map","xfilter","filter","bind","bindMethods","NotFound","AdapterRegistry","registerComparator","compare","registerRepr","repr","objEqual","arrayEqual","concat","keyComparator","reverseKeyComparator","partial","merge","listMinMax","listMax","listMin","objMax","objMin","nodeWalk","zip","urlEncode","queryString","serializeJSON","registerJSON","evalJSON","parseQueryString","findValue","findIdentical","flattenArguments","method"];
+MochiKit.Base.EXPORT_OK=["nameFunctions","comparatorRegistry","reprRegistry","jsonRegistry","compareDateLike","compareArrayLike","reprArrayLike","reprString","reprNumber"];
+MochiKit.Base._exportSymbols=function(_127,_128){
+if(typeof (MochiKit.__export__)=="undefined"){
+MochiKit.__export__=(MochiKit.__compat__||(typeof (JSAN)=="undefined"&&typeof (dojo)=="undefined"));
+}
+if(!MochiKit.__export__){
+return;
+}
+var all=_128.EXPORT_TAGS[":all"];
+for(var i=0;i<all.length;i++){
+_127[all[i]]=_128[all[i]];
+}
+};
+MochiKit.Base.__new__=function(){
+var m=this;
+m.forward=m.forwardCall;
+m.find=m.findValue;
+if(typeof (encodeURIComponent)!="undefined"){
+m.urlEncode=function(_130){
+return encodeURIComponent(_130).replace(/\'/g,"%27");
+};
+}else{
+m.urlEncode=function(_131){
+return escape(_131).replace(/\+/g,"%2B").replace(/\"/g,"%22").rval.replace(/\'/g,"%27");
+};
+}
+m.NamedError=function(name){
+this.message=name;
+this.name=name;
+};
+m.NamedError.prototype=new Error();
+m.update(m.NamedError.prototype,{repr:function(){
+if(this.message&&this.message!=this.name){
+return this.name+"("+m.repr(this.message)+")";
+}else{
+return this.name+"()";
+}
+},toString:m.forwardCall("repr")});
+m.NotFound=new m.NamedError("MochiKit.Base.NotFound");
+m.listMax=m.partial(m.listMinMax,1);
+m.listMin=m.partial(m.listMinMax,-1);
+m.isCallable=m.typeMatcher("function");
+m.isUndefined=m.typeMatcher("undefined");
+m.merge=m.partial(m.update,null);
+m.zip=m.partial(m.map,null);
+m.comparatorRegistry=new m.AdapterRegistry();
+m.registerComparator("dateLike",m.isDateLike,m.compareDateLike);
+m.registerComparator("arrayLike",m.isArrayLike,m.compareArrayLike);
+m.reprRegistry=new m.AdapterRegistry();
+m.registerRepr("arrayLike",m.isArrayLike,m.reprArrayLike);
+m.registerRepr("string",m.typeMatcher("string"),m.reprString);
+m.registerRepr("numbers",m.typeMatcher("number","boolean"),m.reprNumber);
+m.jsonRegistry=new m.AdapterRegistry();
+var all=m.concat(m.EXPORT,m.EXPORT_OK);
+m.EXPORT_TAGS={":common":m.concat(m.EXPORT_OK),":all":all};
+m.nameFunctions(this);
+};
+MochiKit.Base.__new__();
+if(!MochiKit.__compat__){
+compare=MochiKit.Base.compare;
+}
+MochiKit.Base._exportSymbols(this,MochiKit.Base);
+if(typeof (dojo)!="undefined"){
+dojo.provide("MochiKit.Iter");
+dojo.require("MochiKit.Base");
+}
+if(typeof (JSAN)!="undefined"){
+JSAN.use("MochiKit.Base",[]);
+}
+try{
+if(typeof (MochiKit.Base)=="undefined"){
+throw "";
+}
+}
+catch(e){
+throw "MochiKit.Iter depends on MochiKit.Base!";
+}
+if(typeof (MochiKit.Iter)=="undefined"){
+MochiKit.Iter={};
+}
+MochiKit.Iter.NAME="MochiKit.Iter";
+MochiKit.Iter.VERSION="1.3.1";
+MochiKit.Base.update(MochiKit.Iter,{__repr__:function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+},toString:function(){
+return this.__repr__();
+},registerIteratorFactory:function(name,_132,_133,_134){
+MochiKit.Iter.iteratorRegistry.register(name,_132,_133,_134);
+},iter:function(_135,_136){
+var self=MochiKit.Iter;
+if(arguments.length==2){
+return self.takewhile(function(a){
+return a!=_136;
+},_135);
+}
+if(typeof (_135.next)=="function"){
+return _135;
+}else{
+if(typeof (_135.iter)=="function"){
+return _135.iter();
+}
+}
+try{
+return self.iteratorRegistry.match(_135);
+}
+catch(e){
+var m=MochiKit.Base;
+if(e==m.NotFound){
+e=new TypeError(typeof (_135)+": "+m.repr(_135)+" is not iterable");
+}
+throw e;
+}
+},count:function(n){
+if(!n){
+n=0;
+}
+var m=MochiKit.Base;
+return {repr:function(){
+return "count("+n+")";
+},toString:m.forwardCall("repr"),next:m.counter(n)};
+},cycle:function(p){
+var self=MochiKit.Iter;
+var m=MochiKit.Base;
+var lst=[];
+var _139=self.iter(p);
+return {repr:function(){
+return "cycle(...)";
+},toString:m.forwardCall("repr"),next:function(){
+try{
+var rval=_139.next();
+lst.push(rval);
+return rval;
+}
+catch(e){
+if(e!=self.StopIteration){
+throw e;
+}
+if(lst.length===0){
+this.next=function(){
+throw self.StopIteration;
+};
+}else{
+var i=-1;
+this.next=function(){
+i=(i+1)%lst.length;
+return lst[i];
+};
+}
+return this.next();
+}
+}};
+},repeat:function(elem,n){
+var m=MochiKit.Base;
+if(typeof (n)=="undefined"){
+return {repr:function(){
+return "repeat("+m.repr(elem)+")";
+},toString:m.forwardCall("repr"),next:function(){
+return elem;
+}};
+}
+return {repr:function(){
+return "repeat("+m.repr(elem)+", "+n+")";
+},toString:m.forwardCall("repr"),next:function(){
+if(n<=0){
+throw MochiKit.Iter.StopIteration;
+}
+n-=1;
+return elem;
+}};
+},next:function(_141){
+return _141.next();
+},izip:function(p,q){
+var m=MochiKit.Base;
+var next=MochiKit.Iter.next;
+var _144=m.map(iter,arguments);
+return {repr:function(){
+return "izip(...)";
+},toString:m.forwardCall("repr"),next:function(){
+return m.map(next,_144);
+}};
+},ifilter:function(pred,seq){
+var m=MochiKit.Base;
+seq=MochiKit.Iter.iter(seq);
+if(pred===null){
+pred=m.operator.truth;
+}
+return {repr:function(){
+return "ifilter(...)";
+},toString:m.forwardCall("repr"),next:function(){
+while(true){
+var rval=seq.next();
+if(pred(rval)){
+return rval;
+}
+}
+return undefined;
+}};
+},ifilterfalse:function(pred,seq){
+var m=MochiKit.Base;
+seq=MochiKit.Iter.iter(seq);
+if(pred===null){
+pred=m.operator.truth;
+}
+return {repr:function(){
+return "ifilterfalse(...)";
+},toString:m.forwardCall("repr"),next:function(){
+while(true){
+var rval=seq.next();
+if(!pred(rval)){
+return rval;
+}
+}
+return undefined;
+}};
+},islice:function(seq){
+var self=MochiKit.Iter;
+var m=MochiKit.Base;
+seq=self.iter(seq);
+var _147=0;
+var stop=0;
+var step=1;
+var i=-1;
+if(arguments.length==2){
+stop=arguments[1];
+}else{
+if(arguments.length==3){
+_147=arguments[1];
+stop=arguments[2];
+}else{
+_147=arguments[1];
+stop=arguments[2];
+step=arguments[3];
+}
+}
+return {repr:function(){
+return "islice("+["...",_147,stop,step].join(", ")+")";
+},toString:m.forwardCall("repr"),next:function(){
+var rval;
+while(i<_147){
+rval=seq.next();
+i++;
+}
+if(_147>=stop){
+throw self.StopIteration;
+}
+_147+=step;
+return rval;
+}};
+},imap:function(fun,p,q){
+var m=MochiKit.Base;
+var self=MochiKit.Iter;
+var _151=m.map(self.iter,m.extend(null,arguments,1));
+var map=m.map;
+var next=self.next;
+return {repr:function(){
+return "imap(...)";
+},toString:m.forwardCall("repr"),next:function(){
+return fun.apply(this,map(next,_151));
+}};
+},applymap:function(fun,seq,self){
+seq=MochiKit.Iter.iter(seq);
+var m=MochiKit.Base;
+return {repr:function(){
+return "applymap(...)";
+},toString:m.forwardCall("repr"),next:function(){
+return fun.apply(self,seq.next());
+}};
+},chain:function(p,q){
+var self=MochiKit.Iter;
+var m=MochiKit.Base;
+if(arguments.length==1){
+return self.iter(arguments[0]);
+}
+var _153=m.map(self.iter,arguments);
+return {repr:function(){
+return "chain(...)";
+},toString:m.forwardCall("repr"),next:function(){
+while(_153.length>1){
+try{
+return _153[0].next();
+}
+catch(e){
+if(e!=self.StopIteration){
+throw e;
+}
+_153.shift();
+}
+}
+if(_153.length==1){
+var arg=_153.shift();
+this.next=m.bind("next",arg);
+return this.next();
+}
+throw self.StopIteration;
+}};
+},takewhile:function(pred,seq){
+var self=MochiKit.Iter;
+seq=self.iter(seq);
+return {repr:function(){
+return "takewhile(...)";
+},toString:MochiKit.Base.forwardCall("repr"),next:function(){
+var rval=seq.next();
+if(!pred(rval)){
+this.next=function(){
+throw self.StopIteration;
+};
+this.next();
+}
+return rval;
+}};
+},dropwhile:function(pred,seq){
+seq=MochiKit.Iter.iter(seq);
+var m=MochiKit.Base;
+var bind=m.bind;
+return {"repr":function(){
+return "dropwhile(...)";
+},"toString":m.forwardCall("repr"),"next":function(){
+while(true){
+var rval=seq.next();
+if(!pred(rval)){
+break;
+}
+}
+this.next=bind("next",seq);
+return rval;
+}};
+},_tee:function(_155,sync,_157){
+sync.pos[_155]=-1;
+var m=MochiKit.Base;
+var _158=m.listMin;
+return {repr:function(){
+return "tee("+_155+", ...)";
+},toString:m.forwardCall("repr"),next:function(){
+var rval;
+var i=sync.pos[_155];
+if(i==sync.max){
+rval=_157.next();
+sync.deque.push(rval);
+sync.max+=1;
+sync.pos[_155]+=1;
+}else{
+rval=sync.deque[i-sync.min];
+sync.pos[_155]+=1;
+if(i==sync.min&&_158(sync.pos)!=sync.min){
+sync.min+=1;
+sync.deque.shift();
+}
+}
+return rval;
+}};
+},tee:function(_159,n){
+var rval=[];
+var sync={"pos":[],"deque":[],"max":-1,"min":-1};
+if(arguments.length==1){
+n=2;
+}
+var self=MochiKit.Iter;
+_159=self.iter(_159);
+var _tee=self._tee;
+for(var i=0;i<n;i++){
+rval.push(_tee(i,sync,_159));
+}
+return rval;
+},list:function(_161){
+var m=MochiKit.Base;
+if(typeof (_161.slice)=="function"){
+return _161.slice();
+}else{
+if(m.isArrayLike(_161)){
+return m.concat(_161);
+}
+}
+var self=MochiKit.Iter;
+_161=self.iter(_161);
+var rval=[];
+try{
+while(true){
+rval.push(_161.next());
+}
+}
+catch(e){
+if(e!=self.StopIteration){
+throw e;
+}
+return rval;
+}
+return undefined;
+},reduce:function(fn,_162,_163){
+var i=0;
+var x=_163;
+var self=MochiKit.Iter;
+_162=self.iter(_162);
+if(arguments.length<3){
+try{
+x=_162.next();
+}
+catch(e){
+if(e==self.StopIteration){
+e=new TypeError("reduce() of empty sequence with no initial value");
+}
+throw e;
+}
+i++;
+}
+try{
+while(true){
+x=fn(x,_162.next());
+}
+}
+catch(e){
+if(e!=self.StopIteration){
+throw e;
+}
+}
+return x;
+},range:function(){
+var _165=0;
+var stop=0;
+var step=1;
+if(arguments.length==1){
+stop=arguments[0];
+}else{
+if(arguments.length==2){
+_165=arguments[0];
+stop=arguments[1];
+}else{
+if(arguments.length==3){
+_165=arguments[0];
+stop=arguments[1];
+step=arguments[2];
+}else{
+throw new TypeError("range() takes 1, 2, or 3 arguments!");
+}
+}
+}
+if(step===0){
+throw new TypeError("range() step must not be 0");
+}
+return {next:function(){
+if((step>0&&_165>=stop)||(step<0&&_165<=stop)){
+throw MochiKit.Iter.StopIteration;
+}
+var rval=_165;
+_165+=step;
+return rval;
+},repr:function(){
+return "range("+[_165,stop,step].join(", ")+")";
+},toString:MochiKit.Base.forwardCall("repr")};
+},sum:function(_166,_167){
+var x=_167||0;
+var self=MochiKit.Iter;
+_166=self.iter(_166);
+try{
+while(true){
+x+=_166.next();
+}
+}
+catch(e){
+if(e!=self.StopIteration){
+throw e;
+}
+}
+return x;
+},exhaust:function(_168){
+var self=MochiKit.Iter;
+_168=self.iter(_168);
+try{
+while(true){
+_168.next();
+}
+}
+catch(e){
+if(e!=self.StopIteration){
+throw e;
+}
+}
+},forEach:function(_169,func,self){
+var m=MochiKit.Base;
+if(arguments.length>2){
+func=m.bind(func,self);
+}
+if(m.isArrayLike(_169)){
+try{
+for(var i=0;i<_169.length;i++){
+func(_169[i]);
+}
+}
+catch(e){
+if(e!=MochiKit.Iter.StopIteration){
+throw e;
+}
+}
+}else{
+self=MochiKit.Iter;
+self.exhaust(self.imap(func,_169));
+}
+},every:function(_171,func){
+var self=MochiKit.Iter;
+try{
+self.ifilterfalse(func,_171).next();
+return false;
+}
+catch(e){
+if(e!=self.StopIteration){
+throw e;
+}
+return true;
+}
+},sorted:function(_172,cmp){
+var rval=MochiKit.Iter.list(_172);
+if(arguments.length==1){
+cmp=MochiKit.Base.compare;
+}
+rval.sort(cmp);
+return rval;
+},reversed:function(_173){
+var rval=MochiKit.Iter.list(_173);
+rval.reverse();
+return rval;
+},some:function(_174,func){
+var self=MochiKit.Iter;
+try{
+self.ifilter(func,_174).next();
+return true;
+}
+catch(e){
+if(e!=self.StopIteration){
+throw e;
+}
+return false;
+}
+},iextend:function(lst,_175){
+if(MochiKit.Base.isArrayLike(_175)){
+for(var i=0;i<_175.length;i++){
+lst.push(_175[i]);
+}
+}else{
+var self=MochiKit.Iter;
+_175=self.iter(_175);
+try{
+while(true){
+lst.push(_175.next());
+}
+}
+catch(e){
+if(e!=self.StopIteration){
+throw e;
+}
+}
+}
+return lst;
+},groupby:function(_176,_177){
+var m=MochiKit.Base;
+var self=MochiKit.Iter;
+if(arguments.length<2){
+_177=m.operator.identity;
+}
+_176=self.iter(_176);
+var pk=undefined;
+var k=undefined;
+var v;
+function fetch(){
+v=_176.next();
+k=_177(v);
+}
+function eat(){
+var ret=v;
+v=undefined;
+return ret;
+}
+var _180=true;
+return {repr:function(){
+return "groupby(...)";
+},next:function(){
+while(k==pk){
+fetch();
+if(_180){
+_180=false;
+break;
+}
+}
+pk=k;
+return [k,{next:function(){
+if(v==undefined){
+fetch();
+}
+if(k!=pk){
+throw self.StopIteration;
+}
+return eat();
+}}];
+}};
+},groupby_as_array:function(_181,_182){
+var m=MochiKit.Base;
+var self=MochiKit.Iter;
+if(arguments.length<2){
+_182=m.operator.identity;
+}
+_181=self.iter(_181);
+var _183=[];
+var _184=true;
+var _185;
+while(true){
+try{
+var _186=_181.next();
+var key=_182(_186);
+}
+catch(e){
+if(e==self.StopIteration){
+break;
+}
+throw e;
+}
+if(_184||key!=_185){
+var _187=[];
+_183.push([key,_187]);
+}
+_187.push(_186);
+_184=false;
+_185=key;
+}
+return _183;
+},arrayLikeIter:function(_188){
+var i=0;
+return {repr:function(){
+return "arrayLikeIter(...)";
+},toString:MochiKit.Base.forwardCall("repr"),next:function(){
+if(i>=_188.length){
+throw MochiKit.Iter.StopIteration;
+}
+return _188[i++];
+}};
+},hasIterateNext:function(_189){
+return (_189&&typeof (_189.iterateNext)=="function");
+},iterateNextIter:function(_190){
+return {repr:function(){
+return "iterateNextIter(...)";
+},toString:MochiKit.Base.forwardCall("repr"),next:function(){
+var rval=_190.iterateNext();
+if(rval===null||rval===undefined){
+throw MochiKit.Iter.StopIteration;
+}
+return rval;
+}};
+}});
+MochiKit.Iter.EXPORT_OK=["iteratorRegistry","arrayLikeIter","hasIterateNext","iterateNextIter",];
+MochiKit.Iter.EXPORT=["StopIteration","registerIteratorFactory","iter","count","cycle","repeat","next","izip","ifilter","ifilterfalse","islice","imap","applymap","chain","takewhile","dropwhile","tee","list","reduce","range","sum","exhaust","forEach","every","sorted","reversed","some","iextend","groupby","groupby_as_array"];
+MochiKit.Iter.__new__=function(){
+var m=MochiKit.Base;
+this.StopIteration=new m.NamedError("StopIteration");
+this.iteratorRegistry=new m.AdapterRegistry();
+this.registerIteratorFactory("arrayLike",m.isArrayLike,this.arrayLikeIter);
+this.registerIteratorFactory("iterateNext",this.hasIterateNext,this.iterateNextIter);
+this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
+m.nameFunctions(this);
+};
+MochiKit.Iter.__new__();
+if(!MochiKit.__compat__){
+reduce=MochiKit.Iter.reduce;
+}
+MochiKit.Base._exportSymbols(this,MochiKit.Iter);
+if(typeof (dojo)!="undefined"){
+dojo.provide("MochiKit.Logging");
+dojo.require("MochiKit.Base");
+}
+if(typeof (JSAN)!="undefined"){
+JSAN.use("MochiKit.Base",[]);
+}
+try{
+if(typeof (MochiKit.Base)=="undefined"){
+throw "";
+}
+}
+catch(e){
+throw "MochiKit.Logging depends on MochiKit.Base!";
+}
+if(typeof (MochiKit.Logging)=="undefined"){
+MochiKit.Logging={};
+}
+MochiKit.Logging.NAME="MochiKit.Logging";
+MochiKit.Logging.VERSION="1.3.1";
+MochiKit.Logging.__repr__=function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+};
+MochiKit.Logging.toString=function(){
+return this.__repr__();
+};
+MochiKit.Logging.EXPORT=["LogLevel","LogMessage","Logger","alertListener","logger","log","logError","logDebug","logFatal","logWarning"];
+MochiKit.Logging.EXPORT_OK=["logLevelAtLeast","isLogMessage","compareLogMessage"];
+MochiKit.Logging.LogMessage=function(num,_192,info){
+this.num=num;
+this.level=_192;
+this.info=info;
+this.timestamp=new Date();
+};
+MochiKit.Logging.LogMessage.prototype={repr:function(){
+var m=MochiKit.Base;
+return "LogMessage("+m.map(m.repr,[this.num,this.level,this.info]).join(", ")+")";
+},toString:MochiKit.Base.forwardCall("repr")};
+MochiKit.Base.update(MochiKit.Logging,{logLevelAtLeast:function(_194){
+var self=MochiKit.Logging;
+if(typeof (_194)=="string"){
+_194=self.LogLevel[_194];
+}
+return function(msg){
+var _196=msg.level;
+if(typeof (_196)=="string"){
+_196=self.LogLevel[_196];
+}
+return _196>=_194;
+};
+},isLogMessage:function(){
+var _197=MochiKit.Logging.LogMessage;
+for(var i=0;i<arguments.length;i++){
+if(!(arguments[i] instanceof _197)){
+return false;
+}
+}
+return true;
+},compareLogMessage:function(a,b){
+return MochiKit.Base.compare([a.level,a.info],[b.level,b.info]);
+},alertListener:function(msg){
+alert("num: "+msg.num+"\nlevel: "+msg.level+"\ninfo: "+msg.info.join(" "));
+}});
+MochiKit.Logging.Logger=function(_198){
+this.counter=0;
+if(typeof (_198)=="undefined"||_198===null){
+_198=-1;
+}
+this.maxSize=_198;
+this._messages=[];
+this.listeners={};
+this.useNativeConsole=false;
+};
+MochiKit.Logging.Logger.prototype={clear:function(){
+this._messages.splice(0,this._messages.length);
+},logToConsole:function(msg){
+if(typeof (window)!="undefined"&&window.console&&window.console.log){
+window.console.log(msg);
+}else{
+if(typeof (opera)!="undefined"&&opera.postError){
+opera.postError(msg);
+}else{
+if(typeof (printfire)=="function"){
+printfire(msg);
+}
+}
+}
+},dispatchListeners:function(msg){
+for(var k in this.listeners){
+var pair=this.listeners[k];
+if(pair.ident!=k||(pair[0]&&!pair[0](msg))){
+continue;
+}
+pair[1](msg);
+}
+},addListener:function(_199,_200,_201){
+if(typeof (_200)=="string"){
+_200=MochiKit.Logging.logLevelAtLeast(_200);
+}
+var _202=[_200,_201];
+_202.ident=_199;
+this.listeners[_199]=_202;
+},removeListener:function(_203){
+delete this.listeners[_203];
+},baseLog:function(_204,_205){
+var msg=new MochiKit.Logging.LogMessage(this.counter,_204,MochiKit.Base.extend(null,arguments,1));
+this._messages.push(msg);
+this.dispatchListeners(msg);
+if(this.useNativeConsole){
+this.logToConsole(msg.level+": "+msg.info.join(" "));
+}
+this.counter+=1;
+while(this.maxSize>=0&&this._messages.length>this.maxSize){
+this._messages.shift();
+}
+},getMessages:function(_206){
+var _207=0;
+if(!(typeof (_206)=="undefined"||_206===null)){
+_207=Math.max(0,this._messages.length-_206);
+}
+return this._messages.slice(_207);
+},getMessageText:function(_208){
+if(typeof (_208)=="undefined"||_208===null){
+_208=30;
+}
+var _209=this.getMessages(_208);
+if(_209.length){
+var lst=map(function(m){
+return "\n  ["+m.num+"] "+m.level+": "+m.info.join(" ");
+},_209);
+lst.unshift("LAST "+_209.length+" MESSAGES:");
+return lst.join("");
+}
+return "";
+},debuggingBookmarklet:function(_210){
+if(typeof (MochiKit.LoggingPane)=="undefined"){
+alert(this.getMessageText());
+}else{
+MochiKit.LoggingPane.createLoggingPane(_210||false);
+}
+}};
+MochiKit.Logging.__new__=function(){
+this.LogLevel={ERROR:40,FATAL:50,WARNING:30,INFO:20,DEBUG:10};
+var m=MochiKit.Base;
+m.registerComparator("LogMessage",this.isLogMessage,this.compareLogMessage);
+var _211=m.partial;
+var _212=this.Logger;
+var _213=_212.prototype.baseLog;
+m.update(this.Logger.prototype,{debug:_211(_213,"DEBUG"),log:_211(_213,"INFO"),error:_211(_213,"ERROR"),fatal:_211(_213,"FATAL"),warning:_211(_213,"WARNING")});
+var self=this;
+var _214=function(name){
+return function(){
+self.logger[name].apply(self.logger,arguments);
+};
+};
+this.log=_214("log");
+this.logError=_214("error");
+this.logDebug=_214("debug");
+this.logFatal=_214("fatal");
+this.logWarning=_214("warning");
+this.logger=new _212();
+this.logger.useNativeConsole=true;
+this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
+m.nameFunctions(this);
+};
+if(typeof (printfire)=="undefined"&&typeof (document)!="undefined"&&document.createEvent&&typeof (dispatchEvent)!="undefined"){
+printfire=function(){
+printfire.args=arguments;
+var ev=document.createEvent("Events");
+ev.initEvent("printfire",false,true);
+dispatchEvent(ev);
+};
+}
+MochiKit.Logging.__new__();
+MochiKit.Base._exportSymbols(this,MochiKit.Logging);
+if(typeof (dojo)!="undefined"){
+dojo.provide("MochiKit.DateTime");
+}
+if(typeof (MochiKit)=="undefined"){
+MochiKit={};
+}
+if(typeof (MochiKit.DateTime)=="undefined"){
+MochiKit.DateTime={};
+}
+MochiKit.DateTime.NAME="MochiKit.DateTime";
+MochiKit.DateTime.VERSION="1.3.1";
+MochiKit.DateTime.__repr__=function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+};
+MochiKit.DateTime.toString=function(){
+return this.__repr__();
+};
+MochiKit.DateTime.isoDate=function(str){
+str=str+"";
+if(typeof (str)!="string"||str.length===0){
+return null;
+}
+var iso=str.split("-");
+if(iso.length===0){
+return null;
+}
+return new Date(iso[0],iso[1]-1,iso[2]);
+};
+MochiKit.DateTime._isoRegexp=/(\d{4,})(?:-(\d{1,2})(?:-(\d{1,2})(?:[T ](\d{1,2}):(\d{1,2})(?::(\d{1,2})(?:\.(\d+))?)?(?:(Z)|([+-])(\d{1,2})(?::(\d{1,2}))?)?)?)?)?/;
+MochiKit.DateTime.isoTimestamp=function(str){
+str=str+"";
+if(typeof (str)!="string"||str.length===0){
+return null;
+}
+var res=str.match(MochiKit.DateTime._isoRegexp);
+if(typeof (res)=="undefined"||res===null){
+return null;
+}
+var year,month,day,hour,min,sec,msec;
+year=parseInt(res[1],10);
+if(typeof (res[2])=="undefined"||res[2]===""){
+return new Date(year);
+}
+month=parseInt(res[2],10)-1;
+day=parseInt(res[3],10);
+if(typeof (res[4])=="undefined"||res[4]===""){
+return new Date(year,month,day);
+}
+hour=parseInt(res[4],10);
+min=parseInt(res[5],10);
+sec=(typeof (res[6])!="undefined"&&res[6]!=="")?parseInt(res[6],10):0;
+if(typeof (res[7])!="undefined"&&res[7]!==""){
+msec=Math.round(1000*parseFloat("0."+res[7]));
+}else{
+msec=0;
+}
+if((typeof (res[8])=="undefined"||res[8]==="")&&(typeof (res[9])=="undefined"||res[9]==="")){
+return new Date(year,month,day,hour,min,sec,msec);
+}
+var ofs;
+if(typeof (res[9])!="undefined"&&res[9]!==""){
+ofs=parseInt(res[10],10)*3600000;
+if(typeof (res[11])!="undefined"&&res[11]!==""){
+ofs+=parseInt(res[11],10)*60000;
+}
+if(res[9]=="-"){
+ofs=-ofs;
+}
+}else{
+ofs=0;
+}
+return new Date(Date.UTC(year,month,day,hour,min,sec,msec)-ofs);
+};
+MochiKit.DateTime.toISOTime=function(date,_221){
+if(typeof (date)=="undefined"||date===null){
+return null;
+}
+var hh=date.getHours();
+var mm=date.getMinutes();
+var ss=date.getSeconds();
+var lst=[((_221&&(hh<10))?"0"+hh:hh),((mm<10)?"0"+mm:mm),((ss<10)?"0"+ss:ss)];
+return lst.join(":");
+};
+MochiKit.DateTime.toISOTimestamp=function(date,_225){
+if(typeof (date)=="undefined"||date===null){
+return null;
+}
+var sep=_225?"T":" ";
+var foot=_225?"Z":"";
+if(_225){
+date=new Date(date.getTime()+(date.getTimezoneOffset()*60000));
+}
+return MochiKit.DateTime.toISODate(date)+sep+MochiKit.DateTime.toISOTime(date,_225)+foot;
+};
+MochiKit.DateTime.toISODate=function(date){
+if(typeof (date)=="undefined"||date===null){
+return null;
+}
+var _228=MochiKit.DateTime._padTwo;
+return [date.getFullYear(),_228(date.getMonth()+1),_228(date.getDate())].join("-");
+};
+MochiKit.DateTime.americanDate=function(d){
+d=d+"";
+if(typeof (d)!="string"||d.length===0){
+return null;
+}
+var a=d.split("/");
+return new Date(a[2],a[0]-1,a[1]);
+};
+MochiKit.DateTime._padTwo=function(n){
+return (n>9)?n:"0"+n;
+};
+MochiKit.DateTime.toPaddedAmericanDate=function(d){
+if(typeof (d)=="undefined"||d===null){
+return null;
+}
+var _230=MochiKit.DateTime._padTwo;
+return [_230(d.getMonth()+1),_230(d.getDate()),d.getFullYear()].join("/");
+};
+MochiKit.DateTime.toAmericanDate=function(d){
+if(typeof (d)=="undefined"||d===null){
+return null;
+}
+return [d.getMonth()+1,d.getDate(),d.getFullYear()].join("/");
+};
+MochiKit.DateTime.EXPORT=["isoDate","isoTimestamp","toISOTime","toISOTimestamp","toISODate","americanDate","toPaddedAmericanDate","toAmericanDate"];
+MochiKit.DateTime.EXPORT_OK=[];
+MochiKit.DateTime.EXPORT_TAGS={":common":MochiKit.DateTime.EXPORT,":all":MochiKit.DateTime.EXPORT};
+MochiKit.DateTime.__new__=function(){
+var base=this.NAME+".";
+for(var k in this){
+var o=this[k];
+if(typeof (o)=="function"&&typeof (o.NAME)=="undefined"){
+try{
+o.NAME=base+k;
+}
+catch(e){
+}
+}
+}
+};
+MochiKit.DateTime.__new__();
+if(typeof (MochiKit.Base)!="undefined"){
+MochiKit.Base._exportSymbols(this,MochiKit.DateTime);
+}else{
+(function(_231,_232){
+if((typeof (JSAN)=="undefined"&&typeof (dojo)=="undefined")||(typeof (MochiKit.__compat__)=="boolean"&&MochiKit.__compat__)){
+var all=_232.EXPORT_TAGS[":all"];
+for(var i=0;i<all.length;i++){
+_231[all[i]]=_232[all[i]];
+}
+}
+})(this,MochiKit.DateTime);
+}
+if(typeof (dojo)!="undefined"){
+dojo.provide("MochiKit.Format");
+}
+if(typeof (MochiKit)=="undefined"){
+MochiKit={};
+}
+if(typeof (MochiKit.Format)=="undefined"){
+MochiKit.Format={};
+}
+MochiKit.Format.NAME="MochiKit.Format";
+MochiKit.Format.VERSION="1.3.1";
+MochiKit.Format.__repr__=function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+};
+MochiKit.Format.toString=function(){
+return this.__repr__();
+};
+MochiKit.Format._numberFormatter=function(_233,_234,_235,_236,_237,_238,_239,_240,_241){
+return function(num){
+num=parseFloat(num);
+if(typeof (num)=="undefined"||num===null||isNaN(num)){
+return _233;
+}
+var _242=_234;
+var _243=_235;
+if(num<0){
+num=-num;
+}else{
+_242=_242.replace(/-/,"");
+}
+var me=arguments.callee;
+var fmt=MochiKit.Format.formatLocale(_236);
+if(_237){
+num=num*100;
+_243=fmt.percent+_243;
+}
+num=MochiKit.Format.roundToFixed(num,_238);
+var _245=num.split(/\./);
+var _246=_245[0];
+var frac=(_245.length==1)?"":_245[1];
+var res="";
+while(_246.length<_239){
+_246="0"+_246;
+}
+if(_240){
+while(_246.length>_240){
+var i=_246.length-_240;
+res=fmt.separator+_246.substring(i,_246.length)+res;
+_246=_246.substring(0,i);
+}
+}
+res=_246+res;
+if(_238>0){
+while(frac.length<_241){
+frac=frac+"0";
+}
+res=res+fmt.decimal+frac;
+}
+return _242+res+_243;
+};
+};
+MochiKit.Format.numberFormatter=function(_248,_249,_250){
+if(typeof (_249)=="undefined"){
+_249="";
+}
+var _251=_248.match(/((?:[0#]+,)?[0#]+)(?:\.([0#]+))?(%)?/);
+if(!_251){
+throw TypeError("Invalid pattern");
+}
+var _252=_248.substr(0,_251.index);
+var _253=_248.substr(_251.index+_251[0].length);
+if(_252.search(/-/)==-1){
+_252=_252+"-";
+}
+var _254=_251[1];
+var frac=(typeof (_251[2])=="string"&&_251[2]!="")?_251[2]:"";
+var _255=(typeof (_251[3])=="string"&&_251[3]!="");
+var tmp=_254.split(/,/);
+var _257;
+if(typeof (_250)=="undefined"){
+_250="default";
+}
+if(tmp.length==1){
+_257=null;
+}else{
+_257=tmp[1].length;
+}
+var _258=_254.length-_254.replace(/0/g,"").length;
+var _259=frac.length-frac.replace(/0/g,"").length;
+var _260=frac.length;
+var rval=MochiKit.Format._numberFormatter(_249,_252,_253,_250,_255,_260,_258,_257,_259);
+var m=MochiKit.Base;
+if(m){
+var fn=arguments.callee;
+var args=m.concat(arguments);
+rval.repr=function(){
+return [self.NAME,"(",map(m.repr,args).join(", "),")"].join("");
+};
+}
+return rval;
+};
+MochiKit.Format.formatLocale=function(_262){
+if(typeof (_262)=="undefined"||_262===null){
+_262="default";
+}
+if(typeof (_262)=="string"){
+var rval=MochiKit.Format.LOCALE[_262];
+if(typeof (rval)=="string"){
+rval=arguments.callee(rval);
+MochiKit.Format.LOCALE[_262]=rval;
+}
+return rval;
+}else{
+return _262;
+}
+};
+MochiKit.Format.twoDigitAverage=function(_263,_264){
+if(_264){
+var res=_263/_264;
+if(!isNaN(res)){
+return MochiKit.Format.twoDigitFloat(_263/_264);
+}
+}
+return "0";
+};
+MochiKit.Format.twoDigitFloat=function(_265){
+var sign=(_265<0?"-":"");
+var s=Math.floor(Math.abs(_265)*100).toString();
+if(s=="0"){
+return s;
+}
+if(s.length<3){
+while(s.charAt(s.length-1)=="0"){
+s=s.substring(0,s.length-1);
+}
+return sign+"0."+s;
+}
+var head=sign+s.substring(0,s.length-2);
+var tail=s.substring(s.length-2,s.length);
+if(tail=="00"){
+return head;
+}else{
+if(tail.charAt(1)=="0"){
+return head+"."+tail.charAt(0);
+}else{
+return head+"."+tail;
+}
+}
+};
+MochiKit.Format.lstrip=function(str,_270){
+str=str+"";
+if(typeof (str)!="string"){
+return null;
+}
+if(!_270){
+return str.replace(/^\s+/,"");
+}else{
+return str.replace(new RegExp("^["+_270+"]+"),"");
+}
+};
+MochiKit.Format.rstrip=function(str,_271){
+str=str+"";
+if(typeof (str)!="string"){
+return null;
+}
+if(!_271){
+return str.replace(/\s+$/,"");
+}else{
+return str.replace(new RegExp("["+_271+"]+$"),"");
+}
+};
+MochiKit.Format.strip=function(str,_272){
+var self=MochiKit.Format;
+return self.rstrip(self.lstrip(str,_272),_272);
+};
+MochiKit.Format.truncToFixed=function(_273,_274){
+_273=Math.floor(_273*Math.pow(10,_274));
+var res=(_273*Math.pow(10,-_274)).toFixed(_274);
+if(res.charAt(0)=="."){
+res="0"+res;
+}
+return res;
+};
+MochiKit.Format.roundToFixed=function(_275,_276){
+return MochiKit.Format.truncToFixed(_275+0.5*Math.pow(10,-_276),_276);
+};
+MochiKit.Format.percentFormat=function(_277){
+return MochiKit.Format.twoDigitFloat(100*_277)+"%";
+};
+MochiKit.Format.EXPORT=["truncToFixed","roundToFixed","numberFormatter","formatLocale","twoDigitAverage","twoDigitFloat","percentFormat","lstrip","rstrip","strip"];
+MochiKit.Format.LOCALE={en_US:{separator:",",decimal:".",percent:"%"},de_DE:{separator:".",decimal:",",percent:"%"},fr_FR:{separator:" ",decimal:",",percent:"%"},"default":"en_US"};
+MochiKit.Format.EXPORT_OK=[];
+MochiKit.Format.EXPORT_TAGS={":all":MochiKit.Format.EXPORT,":common":MochiKit.Format.EXPORT};
+MochiKit.Format.__new__=function(){
+var base=this.NAME+".";
+var k,v,o;
+for(k in this.LOCALE){
+o=this.LOCALE[k];
+if(typeof (o)=="object"){
+o.repr=function(){
+return this.NAME;
+};
+o.NAME=base+"LOCALE."+k;
+}
+}
+for(k in this){
+o=this[k];
+if(typeof (o)=="function"&&typeof (o.NAME)=="undefined"){
+try{
+o.NAME=base+k;
+}
+catch(e){
+}
+}
+}
+};
+MochiKit.Format.__new__();
+if(typeof (MochiKit.Base)!="undefined"){
+MochiKit.Base._exportSymbols(this,MochiKit.Format);
+}else{
+(function(_278,_279){
+if((typeof (JSAN)=="undefined"&&typeof (dojo)=="undefined")||(typeof (MochiKit.__compat__)=="boolean"&&MochiKit.__compat__)){
+var all=_279.EXPORT_TAGS[":all"];
+for(var i=0;i<all.length;i++){
+_278[all[i]]=_279[all[i]];
+}
+}
+})(this,MochiKit.Format);
+}
+if(typeof (dojo)!="undefined"){
+dojo.provide("MochiKit.Async");
+dojo.require("MochiKit.Base");
+}
+if(typeof (JSAN)!="undefined"){
+JSAN.use("MochiKit.Base",[]);
+}
+try{
+if(typeof (MochiKit.Base)=="undefined"){
+throw "";
+}
+}
+catch(e){
+throw "MochiKit.Async depends on MochiKit.Base!";
+}
+if(typeof (MochiKit.Async)=="undefined"){
+MochiKit.Async={};
+}
+MochiKit.Async.NAME="MochiKit.Async";
+MochiKit.Async.VERSION="1.3.1";
+MochiKit.Async.__repr__=function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+};
+MochiKit.Async.toString=function(){
+return this.__repr__();
+};
+MochiKit.Async.Deferred=function(_280){
+this.chain=[];
+this.id=this._nextId();
+this.fired=-1;
+this.paused=0;
+this.results=[null,null];
+this.canceller=_280;
+this.silentlyCancelled=false;
+this.chained=false;
+};
+MochiKit.Async.Deferred.prototype={repr:function(){
+var _281;
+if(this.fired==-1){
+_281="unfired";
+}else{
+if(this.fired===0){
+_281="success";
+}else{
+_281="error";
+}
+}
+return "Deferred("+this.id+", "+_281+")";
+},toString:MochiKit.Base.forwardCall("repr"),_nextId:MochiKit.Base.counter(),cancel:function(){
+var self=MochiKit.Async;
+if(this.fired==-1){
+if(this.canceller){
+this.canceller(this);
+}else{
+this.silentlyCancelled=true;
+}
+if(this.fired==-1){
+this.errback(new self.CancelledError(this));
+}
+}else{
+if((this.fired===0)&&(this.results[0] instanceof self.Deferred)){
+this.results[0].cancel();
+}
+}
+},_pause:function(){
+this.paused++;
+},_unpause:function(){
+this.paused--;
+if((this.paused===0)&&(this.fired>=0)){
+this._fire();
+}
+},_continue:function(res){
+this._resback(res);
+this._unpause();
+},_resback:function(res){
+this.fired=((res instanceof Error)?1:0);
+this.results[this.fired]=res;
+this._fire();
+},_check:function(){
+if(this.fired!=-1){
+if(!this.silentlyCancelled){
+throw new MochiKit.Async.AlreadyCalledError(this);
+}
+this.silentlyCancelled=false;
+return;
+}
+},callback:function(res){
+this._check();
+if(res instanceof MochiKit.Async.Deferred){
+throw new Error("Deferred instances can only be chained if they are the result of a callback");
+}
+this._resback(res);
+},errback:function(res){
+this._check();
+var self=MochiKit.Async;
+if(res instanceof self.Deferred){
+throw new Error("Deferred instances can only be chained if they are the result of a callback");
+}
+if(!(res instanceof Error)){
+res=new self.GenericError(res);
+}
+this._resback(res);
+},addBoth:function(fn){
+if(arguments.length>1){
+fn=MochiKit.Base.partial.apply(null,arguments);
+}
+return this.addCallbacks(fn,fn);
+},addCallback:function(fn){
+if(arguments.length>1){
+fn=MochiKit.Base.partial.apply(null,arguments);
+}
+return this.addCallbacks(fn,null);
+},addErrback:function(fn){
+if(arguments.length>1){
+fn=MochiKit.Base.partial.apply(null,arguments);
+}
+return this.addCallbacks(null,fn);
+},addCallbacks:function(cb,eb){
+if(this.chained){
+throw new Error("Chained Deferreds can not be re-used");
+}
+this.chain.push([cb,eb]);
+if(this.fired>=0){
+this._fire();
+}
+return this;
+},_fire:function(){
+var _284=this.chain;
+var _285=this.fired;
+var res=this.results[_285];
+var self=this;
+var cb=null;
+while(_284.length>0&&this.paused===0){
+var pair=_284.shift();
+var f=pair[_285];
+if(f===null){
+continue;
+}
+try{
+res=f(res);
+_285=((res instanceof Error)?1:0);
+if(res instanceof MochiKit.Async.Deferred){
+cb=function(res){
+self._continue(res);
+};
+this._pause();
+}
+}
+catch(err){
+_285=1;
+if(!(err instanceof Error)){
+err=new MochiKit.Async.GenericError(err);
+}
+res=err;
+}
+}
+this.fired=_285;
+this.results[_285]=res;
+if(cb&&this.paused){
+res.addBoth(cb);
+res.chained=true;
+}
+}};
+MochiKit.Base.update(MochiKit.Async,{evalJSONRequest:function(){
+return eval("("+arguments[0].responseText+")");
+},succeed:function(_287){
+var d=new MochiKit.Async.Deferred();
+d.callback.apply(d,arguments);
+return d;
+},fail:function(_288){
+var d=new MochiKit.Async.Deferred();
+d.errback.apply(d,arguments);
+return d;
+},getXMLHttpRequest:function(){
+var self=arguments.callee;
+if(!self.XMLHttpRequest){
+var _289=[function(){
+return new XMLHttpRequest();
+},function(){
+return new ActiveXObject("Msxml2.XMLHTTP");
+},function(){
+return new ActiveXObject("Microsoft.XMLHTTP");
+},function(){
+return new ActiveXObject("Msxml2.XMLHTTP.4.0");
+},function(){
+throw new MochiKit.Async.BrowserComplianceError("Browser does not support XMLHttpRequest");
+}];
+for(var i=0;i<_289.length;i++){
+var func=_289[i];
+try{
+self.XMLHttpRequest=func;
+return func();
+}
+catch(e){
+}
+}
+}
+return self.XMLHttpRequest();
+},_nothing:function(){
+},_xhr_onreadystatechange:function(d){
+if(this.readyState==4){
+try{
+this.onreadystatechange=null;
+}
+catch(e){
+try{
+this.onreadystatechange=MochiKit.Async._nothing;
+}
+catch(e){
+}
+}
+var _290=null;
+try{
+_290=this.status;
+if(!_290&&MochiKit.Base.isNotEmpty(this.responseText)){
+_290=304;
+}
+}
+catch(e){
+}
+if(_290==200||_290==304){
+d.callback(this);
+}else{
+var err=new MochiKit.Async.XMLHttpRequestError(this,"Request failed");
+if(err.number){
+d.errback(err);
+}else{
+d.errback(err);
+}
+}
+}
+},_xhr_canceller:function(req){
+try{
+req.onreadystatechange=null;
+}
+catch(e){
+try{
+req.onreadystatechange=MochiKit.Async._nothing;
+}
+catch(e){
+}
+}
+req.abort();
+},sendXMLHttpRequest:function(req,_293){
+if(typeof (_293)=="undefined"||_293===null){
+_293="";
+}
+var m=MochiKit.Base;
+var self=MochiKit.Async;
+var d=new self.Deferred(m.partial(self._xhr_canceller,req));
+try{
+req.onreadystatechange=m.bind(self._xhr_onreadystatechange,req,d);
+req.send(_293);
+}
+catch(e){
+try{
+req.onreadystatechange=null;
+}
+catch(ignore){
+}
+d.errback(e);
+}
+return d;
+},doSimpleXMLHttpRequest:function(url){
+var self=MochiKit.Async;
+var req=self.getXMLHttpRequest();
+if(arguments.length>1){
+var m=MochiKit.Base;
+var qs=m.queryString.apply(null,m.extend(null,arguments,1));
+if(qs){
+url+="?"+qs;
+}
+}
+req.open("GET",url,true);
+return self.sendXMLHttpRequest(req);
+},loadJSONDoc:function(url){
+var self=MochiKit.Async;
+var d=self.doSimpleXMLHttpRequest.apply(self,arguments);
+d=d.addCallback(self.evalJSONRequest);
+return d;
+},wait:function(_296,_297){
+var d=new MochiKit.Async.Deferred();
+var m=MochiKit.Base;
+if(typeof (_297)!="undefined"){
+d.addCallback(function(){
+return _297;
+});
+}
+var _298=setTimeout(m.bind("callback",d),Math.floor(_296*1000));
+d.canceller=function(){
+try{
+clearTimeout(_298);
+}
+catch(e){
+}
+};
+return d;
+},callLater:function(_299,func){
+var m=MochiKit.Base;
+var _300=m.partial.apply(m,m.extend(null,arguments,1));
+return MochiKit.Async.wait(_299).addCallback(function(res){
+return _300();
+});
+}});
+MochiKit.Async.DeferredLock=function(){
+this.waiting=[];
+this.locked=false;
+this.id=this._nextId();
+};
+MochiKit.Async.DeferredLock.prototype={__class__:MochiKit.Async.DeferredLock,acquire:function(){
+d=new MochiKit.Async.Deferred();
+if(this.locked){
+this.waiting.push(d);
+}else{
+this.locked=true;
+d.callback(this);
+}
+return d;
+},release:function(){
+if(!this.locked){
+throw TypeError("Tried to release an unlocked DeferredLock");
+}
+this.locked=false;
+if(this.waiting.length>0){
+this.locked=true;
+this.waiting.shift().callback(this);
+}
+},_nextId:MochiKit.Base.counter(),repr:function(){
+var _301;
+if(this.locked){
+_301="locked, "+this.waiting.length+" waiting";
+}else{
+_301="unlocked";
+}
+return "DeferredLock("+this.id+", "+_301+")";
+},toString:MochiKit.Base.forwardCall("repr")};
+MochiKit.Async.DeferredList=function(list,_303,_304,_305,_306){
+this.list=list;
+this.resultList=new Array(this.list.length);
+this.chain=[];
+this.id=this._nextId();
+this.fired=-1;
+this.paused=0;
+this.results=[null,null];
+this.canceller=_306;
+this.silentlyCancelled=false;
+if(this.list.length===0&&!_303){
+this.callback(this.resultList);
+}
+this.finishedCount=0;
+this.fireOnOneCallback=_303;
+this.fireOnOneErrback=_304;
+this.consumeErrors=_305;
+var _307=0;
+MochiKit.Base.map(MochiKit.Base.bind(function(d){
+d.addCallback(MochiKit.Base.bind(this._cbDeferred,this),_307,true);
+d.addErrback(MochiKit.Base.bind(this._cbDeferred,this),_307,false);
+_307+=1;
+},this),this.list);
+};
+MochiKit.Base.update(MochiKit.Async.DeferredList.prototype,MochiKit.Async.Deferred.prototype);
+MochiKit.Base.update(MochiKit.Async.DeferredList.prototype,{_cbDeferred:function(_308,_309,_310){
+this.resultList[_308]=[_309,_310];
+this.finishedCount+=1;
+if(this.fired!==0){
+if(_309&&this.fireOnOneCallback){
+this.callback([_308,_310]);
+}else{
+if(!_309&&this.fireOnOneErrback){
+this.errback(_310);
+}else{
+if(this.finishedCount==this.list.length){
+this.callback(this.resultList);
+}
+}
+}
+}
+if(!_309&&this.consumeErrors){
+_310=null;
+}
+return _310;
+}});
+MochiKit.Async.gatherResults=function(_311){
+var d=new MochiKit.Async.DeferredList(_311,false,true,false);
+d.addCallback(function(_312){
+var ret=[];
+for(var i=0;i<_312.length;i++){
+ret.push(_312[i][1]);
+}
+return ret;
+});
+return d;
+};
+MochiKit.Async.maybeDeferred=function(func){
+var self=MochiKit.Async;
+var _313;
+try{
+var r=func.apply(null,MochiKit.Base.extend([],arguments,1));
+if(r instanceof self.Deferred){
+_313=r;
+}else{
+if(r instanceof Error){
+_313=self.fail(r);
+}else{
+_313=self.succeed(r);
+}
+}
+}
+catch(e){
+_313=self.fail(e);
+}
+return _313;
+};
+MochiKit.Async.EXPORT=["AlreadyCalledError","CancelledError","BrowserComplianceError","GenericError","XMLHttpRequestError","Deferred","succeed","fail","getXMLHttpRequest","doSimpleXMLHttpRequest","loadJSONDoc","wait","callLater","sendXMLHttpRequest","DeferredLock","DeferredList","gatherResults","maybeDeferred"];
+MochiKit.Async.EXPORT_OK=["evalJSONRequest"];
+MochiKit.Async.__new__=function(){
+var m=MochiKit.Base;
+var ne=m.partial(m._newNamedError,this);
+ne("AlreadyCalledError",function(_316){
+this.deferred=_316;
+});
+ne("CancelledError",function(_317){
+this.deferred=_317;
+});
+ne("BrowserComplianceError",function(msg){
+this.message=msg;
+});
+ne("GenericError",function(msg){
+this.message=msg;
+});
+ne("XMLHttpRequestError",function(req,msg){
+this.req=req;
+this.message=msg;
+try{
+this.number=req.status;
+}
+catch(e){
+}
+});
+this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
+m.nameFunctions(this);
+};
+MochiKit.Async.__new__();
+MochiKit.Base._exportSymbols(this,MochiKit.Async);
+if(typeof (dojo)!="undefined"){
+dojo.provide("MochiKit.DOM");
+dojo.require("MochiKit.Iter");
+}
+if(typeof (JSAN)!="undefined"){
+JSAN.use("MochiKit.Iter",[]);
+}
+try{
+if(typeof (MochiKit.Iter)=="undefined"){
+throw "";
+}
+}
+catch(e){
+throw "MochiKit.DOM depends on MochiKit.Iter!";
+}
+if(typeof (MochiKit.DOM)=="undefined"){
+MochiKit.DOM={};
+}
+MochiKit.DOM.NAME="MochiKit.DOM";
+MochiKit.DOM.VERSION="1.3.1";
+MochiKit.DOM.__repr__=function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+};
+MochiKit.DOM.toString=function(){
+return this.__repr__();
+};
+MochiKit.DOM.EXPORT=["formContents","currentWindow","currentDocument","withWindow","withDocument","registerDOMConverter","coerceToDOM","createDOM","createDOMFunc","getNodeAttribute","setNodeAttribute","updateNodeAttributes","appendChildNodes","replaceChildNodes","removeElement","swapDOM","BUTTON","TT","PRE","H1","H2","H3","BR","CANVAS","HR","LABEL","TEXTAREA","FORM","STRONG","SELECT","OPTION","OPTGROUP","LEGEND","FIELDSET","P","UL","OL","LI","TD","TR","THEAD","TBODY","TFOOT","TABLE","TH","INPUT","SPAN","A","DIV","IMG","getElement","$","computedStyle","getElementsByTagAndClassName","addToCallStack","addLoadEvent","focusOnLoad","setElementClass","toggleElementClass","addElementClass","removeElementClass","swapElementClass","hasElementClass","escapeHTML","toHTML","emitHTML","setDisplayForElement","hideElement","showElement","scrapeText","elementDimensions","elementPosition","setElementDimensions","setElementPosition","getViewportDimensions","setOpacity"];
+MochiKit.DOM.EXPORT_OK=["domConverters"];
+MochiKit.DOM.Dimensions=function(w,h){
+this.w=w;
+this.h=h;
+};
+MochiKit.DOM.Dimensions.prototype.repr=function(){
+var repr=MochiKit.Base.repr;
+return "{w: "+repr(this.w)+", h: "+repr(this.h)+"}";
+};
+MochiKit.DOM.Coordinates=function(x,y){
+this.x=x;
+this.y=y;
+};
+MochiKit.DOM.Coordinates.prototype.repr=function(){
+var repr=MochiKit.Base.repr;
+return "{x: "+repr(this.x)+", y: "+repr(this.y)+"}";
+};
+MochiKit.DOM.Coordinates.prototype.toString=function(){
+return this.repr();
+};
+MochiKit.Base.update(MochiKit.DOM,{setOpacity:function(elem,o){
+elem=MochiKit.DOM.getElement(elem);
+MochiKit.DOM.updateNodeAttributes(elem,{"style":{"opacity":o,"-moz-opacity":o,"-khtml-opacity":o,"filter":" alpha(opacity="+(o*100)+")"}});
+},getViewportDimensions:function(){
+var d=new MochiKit.DOM.Dimensions();
+var w=MochiKit.DOM._window;
+var b=MochiKit.DOM._document.body;
+if(w.innerWidth){
+d.w=w.innerWidth;
+d.h=w.innerHeight;
+}else{
+if(b.parentElement.clientWidth){
+d.w=b.parentElement.clientWidth;
+d.h=b.parentElement.clientHeight;
+}else{
+if(b&&b.clientWidth){
+d.w=b.clientWidth;
+d.h=b.clientHeight;
+}
+}
+}
+return d;
+},elementDimensions:function(elem){
+var self=MochiKit.DOM;
+if(typeof (elem.w)=="number"||typeof (elem.h)=="number"){
+return new self.Dimensions(elem.w||0,elem.h||0);
+}
+elem=self.getElement(elem);
+if(!elem){
+return undefined;
+}
+if(self.computedStyle(elem,"display")!="none"){
+return new self.Dimensions(elem.offsetWidth||0,elem.offsetHeight||0);
+}
+var s=elem.style;
+var _322=s.visibility;
+var _323=s.position;
+s.visibility="hidden";
+s.position="absolute";
+s.display="";
+var _324=elem.offsetWidth;
+var _325=elem.offsetHeight;
+s.display="none";
+s.position=_323;
+s.visibility=_322;
+return new self.Dimensions(_324,_325);
+},elementPosition:function(elem,_326){
+var self=MochiKit.DOM;
+elem=self.getElement(elem);
+if(!elem){
+return undefined;
+}
+var c=new self.Coordinates(0,0);
+if(elem.x&&elem.y){
+c.x+=elem.x||0;
+c.y+=elem.y||0;
+return c;
+}else{
+if(elem.parentNode===null||self.computedStyle(elem,"display")=="none"){
+return undefined;
+}
+}
+var box=null;
+var _329=null;
+var d=MochiKit.DOM._document;
+var de=d.documentElement;
+var b=d.body;
+if(elem.getBoundingClientRect){
+box=elem.getBoundingClientRect();
+c.x+=box.left+(de.scrollLeft||b.scrollLeft)-(de.clientLeft||b.clientLeft);
+c.y+=box.top+(de.scrollTop||b.scrollTop)-(de.clientTop||b.clientTop);
+}else{
+if(d.getBoxObjectFor){
+box=d.getBoxObjectFor(elem);
+c.x+=box.x;
+c.y+=box.y;
+}else{
+if(elem.offsetParent){
+c.x+=elem.offsetLeft;
+c.y+=elem.offsetTop;
+_329=elem.offsetParent;
+if(_329!=elem){
+while(_329){
+c.x+=_329.offsetLeft;
+c.y+=_329.offsetTop;
+_329=_329.offsetParent;
+}
+}
+var ua=navigator.userAgent.toLowerCase();
+if((typeof (opera)!="undefined"&&parseFloat(opera.version())<9)||(ua.indexOf("safari")!=-1&&self.computedStyle(elem,"position")=="absolute")){
+c.x-=b.offsetLeft;
+c.y-=b.offsetTop;
+}
+}
+}
+}
+if(typeof (_326)!="undefined"){
+_326=arguments.callee(_326);
+if(_326){
+c.x-=(_326.x||0);
+c.y-=(_326.y||0);
+}
+}
+if(elem.parentNode){
+_329=elem.parentNode;
+}else{
+_329=null;
+}
+while(_329&&_329.tagName!="BODY"&&_329.tagName!="HTML"){
+c.x-=_329.scrollLeft;
+c.y-=_329.scrollTop;
+if(_329.parentNode){
+_329=_329.parentNode;
+}else{
+_329=null;
+}
+}
+return c;
+},setElementDimensions:function(elem,_332,_333){
+elem=MochiKit.DOM.getElement(elem);
+if(typeof (_333)=="undefined"){
+_333="px";
+}
+MochiKit.DOM.updateNodeAttributes(elem,{"style":{"width":_332.w+_333,"height":_332.h+_333}});
+},setElementPosition:function(elem,_334,_335){
+elem=MochiKit.DOM.getElement(elem);
+if(typeof (_335)=="undefined"){
+_335="px";
+}
+MochiKit.DOM.updateNodeAttributes(elem,{"style":{"left":_334.x+_335,"top":_334.y+_335}});
+},currentWindow:function(){
+return MochiKit.DOM._window;
+},currentDocument:function(){
+return MochiKit.DOM._document;
+},withWindow:function(win,func){
+var self=MochiKit.DOM;
+var _337=self._document;
+var _338=self._win;
+var rval;
+try{
+self._window=win;
+self._document=win.document;
+rval=func();
+}
+catch(e){
+self._window=_338;
+self._document=_337;
+throw e;
+}
+self._window=_338;
+self._document=_337;
+return rval;
+},formContents:function(elem){
+var _339=[];
+var _340=[];
+var m=MochiKit.Base;
+var self=MochiKit.DOM;
+if(typeof (elem)=="undefined"||elem===null){
+elem=self._document;
+}else{
+elem=self.getElement(elem);
+}
+m.nodeWalk(elem,function(elem){
+var name=elem.name;
+if(m.isNotEmpty(name)){
+var _341=elem.nodeName;
+if(_341=="INPUT"&&(elem.type=="radio"||elem.type=="checkbox")&&!elem.checked){
+return null;
+}
+if(_341=="SELECT"){
+if(elem.selectedIndex>=0){
+var opt=elem.options[elem.selectedIndex];
+_339.push(name);
+_340.push((opt.value)?opt.value:opt.text);
+return null;
+}
+_339.push(name);
+_340.push("");
+return null;
+}
+if(_341=="FORM"||_341=="P"||_341=="SPAN"||_341=="DIV"){
+return elem.childNodes;
+}
+_339.push(name);
+_340.push(elem.value||"");
+return null;
+}
+return elem.childNodes;
+});
+return [_339,_340];
+},withDocument:function(doc,func){
+var self=MochiKit.DOM;
+var _344=self._document;
+var rval;
+try{
+self._document=doc;
+rval=func();
+}
+catch(e){
+self._document=_344;
+throw e;
+}
+self._document=_344;
+return rval;
+},registerDOMConverter:function(name,_345,wrap,_346){
+MochiKit.DOM.domConverters.register(name,_345,wrap,_346);
+},coerceToDOM:function(node,ctx){
+var im=MochiKit.Iter;
+var self=MochiKit.DOM;
+var iter=im.iter;
+var _350=im.repeat;
+var imap=im.imap;
+var _352=self.domConverters;
+var _353=self.coerceToDOM;
+var _354=MochiKit.Base.NotFound;
+while(true){
+if(typeof (node)=="undefined"||node===null){
+return null;
+}
+if(typeof (node.nodeType)!="undefined"&&node.nodeType>0){
+return node;
+}
+if(typeof (node)=="number"||typeof (node)=="boolean"){
+node=node.toString();
+}
+if(typeof (node)=="string"){
+return self._document.createTextNode(node);
+}
+if(typeof (node.toDOM)=="function"){
+node=node.toDOM(ctx);
+continue;
+}
+if(typeof (node)=="function"){
+node=node(ctx);
+continue;
+}
+var _355=null;
+try{
+_355=iter(node);
+}
+catch(e){
+}
+if(_355){
+return imap(_353,_355,_350(ctx));
+}
+try{
+node=_352.match(node,ctx);
+continue;
+}
+catch(e){
+if(e!=_354){
+throw e;
+}
+}
+return self._document.createTextNode(node.toString());
+}
+return undefined;
+},setNodeAttribute:function(node,attr,_357){
+var o={};
+o[attr]=_357;
+try{
+return MochiKit.DOM.updateNodeAttributes(node,o);
+}
+catch(e){
+}
+return null;
+},getNodeAttribute:function(node,attr){
+var self=MochiKit.DOM;
+var _358=self.attributeArray.renames[attr];
+node=self.getElement(node);
+try{
+if(_358){
+return node[_358];
+}
+return node.getAttribute(attr);
+}
+catch(e){
+}
+return null;
+},updateNodeAttributes:function(node,_359){
+var elem=node;
+var self=MochiKit.DOM;
+if(typeof (node)=="string"){
+elem=self.getElement(node);
+}
+if(_359){
+var _360=MochiKit.Base.updatetree;
+if(self.attributeArray.compliant){
+for(var k in _359){
+var v=_359[k];
+if(typeof (v)=="object"&&typeof (elem[k])=="object"){
+_360(elem[k],v);
+}else{
+if(k.substring(0,2)=="on"){
+if(typeof (v)=="string"){
+v=new Function(v);
+}
+elem[k]=v;
+}else{
+elem.setAttribute(k,v);
+}
+}
+}
+}else{
+var _361=self.attributeArray.renames;
+for(k in _359){
+v=_359[k];
+var _362=_361[k];
+if(k=="style"&&typeof (v)=="string"){
+elem.style.cssText=v;
+}else{
+if(typeof (_362)=="string"){
+elem[_362]=v;
+}else{
+if(typeof (elem[k])=="object"&&typeof (v)=="object"){
+_360(elem[k],v);
+}else{
+if(k.substring(0,2)=="on"){
+if(typeof (v)=="string"){
+v=new Function(v);
+}
+elem[k]=v;
+}else{
+elem.setAttribute(k,v);
+}
+}
+}
+}
+}
+}
+}
+return elem;
+},appendChildNodes:function(node){
+var elem=node;
+var self=MochiKit.DOM;
+if(typeof (node)=="string"){
+elem=self.getElement(node);
+}
+var _363=[self.coerceToDOM(MochiKit.Base.extend(null,arguments,1),elem)];
+var _364=MochiKit.Base.concat;
+while(_363.length){
+var n=_363.shift();
+if(typeof (n)=="undefined"||n===null){
+}else{
+if(typeof (n.nodeType)=="number"){
+elem.appendChild(n);
+}else{
+_363=_364(n,_363);
+}
+}
+}
+return elem;
+},replaceChildNodes:function(node){
+var elem=node;
+var self=MochiKit.DOM;
+if(typeof (node)=="string"){
+elem=self.getElement(node);
+arguments[0]=elem;
+}
+var _365;
+while((_365=elem.firstChild)){
+elem.removeChild(_365);
+}
+if(arguments.length<2){
+return elem;
+}else{
+return self.appendChildNodes.apply(this,arguments);
+}
+},createDOM:function(name,_366){
+var elem;
+var self=MochiKit.DOM;
+var m=MochiKit.Base;
+if(typeof (_366)=="string"||typeof (_366)=="number"){
+var args=m.extend([name,null],arguments,1);
+return arguments.callee.apply(this,args);
+}
+if(typeof (name)=="string"){
+if(_366&&"name" in _366&&!self.attributeArray.compliant){
+name=("<"+name+" name=\""+self.escapeHTML(_366.name)+"\">");
+}
+elem=self._document.createElement(name);
+}else{
+elem=name;
+}
+if(_366){
+self.updateNodeAttributes(elem,_366);
+}
+if(arguments.length<=2){
+return elem;
+}else{
+var args=m.extend([elem],arguments,2);
+return self.appendChildNodes.apply(this,args);
+}
+},createDOMFunc:function(){
+var m=MochiKit.Base;
+return m.partial.apply(this,m.extend([MochiKit.DOM.createDOM],arguments));
+},swapDOM:function(dest,src){
+var self=MochiKit.DOM;
+dest=self.getElement(dest);
+var _369=dest.parentNode;
+if(src){
+src=self.getElement(src);
+_369.replaceChild(src,dest);
+}else{
+_369.removeChild(dest);
+}
+return src;
+},getElement:function(id){
+var self=MochiKit.DOM;
+if(arguments.length==1){
+return ((typeof (id)=="string")?self._document.getElementById(id):id);
+}else{
+return MochiKit.Base.map(self.getElement,arguments);
+}
+},computedStyle:function(_371,_372,_373){
+if(arguments.length==2){
+_373=_372;
+}
+var self=MochiKit.DOM;
+var el=self.getElement(_371);
+var _375=self._document;
+if(!el||el==_375){
+return undefined;
+}
+if(el.currentStyle){
+return el.currentStyle[_372];
+}
+if(typeof (_375.defaultView)=="undefined"){
+return undefined;
+}
+if(_375.defaultView===null){
+return undefined;
+}
+var _376=_375.defaultView.getComputedStyle(el,null);
+if(typeof (_376)=="undefined"||_376===null){
+return undefined;
+}
+return _376.getPropertyValue(_373);
+},getElementsByTagAndClassName:function(_377,_378,_379){
+var self=MochiKit.DOM;
+if(typeof (_377)=="undefined"||_377===null){
+_377="*";
+}
+if(typeof (_379)=="undefined"||_379===null){
+_379=self._document;
+}
+_379=self.getElement(_379);
+var _380=(_379.getElementsByTagName(_377)||self._document.all);
+if(typeof (_378)=="undefined"||_378===null){
+return MochiKit.Base.extend(null,_380);
+}
+var _381=[];
+for(var i=0;i<_380.length;i++){
+var _382=_380[i];
+var _383=_382.className.split(" ");
+for(var j=0;j<_383.length;j++){
+if(_383[j]==_378){
+_381.push(_382);
+break;
+}
+}
+}
+return _381;
+},_newCallStack:function(path,once){
+var rval=function(){
+var _386=arguments.callee.callStack;
+for(var i=0;i<_386.length;i++){
+if(_386[i].apply(this,arguments)===false){
+break;
+}
+}
+if(once){
+try{
+this[path]=null;
+}
+catch(e){
+}
+}
+};
+rval.callStack=[];
+return rval;
+},addToCallStack:function(_387,path,func,once){
+var self=MochiKit.DOM;
+var _388=_387[path];
+var _389=_388;
+if(!(typeof (_388)=="function"&&typeof (_388.callStack)=="object"&&_388.callStack!==null)){
+_389=self._newCallStack(path,once);
+if(typeof (_388)=="function"){
+_389.callStack.push(_388);
+}
+_387[path]=_389;
+}
+_389.callStack.push(func);
+},addLoadEvent:function(func){
+var self=MochiKit.DOM;
+self.addToCallStack(self._window,"onload",func,true);
+},focusOnLoad:function(_390){
+var self=MochiKit.DOM;
+self.addLoadEvent(function(){
+_390=self.getElement(_390);
+if(_390){
+_390.focus();
+}
+});
+},setElementClass:function(_391,_392){
+var self=MochiKit.DOM;
+var obj=self.getElement(_391);
+if(self.attributeArray.compliant){
+obj.setAttribute("class",_392);
+}else{
+obj.setAttribute("className",_392);
+}
+},toggleElementClass:function(_393){
+var self=MochiKit.DOM;
+for(var i=1;i<arguments.length;i++){
+var obj=self.getElement(arguments[i]);
+if(!self.addElementClass(obj,_393)){
+self.removeElementClass(obj,_393);
+}
+}
+},addElementClass:function(_394,_395){
+var self=MochiKit.DOM;
+var obj=self.getElement(_394);
+var cls=obj.className;
+if(cls.length===0){
+self.setElementClass(obj,_395);
+return true;
+}
+if(cls==_395){
+return false;
+}
+var _397=obj.className.split(" ");
+for(var i=0;i<_397.length;i++){
+if(_397[i]==_395){
+return false;
+}
+}
+self.setElementClass(obj,cls+" "+_395);
+return true;
+},removeElementClass:function(_398,_399){
+var self=MochiKit.DOM;
+var obj=self.getElement(_398);
+var cls=obj.className;
+if(cls.length===0){
+return false;
+}
+if(cls==_399){
+self.setElementClass(obj,"");
+return true;
+}
+var _400=obj.className.split(" ");
+for(var i=0;i<_400.length;i++){
+if(_400[i]==_399){
+_400.splice(i,1);
+self.setElementClass(obj,_400.join(" "));
+return true;
+}
+}
+return false;
+},swapElementClass:function(_401,_402,_403){
+var obj=MochiKit.DOM.getElement(_401);
+var res=MochiKit.DOM.removeElementClass(obj,_402);
+if(res){
+MochiKit.DOM.addElementClass(obj,_403);
+}
+return res;
+},hasElementClass:function(_404,_405){
+var obj=MochiKit.DOM.getElement(_404);
+var _406=obj.className.split(" ");
+for(var i=1;i<arguments.length;i++){
+var good=false;
+for(var j=0;j<_406.length;j++){
+if(_406[j]==arguments[i]){
+good=true;
+break;
+}
+}
+if(!good){
+return false;
+}
+}
+return true;
+},escapeHTML:function(s){
+return s.replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
+},toHTML:function(dom){
+return MochiKit.DOM.emitHTML(dom).join("");
+},emitHTML:function(dom,lst){
+if(typeof (lst)=="undefined"||lst===null){
+lst=[];
+}
+var _409=[dom];
+var self=MochiKit.DOM;
+var _410=self.escapeHTML;
+var _411=self.attributeArray;
+while(_409.length){
+dom=_409.pop();
+if(typeof (dom)=="string"){
+lst.push(dom);
+}else{
+if(dom.nodeType==1){
+lst.push("<"+dom.nodeName.toLowerCase());
+var _412=[];
+var _413=_411(dom);
+for(var i=0;i<_413.length;i++){
+var a=_413[i];
+_412.push([" ",a.name,"=\"",_410(a.value),"\""]);
+}
+_412.sort();
+for(i=0;i<_412.length;i++){
+var _414=_412[i];
+for(var j=0;j<_414.length;j++){
+lst.push(_414[j]);
+}
+}
+if(dom.hasChildNodes()){
+lst.push(">");
+_409.push("</"+dom.nodeName.toLowerCase()+">");
+var _415=dom.childNodes;
+for(i=_415.length-1;i>=0;i--){
+_409.push(_415[i]);
+}
+}else{
+lst.push("/>");
+}
+}else{
+if(dom.nodeType==3){
+lst.push(_410(dom.nodeValue));
+}
+}
+}
+}
+return lst;
+},setDisplayForElement:function(_416,_417){
+var m=MochiKit.Base;
+var _418=m.extend(null,arguments,1);
+MochiKit.Iter.forEach(m.filter(null,m.map(MochiKit.DOM.getElement,_418)),function(_417){
+_417.style.display=_416;
+});
+},scrapeText:function(node,_419){
+var rval=[];
+(function(node){
+var cn=node.childNodes;
+if(cn){
+for(var i=0;i<cn.length;i++){
+arguments.callee.call(this,cn[i]);
+}
+}
+var _421=node.nodeValue;
+if(typeof (_421)=="string"){
+rval.push(_421);
+}
+})(MochiKit.DOM.getElement(node));
+if(_419){
+return rval;
+}else{
+return rval.join("");
+}
+},__new__:function(win){
+var m=MochiKit.Base;
+this._document=document;
+this._window=win;
+this.domConverters=new m.AdapterRegistry();
+var _422=this._document.createElement("span");
+var _423;
+if(_422&&_422.attributes&&_422.attributes.length>0){
+var _424=m.filter;
+_423=function(node){
+return _424(_423.ignoreAttrFilter,node.attributes);
+};
+_423.ignoreAttr={};
+MochiKit.Iter.forEach(_422.attributes,function(a){
+_423.ignoreAttr[a.name]=a.value;
+});
+_423.ignoreAttrFilter=function(a){
+return (_423.ignoreAttr[a.name]!=a.value);
+};
+_423.compliant=false;
+_423.renames={"class":"className","checked":"defaultChecked","usemap":"useMap","for":"htmlFor"};
+}else{
+_423=function(node){
+return node.attributes;
+};
+_423.compliant=true;
+_423.renames={};
+}
+this.attributeArray=_423;
+var _425=this.createDOMFunc;
+this.UL=_425("ul");
+this.OL=_425("ol");
+this.LI=_425("li");
+this.TD=_425("td");
+this.TR=_425("tr");
+this.TBODY=_425("tbody");
+this.THEAD=_425("thead");
+this.TFOOT=_425("tfoot");
+this.TABLE=_425("table");
+this.TH=_425("th");
+this.INPUT=_425("input");
+this.SPAN=_425("span");
+this.A=_425("a");
+this.DIV=_425("div");
+this.IMG=_425("img");
+this.BUTTON=_425("button");
+this.TT=_425("tt");
+this.PRE=_425("pre");
+this.H1=_425("h1");
+this.H2=_425("h2");
+this.H3=_425("h3");
+this.BR=_425("br");
+this.HR=_425("hr");
+this.LABEL=_425("label");
+this.TEXTAREA=_425("textarea");
+this.FORM=_425("form");
+this.P=_425("p");
+this.SELECT=_425("select");
+this.OPTION=_425("option");
+this.OPTGROUP=_425("optgroup");
+this.LEGEND=_425("legend");
+this.FIELDSET=_425("fieldset");
+this.STRONG=_425("strong");
+this.CANVAS=_425("canvas");
+this.hideElement=m.partial(this.setDisplayForElement,"none");
+this.showElement=m.partial(this.setDisplayForElement,"block");
+this.removeElement=this.swapDOM;
+this.$=this.getElement;
+this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
+m.nameFunctions(this);
+}});
+MochiKit.DOM.__new__(((typeof (window)=="undefined")?this:window));
+if(!MochiKit.__compat__){
+withWindow=MochiKit.DOM.withWindow;
+withDocument=MochiKit.DOM.withDocument;
+}
+MochiKit.Base._exportSymbols(this,MochiKit.DOM);
+if(typeof (dojo)!="undefined"){
+dojo.provide("MochiKit.LoggingPane");
+dojo.require("MochiKit.Logging");
+dojo.require("MochiKit.Base");
+}
+if(typeof (JSAN)!="undefined"){
+JSAN.use("MochiKit.Logging",[]);
+JSAN.use("MochiKit.Base",[]);
+}
+try{
+if(typeof (MochiKit.Base)=="undefined"||typeof (MochiKit.Logging)=="undefined"){
+throw "";
+}
+}
+catch(e){
+throw "MochiKit.LoggingPane depends on MochiKit.Base and MochiKit.Logging!";
+}
+if(typeof (MochiKit.LoggingPane)=="undefined"){
+MochiKit.LoggingPane={};
+}
+MochiKit.LoggingPane.NAME="MochiKit.LoggingPane";
+MochiKit.LoggingPane.VERSION="1.3.1";
+MochiKit.LoggingPane.__repr__=function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+};
+MochiKit.LoggingPane.toString=function(){
+return this.__repr__();
+};
+MochiKit.LoggingPane.createLoggingPane=function(_426){
+var m=MochiKit.LoggingPane;
+_426=!(!_426);
+if(m._loggingPane&&m._loggingPane.inline!=_426){
+m._loggingPane.closePane();
+m._loggingPane=null;
+}
+if(!m._loggingPane||m._loggingPane.closed){
+m._loggingPane=new m.LoggingPane(_426,MochiKit.Logging.logger);
+}
+return m._loggingPane;
+};
+MochiKit.LoggingPane.LoggingPane=function(_427,_428){
+if(typeof (_428)=="undefined"||_428===null){
+_428=MochiKit.Logging.logger;
+}
+this.logger=_428;
+var _429=MochiKit.Base.update;
+var _430=MochiKit.Base.updatetree;
+var bind=MochiKit.Base.bind;
+var _431=MochiKit.Base.clone;
+var win=window;
+var uid="_MochiKit_LoggingPane";
+if(typeof (MochiKit.DOM)!="undefined"){
+win=MochiKit.DOM.currentWindow();
+}
+if(!_427){
+var url=win.location.href.split("?")[0].replace(/[:\/.><&]/g,"_");
+var name=uid+"_"+url;
+var nwin=win.open("",name,"dependent,resizable,height=200");
+if(!nwin){
+alert("Not able to open debugging window due to pop-up blocking.");
+return undefined;
+}
+nwin.document.write("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" "+"\"http://www.w3.org/TR/html4/loose.dtd\">"+"<html><head><title>[MochiKit.LoggingPane]</title></head>"+"<body></body></html>");
+nwin.document.close();
+nwin.document.title+=" "+win.document.title;
+win=nwin;
+}
+var doc=win.document;
+this.doc=doc;
+var _434=doc.getElementById(uid);
+var _435=!!_434;
+if(_434&&typeof (_434.loggingPane)!="undefined"){
+_434.loggingPane.logger=this.logger;
+_434.loggingPane.buildAndApplyFilter();
+return _434.loggingPane;
+}
+if(_435){
+var _436;
+while((_436=_434.firstChild)){
+_434.removeChild(_436);
+}
+}else{
+_434=doc.createElement("div");
+_434.id=uid;
+}
+_434.loggingPane=this;
+var _437=doc.createElement("input");
+var _438=doc.createElement("input");
+var _439=doc.createElement("button");
+var _440=doc.createElement("button");
+var _441=doc.createElement("button");
+var _442=doc.createElement("button");
+var _443=doc.createElement("div");
+var _444=doc.createElement("div");
+var _445=uid+"_Listener";
+this.colorTable=_431(this.colorTable);
+var _446=[];
+var _447=null;
+var _448=function(msg){
+var _449=msg.level;
+if(typeof (_449)=="number"){
+_449=MochiKit.Logging.LogLevel[_449];
+}
+return _449;
+};
+var _450=function(msg){
+return msg.info.join(" ");
+};
+var _451=bind(function(msg){
+var _452=_448(msg);
+var text=_450(msg);
+var c=this.colorTable[_452];
+var p=doc.createElement("span");
+p.className="MochiKit-LogMessage MochiKit-LogLevel-"+_452;
+p.style.cssText="margin: 0px; white-space: -moz-pre-wrap; white-space: -o-pre-wrap; white-space: pre-wrap; white-space: pre-line; word-wrap: break-word; wrap-option: emergency; color: "+c;
+p.appendChild(doc.createTextNode(_452+": "+text));
+_444.appendChild(p);
+_444.appendChild(doc.createElement("br"));
+if(_443.offsetHeight>_443.scrollHeight){
+_443.scrollTop=0;
+}else{
+_443.scrollTop=_443.scrollHeight;
+}
+},this);
+var _454=function(msg){
+_446[_446.length]=msg;
+_451(msg);
+};
+var _455=function(){
+var _456,infore;
+try{
+_456=new RegExp(_437.value);
+infore=new RegExp(_438.value);
+}
+catch(e){
+logDebug("Error in filter regex: "+e.message);
+return null;
+}
+return function(msg){
+return (_456.test(_448(msg))&&infore.test(_450(msg)));
+};
+};
+var _457=function(){
+while(_444.firstChild){
+_444.removeChild(_444.firstChild);
+}
+};
+var _458=function(){
+_446=[];
+_457();
+};
+var _459=bind(function(){
+if(this.closed){
+return;
+}
+this.closed=true;
+if(MochiKit.LoggingPane._loggingPane==this){
+MochiKit.LoggingPane._loggingPane=null;
+}
+this.logger.removeListener(_445);
+_434.loggingPane=null;
+if(_427){
+_434.parentNode.removeChild(_434);
+}else{
+this.win.close();
+}
+},this);
+var _460=function(){
+_457();
+for(var i=0;i<_446.length;i++){
+var msg=_446[i];
+if(_447===null||_447(msg)){
+_451(msg);
+}
+}
+};
+this.buildAndApplyFilter=function(){
+_447=_455();
+_460();
+this.logger.removeListener(_445);
+this.logger.addListener(_445,_447,_454);
+};
+var _461=bind(function(){
+_446=this.logger.getMessages();
+_460();
+},this);
+var _462=bind(function(_463){
+_463=_463||window.event;
+key=_463.which||_463.keyCode;
+if(key==13){
+this.buildAndApplyFilter();
+}
+},this);
+var _464="display: block; z-index: 1000; left: 0px; bottom: 0px; position: fixed; width: 100%; background-color: white; font: "+this.logFont;
+if(_427){
+_464+="; height: 10em; border-top: 2px solid black";
+}else{
+_464+="; height: 100%;";
+}
+_434.style.cssText=_464;
+if(!_435){
+doc.body.appendChild(_434);
+}
+_464={"cssText":"width: 33%; display: inline; font: "+this.logFont};
+_430(_437,{"value":"FATAL|ERROR|WARNING|INFO|DEBUG","onkeypress":_462,"style":_464});
+_434.appendChild(_437);
+_430(_438,{"value":".*","onkeypress":_462,"style":_464});
+_434.appendChild(_438);
+_464="width: 8%; display:inline; font: "+this.logFont;
+_439.appendChild(doc.createTextNode("Filter"));
+_439.onclick=bind("buildAndApplyFilter",this);
+_439.style.cssText=_464;
+_434.appendChild(_439);
+_440.appendChild(doc.createTextNode("Load"));
+_440.onclick=_461;
+_440.style.cssText=_464;
+_434.appendChild(_440);
+_441.appendChild(doc.createTextNode("Clear"));
+_441.onclick=_458;
+_441.style.cssText=_464;
+_434.appendChild(_441);
+_442.appendChild(doc.createTextNode("Close"));
+_442.onclick=_459;
+_442.style.cssText=_464;
+_434.appendChild(_442);
+_443.style.cssText="overflow: auto; width: 100%";
+_444.style.cssText="width: 100%; height: "+(_427?"8em":"100%");
+_443.appendChild(_444);
+_434.appendChild(_443);
+this.buildAndApplyFilter();
+_461();
+if(_427){
+this.win=undefined;
+}else{
+this.win=win;
+}
+this.inline=_427;
+this.closePane=_459;
+this.closed=false;
+return this;
+};
+MochiKit.LoggingPane.LoggingPane.prototype={"logFont":"8pt Verdana,sans-serif","colorTable":{"ERROR":"red","FATAL":"darkred","WARNING":"blue","INFO":"black","DEBUG":"green"}};
+MochiKit.LoggingPane.EXPORT_OK=["LoggingPane"];
+MochiKit.LoggingPane.EXPORT=["createLoggingPane"];
+MochiKit.LoggingPane.__new__=function(){
+this.EXPORT_TAGS={":common":this.EXPORT,":all":MochiKit.Base.concat(this.EXPORT,this.EXPORT_OK)};
+MochiKit.Base.nameFunctions(this);
+MochiKit.LoggingPane._loggingPane=null;
+};
+MochiKit.LoggingPane.__new__();
+MochiKit.Base._exportSymbols(this,MochiKit.LoggingPane);
+if(typeof (dojo)!="undefined"){
+dojo.provide("MochiKit.Color");
+dojo.require("MochiKit.Base");
+}
+if(typeof (JSAN)!="undefined"){
+JSAN.use("MochiKit.Base",[]);
+}
+try{
+if(typeof (MochiKit.Base)=="undefined"){
+throw "";
+}
+}
+catch(e){
+throw "MochiKit.Color depends on MochiKit.Base";
+}
+if(typeof (MochiKit.Color)=="undefined"){
+MochiKit.Color={};
+}
+MochiKit.Color.NAME="MochiKit.Color";
+MochiKit.Color.VERSION="1.3.1";
+MochiKit.Color.__repr__=function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+};
+MochiKit.Color.toString=function(){
+return this.__repr__();
+};
+MochiKit.Color.Color=function(red,_466,blue,_468){
+if(typeof (_468)=="undefined"||_468===null){
+_468=1;
+}
+this.rgb={r:red,g:_466,b:blue,a:_468};
+};
+MochiKit.Color.Color.prototype={__class__:MochiKit.Color.Color,colorWithAlpha:function(_469){
+var rgb=this.rgb;
+var m=MochiKit.Color;
+return m.Color.fromRGB(rgb.r,rgb.g,rgb.b,_469);
+},colorWithHue:function(hue){
+var hsl=this.asHSL();
+hsl.h=hue;
+var m=MochiKit.Color;
+return m.Color.fromHSL(hsl);
+},colorWithSaturation:function(_473){
+var hsl=this.asHSL();
+hsl.s=_473;
+var m=MochiKit.Color;
+return m.Color.fromHSL(hsl);
+},colorWithLightness:function(_474){
+var hsl=this.asHSL();
+hsl.l=_474;
+var m=MochiKit.Color;
+return m.Color.fromHSL(hsl);
+},darkerColorWithLevel:function(_475){
+var hsl=this.asHSL();
+hsl.l=Math.max(hsl.l-_475,0);
+var m=MochiKit.Color;
+return m.Color.fromHSL(hsl);
+},lighterColorWithLevel:function(_476){
+var hsl=this.asHSL();
+hsl.l=Math.min(hsl.l+_476,1);
+var m=MochiKit.Color;
+return m.Color.fromHSL(hsl);
+},blendedColor:function(_477,_478){
+if(typeof (_478)=="undefined"||_478===null){
+_478=0.5;
+}
+var sf=1-_478;
+var s=this.rgb;
+var d=_477.rgb;
+var df=_478;
+return MochiKit.Color.Color.fromRGB((s.r*sf)+(d.r*df),(s.g*sf)+(d.g*df),(s.b*sf)+(d.b*df),(s.a*sf)+(d.a*df));
+},compareRGB:function(_481){
+var a=this.asRGB();
+var b=_481.asRGB();
+return MochiKit.Base.compare([a.r,a.g,a.b,a.a],[b.r,b.g,b.b,b.a]);
+},isLight:function(){
+return this.asHSL().b>0.5;
+},isDark:function(){
+return (!this.isLight());
+},toHSLString:function(){
+var c=this.asHSL();
+var ccc=MochiKit.Color.clampColorComponent;
+var rval=this._hslString;
+if(!rval){
+var mid=(ccc(c.h,360).toFixed(0)+","+ccc(c.s,100).toPrecision(4)+"%"+","+ccc(c.l,100).toPrecision(4)+"%");
+var a=c.a;
+if(a>=1){
+a=1;
+rval="hsl("+mid+")";
+}else{
+if(a<=0){
+a=0;
+}
+rval="hsla("+mid+","+a+")";
+}
+this._hslString=rval;
+}
+return rval;
+},toRGBString:function(){
+var c=this.rgb;
+var ccc=MochiKit.Color.clampColorComponent;
+var rval=this._rgbString;
+if(!rval){
+var mid=(ccc(c.r,255).toFixed(0)+","+ccc(c.g,255).toFixed(0)+","+ccc(c.b,255).toFixed(0));
+if(c.a!=1){
+rval="rgba("+mid+","+c.a+")";
+}else{
+rval="rgb("+mid+")";
+}
+this._rgbString=rval;
+}
+return rval;
+},asRGB:function(){
+return MochiKit.Base.clone(this.rgb);
+},toHexString:function(){
+var m=MochiKit.Color;
+var c=this.rgb;
+var ccc=MochiKit.Color.clampColorComponent;
+var rval=this._hexString;
+if(!rval){
+rval=("#"+m.toColorPart(ccc(c.r,255))+m.toColorPart(ccc(c.g,255))+m.toColorPart(ccc(c.b,255)));
+this._hexString=rval;
+}
+return rval;
+},asHSV:function(){
+var hsv=this.hsv;
+var c=this.rgb;
+if(typeof (hsv)=="undefined"||hsv===null){
+hsv=MochiKit.Color.rgbToHSV(this.rgb);
+this.hsv=hsv;
+}
+return MochiKit.Base.clone(hsv);
+},asHSL:function(){
+var hsl=this.hsl;
+var c=this.rgb;
+if(typeof (hsl)=="undefined"||hsl===null){
+hsl=MochiKit.Color.rgbToHSL(this.rgb);
+this.hsl=hsl;
+}
+return MochiKit.Base.clone(hsl);
+},toString:function(){
+return this.toRGBString();
+},repr:function(){
+var c=this.rgb;
+var col=[c.r,c.g,c.b,c.a];
+return this.__class__.NAME+"("+col.join(", ")+")";
+}};
+MochiKit.Base.update(MochiKit.Color.Color,{fromRGB:function(red,_486,blue,_487){
+var _488=MochiKit.Color.Color;
+if(arguments.length==1){
+var rgb=red;
+red=rgb.r;
+_486=rgb.g;
+blue=rgb.b;
+if(typeof (rgb.a)=="undefined"){
+_487=undefined;
+}else{
+_487=rgb.a;
+}
+}
+return new _488(red,_486,blue,_487);
+},fromHSL:function(hue,_489,_490,_491){
+var m=MochiKit.Color;
+return m.Color.fromRGB(m.hslToRGB.apply(m,arguments));
+},fromHSV:function(hue,_492,_493,_494){
+var m=MochiKit.Color;
+return m.Color.fromRGB(m.hsvToRGB.apply(m,arguments));
+},fromName:function(name){
+var _495=MochiKit.Color.Color;
+if(name.charAt(0)=="\""){
+name=name.substr(1,name.length-2);
+}
+var _496=_495._namedColors[name.toLowerCase()];
+if(typeof (_496)=="string"){
+return _495.fromHexString(_496);
+}else{
+if(name=="transparent"){
+return _495.transparentColor();
+}
+}
+return null;
+},fromString:function(_497){
+var self=MochiKit.Color.Color;
+var _498=_497.substr(0,3);
+if(_498=="rgb"){
+return self.fromRGBString(_497);
+}else{
+if(_498=="hsl"){
+return self.fromHSLString(_497);
+}else{
+if(_497.charAt(0)=="#"){
+return self.fromHexString(_497);
+}
+}
+}
+return self.fromName(_497);
+},fromHexString:function(_499){
+if(_499.charAt(0)=="#"){
+_499=_499.substring(1);
+}
+var _500=[];
+var i,hex;
+if(_499.length==3){
+for(i=0;i<3;i++){
+hex=_499.substr(i,1);
+_500.push(parseInt(hex+hex,16)/255);
+}
+}else{
+for(i=0;i<6;i+=2){
+hex=_499.substr(i,2);
+_500.push(parseInt(hex,16)/255);
+}
+}
+var _501=MochiKit.Color.Color;
+return _501.fromRGB.apply(_501,_500);
+},_fromColorString:function(pre,_503,_504,_505){
+if(_505.indexOf(pre)===0){
+_505=_505.substring(_505.indexOf("(",3)+1,_505.length-1);
+}
+var _506=_505.split(/\s*,\s*/);
+var _507=[];
+for(var i=0;i<_506.length;i++){
+var c=_506[i];
+var val;
+var _508=c.substring(c.length-3);
+if(c.charAt(c.length-1)=="%"){
+val=0.01*parseFloat(c.substring(0,c.length-1));
+}else{
+if(_508=="deg"){
+val=parseFloat(c)/360;
+}else{
+if(_508=="rad"){
+val=parseFloat(c)/(Math.PI*2);
+}else{
+val=_504[i]*parseFloat(c);
+}
+}
+}
+_507.push(val);
+}
+return this[_503].apply(this,_507);
+},fromComputedStyle:function(elem,_509,_510){
+var d=MochiKit.DOM;
+var cls=MochiKit.Color.Color;
+for(elem=d.getElement(elem);elem;elem=elem.parentNode){
+var _511=d.computedStyle.apply(d,arguments);
+if(!_511){
+continue;
+}
+var _512=cls.fromString(_511);
+if(!_512){
+break;
+}
+if(_512.asRGB().a>0){
+return _512;
+}
+}
+return null;
+},fromBackground:function(elem){
+var cls=MochiKit.Color.Color;
+return cls.fromComputedStyle(elem,"backgroundColor","background-color")||cls.whiteColor();
+},fromText:function(elem){
+var cls=MochiKit.Color.Color;
+return cls.fromComputedStyle(elem,"color","color")||cls.blackColor();
+},namedColors:function(){
+return MochiKit.Base.clone(MochiKit.Color.Color._namedColors);
+}});
+MochiKit.Base.update(MochiKit.Color,{clampColorComponent:function(v,_513){
+v*=_513;
+if(v<0){
+return 0;
+}else{
+if(v>_513){
+return _513;
+}else{
+return v;
+}
+}
+},_hslValue:function(n1,n2,hue){
+if(hue>6){
+hue-=6;
+}else{
+if(hue<0){
+hue+=6;
+}
+}
+var val;
+if(hue<1){
+val=n1+(n2-n1)*hue;
+}else{
+if(hue<3){
+val=n2;
+}else{
+if(hue<4){
+val=n1+(n2-n1)*(4-hue);
+}else{
+val=n1;
+}
+}
+}
+return val;
+},hsvToRGB:function(hue,_516,_517,_518){
+if(arguments.length==1){
+var hsv=hue;
+hue=hsv.h;
+_516=hsv.s;
+_517=hsv.v;
+_518=hsv.a;
+}
+var red;
+var _519;
+var blue;
+if(_516===0){
+red=0;
+_519=0;
+blue=0;
+}else{
+var i=Math.floor(hue*6);
+var f=(hue*6)-i;
+var p=_517*(1-_516);
+var q=_517*(1-(_516*f));
+var t=_517*(1-(_516*(1-f)));
+switch(i){
+case 1:
+red=q;
+_519=_517;
+blue=p;
+break;
+case 2:
+red=p;
+_519=_517;
+blue=t;
+break;
+case 3:
+red=p;
+_519=q;
+blue=_517;
+break;
+case 4:
+red=t;
+_519=p;
+blue=_517;
+break;
+case 5:
+red=_517;
+_519=p;
+blue=q;
+break;
+case 6:
+case 0:
+red=_517;
+_519=t;
+blue=p;
+break;
+}
+}
+return {r:red,g:_519,b:blue,a:_518};
+},hslToRGB:function(hue,_521,_522,_523){
+if(arguments.length==1){
+var hsl=hue;
+hue=hsl.h;
+_521=hsl.s;
+_522=hsl.l;
+_523=hsl.a;
+}
+var red;
+var _524;
+var blue;
+if(_521===0){
+red=_522;
+_524=_522;
+blue=_522;
+}else{
+var m2;
+if(_522<=0.5){
+m2=_522*(1+_521);
+}else{
+m2=_522+_521-(_522*_521);
+}
+var m1=(2*_522)-m2;
+var f=MochiKit.Color._hslValue;
+var h6=hue*6;
+red=f(m1,m2,h6+2);
+_524=f(m1,m2,h6);
+blue=f(m1,m2,h6-2);
+}
+return {r:red,g:_524,b:blue,a:_523};
+},rgbToHSV:function(red,_528,blue,_529){
+if(arguments.length==1){
+var rgb=red;
+red=rgb.r;
+_528=rgb.g;
+blue=rgb.b;
+_529=rgb.a;
+}
+var max=Math.max(Math.max(red,_528),blue);
+var min=Math.min(Math.min(red,_528),blue);
+var hue;
+var _532;
+var _533=max;
+if(min==max){
+hue=0;
+_532=0;
+}else{
+var _534=(max-min);
+_532=_534/max;
+if(red==max){
+hue=(_528-blue)/_534;
+}else{
+if(_528==max){
+hue=2+((blue-red)/_534);
+}else{
+hue=4+((red-_528)/_534);
+}
+}
+hue/=6;
+if(hue<0){
+hue+=1;
+}
+if(hue>1){
+hue-=1;
+}
+}
+return {h:hue,s:_532,v:_533,a:_529};
+},rgbToHSL:function(red,_535,blue,_536){
+if(arguments.length==1){
+var rgb=red;
+red=rgb.r;
+_535=rgb.g;
+blue=rgb.b;
+_536=rgb.a;
+}
+var max=Math.max(red,Math.max(_535,blue));
+var min=Math.min(red,Math.min(_535,blue));
+var hue;
+var _537;
+var _538=(max+min)/2;
+var _539=max-min;
+if(_539===0){
+hue=0;
+_537=0;
+}else{
+if(_538<=0.5){
+_537=_539/(max+min);
+}else{
+_537=_539/(2-max-min);
+}
+if(red==max){
+hue=(_535-blue)/_539;
+}else{
+if(_535==max){
+hue=2+((blue-red)/_539);
+}else{
+hue=4+((red-_535)/_539);
+}
+}
+hue/=6;
+if(hue<0){
+hue+=1;
+}
+if(hue>1){
+hue-=1;
+}
+}
+return {h:hue,s:_537,l:_538,a:_536};
+},toColorPart:function(num){
+num=Math.round(num);
+var _540=num.toString(16);
+if(num<16){
+return "0"+_540;
+}
+return _540;
+},__new__:function(){
+var m=MochiKit.Base;
+this.Color.fromRGBString=m.bind(this.Color._fromColorString,this.Color,"rgb","fromRGB",[1/255,1/255,1/255,1]);
+this.Color.fromHSLString=m.bind(this.Color._fromColorString,this.Color,"hsl","fromHSL",[1/360,0.01,0.01,1]);
+var _541=1/3;
+var _542={black:[0,0,0],blue:[0,0,1],brown:[0.6,0.4,0.2],cyan:[0,1,1],darkGray:[_541,_541,_541],gray:[0.5,0.5,0.5],green:[0,1,0],lightGray:[2*_541,2*_541,2*_541],magenta:[1,0,1],orange:[1,0.5,0],purple:[0.5,0,0.5],red:[1,0,0],transparent:[0,0,0,0],white:[1,1,1],yellow:[1,1,0]};
+var _543=function(name,r,g,b,a){
+var rval=this.fromRGB(r,g,b,a);
+this[name]=function(){
+return rval;
+};
+return rval;
+};
+for(var k in _542){
+var name=k+"Color";
+var _545=m.concat([_543,this.Color,name],_542[k]);
+this.Color[name]=m.bind.apply(null,_545);
+}
+var _546=function(){
+for(var i=0;i<arguments.length;i++){
+if(!(arguments[i] instanceof Color)){
+return false;
+}
+}
+return true;
+};
+var _547=function(a,b){
+return a.compareRGB(b);
+};
+m.nameFunctions(this);
+m.registerComparator(this.Color.NAME,_546,_547);
+this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
+}});
+MochiKit.Color.EXPORT=["Color"];
+MochiKit.Color.EXPORT_OK=["clampColorComponent","rgbToHSL","hslToRGB","rgbToHSV","hsvToRGB","toColorPart"];
+MochiKit.Color.__new__();
+MochiKit.Base._exportSymbols(this,MochiKit.Color);
+MochiKit.Color.Color._namedColors={aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgreen:"#006400",darkgrey:"#a9a9a9",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",green:"#008000",greenyellow:"#adff2f",grey:"#808080",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgreen:"#90ee90",lightgrey:"#d3d3d3",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370db",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#db7093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"};
+if(typeof (dojo)!="undefined"){
+dojo.provide("MochiKit.Signal");
+dojo.require("MochiKit.Base");
+dojo.require("MochiKit.DOM");
+}
+if(typeof (JSAN)!="undefined"){
+JSAN.use("MochiKit.Base",[]);
+JSAN.use("MochiKit.DOM",[]);
+}
+try{
+if(typeof (MochiKit.Base)=="undefined"){
+throw "";
+}
+}
+catch(e){
+throw "MochiKit.Signal depends on MochiKit.Base!";
+}
+try{
+if(typeof (MochiKit.DOM)=="undefined"){
+throw "";
+}
+}
+catch(e){
+throw "MochiKit.Signal depends on MochiKit.DOM!";
+}
+if(typeof (MochiKit.Signal)=="undefined"){
+MochiKit.Signal={};
+}
+MochiKit.Signal.NAME="MochiKit.Signal";
+MochiKit.Signal.VERSION="1.3.1";
+MochiKit.Signal._observers=[];
+MochiKit.Signal.Event=function(src,e){
+this._event=e||window.event;
+this._src=src;
+};
+MochiKit.Base.update(MochiKit.Signal.Event.prototype,{__repr__:function(){
+var repr=MochiKit.Base.repr;
+var str="{event(): "+repr(this.event())+", src(): "+repr(this.src())+", type(): "+repr(this.type())+", target(): "+repr(this.target())+", modifier(): "+"{alt: "+repr(this.modifier().alt)+", ctrl: "+repr(this.modifier().ctrl)+", meta: "+repr(this.modifier().meta)+", shift: "+repr(this.modifier().shift)+", any: "+repr(this.modifier().any)+"}";
+if(this.type()&&this.type().indexOf("key")===0){
+str+=", key(): {code: "+repr(this.key().code)+", string: "+repr(this.key().string)+"}";
+}
+if(this.type()&&(this.type().indexOf("mouse")===0||this.type().indexOf("click")!=-1||this.type()=="contextmenu")){
+str+=", mouse(): {page: "+repr(this.mouse().page)+", client: "+repr(this.mouse().client);
+if(this.type()!="mousemove"){
+str+=", button: {left: "+repr(this.mouse().button.left)+", middle: "+repr(this.mouse().button.middle)+", right: "+repr(this.mouse().button.right)+"}}";
+}else{
+str+="}";
+}
+}
+if(this.type()=="mouseover"||this.type()=="mouseout"){
+str+=", relatedTarget(): "+repr(this.relatedTarget());
+}
+str+="}";
+return str;
+},toString:function(){
+return this.__repr__();
+},src:function(){
+return this._src;
+},event:function(){
+return this._event;
+},type:function(){
+return this._event.type||undefined;
+},target:function(){
+return this._event.target||this._event.srcElement;
+},relatedTarget:function(){
+if(this.type()=="mouseover"){
+return (this._event.relatedTarget||this._event.fromElement);
+}else{
+if(this.type()=="mouseout"){
+return (this._event.relatedTarget||this._event.toElement);
+}
+}
+return undefined;
+},modifier:function(){
+var m={};
+m.alt=this._event.altKey;
+m.ctrl=this._event.ctrlKey;
+m.meta=this._event.metaKey||false;
+m.shift=this._event.shiftKey;
+m.any=m.alt||m.ctrl||m.shift||m.meta;
+return m;
+},key:function(){
+var k={};
+if(this.type()&&this.type().indexOf("key")===0){
+if(this.type()=="keydown"||this.type()=="keyup"){
+k.code=this._event.keyCode;
+k.string=(MochiKit.Signal._specialKeys[k.code]||"KEY_UNKNOWN");
+return k;
+}else{
+if(this.type()=="keypress"){
+k.code=0;
+k.string="";
+if(typeof (this._event.charCode)!="undefined"&&this._event.charCode!==0&&!MochiKit.Signal._specialMacKeys[this._event.charCode]){
+k.code=this._event.charCode;
+k.string=String.fromCharCode(k.code);
+}else{
+if(this._event.keyCode&&typeof (this._event.charCode)=="undefined"){
+k.code=this._event.keyCode;
+k.string=String.fromCharCode(k.code);
+}
+}
+return k;
+}
+}
+}
+return undefined;
+},mouse:function(){
+var m={};
+var e=this._event;
+if(this.type()&&(this.type().indexOf("mouse")===0||this.type().indexOf("click")!=-1||this.type()=="contextmenu")){
+m.client=new MochiKit.DOM.Coordinates(0,0);
+if(e.clientX||e.clientY){
+m.client.x=(!e.clientX||e.clientX<0)?0:e.clientX;
+m.client.y=(!e.clientY||e.clientY<0)?0:e.clientY;
+}
+m.page=new MochiKit.DOM.Coordinates(0,0);
+if(e.pageX||e.pageY){
+m.page.x=(!e.pageX||e.pageX<0)?0:e.pageX;
+m.page.y=(!e.pageY||e.pageY<0)?0:e.pageY;
+}else{
+var de=MochiKit.DOM._document.documentElement;
+var b=MochiKit.DOM._document.body;
+m.page.x=e.clientX+(de.scrollLeft||b.scrollLeft)-(de.clientLeft||b.clientLeft);
+m.page.y=e.clientY+(de.scrollTop||b.scrollTop)-(de.clientTop||b.clientTop);
+}
+if(this.type()!="mousemove"){
+m.button={};
+m.button.left=false;
+m.button.right=false;
+m.button.middle=false;
+if(e.which){
+m.button.left=(e.which==1);
+m.button.middle=(e.which==2);
+m.button.right=(e.which==3);
+}else{
+m.button.left=!!(e.button&1);
+m.button.right=!!(e.button&2);
+m.button.middle=!!(e.button&4);
+}
+}
+return m;
+}
+return undefined;
+},stop:function(){
+this.stopPropagation();
+this.preventDefault();
+},stopPropagation:function(){
+if(this._event.stopPropagation){
+this._event.stopPropagation();
+}else{
+this._event.cancelBubble=true;
+}
+},preventDefault:function(){
+if(this._event.preventDefault){
+this._event.preventDefault();
+}else{
+this._event.returnValue=false;
+}
+}});
+MochiKit.Signal._specialMacKeys={3:"KEY_ENTER",63289:"KEY_NUM_PAD_CLEAR",63276:"KEY_PAGE_UP",63277:"KEY_PAGE_DOWN",63275:"KEY_END",63273:"KEY_HOME",63234:"KEY_ARROW_LEFT",63232:"KEY_ARROW_UP",63235:"KEY_ARROW_RIGHT",63233:"KEY_ARROW_DOWN",63302:"KEY_INSERT",63272:"KEY_DELETE"};
+for(i=63236;i<=63242;i++){
+MochiKit.Signal._specialMacKeys[i]="KEY_F"+(i-63236+1);
+}
+MochiKit.Signal._specialKeys={8:"KEY_BACKSPACE",9:"KEY_TAB",12:"KEY_NUM_PAD_CLEAR",13:"KEY_ENTER",16:"KEY_SHIFT",17:"KEY_CTRL",18:"KEY_ALT",19:"KEY_PAUSE",20:"KEY_CAPS_LOCK",27:"KEY_ESCAPE",32:"KEY_SPACEBAR",33:"KEY_PAGE_UP",34:"KEY_PAGE_DOWN",35:"KEY_END",36:"KEY_HOME",37:"KEY_ARROW_LEFT",38:"KEY_ARROW_UP",39:"KEY_ARROW_RIGHT",40:"KEY_ARROW_DOWN",44:"KEY_PRINT_SCREEN",45:"KEY_INSERT",46:"KEY_DELETE",59:"KEY_SEMICOLON",91:"KEY_WINDOWS_LEFT",92:"KEY_WINDOWS_RIGHT",93:"KEY_SELECT",106:"KEY_NUM_PAD_ASTERISK",107:"KEY_NUM_PAD_PLUS_SIGN",109:"KEY_NUM_PAD_HYPHEN-MINUS",110:"KEY_NUM_PAD_FULL_STOP",111:"KEY_NUM_PAD_SOLIDUS",144:"KEY_NUM_LOCK",145:"KEY_SCROLL_LOCK",186:"KEY_SEMICOLON",187:"KEY_EQUALS_SIGN",188:"KEY_COMMA",189:"KEY_HYPHEN-MINUS",190:"KEY_FULL_STOP",191:"KEY_SOLIDUS",192:"KEY_GRAVE_ACCENT",219:"KEY_LEFT_SQUARE_BRACKET",220:"KEY_REVERSE_SOLIDUS",221:"KEY_RIGHT_SQUARE_BRACKET",222:"KEY_APOSTROPHE"};
+for(var i=48;i<=57;i++){
+MochiKit.Signal._specialKeys[i]="KEY_"+(i-48);
+}
+for(i=65;i<=90;i++){
+MochiKit.Signal._specialKeys[i]="KEY_"+String.fromCharCode(i);
+}
+for(i=96;i<=105;i++){
+MochiKit.Signal._specialKeys[i]="KEY_NUM_PAD_"+(i-96);
+}
+for(i=112;i<=123;i++){
+MochiKit.Signal._specialKeys[i]="KEY_F"+(i-112+1);
+}
+MochiKit.Base.update(MochiKit.Signal,{__repr__:function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+},toString:function(){
+return this.__repr__();
+},_unloadCache:function(){
+var self=MochiKit.Signal;
+var _548=self._observers;
+for(var i=0;i<_548.length;i++){
+self._disconnect(_548[i]);
+}
+delete self._observers;
+try{
+window.onload=undefined;
+}
+catch(e){
+}
+try{
+window.onunload=undefined;
+}
+catch(e){
+}
+},_listener:function(src,func,obj,_549){
+var E=MochiKit.Signal.Event;
+if(!_549){
+return MochiKit.Base.bind(func,obj);
+}
+obj=obj||src;
+if(typeof (func)=="string"){
+return function(_551){
+obj[func].apply(obj,[new E(src,_551)]);
+};
+}else{
+return function(_552){
+func.apply(obj,[new E(src,_552)]);
+};
+}
+},connect:function(src,sig,_554,_555){
+src=MochiKit.DOM.getElement(src);
+var self=MochiKit.Signal;
+if(typeof (sig)!="string"){
+throw new Error("'sig' must be a string");
+}
+var obj=null;
+var func=null;
+if(typeof (_555)!="undefined"){
+obj=_554;
+func=_555;
+if(typeof (_555)=="string"){
+if(typeof (_554[_555])!="function"){
+throw new Error("'funcOrStr' must be a function on 'objOrFunc'");
+}
+}else{
+if(typeof (_555)!="function"){
+throw new Error("'funcOrStr' must be a function or string");
+}
+}
+}else{
+if(typeof (_554)!="function"){
+throw new Error("'objOrFunc' must be a function if 'funcOrStr' is not given");
+}else{
+func=_554;
+}
+}
+if(typeof (obj)=="undefined"||obj===null){
+obj=src;
+}
+var _556=!!(src.addEventListener||src.attachEvent);
+var _557=self._listener(src,func,obj,_556);
+if(src.addEventListener){
+src.addEventListener(sig.substr(2),_557,false);
+}else{
+if(src.attachEvent){
+src.attachEvent(sig,_557);
+}
+}
+var _558=[src,sig,_557,_556,_554,_555];
+self._observers.push(_558);
+return _558;
+},_disconnect:function(_559){
+if(!_559[3]){
+return;
+}
+var src=_559[0];
+var sig=_559[1];
+var _560=_559[2];
+if(src.removeEventListener){
+src.removeEventListener(sig.substr(2),_560,false);
+}else{
+if(src.detachEvent){
+src.detachEvent(sig,_560);
+}else{
+throw new Error("'src' must be a DOM element");
+}
+}
+},disconnect:function(_561){
+var self=MochiKit.Signal;
+var _562=self._observers;
+var m=MochiKit.Base;
+if(arguments.length>1){
+var src=MochiKit.DOM.getElement(arguments[0]);
+var sig=arguments[1];
+var obj=arguments[2];
+var func=arguments[3];
+for(var i=_562.length-1;i>=0;i--){
+var o=_562[i];
+if(o[0]===src&&o[1]===sig&&o[4]===obj&&o[5]===func){
+self._disconnect(o);
+_562.splice(i,1);
+return true;
+}
+}
+}else{
+var idx=m.findIdentical(_562,_561);
+if(idx>=0){
+self._disconnect(_561);
+_562.splice(idx,1);
+return true;
+}
+}
+return false;
+},disconnectAll:function(src,sig){
+src=MochiKit.DOM.getElement(src);
+var m=MochiKit.Base;
+var _563=m.flattenArguments(m.extend(null,arguments,1));
+var self=MochiKit.Signal;
+var _564=self._disconnect;
+var _565=self._observers;
+if(_563.length===0){
+for(var i=_565.length-1;i>=0;i--){
+var _566=_565[i];
+if(_566[0]===src){
+_564(_566);
+_565.splice(i,1);
+}
+}
+}else{
+var sigs={};
+for(var i=0;i<_563.length;i++){
+sigs[_563[i]]=true;
+}
+for(var i=_565.length-1;i>=0;i--){
+var _566=_565[i];
+if(_566[0]===src&&_566[1] in sigs){
+_564(_566);
+_565.splice(i,1);
+}
+}
+}
+},signal:function(src,sig){
+var _568=MochiKit.Signal._observers;
+src=MochiKit.DOM.getElement(src);
+var args=MochiKit.Base.extend(null,arguments,2);
+var _569=[];
+for(var i=0;i<_568.length;i++){
+var _570=_568[i];
+if(_570[0]===src&&_570[1]===sig){
+try{
+_570[2].apply(src,args);
+}
+catch(e){
+_569.push(e);
+}
+}
+}
+if(_569.length==1){
+throw _569[0];
+}else{
+if(_569.length>1){
+var e=new Error("Multiple errors thrown in handling 'sig', see errors property");
+e.errors=_569;
+throw e;
+}
+}
+}});
+MochiKit.Signal.EXPORT_OK=[];
+MochiKit.Signal.EXPORT=["connect","disconnect","signal","disconnectAll"];
+MochiKit.Signal.__new__=function(win){
+var m=MochiKit.Base;
+this._document=document;
+this._window=win;
+try{
+this.connect(window,"onunload",this._unloadCache);
+}
+catch(e){
+}
+this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
+m.nameFunctions(this);
+};
+MochiKit.Signal.__new__(this);
+if(!MochiKit.__compat__){
+connect=MochiKit.Signal.connect;
+disconnect=MochiKit.Signal.disconnect;
+disconnectAll=MochiKit.Signal.disconnectAll;
+signal=MochiKit.Signal.signal;
+}
+MochiKit.Base._exportSymbols(this,MochiKit.Signal);
+if(typeof (dojo)!="undefined"){
+dojo.provide("MochiKit.Visual");
+dojo.require("MochiKit.Base");
+dojo.require("MochiKit.DOM");
+dojo.require("MochiKit.Color");
+}
+if(typeof (JSAN)!="undefined"){
+JSAN.use("MochiKit.Base",[]);
+JSAN.use("MochiKit.DOM",[]);
+JSAN.use("MochiKit.Color",[]);
+}
+try{
+if(typeof (MochiKit.Base)=="undefined"||typeof (MochiKit.DOM)=="undefined"||typeof (MochiKit.Color)=="undefined"){
+throw "";
+}
+}
+catch(e){
+throw "MochiKit.Visual depends on MochiKit.Base, MochiKit.DOM and MochiKit.Color!";
+}
+if(typeof (MochiKit.Visual)=="undefined"){
+MochiKit.Visual={};
+}
+MochiKit.Visual.NAME="MochiKit.Visual";
+MochiKit.Visual.VERSION="1.3.1";
+MochiKit.Visual.__repr__=function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+};
+MochiKit.Visual.toString=function(){
+return this.__repr__();
+};
+MochiKit.Visual._RoundCorners=function(e,_571){
+e=MochiKit.DOM.getElement(e);
+this._setOptions(_571);
+if(this.options.__unstable__wrapElement){
+e=this._doWrap(e);
+}
+var _572=this.options.color;
+var C=MochiKit.Color.Color;
+if(this.options.color=="fromElement"){
+_572=C.fromBackground(e);
+}else{
+if(!(_572 instanceof C)){
+_572=C.fromString(_572);
+}
+}
+this.isTransparent=(_572.asRGB().a<=0);
+var _574=this.options.bgColor;
+if(this.options.bgColor=="fromParent"){
+_574=C.fromBackground(e.offsetParent);
+}else{
+if(!(_574 instanceof C)){
+_574=C.fromString(_574);
+}
+}
+this._roundCornersImpl(e,_572,_574);
+};
+MochiKit.Visual._RoundCorners.prototype={_doWrap:function(e){
+var _575=e.parentNode;
+var doc=MochiKit.DOM.currentDocument();
+if(typeof (doc.defaultView)=="undefined"||doc.defaultView===null){
+return e;
+}
+var _576=doc.defaultView.getComputedStyle(e,null);
+if(typeof (_576)=="undefined"||_576===null){
+return e;
+}
+var _577=MochiKit.DOM.DIV({"style":{display:"block",marginTop:_576.getPropertyValue("padding-top"),marginRight:_576.getPropertyValue("padding-right"),marginBottom:_576.getPropertyValue("padding-bottom"),marginLeft:_576.getPropertyValue("padding-left"),padding:"0px"}});
+_577.innerHTML=e.innerHTML;
+e.innerHTML="";
+e.appendChild(_577);
+return e;
+},_roundCornersImpl:function(e,_578,_579){
+if(this.options.border){
+this._renderBorder(e,_579);
+}
+if(this._isTopRounded()){
+this._roundTopCorners(e,_578,_579);
+}
+if(this._isBottomRounded()){
+this._roundBottomCorners(e,_578,_579);
+}
+},_renderBorder:function(el,_580){
+var _581="1px solid "+this._borderColor(_580);
+var _582="border-left: "+_581;
+var _583="border-right: "+_581;
+var _584="style='"+_582+";"+_583+"'";
+el.innerHTML="<div "+_584+">"+el.innerHTML+"</div>";
+},_roundTopCorners:function(el,_585,_586){
+var _587=this._createCorner(_586);
+for(var i=0;i<this.options.numSlices;i++){
+_587.appendChild(this._createCornerSlice(_585,_586,i,"top"));
+}
+el.style.paddingTop=0;
+el.insertBefore(_587,el.firstChild);
+},_roundBottomCorners:function(el,_588,_589){
+var _590=this._createCorner(_589);
+for(var i=(this.options.numSlices-1);i>=0;i--){
+_590.appendChild(this._createCornerSlice(_588,_589,i,"bottom"));
+}
+el.style.paddingBottom=0;
+el.appendChild(_590);
+},_createCorner:function(_591){
+var dom=MochiKit.DOM;
+return dom.DIV({style:{backgroundColor:_591.toString()}});
+},_createCornerSlice:function(_592,_593,n,_594){
+var _595=MochiKit.DOM.SPAN();
+var _596=_595.style;
+_596.backgroundColor=_592.toString();
+_596.display="block";
+_596.height="1px";
+_596.overflow="hidden";
+_596.fontSize="1px";
+var _597=this._borderColor(_592,_593);
+if(this.options.border&&n===0){
+_596.borderTopStyle="solid";
+_596.borderTopWidth="1px";
+_596.borderLeftWidth="0px";
+_596.borderRightWidth="0px";
+_596.borderBottomWidth="0px";
+_596.height="0px";
+_596.borderColor=_597.toString();
+}else{
+if(_597){
+_596.borderColor=_597.toString();
+_596.borderStyle="solid";
+_596.borderWidth="0px 1px";
+}
+}
+if(!this.options.compact&&(n==(this.options.numSlices-1))){
+_596.height="2px";
+}
+this._setMargin(_595,n,_594);
+this._setBorder(_595,n,_594);
+return _595;
+},_setOptions:function(_598){
+this.options={corners:"all",color:"fromElement",bgColor:"fromParent",blend:true,border:false,compact:false,__unstable__wrapElement:false};
+MochiKit.Base.update(this.options,_598);
+this.options.numSlices=(this.options.compact?2:4);
+},_whichSideTop:function(){
+var _599=this.options.corners;
+if(this._hasString(_599,"all","top")){
+return "";
+}
+var _600=(_599.indexOf("tl")!=-1);
+var _601=(_599.indexOf("tr")!=-1);
+if(_600&&_601){
+return "";
+}
+if(_600){
+return "left";
+}
+if(_601){
+return "right";
+}
+return "";
+},_whichSideBottom:function(){
+var _602=this.options.corners;
+if(this._hasString(_602,"all","bottom")){
+return "";
+}
+var _603=(_602.indexOf("bl")!=-1);
+var _604=(_602.indexOf("br")!=-1);
+if(_603&&_604){
+return "";
+}
+if(_603){
+return "left";
+}
+if(_604){
+return "right";
+}
+return "";
+},_borderColor:function(_605,_606){
+if(_605=="transparent"){
+return _606;
+}else{
+if(this.options.border){
+return this.options.border;
+}else{
+if(this.options.blend){
+return _606.blendedColor(_605);
+}
+}
+}
+return "";
+},_setMargin:function(el,n,_607){
+var _608=this._marginSize(n)+"px";
+var _609=(_607=="top"?this._whichSideTop():this._whichSideBottom());
+var _610=el.style;
+if(_609=="left"){
+_610.marginLeft=_608;
+_610.marginRight="0px";
+}else{
+if(_609=="right"){
+_610.marginRight=_608;
+_610.marginLeft="0px";
+}else{
+_610.marginLeft=_608;
+_610.marginRight=_608;
+}
+}
+},_setBorder:function(el,n,_611){
+var _612=this._borderSize(n)+"px";
+var _613=(_611=="top"?this._whichSideTop():this._whichSideBottom());
+var _614=el.style;
+if(_613=="left"){
+_614.borderLeftWidth=_612;
+_614.borderRightWidth="0px";
+}else{
+if(_613=="right"){
+_614.borderRightWidth=_612;
+_614.borderLeftWidth="0px";
+}else{
+_614.borderLeftWidth=_612;
+_614.borderRightWidth=_612;
+}
+}
+},_marginSize:function(n){
+if(this.isTransparent){
+return 0;
+}
+var o=this.options;
+if(o.compact&&o.blend){
+var _615=[1,0];
+return _615[n];
+}else{
+if(o.compact){
+var _616=[2,1];
+return _616[n];
+}else{
+if(o.blend){
+var _617=[3,2,1,0];
+return _617[n];
+}else{
+var _618=[5,3,2,1];
+return _618[n];
+}
+}
+}
+},_borderSize:function(n){
+var o=this.options;
+var _619;
+if(o.compact&&(o.blend||this.isTransparent)){
+return 1;
+}else{
+if(o.compact){
+_619=[1,0];
+}else{
+if(o.blend){
+_619=[2,1,1,1];
+}else{
+if(o.border){
+_619=[0,2,0,0];
+}else{
+if(this.isTransparent){
+_619=[5,3,2,1];
+}else{
+return 0;
+}
+}
+}
+}
+}
+return _619[n];
+},_hasString:function(str){
+for(var i=1;i<arguments.length;i++){
+if(str.indexOf(arguments[i])!=-1){
+return true;
+}
+}
+return false;
+},_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);
+}};
+MochiKit.Visual.roundElement=function(e,_620){
+new MochiKit.Visual._RoundCorners(e,_620);
+};
+MochiKit.Visual.roundClass=function(_621,_622,_623){
+var _624=MochiKit.DOM.getElementsByTagAndClassName(_621,_622);
+for(var i=0;i<_624.length;i++){
+MochiKit.Visual.roundElement(_624[i],_623);
+}
+};
+MochiKit.Visual.Color=MochiKit.Color.Color;
+MochiKit.Visual.getElementsComputedStyle=MochiKit.DOM.computedStyle;
+MochiKit.Visual.__new__=function(){
+var m=MochiKit.Base;
+m.nameFunctions(this);
+this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
+};
+MochiKit.Visual.EXPORT=["roundElement","roundClass"];
+MochiKit.Visual.EXPORT_OK=[];
+MochiKit.Visual.__new__();
+MochiKit.Base._exportSymbols(this,MochiKit.Visual);
+if(typeof (MochiKit)=="undefined"){
+MochiKit={};
+}
+if(typeof (MochiKit.MochiKit)=="undefined"){
+MochiKit.MochiKit={};
+}
+MochiKit.MochiKit.NAME="MochiKit.MochiKit";
+MochiKit.MochiKit.VERSION="1.3.1";
+MochiKit.MochiKit.__repr__=function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+};
+MochiKit.MochiKit.toString=function(){
+return this.__repr__();
+};
+MochiKit.MochiKit.SUBMODULES=["Base","Iter","Logging","DateTime","Format","Async","DOM","LoggingPane","Color","Signal","Visual"];
+if(typeof (JSAN)!="undefined"||typeof (dojo)!="undefined"){
+if(typeof (dojo)!="undefined"){
+dojo.provide("MochiKit.MochiKit");
+dojo.require("MochiKit.*");
+}
+if(typeof (JSAN)!="undefined"){
+JSAN.use("MochiKit.Base",[]);
+JSAN.use("MochiKit.Iter",[]);
+JSAN.use("MochiKit.Logging",[]);
+JSAN.use("MochiKit.DateTime",[]);
+JSAN.use("MochiKit.Format",[]);
+JSAN.use("MochiKit.Async",[]);
+JSAN.use("MochiKit.DOM",[]);
+JSAN.use("MochiKit.LoggingPane",[]);
+JSAN.use("MochiKit.Color",[]);
+JSAN.use("MochiKit.Signal",[]);
+JSAN.use("MochiKit.Visual",[]);
+}
+(function(){
+var _625=MochiKit.Base.extend;
+var self=MochiKit.MochiKit;
+var _626=self.SUBMODULES;
+var _627=[];
+var _628=[];
+var _629={};
+var i,k,m,all;
+for(i=0;i<_626.length;i++){
+m=MochiKit[_626[i]];
+_625(_627,m.EXPORT);
+_625(_628,m.EXPORT_OK);
+for(k in m.EXPORT_TAGS){
+_629[k]=_625(_629[k],m.EXPORT_TAGS[k]);
+}
+all=m.EXPORT_TAGS[":all"];
+if(!all){
+all=_625(null,m.EXPORT,m.EXPORT_OK);
+}
+var j;
+for(j=0;j<all.length;j++){
+k=all[j];
+self[k]=m[k];
+}
+}
+self.EXPORT=_627;
+self.EXPORT_OK=_628;
+self.EXPORT_TAGS=_629;
+}());
+}else{
+if(typeof (MochiKit.__compat__)=="undefined"){
+MochiKit.__compat__=true;
+}
+(function(){
+var _630=document.getElementsByTagName("script");
+var _631="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+var base=null;
+var _632=null;
+var _633={};
+var i;
+for(i=0;i<_630.length;i++){
+var src=_630[i].getAttribute("src");
+if(!src){
+continue;
+}
+_633[src]=true;
+if(src.match(/MochiKit.js$/)){
+base=src.substring(0,src.lastIndexOf("MochiKit.js"));
+_632=_630[i];
+}
+}
+if(base===null){
+return;
+}
+var _634=MochiKit.MochiKit.SUBMODULES;
+for(var i=0;i<_634.length;i++){
+if(MochiKit[_634[i]]){
+continue;
+}
+var uri=base+_634[i]+".js";
+if(uri in _633){
+continue;
+}
+if(document.documentElement&&document.documentElement.namespaceURI==_631){
+var s=document.createElementNS(_631,"script");
+s.setAttribute("id","MochiKit_"+base+_634[i]);
+s.setAttribute("src",uri);
+s.setAttribute("type","application/x-javascript");
+_632.parentNode.appendChild(s);
+}else{
+document.write("<script src=\""+uri+"\" type=\"text/javascript\"></script>");
+}
+}
+})();
+}
+
+

Added: jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/js/MochiKit/__package__.js
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/js/MochiKit/__package__.js	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,2 @@
+dojo.hostenv.conditionalLoadModule({"common": ["MochiKit.MochiKit"]});
+dojo.hostenv.moduleLoaded("MochiKit.*");

Added: jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/js/PlotKit/PlotKit_Packed-20060807-custom.js
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/js/PlotKit/PlotKit_Packed-20060807-custom.js	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,1592 @@
+/***
+
+    PlotKit.PlotKit 0.9 : PACKED VERSION
+
+    THIS FILE IS AUTOMATICALLY GENERATED.  If creating patches, please
+    diff against the source tree, not this file.
+
+    For more information, <http://www.liquidx.net/plotkit/>.
+    
+    Copyright (c) 2006. Alastair Tse.
+
+***/
+
+try{
+if(typeof (MochiKit.Base)=="undefined"||typeof (MochiKit.DOM)=="undefined"||typeof (MochiKit.Color)=="undefined"||typeof (MochiKit.Format)=="undefined"){
+throw "";
+}
+}
+catch(e){
+throw "PlotKit depends on MochiKit.{Base,Color,DOM,Format}";
+}
+MochiKit.Base.update(MochiKit.Color.Color.prototype,{asFillColor:function(){
+return this.lighterColorWithLevel(0.3);
+},asStrokeColor:function(){
+return this.darkerColorWithLevel(0.1);
+},asPointColor:function(){
+return this.lighterColorWithLevel(0.1);
+}});
+if(typeof (PlotKit)=="undefined"){
+PlotKit={};
+}
+PlotKit.NAME="PlotKit";
+PlotKit.VERSION="0.8";
+PlotKit.__repr__=function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+};
+PlotKit.toString=function(){
+return this.__repr__();
+};
+if(typeof (PlotKit.Base)=="undefined"){
+PlotKit.Base={};
+}
+PlotKit.Base.NAME="PlotKit.Base";
+PlotKit.Base.VERSION=PlotKit.VERSION;
+PlotKit.Base.__repr__=function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+};
+PlotKit.Base.toString=function(){
+return this.__repr__();
+};
+PlotKit.Base.usingPrototype=function(){
+try{
+return (typeof (Object.extend)=="function");
+}
+catch(e){
+return false;
+}
+};
+MochiKit.Base.update(PlotKit.Base,{roundInterval:function(_1,_2,_3){
+var _4=MochiKit.Format.roundToFixed;
+var _5=_1/_2;
+return parseFloat(_4(_5,_3));
+},collapse:function(_6){
+var m=MochiKit.Base;
+var _8=new Array();
+for(var i=0;i<_6.length;i++){
+_8=m.concat(_8,_6[i]);
+}
+if(PlotKit.Base.usingPrototype()){
+delete _8.extend;
+delete _8.from;
+delete _8.inspect;
+}
+return _8;
+},uniq:function(_10){
+var m=MochiKit.Base;
+if(!m.isArrayLike(_10)||(_10.length<1)){
+return new Array();
+}
+var _11=new Array();
+var _12=_10[0];
+_11.push(_10[0]);
+for(var i=1;i<_10.length;i++){
+if(m.compare(_10[i],_12)!=0){
+_12=_10[i];
+_11.push(_10[i]);
+}
+}
+return _11;
+},colorScheme:function(){
+var mb=MochiKit.Base;
+var mc=MochiKit.Color;
+var _15=["red","orange","yellow","green","cyan","blue","purple","magenta"];
+var _16=function(_17){
+return mc.Color[_17+"Color"]();
+};
+return mb.map(_16,_15);
+},baseDarkPrimaryColors:function(){
+var _18=MochiKit.Color.Color.fromHexString;
+return [_18("#ad3f40"),_18("#ddac2c"),_18("#dfdd0c"),_18("#5276c4"),_18("#739c5a")];
+},basePrimaryColors:function(){
+var _19=MochiKit.Color.Color.fromHexString;
+return [_19("#d24c4d"),_19("#f2b32f"),_19("#ece90e"),_19("#5d83da"),_19("#78a15d")];
+},baseBlueColors:function(){
+var _20=MochiKit.Color.Color.fromHexString;
+return [_20("#4b6b94"),_20("#5d81b4"),_20("#acbad2")];
+},palette:function(_21,_22,_23,_24){
+var _25=MochiKit.Base.isUndefinedOrNull;
+var _26=new Array();
+if(_25(_24)){
+_24=0.1;
+}
+if(_25(_23)){
+_23=0.4;
+}
+if(_25(_22)){
+_22=-0.2;
+}
+var _27=_22;
+while(_27<=_23){
+_26.push(_27);
+_27+=_24;
+}
+var _28=function(_29,_30){
+return _29.lighterColorWithLevel(_30);
+};
+return MochiKit.Base.map(MochiKit.Base.partial(_28,_21),_26);
+},excanvasSupported:function(){
+if(/MSIE/.test(navigator.userAgent)&&!window.opera){
+return true;
+}
+return false;
+},findPosX:function(obj){
+var _32=0;
+if(obj.offsetParent){
+while(obj.offsetParent){
+_32+=obj.offsetLeft;
+obj=obj.offsetParent;
+}
+}else{
+if(obj.x){
+_32+=obj.x;
+}
+}
+return _32;
+},findPosY:function(obj){
+var _33=0;
+if(obj.offsetParent){
+while(obj.offsetParent){
+_33+=obj.offsetTop;
+obj=obj.offsetParent;
+}
+}else{
+if(obj.y){
+_33+=obj.y;
+}
+}
+return _33;
+},isFuncLike:function(obj){
+return (typeof (obj)=="function");
+}});
+PlotKit.Base.map=function(fn,lst){
+if(PlotKit.Base.usingPrototype()){
+var _36=[];
+for(var x in lst){
+if(typeof (lst[x])=="function"){
+continue;
+}
+_36.push(fn(lst[x]));
+}
+return _36;
+}else{
+return MochiKit.Base.map(fn,lst);
+}
+};
+PlotKit.Base.items=function(lst){
+if(PlotKit.Base.usingPrototype()){
+var _38=[];
+for(var x in lst){
+if(typeof (lst[x])=="function"){
+continue;
+}
+_38.push([x,lst[x]]);
+}
+return _38;
+}else{
+return MochiKit.Base.items(lst);
+}
+};
+PlotKit.Base.keys=function(lst){
+if(PlotKit.Base.usingPrototype()){
+var _39=[];
+for(var x in lst){
+if(typeof (lst[x])=="function"){
+continue;
+}
+_39.push(x);
+}
+return _39;
+}else{
+return MochiKit.Base.keys(lst);
+}
+};
+PlotKit.Base.baseColors=function(){
+var _40=MochiKit.Color.Color.fromHexString;
+return [_40("#476fb2"),_40("#be2c2b"),_40("#85b730"),_40("#734a99"),_40("#26a1c5"),_40("#fb8707"),_40("#000000")];
+};
+PlotKit.Base.officeBaseStyle={"axisLineWidth":2,"axisLabelColor":MochiKit.Color.Color.grayColor(),"axisLineColor":MochiKit.Color.Color.whiteColor(),"padding":{top:5,bottom:10,left:30,right:30}};
+MochiKit.Base.update(PlotKit.Base,{officeBlue:function(){
+var r={"colorScheme":PlotKit.Base.palette(PlotKit.Base.baseColors()[0]),"backgroundColor":PlotKit.Base.baseColors()[0].lighterColorWithLevel(0.45)};
+MochiKit.Base.update(r,PlotKit.Base.officeBaseStyle);
+return r;
+},officeRed:function(){
+var r={"colorScheme":PlotKit.Base.palette(PlotKit.Base.baseColors()[1]),"backgroundColor":PlotKit.Base.baseColors()[1].lighterColorWithLevel(0.5)};
+MochiKit.Base.update(r,PlotKit.Base.officeBaseStyle);
+return r;
+},officeGreen:function(){
+var r={"colorScheme":PlotKit.Base.palette(PlotKit.Base.baseColors()[2]),"backgroundColor":PlotKit.Base.baseColors()[2].lighterColorWithLevel(0.5)};
+MochiKit.Base.update(r,PlotKit.Base.officeBaseStyle);
+return r;
+},officePurple:function(){
+var r={"colorScheme":PlotKit.Base.palette(PlotKit.Base.baseColors()[3]),"backgroundColor":PlotKit.Base.baseColors()[3].lighterColorWithLevel(0.5)};
+MochiKit.Base.update(r,PlotKit.Base.officeBaseStyle);
+return r;
+},officeCyan:function(){
+var r={"colorScheme":PlotKit.Base.palette(PlotKit.Base.baseColors()[4]),"backgroundColor":PlotKit.Base.baseColors()[4].lighterColorWithLevel(0.5)};
+MochiKit.Base.update(r,PlotKit.Base.officeBaseStyle);
+return r;
+},officeOrange:function(){
+var r={"colorScheme":PlotKit.Base.palette(PlotKit.Base.baseColors()[5]),"backgroundColor":PlotKit.Base.baseColors()[5].lighterColorWithLevel(0.4)};
+MochiKit.Base.update(r,PlotKit.Base.officeBaseStyle);
+return r;
+},officeBlack:function(){
+var r={"colorScheme":PlotKit.Base.palette(PlotKit.Base.baseColors()[6],0,0.6),"backgroundColor":PlotKit.Base.baseColors()[6].lighterColorWithLevel(0.9)};
+MochiKit.Base.update(r,PlotKit.Base.officeBaseStyle);
+return r;
+}});
+PlotKit.Base.EXPORT=["baseColors","collapse","colorScheme","findPosX","findPosY","officeBaseStyle","officeBlue","officeRed","officeGreen","officePurple","officeCyan","officeOrange","officeBlack","roundInterval","uniq","isFuncLike","excanvasSupported"];
+PlotKit.Base.EXPORT_OK=[];
+PlotKit.Base.__new__=function(){
+var m=MochiKit.Base;
+m.nameFunctions(this);
+this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
+};
+PlotKit.Base.__new__();
+MochiKit.Base._exportSymbols(this,PlotKit.Base);
+try{
+if(typeof (PlotKit.Base)=="undefined"){
+throw "";
+}
+}
+catch(e){
+throw "PlotKit.Layout depends on MochiKit.{Base,Color,DOM,Format} and PlotKit.Base";
+}
+if(typeof (PlotKit.Layout)=="undefined"){
+PlotKit.Layout={};
+}
+PlotKit.Layout.NAME="PlotKit.Layout";
+PlotKit.Layout.VERSION=PlotKit.VERSION;
+PlotKit.Layout.__repr__=function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+};
+PlotKit.Layout.toString=function(){
+return this.__repr__();
+};
+PlotKit.Layout.valid_styles=["bar","line","pie","point"];
+PlotKit.Layout=function(_42,_43){
+this.options={"barWidthFillFraction":0.75,"barOrientation":"vertical","xOriginIsZero":true,"yOriginIsZero":true,"xAxis":null,"yAxis":null,"xTicks":null,"yTicks":null,"xNumberOfTicks":10,"yNumberOfTicks":5,"xTickPrecision":1,"yTickPrecision":1,"pieRadius":0.4};
+this.style=_42;
+MochiKit.Base.update(this.options,_43?_43:{});
+if(!MochiKit.Base.isUndefinedOrNull(this.options.xAxis)){
+this.minxval=this.options.xAxis[0];
+this.maxxval=this.options.xAxis[1];
+this.xscale=this.maxxval-this.minxval;
+}else{
+this.minxval=0;
+this.maxxval=null;
+this.xscale=null;
+}
+if(!MochiKit.Base.isUndefinedOrNull(this.options.yAxis)){
+this.minyval=this.options.yAxis[0];
+this.maxyval=this.options.yAxis[1];
+this.yscale=this.maxyval-this.minyval;
+}else{
+this.minyval=0;
+this.maxyval=null;
+this.yscale=null;
+}
+this.bars=new Array();
+this.points=new Array();
+this.slices=new Array();
+this.xticks=new Array();
+this.yticks=new Array();
+this.datasets=new Array();
+this.minxdelta=0;
+this.xrange=1;
+this.yrange=1;
+this.hitTestCache={x2maxy:null};
+};
+PlotKit.Layout.prototype.addDataset=function(_44,_45){
+this.datasets[_44]=_45;
+};
+PlotKit.Layout.prototype.removeDataset=function(_46,_47){
+delete this.datasets[_46];
+};
+PlotKit.Layout.prototype.addDatasetFromTable=function(_48,_49,_50,_51){
+var _52=MochiKit.Base.isUndefinedOrNull;
+var _53=MochiKit.DOM.scrapeText;
+var _54=MochiKit.Format.strip;
+if(_52(_50)){
+_50=0;
+}
+if(_52(_51)){
+_51=1;
+}
+var _55=_49.tBodies[0].rows;
+var _56=new Array();
+if(!_52(_55)){
+for(var i=0;i<_55.length;i++){
+_56.push([parseFloat(_54(_53(_55[i].cells[_50]))),parseFloat(_54(_53(_55[i].cells[_51])))]);
+}
+this.addDataset(_48,_56);
+return true;
+}
+return false;
+};
+PlotKit.Layout.prototype.evaluate=function(){
+this._evaluateLimits();
+this._evaluateScales();
+if(this.style=="bar"){
+if(this.options.barOrientation=="horizontal"){
+this._evaluateHorizBarCharts();
+}else{
+this._evaluateBarCharts();
+}
+this._evaluateBarTicks();
+}else{
+if(this.style=="line"){
+this._evaluateLineCharts();
+this._evaluateLineTicks();
+}else{
+if(this.style=="pie"){
+this._evaluatePieCharts();
+this._evaluatePieTicks();
+}
+}
+}
+};
+PlotKit.Layout.prototype.hitTest=function(x,y){
+var f=MochiKit.Format.twoDigitFloat;
+if((this.style=="bar")&&this.bars&&(this.bars.length>0)){
+for(var i=0;i<this.bars.length;i++){
+var bar=this.bars[i];
+if((x>=bar.x)&&(x<=bar.x+bar.w)&&(y>=bar.y)&&(y-bar.y<=bar.h)){
+return bar;
+}
+}
+}else{
+if(this.style=="line"){
+if(this.hitTestCache.x2maxy==null){
+this._regenerateHitTestCache();
+}
+var _60=x/this.xscale;
+var _61=this.hitTestCache.xvalues;
+var _62=null;
+var _63=null;
+for(var i=1;i<_61.length;i++){
+if(_61[i]>_60){
+_62=_61[i-1];
+_63=_61[i];
+break;
+}
+}
+if((_62!=null)){
+var _64=this.hitTestCache.x2maxy[_62];
+var _65=this.hitTestCache.x2maxy[_63];
+var _66=(1-y)/this.yscale;
+var _67=(_65-_64)/(_63-_62);
+var _68=_64+_67*(_60-_62);
+if(_68>=_66){
+var obj={xval:_60,yval:_66,xafter:_63,yafter:_65,xbefore:_62,ybefore:_64,yprojected:_68};
+return obj;
+}
+}
+}else{
+if(this.style=="pie"){
+var _69=Math.sqrt((y-0.5)*(y-0.5)+(x-0.5)*(x-0.5));
+if(_69>this.options.pieRadius){
+return null;
+}
+var _70=Math.atan2(y-0.5,x-0.5)-Math.PI/2;
+for(var i=0;i<this.slices.length;i++){
+var _71=this.slices[i];
+if(_71.startAngle<_70&&_71.endAngle>=_70){
+return _71;
+}
+}
+}
+}
+}
+return null;
+};
+PlotKit.Layout.prototype.rectForX=function(x){
+return null;
+};
+PlotKit.Layout.prototype.angleRangeForX=function(x){
+return null;
+};
+PlotKit.Layout.prototype._evaluateLimits=function(){
+var map=PlotKit.Base.map;
+var _73=PlotKit.Base.items;
+var _74=MochiKit.Base.itemgetter;
+var _75=PlotKit.Base.collapse;
+var _76=MochiKit.Base.listMin;
+var _77=MochiKit.Base.listMax;
+var _78=MochiKit.Base.isUndefinedOrNull;
+var all=_75(map(_74(1),_73(this.datasets)));
+if(_78(this.options.xAxis)){
+if(this.options.xOriginIsZero){
+this.minxval=0;
+}else{
+this.minxval=_76(map(parseFloat,map(_74(0),all)));
+}
+this.maxxval=_77(map(parseFloat,map(_74(0),all)));
+}else{
+this.minxval=this.options.xAxis[0];
+this.maxxval=this.options.xAxis[1];
+this.xscale=this.maxval-this.minxval;
+}
+if(_78(this.options.yAxis)){
+if(this.options.yOriginIsZero){
+this.minyval=0;
+}else{
+this.minyval=_76(map(parseFloat,map(_74(1),all)));
+}
+this.maxyval=_77(map(parseFloat,map(_74(1),all)));
+}else{
+this.minyval=this.options.yAxis[0];
+this.maxyval=this.options.yAxis[1];
+this.yscale=this.maxyval-this.minyval;
+}
+};
+PlotKit.Layout.prototype._evaluateScales=function(){
+var _80=MochiKit.Base.isUndefinedOrNull;
+this.xrange=this.maxxval-this.minxval;
+if(this.xrange==0){
+this.xscale=1;
+}else{
+this.xscale=1/this.xrange;
+}
+this.yrange=this.maxyval-this.minyval;
+if(this.yrange==0){
+this.yscale=1;
+}else{
+this.yscale=1/this.yrange;
+}
+};
+PlotKit.Layout.prototype._uniqueXValues=function(){
+var _81=PlotKit.Base.collapse;
+var map=PlotKit.Base.map;
+var _82=PlotKit.Base.uniq;
+var _83=MochiKit.Base.itemgetter;
+var _84=PlotKit.Base.items;
+var _85=map(parseFloat,map(_83(0),_81(map(_83(1),_84(this.datasets)))));
+_85.sort(MochiKit.Base.compare);
+return _82(_85);
+};
+PlotKit.Layout.prototype._evaluateBarCharts=function(){
+var _86=PlotKit.Base.items;
+var _87=_86(this.datasets).length;
+var _88=10000000;
+var _89=this._uniqueXValues();
+for(var i=1;i<_89.length;i++){
+_88=Math.min(Math.abs(_89[i]-_89[i-1]),_88);
+}
+var _90=0;
+var _91=0;
+var _92=0;
+if(_89.length==1){
+_88=1;
+this.xscale=1;
+this.minxval=_89[0];
+_90=1*this.options.barWidthFillFraction;
+_91=_90/_87;
+_92=(1-this.options.barWidthFillFraction)/2;
+}else{
+this.xscale=(1-_88/this.xrange)/this.xrange;
+_90=_88*this.xscale*this.options.barWidthFillFraction;
+_91=_90/_87;
+_92=_88*this.xscale*(1-this.options.barWidthFillFraction)/2;
+}
+this.minxdelta=_88;
+this.bars=new Array();
+var i=0;
+for(var _93 in this.datasets){
+var _94=this.datasets[_93];
+if(PlotKit.Base.isFuncLike(_94)){
+continue;
+}
+for(var j=0;j<_94.length;j++){
+var _96=_94[j];
+var _97={x:((parseFloat(_96[0])-this.minxval)*this.xscale)+(i*_91)+_92,y:1-((parseFloat(_96[1])-this.minyval)*this.yscale),w:_91,h:((parseFloat(_96[1])-this.minyval)*this.yscale),xval:parseFloat(_96[0]),yval:parseFloat(_96[1]),name:_93};
+if((_97.x>=0)&&(_97.x<=1)&&(_97.y>=0)&&(_97.y<=1)){
+this.bars.push(_97);
+}
+}
+i++;
+}
+};
+PlotKit.Layout.prototype._evaluateHorizBarCharts=function(){
+var _98=PlotKit.Base.items;
+var _99=_98(this.datasets).length;
+var _100=10000000;
+var _101=this._uniqueXValues();
+for(var i=1;i<_101.length;i++){
+_100=Math.min(Math.abs(_101[i]-_101[i-1]),_100);
+}
+var _102=0;
+var _103=0;
+var _104=0;
+if(_101.length==1){
+_100=1;
+this.xscale=1;
+this.minxval=_101[0];
+_102=1*this.options.barWidthFillFraction;
+_103=_102/_99;
+_104=(1-this.options.barWidthFillFraction)/2;
+}else{
+this.xscale=(1-_100/this.xrange)/this.xrange;
+_102=_100*this.xscale*this.options.barWidthFillFraction;
+_103=_102/_99;
+_104=_100*this.xscale*(1-this.options.barWidthFillFraction)/2;
+}
+this.minxdelta=_100;
+this.bars=new Array();
+var i=0;
+for(var _105 in this.datasets){
+var _106=this.datasets[_105];
+if(PlotKit.Base.isFuncLike(_106)){
+continue;
+}
+for(var j=0;j<_106.length;j++){
+var item=_106[j];
+var rect={y:((parseFloat(item[0])-this.minxval)*this.xscale)+(i*_103)+_104,x:0,h:_103,w:((parseFloat(item[1])-this.minyval)*this.yscale),xval:parseFloat(item[0]),yval:parseFloat(item[1]),name:_105};
+if(rect.y<=0){
+rect.y=0;
+}
+if(rect.y>=1){
+rect.y=1;
+}
+if((rect.x>=0)&&(rect.x<=1)){
+this.bars.push(rect);
+}
+}
+i++;
+}
+};
+PlotKit.Layout.prototype._evaluateLineCharts=function(){
+var _109=PlotKit.Base.items;
+var _110=_109(this.datasets).length;
+this.points=new Array();
+var i=0;
+for(var _111 in this.datasets){
+var _112=this.datasets[_111];
+if(PlotKit.Base.isFuncLike(_112)){
+continue;
+}
+_112.sort(function(a,b){
+return compare(parseFloat(a[0]),parseFloat(b[0]));
+});
+for(var j=0;j<_112.length;j++){
+var item=_112[j];
+var _115={x:((parseFloat(item[0])-this.minxval)*this.xscale),y:1-((parseFloat(item[1])-this.minyval)*this.yscale),xval:parseFloat(item[0]),yval:parseFloat(item[1]),name:_111};
+if(_115.y<=0){
+_115.y=0;
+}
+if(_115.y>=1){
+_115.y=1;
+}
+if((_115.x>=0)&&(_115.x<=1)){
+this.points.push(_115);
+}
+}
+i++;
+}
+};
+PlotKit.Layout.prototype._evaluatePieCharts=function(){
+var _116=PlotKit.Base.items;
+var sum=MochiKit.Iter.sum;
+var _118=MochiKit.Base.itemgetter;
+var _119=_116(this.datasets).length;
+var _120=_116(this.datasets)[0][1];
+var _121=sum(PlotKit.Base.map(_118(1),_120));
+this.slices=new Array();
+var _122=0;
+for(var i=0;i<_120.length;i++){
+var _123=_120[i][1]/_121;
+var _124=_122*Math.PI*2;
+var _125=(_122+_123)*Math.PI*2;
+var _126={fraction:_123,xval:_120[i][0],yval:_120[i][1],startAngle:_124,endAngle:_125};
+this.slices.push(_126);
+_122+=_123;
+}
+};
+PlotKit.Layout.prototype._evaluateLineTicksForXAxis=function(){
+var _127=MochiKit.Base.isUndefinedOrNull;
+if(this.options.xTicks){
+this.xticks=new Array();
+var _128=function(tick){
+var _130=tick.label;
+if(_127(_130)){
+_130=tick.v.toString();
+}
+var pos=this.xscale*(tick.v-this.minxval);
+if((pos>=0)&&(pos<=1)){
+this.xticks.push([pos,_130]);
+}
+};
+MochiKit.Iter.forEach(this.options.xTicks,MochiKit.Base.bind(_128,this));
+}else{
+if(this.options.xNumberOfTicks){
+var _132=this._uniqueXValues();
+var _133=this.xrange/this.options.xNumberOfTicks;
+var _134=0;
+this.xticks=new Array();
+for(var i=0;i<=_132.length;i++){
+if((_132[i]-this.minxval)>=(_134*_133)){
+var pos=this.xscale*(_132[i]-this.minxval);
+if((pos>1)||(pos<0)){
+continue;
+}
+this.xticks.push([pos,_132[i]]);
+_134++;
+}
+if(_134>this.options.xNumberOfTicks){
+break;
+}
+}
+}
+}
+};
+PlotKit.Layout.prototype._evaluateLineTicksForYAxis=function(){
+var _135=MochiKit.Base.isUndefinedOrNull;
+if(this.options.yTicks){
+this.yticks=new Array();
+var _136=function(tick){
+var _137=tick.label;
+if(_135(_137)){
+_137=tick.v.toString();
+}
+var pos=1-(this.yscale*(tick.v-this.minyval));
+if((pos>=0)&&(pos<=1)){
+this.yticks.push([pos,_137]);
+}
+};
+MochiKit.Iter.forEach(this.options.yTicks,MochiKit.Base.bind(_136,this));
+}else{
+if(this.options.yNumberOfTicks){
+this.yticks=new Array();
+var _138=PlotKit.Base.roundInterval;
+var prec=this.options.yTickPrecision;
+var _140=_138(this.yrange,this.options.yNumberOfTicks,prec);
+for(var i=0;i<=this.options.yNumberOfTicks;i++){
+var yval=this.minyval+(i*_140);
+var pos=1-((yval-this.minyval)*this.yscale);
+if((pos>1)||(pos<0)){
+continue;
+}
+this.yticks.push([pos,MochiKit.Format.roundToFixed(yval,prec)]);
+}
+}
+}
+};
+PlotKit.Layout.prototype._evaluateLineTicks=function(){
+this._evaluateLineTicksForXAxis();
+this._evaluateLineTicksForYAxis();
+};
+PlotKit.Layout.prototype._evaluateBarTicks=function(){
+this._evaluateLineTicks();
+var _142=function(tick){
+return [tick[0]+(this.minxdelta*this.xscale)/2,tick[1]];
+};
+this.xticks=PlotKit.Base.map(MochiKit.Base.bind(_142,this),this.xticks);
+if(this.options.barOrientation=="horizontal"){
+var _143=this.xticks;
+this.xticks=this.yticks;
+this.yticks=_143;
+var _144=function(tick){
+return [1-tick[0],tick[1]];
+};
+this.xticks=PlotKit.Base.map(_144,this.xticks);
+}
+};
+PlotKit.Layout.prototype._evaluatePieTicks=function(){
+var _145=MochiKit.Base.isUndefinedOrNull;
+var _146=MochiKit.Format.numberFormatter("#%");
+this.xticks=new Array();
+if(this.options.xTicks){
+var _147=new Array();
+for(var i=0;i<this.slices.length;i++){
+_147[this.slices[i].xval]=this.slices[i];
+}
+for(var i=0;i<this.options.xTicks.length;i++){
+var tick=this.options.xTicks[i];
+var _148=_147[tick.v];
+var _149=tick.label;
+if(_148){
+if(_145(_149)){
+_149=tick.v.toString();
+}
+_149+=" ("+_146(_148.fraction)+")";
+this.xticks.push([tick.v,_149]);
+}
+}
+}else{
+for(var i=0;i<this.slices.length;i++){
+var _148=this.slices[i];
+var _149=_148.xval+" ("+_146(_148.fraction)+")";
+this.xticks.push([_148.xval,_149]);
+}
+}
+};
+PlotKit.Layout.prototype._regenerateHitTestCache=function(){
+this.hitTestCache.xvalues=this._uniqueXValues();
+this.hitTestCache.xlookup=new Array();
+this.hitTestCache.x2maxy=new Array();
+var _150=MochiKit.Base.listMax;
+var _151=MochiKit.Base.itemgetter;
+var map=MochiKit.Base.map;
+var _152=keys(this.datasets);
+for(var i=0;i<_152.length;i++){
+var _153=this.datasets[_152[i]];
+for(var j=0;j<_153.length;j++){
+var xval=_153[j][0];
+var yval=_153[j][1];
+if(this.hitTestCache.xlookup[xval]){
+this.hitTestCache.xlookup[xval].push([yval,_152[i]]);
+}else{
+this.hitTestCache.xlookup[xval]=[[yval,_152[i]]];
+}
+}
+}
+for(var x in this.hitTestCache.xlookup){
+var _155=this.hitTestCache.xlookup[x];
+this.hitTestCache.x2maxy[x]=_150(map(_151(0),_155));
+}
+};
+PlotKit.LayoutModule={};
+PlotKit.LayoutModule.Layout=PlotKit.Layout;
+PlotKit.LayoutModule.EXPORT=["Layout"];
+PlotKit.LayoutModule.EXPORT_OK=[];
+PlotKit.LayoutModule.__new__=function(){
+var m=MochiKit.Base;
+m.nameFunctions(this);
+this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
+};
+PlotKit.LayoutModule.__new__();
+MochiKit.Base._exportSymbols(this,PlotKit.LayoutModule);
+try{
+if((typeof (PlotKit.Base)=="undefined")||(typeof (PlotKit.Layout)=="undefined")){
+throw "";
+}
+}
+catch(e){
+throw "PlotKit.Layout depends on MochiKit.{Base,Color,DOM,Format} and PlotKit.{Base,Layout}";
+}
+if(typeof (PlotKit.CanvasRenderer)=="undefined"){
+PlotKit.CanvasRenderer={};
+}
+PlotKit.CanvasRenderer.NAME="PlotKit.CanvasRenderer";
+PlotKit.CanvasRenderer.VERSION=PlotKit.VERSION;
+PlotKit.CanvasRenderer.__repr__=function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+};
+PlotKit.CanvasRenderer.toString=function(){
+return this.__repr__();
+};
+PlotKit.CanvasRenderer=function(_156,_157,_158){
+if(arguments.length>0){
+this.__init__(_156,_157,_158);
+}
+};
+PlotKit.CanvasRenderer.prototype.__init__=function(_159,_160,_161){
+var _162=MochiKit.Base.isUndefinedOrNull;
+var _163=MochiKit.Color.Color;
+this.options={"drawBackground":true,"backgroundColor":_163.whiteColor(),"padding":{left:30,right:30,top:5,bottom:10},"colorScheme":PlotKit.Base.palette(PlotKit.Base.baseColors()[0]),"strokeColor":_163.whiteColor(),"strokeColorTransform":"asStrokeColor","strokeWidth":0.5,"shouldFill":true,"shouldStroke":true,"drawXAxis":true,"drawYAxis":true,"axisLineColor":_163.blackColor(),"axisLineWidth":0.5,"axisTickSize":3,"axisLabelColor":_163.blackColor(),"axisLabelFont":"Arial","axisLabelFontSize":9,"axisLabelWidth":50,"pieRadius":0.4,"enableEvents":true};
+MochiKit.Base.update(this.options,_161?_161:{});
+this.layout=_160;
+this.element=MochiKit.DOM.getElement(_159);
+this.container=this.element.parentNode;
+this.isIE=PlotKit.Base.excanvasSupported();
+if(this.isIE&&!_162(G_vmlCanvasManager)){
+this.IEDelay=0.5;
+this.maxTries=5;
+this.renderDelay=null;
+this.clearDelay=null;
+this.element=G_vmlCanvasManager.initElement(this.element);
+}
+this.height=this.element.height;
+this.width=this.element.width;
+if(_162(this.element)){
+throw "CanvasRenderer() - passed canvas is not found";
+}
+if(!this.isIE&&!(PlotKit.CanvasRenderer.isSupported(this.element))){
+throw "CanvasRenderer() - Canvas is not supported.";
+}
+if(_162(this.container)||(this.container.nodeName.toLowerCase()!="div")){
+throw "CanvasRenderer() - <canvas> needs to be enclosed in <div>";
+}
+this.xlabels=new Array();
+this.ylabels=new Array();
+this.isFirstRender=true;
+this.area={x:this.options.padding.left,y:this.options.padding.top,w:this.width-this.options.padding.left-this.options.padding.right,h:this.height-this.options.padding.top-this.options.padding.bottom};
+MochiKit.DOM.updateNodeAttributes(this.container,{"style":{"position":"relative","width":this.width+"px"}});
+};
+PlotKit.CanvasRenderer.prototype.render=function(){
+if(this.isIE){
+try{
+if(this.renderDelay){
+this.renderDelay.cancel();
+this.renderDelay=null;
+}
+var _164=this.element.getContext("2d");
+}
+catch(e){
+this.isFirstRender=false;
+if(this.maxTries-->0){
+this.renderDelay=MochiKit.Async.wait(this.IEDelay);
+this.renderDelay.addCallback(MochiKit.Base.bind(this.render,this));
+}
+return;
+}
+}
+if(this.options.drawBackground){
+this._renderBackground();
+}
+if(this.layout.style=="bar"){
+this._renderBarChart();
+this._renderBarAxis();
+}else{
+if(this.layout.style=="pie"){
+this._renderPieChart();
+this._renderPieAxis();
+}else{
+if(this.layout.style=="line"){
+this._renderLineChart();
+this._renderLineAxis();
+}
+}
+}
+};
+PlotKit.CanvasRenderer.prototype._renderBarChartWrap=function(data,_166){
+var _167=this.element.getContext("2d");
+var _168=this.options.colorScheme.length;
+var _169=this.options.colorScheme;
+var _170=MochiKit.Base.keys(this.layout.datasets);
+var _171=_170.length;
+for(var i=0;i<_171;i++){
+var _172=_170[i];
+var _173=_169[i%_168];
+_167.save();
+_167.fillStyle=_173.toRGBString();
+if(this.options.strokeColor){
+_167.strokeStyle=this.options.strokeColor.toRGBString();
+}else{
+if(this.options.strokeColorTransform){
+_167.strokeStyle=_173[this.options.strokeColorTransform]().toRGBString();
+}
+}
+_167.lineWidth=this.options.strokeWidth;
+var _174=function(obj){
+if(obj.name==_172){
+_166(_167,obj);
+}
+};
+MochiKit.Iter.forEach(data,MochiKit.Base.bind(_174,this));
+_167.restore();
+}
+};
+PlotKit.CanvasRenderer.prototype._renderBarChart=function(){
+var bind=MochiKit.Base.bind;
+var _176=function(_177,bar){
+var x=this.area.w*bar.x+this.area.x;
+var y=this.area.h*bar.y+this.area.y;
+var w=this.area.w*bar.w;
+var h=this.area.h*bar.h;
+if((w<1)||(h<1)){
+return;
+}
+if(this.options.shouldFill){
+_177.fillRect(x,y,w,h);
+}
+if(this.options.shouldStroke){
+_177.strokeRect(x,y,w,h);
+}
+};
+this._renderBarChartWrap(this.layout.bars,bind(_176,this));
+};
+PlotKit.CanvasRenderer.prototype._renderLineChart=function(){
+var _180=this.element.getContext("2d");
+var _181=this.options.colorScheme.length;
+var _182=this.options.colorScheme;
+var _183=MochiKit.Base.keys(this.layout.datasets);
+var _184=_183.length;
+var bind=MochiKit.Base.bind;
+var _185=MochiKit.Base.partial;
+for(var i=0;i<_184;i++){
+var _186=_183[i];
+var _187=_182[i%_181];
+var _188=this.options.strokeColorTransform;
+_180.save();
+_180.fillStyle=_187.toRGBString();
+if(this.options.strokeColor){
+_180.strokeStyle=this.options.strokeColor.toRGBString();
+}else{
+if(this.options.strokeColorTransform){
+_180.strokeStyle=_187[_188]().toRGBString();
+}
+}
+_180.lineWidth=this.options.strokeWidth;
+var _189=function(ctx){
+ctx.beginPath();
+ctx.moveTo(this.area.x,this.area.y+this.area.h);
+var _191=function(ctx_,_193){
+if(_193.name==_186){
+ctx_.lineTo(this.area.w*_193.x+this.area.x,this.area.h*_193.y+this.area.y);
+}
+};
+MochiKit.Iter.forEach(this.layout.points,_185(_191,ctx),this);
+ctx.lineTo(this.area.w+this.area.x,this.area.h+this.area.y);
+ctx.lineTo(this.area.x,this.area.y+this.area.h);
+ctx.closePath();
+};
+if(this.options.shouldFill){
+bind(_189,this)(_180);
+_180.fill();
+}
+if(this.options.shouldStroke){
+bind(_189,this)(_180);
+_180.stroke();
+}
+_180.restore();
+}
+};
+PlotKit.CanvasRenderer.prototype._renderPieChart=function(){
+var _194=this.element.getContext("2d");
+var _195=this.options.colorScheme.length;
+var _196=this.layout.slices;
+var _197=this.area.x+this.area.w*0.5;
+var _198=this.area.y+this.area.h*0.5;
+var _199=Math.min(this.area.w*this.options.pieRadius,this.area.h*this.options.pieRadius);
+if(this.isIE){
+_197=parseInt(_197);
+_198=parseInt(_198);
+_199=parseInt(_199);
+}
+for(var i=0;i<_196.length;i++){
+var _200=this.options.colorScheme[i%_195];
+_194.save();
+_194.fillStyle=_200.toRGBString();
+var _201=function(){
+_194.beginPath();
+_194.moveTo(_197,_198);
+_194.arc(_197,_198,_199,_196[i].startAngle-Math.PI/2,_196[i].endAngle-Math.PI/2,false);
+_194.lineTo(_197,_198);
+_194.closePath();
+};
+if(Math.abs(_196[i].startAngle-_196[i].endAngle)>0.001){
+if(this.options.shouldFill){
+_201();
+_194.fill();
+}
+if(this.options.shouldStroke){
+_201();
+_194.lineWidth=this.options.strokeWidth;
+if(this.options.strokeColor){
+_194.strokeStyle=this.options.strokeColor.toRGBString();
+}else{
+if(this.options.strokeColorTransform){
+_194.strokeStyle=_200[this.options.strokeColorTransform]().toRGBString();
+}
+}
+_194.stroke();
+}
+}
+_194.restore();
+}
+};
+PlotKit.CanvasRenderer.prototype._renderBarAxis=function(){
+this._renderAxis();
+};
+PlotKit.CanvasRenderer.prototype._renderLineAxis=function(){
+this._renderAxis();
+};
+PlotKit.CanvasRenderer.prototype._renderAxis=function(){
+if(!this.options.drawXAxis&&!this.options.drawYAxis){
+return;
+}
+var _202=this.element.getContext("2d");
+var _203={"style":{"position":"absolute","fontSize":this.options.axisLabelFontSize+"px","zIndex":10,"color":this.options.axisLabelColor.toRGBString(),"width":this.options.axisLabelWidth+"px","overflow":"hidden"}};
+_202.save();
+_202.strokeStyle=this.options.axisLineColor.toRGBString();
+_202.lineWidth=this.options.axisLineWidth;
+if(this.options.drawYAxis){
+if(this.layout.yticks){
+var _204=function(tick){
+if(typeof (tick)=="function"){
+return;
+}
+var x=this.area.x;
+var y=this.area.y+tick[0]*this.area.h;
+_202.beginPath();
+_202.moveTo(x,y);
+_202.lineTo(x-this.options.axisTickSize,y);
+_202.closePath();
+_202.stroke();
+var _205=MochiKit.DOM.DIV(_203,tick[1]);
+_205.style.top=(y-this.options.axisLabelFontSize)+"px";
+_205.style.left=(x-this.options.padding.left-this.options.axisTickSize)+"px";
+_205.style.textAlign="right";
+_205.style.width=(this.options.padding.left-this.options.axisTickSize*2)+"px";
+MochiKit.DOM.appendChildNodes(this.container,_205);
+this.ylabels.push(_205);
+};
+MochiKit.Iter.forEach(this.layout.yticks,MochiKit.Base.bind(_204,this));
+}
+_202.beginPath();
+_202.moveTo(this.area.x,this.area.y);
+_202.lineTo(this.area.x,this.area.y+this.area.h);
+_202.closePath();
+_202.stroke();
+}
+if(this.options.drawXAxis){
+if(this.layout.xticks){
+var _204=function(tick){
+if(typeof (dataset)=="function"){
+return;
+}
+var x=this.area.x+tick[0]*this.area.w;
+var y=this.area.y+this.area.h;
+_202.beginPath();
+_202.moveTo(x,y);
+_202.lineTo(x,y+this.options.axisTickSize);
+_202.closePath();
+_202.stroke();
+var _206=MochiKit.DOM.DIV(_203,tick[1]);
+_206.style.top=(y+this.options.axisTickSize)+"px";
+_206.style.left=(x-this.options.axisLabelWidth/2)+"px";
+_206.style.textAlign="center";
+_206.style.width=this.options.axisLabelWidth+"px";
+MochiKit.DOM.appendChildNodes(this.container,_206);
+this.xlabels.push(_206);
+};
+MochiKit.Iter.forEach(this.layout.xticks,MochiKit.Base.bind(_204,this));
+}
+_202.beginPath();
+_202.moveTo(this.area.x,this.area.y+this.area.h);
+_202.lineTo(this.area.x+this.area.w,this.area.y+this.area.h);
+_202.closePath();
+_202.stroke();
+}
+_202.restore();
+};
+PlotKit.CanvasRenderer.prototype._renderPieAxis=function(){
+if(!this.options.drawXAxis){
+return;
+}
+if(this.layout.xticks){
+var _207=new Array();
+for(var i=0;i<this.layout.slices.length;i++){
+_207[this.layout.slices[i].xval]=this.layout.slices[i];
+}
+var _208=this.area.x+this.area.w*0.5;
+var _209=this.area.y+this.area.h*0.5;
+var _210=Math.min(this.area.w*this.options.pieRadius,this.area.h*this.options.pieRadius);
+var _211=this.options.axisLabelWidth;
+for(var i=0;i<this.layout.xticks.length;i++){
+var _212=_207[this.layout.xticks[i][0]];
+if(MochiKit.Base.isUndefinedOrNull(_212)){
+continue;
+}
+var _213=(_212.startAngle+_212.endAngle)/2;
+var _214=_213;
+if(_214>Math.PI*2){
+_214=_214-Math.PI*2;
+}else{
+if(_214<0){
+_214=_214+Math.PI*2;
+}
+}
+var _215=_208+Math.sin(_214)*(_210+10);
+var _216=_209-Math.cos(_214)*(_210+10);
+var _217={"position":"absolute","zIndex":11,"width":_211+"px","fontSize":this.options.axisLabelFontSize+"px","overflow":"hidden","color":this.options.axisLabelColor.toHexString()};
+if(_214<=Math.PI*0.5){
+_217["textAlign"]="left";
+_217["verticalAlign"]="top";
+_217["left"]=_215+"px";
+_217["top"]=(_216-this.options.axisLabelFontSize)+"px";
+}else{
+if((_214>Math.PI*0.5)&&(_214<=Math.PI)){
+_217["textAlign"]="left";
+_217["verticalAlign"]="bottom";
+_217["left"]=_215+"px";
+_217["top"]=_216+"px";
+}else{
+if((_214>Math.PI)&&(_214<=Math.PI*1.5)){
+_217["textAlign"]="right";
+_217["verticalAlign"]="bottom";
+_217["left"]=(_215-_211)+"px";
+_217["top"]=_216+"px";
+}else{
+_217["textAlign"]="right";
+_217["verticalAlign"]="bottom";
+_217["left"]=(_215-_211)+"px";
+_217["top"]=(_216-this.options.axisLabelFontSize)+"px";
+}
+}
+}
+var _218=MochiKit.DOM.DIV({"style":_217},this.layout.xticks[i][1]);
+this.xlabels.push(_218);
+MochiKit.DOM.appendChildNodes(this.container,_218);
+}
+}
+};
+PlotKit.CanvasRenderer.prototype._renderBackground=function(){
+var _219=this.element.getContext("2d");
+_219.save();
+_219.fillStyle=this.options.backgroundColor.toRGBString();
+_219.fillRect(0,0,this.width,this.height);
+_219.restore();
+};
+PlotKit.CanvasRenderer.prototype.clear=function(){
+if(this.isIE){
+try{
+if(this.clearDelay){
+this.clearDelay.cancel();
+this.clearDelay=null;
+}
+var _220=this.element.getContext("2d");
+}
+catch(e){
+this.isFirstRender=false;
+this.clearDelay=MochiKit.Async.wait(this.IEDelay);
+this.clearDelay.addCallback(MochiKit.Base.bind(this.clear,this));
+return;
+}
+}
+var _220=this.element.getContext("2d");
+_220.clearRect(0,0,this.width,this.height);
+MochiKit.Iter.forEach(this.xlabels,MochiKit.DOM.removeElement);
+MochiKit.Iter.forEach(this.ylabels,MochiKit.DOM.removeElement);
+this.xlabels=new Array();
+this.ylabels=new Array();
+};
+PlotKit.CanvasRenderer.prototype._initialiseEvents=function(){
+var _221=MochiKit.Signal.connect;
+var bind=MochiKit.Base.bind;
+MochiKit.Signal.registerSignals(this,["onmouseover","onclick","onmouseout","onmousemove"]);
+_221(this.element,"onclick",bind(this.onclick,this));
+};
+PlotKit.CanvasRenderer.prototype._resolveObject=function(e){
+var x=(e.mouse().page.x-PlotKit.Base.findPosX(this.element)-this.area.x)/this.area.w;
+var y=(e.mouse().page.y-PlotKit.Base.findPosY(this.element)-this.area.y)/this.area.h;
+var _223=this.layout.hitTest(x,y);
+if(_223){
+return _223;
+}
+return null;
+};
+PlotKit.CanvasRenderer.prototype._createEventObject=function(_224,e){
+if(_224==null){
+return null;
+}
+e.chart=_224;
+return e;
+};
+PlotKit.CanvasRenderer.prototype.onclick=function(e){
+var _225=this._resolveObject(e);
+var _226=this._createEventObject(_225,e);
+if(_226!=null){
+MochiKit.Signal.signal(this,"onclick",_226);
+}
+};
+PlotKit.CanvasRenderer.prototype.onmouseover=function(e){
+var _227=this._resolveObject(e);
+var _228=this._createEventObject(_227,e);
+if(_228!=null){
+signal(this,"onmouseover",_228);
+}
+};
+PlotKit.CanvasRenderer.prototype.onmouseout=function(e){
+var _229=this._resolveObject(e);
+var _230=this._createEventObject(_229,e);
+if(_230==null){
+signal(this,"onmouseout",e);
+}else{
+signal(this,"onmouseout",_230);
+}
+};
+PlotKit.CanvasRenderer.prototype.onmousemove=function(e){
+var _231=this._resolveObject(e);
+var _232=this._createEventObject(_231,e);
+if((_231==null)&&(this.event_isinside==null)){
+return;
+}
+if((_231!=null)&&(this.event_isinside==null)){
+signal(this,"onmouseover",_232);
+}
+if((_231==null)&&(this.event_isinside!=null)){
+signal(this,"onmouseout",_232);
+}
+if((_231!=null)&&(this.event_isinside!=null)){
+signal(this,"onmousemove",_232);
+}
+this.event_isinside=_231;
+};
+PlotKit.CanvasRenderer.isSupported=function(_233){
+var _234=null;
+try{
+if(MochiKit.Base.isUndefinedOrNull(_233)){
+_234=MochiKit.DOM.CANVAS({});
+}else{
+_234=MochiKit.DOM.getElement(_233);
+}
+var _235=_234.getContext("2d");
+}
+catch(e){
+var ie=navigator.appVersion.match(/MSIE (\d\.\d)/);
+var _237=(navigator.userAgent.toLowerCase().indexOf("opera")!=-1);
+if((!ie)||(ie[1]<6)||(_237)){
+return false;
+}
+return true;
+}
+return true;
+};
+PlotKit.Canvas={};
+PlotKit.Canvas.CanvasRenderer=PlotKit.CanvasRenderer;
+PlotKit.Canvas.EXPORT=["CanvasRenderer"];
+PlotKit.Canvas.EXPORT_OK=["CanvasRenderer"];
+PlotKit.Canvas.__new__=function(){
+var m=MochiKit.Base;
+m.nameFunctions(this);
+this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
+};
+PlotKit.Canvas.__new__();
+MochiKit.Base._exportSymbols(this,PlotKit.Canvas);
+try{
+if(typeof (PlotKit.CanvasRenderer)=="undefined"){
+throw "";
+}
+}
+catch(e){
+throw "SweetCanvas depends on MochiKit.{Base,Color,DOM,Format} and PlotKit.{Layout, Canvas}";
+}
+if(typeof (PlotKit.SweetCanvasRenderer)=="undefined"){
+PlotKit.SweetCanvasRenderer={};
+}
+PlotKit.SweetCanvasRenderer=function(_238,_239,_240){
+if(arguments.length>0){
+this.__init__(_238,_239,_240);
+}
+};
+PlotKit.SweetCanvasRenderer.NAME="PlotKit.SweetCanvasRenderer";
+PlotKit.SweetCanvasRenderer.VERSION=PlotKit.VERSION;
+PlotKit.SweetCanvasRenderer.__repr__=function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+};
+PlotKit.SweetCanvasRenderer.toString=function(){
+return this.__repr__();
+};
+PlotKit.SweetCanvasRenderer.prototype=new PlotKit.CanvasRenderer();
+PlotKit.SweetCanvasRenderer.prototype.constructor=PlotKit.SweetCanvasRenderer;
+PlotKit.SweetCanvasRenderer.__super__=PlotKit.CanvasRenderer.prototype;
+PlotKit.SweetCanvasRenderer.prototype.__init__=function(el,_242,opts){
+var _244=PlotKit.Base.officeBlue();
+MochiKit.Base.update(_244,opts);
+PlotKit.SweetCanvasRenderer.__super__.__init__.call(this,el,_242,_244);
+};
+PlotKit.SweetCanvasRenderer.prototype._renderBarChart=function(){
+var bind=MochiKit.Base.bind;
+var _245=MochiKit.Color.Color.blackColor().colorWithAlpha(0.1).toRGBString();
+var _246=function(_247,x,y,w,h){
+_247.fillStyle=_245;
+_247.fillRect(x-2,y-2,w+4,h+2);
+_247.fillStyle=_245;
+_247.fillRect(x-1,y-1,w+2,h+1);
+};
+var _248=this.options.colorScheme.length;
+var _249=this.options.colorScheme;
+var _250=PlotKit.Base.keys(this.layout.datasets);
+var _251=_250.length;
+var _252=function(name){
+for(var i=0;i<_251;i++){
+if(name==_250[i]){
+return _249[i%_248];
+}
+}
+return _249[0];
+};
+var _254=function(_255,bar){
+var x=this.area.w*bar.x+this.area.x;
+var y=this.area.h*bar.y+this.area.y;
+var w=this.area.w*bar.w;
+var h=this.area.h*bar.h;
+if((w<1)||(h<1)){
+return;
+}
+_255.save();
+_255.shadowBlur=5;
+_255.shadowColor=MochiKit.Color.Color.fromHexString("#888888").toRGBString();
+if(this.isIE){
+_255.save();
+_255.fillStyle="#cccccc";
+_255.fillRect(x-2,y-2,w+4,h+2);
+_255.restore();
+}else{
+_246(_255,x,y,w,h);
+}
+if(this.options.shouldFill){
+_255.fillStyle=_252(bar.name).toRGBString();
+_255.fillRect(x,y,w,h);
+}
+_255.shadowBlur=0;
+_255.strokeStyle=MochiKit.Color.Color.whiteColor().toRGBString();
+_255.lineWidth=2;
+if(this.options.shouldStroke){
+_255.strokeRect(x,y,w,h);
+}
+_255.restore();
+};
+this._renderBarChartWrap(this.layout.bars,MochiKit.Base.bind(_254,this));
+};
+PlotKit.SweetCanvasRenderer.prototype._renderLineChart=function(){
+var _256=this.element.getContext("2d");
+var _257=this.options.colorScheme.length;
+var _258=this.options.colorScheme;
+var _259=PlotKit.Base.keys(this.layout.datasets);
+var _260=_259.length;
+var bind=MochiKit.Base.bind;
+for(var i=0;i<_260;i++){
+var _261=_259[i];
+var _262=_258[i%_257];
+var _263=this.options.strokeColorTransform;
+_256.save();
+var _264=function(ctx){
+ctx.beginPath();
+ctx.moveTo(this.area.x,this.area.y+this.area.h);
+var _265=function(ctx_,_266){
+if(_266.name==_261){
+ctx_.lineTo(this.area.w*_266.x+this.area.x,this.area.h*_266.y+this.area.y);
+}
+};
+MochiKit.Iter.forEach(this.layout.points,MochiKit.Base.partial(_265,ctx),this);
+ctx.lineTo(this.area.w+this.area.x,this.area.h+this.area.y);
+ctx.lineTo(this.area.x,this.area.y+this.area.h);
+ctx.closePath();
+};
+if(this.options.shouldFill){
+_256.save();
+if(this.isIE){
+_256.fillStyle="#cccccc";
+}else{
+_256.fillStyle=MochiKit.Color.Color.blackColor().colorWithAlpha(0.2).toRGBString();
+}
+_256.translate(-1,-2);
+bind(_264,this)(_256);
+if(this.options.shouldFill){
+_256.fill();
+}
+_256.restore();
+}
+_256.shadowBlur=5;
+_256.shadowColor=MochiKit.Color.Color.fromHexString("#888888").toRGBString();
+_256.fillStyle=_262.toRGBString();
+_256.lineWidth=2;
+_256.strokeStyle=MochiKit.Color.Color.whiteColor().toRGBString();
+if(this.options.shouldFill){
+bind(_264,this)(_256);
+_256.fill();
+}
+if(this.options.shouldStroke){
+bind(_264,this)(_256);
+_256.stroke();
+}
+_256.restore();
+}
+};
+PlotKit.SweetCanvasRenderer.prototype._renderPieChart=function(){
+var _267=this.element.getContext("2d");
+var _268=this.options.colorScheme.length;
+var _269=this.layout.slices;
+var _270=this.area.x+this.area.w*0.5;
+var _271=this.area.y+this.area.h*0.5;
+var _272=Math.min(this.area.w*this.options.pieRadius,this.area.h*this.options.pieRadius);
+if(this.isIE){
+_270=parseInt(_270);
+_271=parseInt(_271);
+_272=parseInt(_272);
+}
+if(!this.isIE){
+_267.save();
+var _273=MochiKit.Color.Color.blackColor().colorWithAlpha(0.2);
+_267.fillStyle=_273.toRGBString();
+_267.shadowBlur=5;
+_267.shadowColor=MochiKit.Color.Color.fromHexString("#888888").toRGBString();
+_267.translate(1,1);
+_267.beginPath();
+_267.moveTo(_270,_271);
+_267.arc(_270,_271,_272+2,0,Math.PI*2,false);
+_267.closePath();
+_267.fill();
+_267.restore();
+}
+_267.save();
+_267.strokeStyle=MochiKit.Color.Color.whiteColor().toRGBString();
+_267.lineWidth=2;
+for(var i=0;i<_269.length;i++){
+var _274=this.options.colorScheme[i%_268];
+_267.fillStyle=_274.toRGBString();
+var _275=function(){
+_267.beginPath();
+_267.moveTo(_270,_271);
+_267.arc(_270,_271,_272,_269[i].startAngle-Math.PI/2,_269[i].endAngle-Math.PI/2,false);
+_267.lineTo(_270,_271);
+_267.closePath();
+};
+if(Math.abs(_269[i].startAngle-_269[i].endAngle)>0.0001){
+if(this.options.shouldFill){
+_275();
+_267.fill();
+}
+if(this.options.shouldStroke){
+_275();
+_267.stroke();
+}
+}
+}
+_267.restore();
+};
+PlotKit.SweetCanvasRenderer.prototype._renderBackground=function(){
+var _276=this.element.getContext("2d");
+if(this.layout.style=="bar"||this.layout.style=="line"){
+_276.save();
+_276.fillStyle=this.options.backgroundColor.toRGBString();
+_276.fillRect(this.area.x,this.area.y,this.area.w,this.area.h);
+_276.strokeStyle=this.options.axisLineColor.toRGBString();
+_276.lineWidth=1;
+var _277=this.layout.yticks;
+var _278=false;
+if(this.layout.style=="bar"&&this.layout.options.barOrientation=="horizontal"){
+_277=this.layout.xticks;
+_278=true;
+}
+for(var i=0;i<_277.length;i++){
+var x1=0;
+var y1=0;
+var x2=0;
+var y2=0;
+if(_278){
+x1=_277[i][0]*this.area.w+this.area.x;
+y1=this.area.y;
+x2=x1;
+y2=y1+this.area.h;
+}else{
+x1=this.area.x;
+y1=_277[i][0]*this.area.h+this.area.y;
+x2=x1+this.area.w;
+y2=y1;
+}
+_276.beginPath();
+_276.moveTo(x1,y1);
+_276.lineTo(x2,y2);
+_276.closePath();
+_276.stroke();
+}
+_276.restore();
+}else{
+PlotKit.SweetCanvasRenderer.__super__._renderBackground.call(this);
+}
+};
+PlotKit.SweetCanvas={};
+PlotKit.SweetCanvas.SweetCanvasRenderer=PlotKit.SweetCanvasRenderer;
+PlotKit.SweetCanvas.EXPORT=["SweetCanvasRenderer"];
+PlotKit.SweetCanvas.EXPORT_OK=["SweetCanvasRenderer"];
+PlotKit.SweetCanvas.__new__=function(){
+var m=MochiKit.Base;
+m.nameFunctions(this);
+this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
+};
+PlotKit.SweetCanvas.__new__();
+MochiKit.Base._exportSymbols(this,PlotKit.SweetCanvas);
+try{
+if(typeof (PlotKit.CanvasRenderer)=="undefined"){
+throw "";
+}
+}
+catch(e){
+throw "PlotKit.EasyPlot depends on all of PlotKit's components";
+}
+if(typeof (PlotKit.EasyPlot)=="undefined"){
+PlotKit.EasyPlot={};
+}
+PlotKit.EasyPlot.NAME="PlotKit.EasyPlot";
+PlotKit.EasyPlot.VERSION=PlotKit.VERSION;
+PlotKit.EasyPlot.__repr__=function(){
+return "["+this.NAME+" "+this.VERSION+"]";
+};
+PlotKit.EasyPlot.toString=function(){
+return this.__repr__();
+};
+PlotKit.EasyPlot=function(_283,_284,_285,_286){
+this.layout=new PlotKit.Layout(_283,_284);
+this.divElem=_285;
+this.width=parseInt(_285.getAttribute("width"));
+this.height=parseInt(_285.getAttribute("height"));
+this.deferredCount=0;
+if(this.width<1){
+this.width=this.divElem.width?this.divElem.width:300;
+}
+if(this.height<1){
+this.height=this.divElem.height?this.divElem.height:300;
+}
+if(MochiKit.Base.isArrayLike(_286)){
+for(var i=0;i<_286.length;i++){
+if(typeof (_286[i])=="string"){
+this.deferredCount++;
+var d=MochiKit.Async.doSimpleXMLHttpRequest(_286[i]);
+d.addCallback(MochiKit.Base.bind(PlotKit.EasyPlot.onDataLoaded,this));
+}else{
+if(MochiKit.Base.isArrayLike(_286[i])){
+this.layout.addDataset("data-"+i,_286[i]);
+}
+}
+}
+}else{
+if(!MochiKit.Base.isUndefinedOrNull(_286)){
+throw "Passed datasources are not Array like";
+}
+}
+if(PlotKit.CanvasRenderer.isSupported()){
+this.element=MochiKit.DOM.CANVAS({"id":this.divElem.getAttribute("id")+"-canvas","width":this.width,"height":this.height},"");
+this.divElem.appendChild(this.element);
+this.renderer=new PlotKit.SweetCanvasRenderer(this.element,this.layout,_284);
+}else{
+if(PlotKit.SVGRenderer.isSupported()){
+this.element=PlotKit.SVGRenderer.SVG({"id":this.divElem.getAttribute("id")+"-svg","width":this.width,"height":this.height,"version":"1.1","baseProfile":"full"},"");
+this.divElem.appendChild(this.element);
+this.renderer=new PlotKit.SweetSVGRenderer(this.element,this.layout,_284);
+}
+}
+if((this.deferredCount==0)&&(PlotKit.Base.keys(this.layout.datasets).length>0)){
+this.layout.evaluate();
+this.renderer.clear();
+this.renderer.render();
+}
+};
+PlotKit.EasyPlot.onDataLoaded=function(_288){
+var _289=new Array();
+var _290=_288.responseText.split("\n");
+for(var i=0;i<_290.length;i++){
+var _291=MochiKit.Format.strip(_290[i]);
+if((_291.length>1)&&(_291.charAt(0)!="#")){
+_289.push(_291.split(","));
+}
+}
+this.layout.addDataset("data-ajax-"+this.deferredCount,_289);
+this.deferredCount--;
+if((this.deferredCount==0)&&(PlotKit.Base.keys(this.layout.datasets).length>0)){
+this.layout.evaluate();
+this.renderer.clear();
+this.renderer.render();
+}
+};
+PlotKit.EasyPlot.prototype.reload=function(){
+this.layout.evaluate();
+this.renderer.clear();
+this.renderer.render();
+};
+PlotKit.EasyPlotModule={};
+PlotKit.EasyPlotModule.EasyPlot=PlotKit.EasyPlot;
+PlotKit.EasyPlotModule.EXPORT=["EasyPlot"];
+PlotKit.EasyPlotModule.EXPORT_OK=[];
+PlotKit.EasyPlotModule.__new__=function(){
+var m=MochiKit.Base;
+m.nameFunctions(this);
+this.EXPORT_TAGS={":common":this.EXPORT,":all":m.concat(this.EXPORT,this.EXPORT_OK)};
+};
+PlotKit.EasyPlotModule.__new__();
+MochiKit.Base._exportSymbols(this,PlotKit.EasyPlotModule);
+
+

Added: jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/js/PlotKit/excanvas.js
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/js/PlotKit/excanvas.js	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,723 @@
+// Copyright 2006 Google Inc.
+//
+// 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.
+
+// TODO: Patterns
+// TODO: Radial gradient
+// TODO: Clipping paths
+// TODO: Coordsize (still need to support stretching)
+// TODO: Painting mode
+// TODO: Optimize
+// TODO: canvas width/height sets content size in moz, border size in ie
+
+// only add this code if we do not already have a canvas implementation
+if (!window.CanvasRenderingContext2D) {
+
+(function () {
+
+  // alias some functions to make (compiled) code shorter
+  var m = Math;
+  var mr = m.round;
+  var ms = m.sin;
+  var mc = m.cos;
+
+  var G_vmlCanvasManager_ = {
+    init: function (opt_doc) {
+      var doc = opt_doc || document;
+      if (/MSIE/.test(navigator.userAgent) && !window.opera) {
+        var self = this;
+        doc.attachEvent("onreadystatechange", function () {
+          self.init_(doc);
+        });
+      }
+    },
+
+    init_: function (doc, e) {
+      if (doc.readyState == "complete") {
+        // create xmlns
+        if (!doc.namespaces["g_vml_"]) {
+          doc.namespaces.add("g_vml_", "urn:schemas-microsoft-com:vml");
+        }
+
+        // setup default css
+        var ss = doc.createStyleSheet();
+        ss.cssText = "canvas{display:inline-block;overflow:hidden;" +
+            "text-align:left;}" +
+            "g_vml_\\:*{behavior:url(#default#VML)}";
+
+        // find all canvas elements
+        var els = doc.getElementsByTagName("canvas");
+        for (var i = 0; i < els.length; i++) {
+          if (!els[i].getContext) {
+            this.initElement(els[i]);
+          }
+        }
+      }
+    },
+
+    fixElement_: function (el) {
+      // in IE before version 5.5 we would need to add HTML: to the tag name
+      // but we do not care about IE before version 6
+      var outerHTML = el.outerHTML;
+      var newEl = document.createElement(outerHTML);
+      // if the tag is still open IE has created the children as siblings and
+      // it has also created a tag with the name "/FOO"
+      if (outerHTML.slice(-2) != "/>") {
+        var tagName = "/" + el.tagName;
+        var ns;
+        // remove content
+        while ((ns = el.nextSibling) && ns.tagName != tagName) {
+          ns.removeNode();
+        }
+        // remove the incorrect closing tag
+        if (ns) {
+          ns.removeNode();
+        }
+      }
+      el.parentNode.replaceChild(newEl, el);
+      return newEl;
+    },
+
+    /**
+     * Public initializes a canvas element so that it can be used as canvas
+     * element from now on. This is called automatically before the page is
+     * loaded but if you are creating elements using createElement you need to
+     * make sure this is called on the element.
+     * @param {HTMLElement} el The canvas element to initialize.
+     * @return {HTMLElement} the element that was created.
+     */
+    initElement: function (el) {
+      el = this.fixElement_(el);
+      el.getContext = function () {
+        if (this.context_) {
+          return this.context_;
+        }
+        return this.context_ = new CanvasRenderingContext2D_(this);
+      };
+
+      // do not use inline function because that will leak memory
+      // el.attachEvent('onpropertychange', onPropertyChange)
+      el.attachEvent('onresize', onResize);
+
+      var attrs = el.attributes;
+      if (attrs.width && attrs.width.specified) {
+        // TODO: use runtimeStyle and coordsize
+        // el.getContext().setWidth_(attrs.width.nodeValue);
+        el.style.width = attrs.width.nodeValue + "px";
+      }
+      if (attrs.height && attrs.height.specified) {
+        // TODO: use runtimeStyle and coordsize
+        // el.getContext().setHeight_(attrs.height.nodeValue);
+        el.style.height = attrs.height.nodeValue + "px";
+      }
+      //el.getContext().setCoordsize_()
+      return el;
+    }
+  };
+
+  function onPropertyChange(e) {
+    // we need to watch changes to width and height
+    switch (e.propertyName) {
+      case 'width':
+      case 'height':
+        // TODO: coordsize and size
+        break;
+    }
+  }
+
+  function onResize(e) {
+    var el = e.srcElement;
+    if (el.firstChild) {
+      el.firstChild.style.width =  el.clientWidth + 'px';
+      el.firstChild.style.height = el.clientHeight + 'px';
+    }
+  }
+
+  G_vmlCanvasManager_.init();
+
+  // precompute "00" to "FF"
+  var dec2hex = [];
+  for (var i = 0; i < 16; i++) {
+    for (var j = 0; j < 16; j++) {
+      dec2hex[i * 16 + j] = i.toString(16) + j.toString(16);
+    }
+  }
+
+  function createMatrixIdentity() {
+    return [
+      [1, 0, 0],
+      [0, 1, 0],
+      [0, 0, 1]
+    ];
+  }
+
+  function matrixMultiply(m1, m2) {
+    var result = createMatrixIdentity();
+
+    for (var x = 0; x < 3; x++) {
+      for (var y = 0; y < 3; y++) {
+        var sum = 0;
+
+        for (var z = 0; z < 3; z++) {
+          sum += m1[x][z] * m2[z][y];
+        }
+
+        result[x][y] = sum;
+      }
+    }
+    return result;
+  }
+
+  function copyState(o1, o2) {
+    o2.fillStyle     = o1.fillStyle;
+    o2.lineCap       = o1.lineCap;
+    o2.lineJoin      = o1.lineJoin;
+    o2.lineWidth     = o1.lineWidth;
+    o2.miterLimit    = o1.miterLimit;
+    o2.shadowBlur    = o1.shadowBlur;
+    o2.shadowColor   = o1.shadowColor;
+    o2.shadowOffsetX = o1.shadowOffsetX;
+    o2.shadowOffsetY = o1.shadowOffsetY;
+    o2.strokeStyle   = o1.strokeStyle;
+  }
+
+  function processStyle(styleString) {
+    var str, alpha = 1;
+
+    styleString = String(styleString);
+    if (styleString.substring(0, 3) == "rgb") {
+      var start = styleString.indexOf("(", 3);
+      var end = styleString.indexOf(")", start + 1);
+      var guts = styleString.substring(start + 1, end).split(",");
+
+      str = "#";
+      for (var i = 0; i < 3; i++) {
+        str += dec2hex[parseInt(guts[i])];
+      }
+
+      if ((guts.length == 4) && (styleString.substr(3, 1) == "a")) {
+        alpha = guts[3];
+      }
+    } else {
+      str = styleString;
+    }
+
+    return [str, alpha];
+  }
+
+  function processLineCap(lineCap) {
+    switch (lineCap) {
+      case "butt":
+        return "flat";
+      case "round":
+        return "round";
+      case "square":
+      default:
+        return "square";
+    }
+  }
+
+  /**
+   * This class implements CanvasRenderingContext2D interface as described by
+   * the WHATWG.
+   * @param {HTMLElement} surfaceElement The element that the 2D context should
+   * be associated with
+   */
+   function CanvasRenderingContext2D_(surfaceElement) {
+    this.m_ = createMatrixIdentity();
+
+    this.mStack_ = [];
+    this.aStack_ = [];
+    this.currentPath_ = [];
+
+    // Canvas context properties
+    this.strokeStyle = "#000";
+    this.fillStyle = "#ccc";
+
+    this.lineWidth = 1;
+    this.lineJoin = "miter";
+    this.lineCap = "butt";
+    this.miterLimit = 10;
+    this.globalAlpha = 1;
+
+    var el = document.createElement('div');
+    el.style.width =  surfaceElement.clientWidth + 'px';
+    el.style.height = surfaceElement.clientHeight + 'px';
+    el.style.overflow = 'hidden';
+    el.style.position = 'absolute';
+    surfaceElement.appendChild(el);
+
+    this.element_ = el;
+    this.arcScaleX_ = 1;
+    this.arcScaleY_ = 1;
+  };
+
+  var contextPrototype = CanvasRenderingContext2D_.prototype;
+  contextPrototype.clearRect = function() {
+    this.element_.innerHTML = "";
+    this.currentPath_ = [];
+  };
+
+  contextPrototype.beginPath = function() {
+    // TODO: Branch current matrix so that save/restore has no effect
+    //       as per safari docs.
+
+    this.currentPath_ = [];
+  };
+
+  contextPrototype.moveTo = function(aX, aY) {
+    this.currentPath_.push({type: "moveTo", x: aX, y: aY});
+  };
+
+  contextPrototype.lineTo = function(aX, aY) {
+    this.currentPath_.push({type: "lineTo", x: aX, y: aY});
+  };
+
+  contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,
+                                            aCP2x, aCP2y,
+                                            aX, aY) {
+    this.currentPath_.push({type: "bezierCurveTo",
+                           cp1x: aCP1x,
+                           cp1y: aCP1y,
+                           cp2x: aCP2x,
+                           cp2y: aCP2y,
+                           x: aX,
+                           y: aY});
+  };
+
+  contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
+    // VML's qb produces different output to Firefox's
+    // FF's behaviour seems to have changed in 1.5.0.1, check this
+    this.bezierCurveTo(aCPx, aCPy, aCPx, aCPy, aX, aY);
+  };
+
+  contextPrototype.arc = function(aX, aY, aRadius,
+                                  aStartAngle, aEndAngle, aClockwise) {
+    aRadius *= 10;
+    var arcType = aClockwise ? "at" : "wa";
+
+    var xStart = aX + (mc(aStartAngle) * aRadius) - 5;
+    var yStart = aY + (ms(aStartAngle) * aRadius) - 5;
+
+    var xEnd = aX + (mc(aEndAngle) * aRadius) - 5;
+    var yEnd = aY + (ms(aEndAngle) * aRadius) - 5;
+
+    this.currentPath_.push({type: arcType,
+                           x: aX,
+                           y: aY,
+                           radius: aRadius,
+                           xStart: xStart,
+                           yStart: yStart,
+                           xEnd: xEnd,
+                           yEnd: yEnd});
+
+  };
+
+  contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
+    this.moveTo(aX, aY);
+    this.lineTo(aX + aWidth, aY);
+    this.lineTo(aX + aWidth, aY + aHeight);
+    this.lineTo(aX, aY + aHeight);
+    this.closePath();
+  };
+
+  contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
+    // Will destroy any existing path (same as FF behaviour)
+    this.beginPath();
+    this.moveTo(aX, aY);
+    this.lineTo(aX + aWidth, aY);
+    this.lineTo(aX + aWidth, aY + aHeight);
+    this.lineTo(aX, aY + aHeight);
+    this.closePath();
+    this.stroke();
+  };
+
+  contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
+    // Will destroy any existing path (same as FF behaviour)
+    this.beginPath();
+    this.moveTo(aX, aY);
+    this.lineTo(aX + aWidth, aY);
+    this.lineTo(aX + aWidth, aY + aHeight);
+    this.lineTo(aX, aY + aHeight);
+    this.closePath();
+    this.fill();
+  };
+
+  contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
+    var gradient = new CanvasGradient_("gradient");
+    return gradient;
+  };
+
+  contextPrototype.createRadialGradient = function(aX0, aY0,
+                                                   aR0, aX1,
+                                                   aY1, aR1) {
+    var gradient = new CanvasGradient_("gradientradial");
+    gradient.radius1_ = aR0;
+    gradient.radius2_ = aR1;
+    gradient.focus_.x = aX0;
+    gradient.focus_.y = aY0;
+    return gradient;
+  };
+
+  contextPrototype.drawImage = function (image, var_args) {
+    var dx, dy, dw, dh, sx, sy, sw, sh;
+    var w = image.width;
+    var h = image.height;
+
+    if (arguments.length == 3) {
+      dx = arguments[1];
+      dy = arguments[2];
+      sx = sy = 0;
+      sw = dw = w;
+      sh = dh = h;
+    } else if (arguments.length == 5) {
+      dx = arguments[1];
+      dy = arguments[2];
+      dw = arguments[3];
+      dh = arguments[4];
+      sx = sy = 0;
+      sw = w;
+      sh = h;
+    } else if (arguments.length == 9) {
+      sx = arguments[1];
+      sy = arguments[2];
+      sw = arguments[3];
+      sh = arguments[4];
+      dx = arguments[5];
+      dy = arguments[6];
+      dw = arguments[7];
+      dh = arguments[8];
+    } else {
+      throw "Invalid number of arguments";
+    }
+
+    var d = this.getCoords_(dx, dy);
+
+    var w2 = (sw / 2);
+    var h2 = (sh / 2);
+
+    var vmlStr = [];
+
+    // For some reason that I've now forgotten, using divs didn't work
+    vmlStr.push(' <g_vml_:group',
+                ' coordsize="1000,1000"',
+                ' coordorigin="0, 0"' ,
+                ' style="width:100px;height:100px;position:absolute;');
+
+    // If filters are necessary (rotation exists), create them
+    // filters are bog-slow, so only create them if abbsolutely necessary
+    // The following check doesn't account for skews (which don't exist
+    // in the canvas spec (yet) anyway.
+
+    if (this.m_[0][0] != 1 || this.m_[0][1]) {
+      var filter = [];
+
+      // Note the 12/21 reversal
+      filter.push("M11='", this.m_[0][0], "',",
+                  "M12='", this.m_[1][0], "',",
+                  "M21='", this.m_[0][1], "',",
+                  "M22='", this.m_[1][1], "',",
+                  "Dx='", d.x, "',",
+                  "Dy='", d.y, "'");
+
+      // Bounding box calculation (need to minimize displayed area so that
+      // filters don't waste time on unused pixels.
+      var max = d;
+      var c2 = this.getCoords_(dx+dw, dy);
+      var c3 = this.getCoords_(dx, dy+dh);
+      var c4 = this.getCoords_(dx+dw, dy+dh);
+
+      max.x = Math.max(max.x, c2.x, c3.x, c4.x);
+      max.y = Math.max(max.y, c2.y, c3.y, c4.y);
+
+      vmlStr.push(" padding:0 ", mr(max.x), "px ", mr(max.y),
+                  "px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",
+                  filter.join(""), ", sizingmethod='clip');")
+    } else {
+      vmlStr.push(" top:", d.y, "px;left:", d.x, "px;")
+    }
+
+    vmlStr.push(' ">' ,
+                '<g_vml_:image src="', image.src, '"',
+                ' style="width:', dw, ';',
+                ' height:', dh, ';"',
+                ' cropleft="', sx / w, '"',
+                ' croptop="', sy / h, '"',
+                ' cropright="', (w - sx - sw) / w, '"',
+                ' cropbottom="', (h - sy - sh) / h, '"',
+                ' />',
+                '</g_vml_:group>');
+
+    this.element_.insertAdjacentHTML("BeforeEnd",
+                                    vmlStr.join(""));
+  };
+
+  contextPrototype.stroke = function(aFill) {
+    var lineStr = [];
+    var lineOpen = false;
+    var a = processStyle(aFill ? this.fillStyle : this.strokeStyle);
+    var color = a[0];
+    var opacity = a[1] * this.globalAlpha;
+
+    lineStr.push('<g_vml_:shape',
+                 ' fillcolor="', color, '"',
+                 ' filled="', Boolean(aFill), '"',
+                 ' style="position:absolute;width:10;height:10;"',
+                 ' coordorigin="0 0" coordsize="100 100"',
+                 ' stroked="', !aFill, '"',
+                 ' strokeweight="', this.lineWidth, '"',
+                 ' strokecolor="', color, '"',
+                 ' path="');
+
+    var newSeq = false;
+    var min = {x: null, y: null};
+    var max = {x: null, y: null};
+
+    for (var i = 0; i < this.currentPath_.length; i++) {
+      var p = this.currentPath_[i];
+
+      if (p.type == "moveTo") {
+        lineStr.push(" m ");
+        var c = this.getCoords_(p.x, p.y);
+        lineStr.push(mr(c.x), ",", mr(c.y));
+      } else if (p.type == "lineTo") {
+        lineStr.push(" l ");
+        var c = this.getCoords_(p.x, p.y);
+        lineStr.push(mr(c.x), ",", mr(c.y));
+      } else if (p.type == "close") {
+        lineStr.push(" x ");
+      } else if (p.type == "bezierCurveTo") {
+        lineStr.push(" c ");
+        var c = this.getCoords_(p.x, p.y);
+        var c1 = this.getCoords_(p.cp1x, p.cp1y);
+        var c2 = this.getCoords_(p.cp2x, p.cp2y);
+        lineStr.push(mr(c1.x), ",", mr(c1.y), ",",
+                     mr(c2.x), ",", mr(c2.y), ",",
+                     mr(c.x), ",", mr(c.y));
+      } else if (p.type == "at" || p.type == "wa") {
+        lineStr.push(" ", p.type, " ");
+        var c  = this.getCoords_(p.x, p.y);
+        var cStart = this.getCoords_(p.xStart, p.yStart);
+        var cEnd = this.getCoords_(p.xEnd, p.yEnd);
+
+        lineStr.push(mr(c.x - this.arcScaleX_ * p.radius), ",",
+                     mr(c.y - this.arcScaleY_ * p.radius), " ",
+                     mr(c.x + this.arcScaleX_ * p.radius), ",",
+                     mr(c.y + this.arcScaleY_ * p.radius), " ",
+                     mr(cStart.x), ",", mr(cStart.y), " ",
+                     mr(cEnd.x), ",", mr(cEnd.y));
+      }
+
+
+      // TODO: Following is broken for curves due to
+      //       move to proper paths.
+
+      // Figure out dimensions so we can do gradient fills
+      // properly
+      if(c) {
+        if (min.x == null || c.x < min.x) {
+          min.x = c.x;
+        }
+        if (max.x == null || c.x > max.x) {
+          max.x = c.x;
+        }
+        if (min.y == null || c.y < min.y) {
+          min.y = c.y;
+        }
+        if (max.y == null || c.y > max.y) {
+          max.y = c.y;
+        }
+      }
+    }
+    lineStr.push(' ">');
+
+    if (typeof this.fillStyle == "object") {
+      var focus = {x: "50%", y: "50%"};
+      var width = (max.x - min.x);
+      var height = (max.y - min.y);
+      var dimension = (width > height) ? width : height;
+
+      focus.x = mr((this.fillStyle.focus_.x / width) * 100 + 50) + "%";
+      focus.y = mr((this.fillStyle.focus_.y / height) * 100 + 50) + "%";
+
+      var colors = [];
+
+      // inside radius (%)
+      if (this.fillStyle.type_ == "gradientradial") {
+        var inside = (this.fillStyle.radius1_ / dimension * 100);
+
+        // percentage that outside radius exceeds inside radius
+        var expansion = (this.fillStyle.radius2_ / dimension * 100) - inside;
+      } else {
+        var inside = 0;
+        var expansion = 100;
+      }
+
+      var insidecolor = {offset: null, color: null};
+      var outsidecolor = {offset: null, color: null};
+
+      // We need to sort 'colors' by percentage, from 0 > 100 otherwise ie
+      // won't interpret it correctly
+      this.fillStyle.colors_.sort(function (cs1, cs2) {
+        return cs1.offset - cs2.offset;
+      });
+
+      for (var i = 0; i < this.fillStyle.colors_.length; i++) {
+        var fs = this.fillStyle.colors_[i];
+
+        colors.push( (fs.offset * expansion) + inside, "% ", fs.color, ",");
+
+        if (fs.offset > insidecolor.offset || insidecolor.offset == null) {
+          insidecolor.offset = fs.offset;
+          insidecolor.color = fs.color;
+        }
+
+        if (fs.offset < outsidecolor.offset || outsidecolor.offset == null) {
+          outsidecolor.offset = fs.offset;
+          outsidecolor.color = fs.color;
+        }
+      }
+      colors.pop();
+
+      lineStr.push('<g_vml_:fill',
+                   ' color="', outsidecolor.color, '"',
+                   ' color2="', insidecolor.color, '"',
+                   ' type="', this.fillStyle.type_, '"',
+                   ' focusposition="', focus.x, ', ', focus.y, '"',
+                   ' colors="', colors.join(""), '"',
+                   ' opacity="', opacity, '" />');
+    } else if (aFill) {
+      lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity, '" />');
+    } else {
+      lineStr.push(
+        '<g_vml_:stroke',
+        ' opacity="', opacity,'"',
+        ' joinstyle="', this.lineJoin, '"',
+        ' miterlimit="', this.miterLimit, '"',
+        ' endcap="', processLineCap(this.lineCap) ,'"',
+        ' weight="', this.lineWidth, 'px"',
+        ' color="', color,'" />'
+      );
+    }
+
+    lineStr.push("</g_vml_:shape>");
+
+    this.element_.insertAdjacentHTML("beforeEnd", lineStr.join(""));
+
+    this.currentPath_ = [];
+  };
+
+  contextPrototype.fill = function() {
+    this.stroke(true);
+  }
+
+  contextPrototype.closePath = function() {
+    this.currentPath_.push({type: "close"});
+  };
+
+  /**
+   * @private
+   */
+  contextPrototype.getCoords_ = function(aX, aY) {
+    return {
+      x: 10 * (aX * this.m_[0][0] + aY * this.m_[1][0] + this.m_[2][0]) - 5,
+      y: 10 * (aX * this.m_[0][1] + aY * this.m_[1][1] + this.m_[2][1]) - 5
+    }
+  };
+
+  contextPrototype.save = function() {
+    var o = {};
+    copyState(this, o);
+    this.aStack_.push(o);
+    this.mStack_.push(this.m_);
+    this.m_ = matrixMultiply(createMatrixIdentity(), this.m_);
+  };
+
+  contextPrototype.restore = function() {
+    copyState(this.aStack_.pop(), this);
+    this.m_ = this.mStack_.pop();
+  };
+
+  contextPrototype.translate = function(aX, aY) {
+    var m1 = [
+      [1,  0,  0],
+      [0,  1,  0],
+      [aX, aY, 1]
+    ];
+
+    this.m_ = matrixMultiply(m1, this.m_);
+  };
+
+  contextPrototype.rotate = function(aRot) {
+    var c = mc(aRot);
+    var s = ms(aRot);
+
+    var m1 = [
+      [c,  s, 0],
+      [-s, c, 0],
+      [0,  0, 1]
+    ];
+
+    this.m_ = matrixMultiply(m1, this.m_);
+  };
+
+  contextPrototype.scale = function(aX, aY) {
+    this.arcScaleX_ *= aX;
+    this.arcScaleY_ *= aY;
+    var m1 = [
+      [aX, 0,  0],
+      [0,  aY, 0],
+      [0,  0,  1]
+    ];
+
+    this.m_ = matrixMultiply(m1, this.m_);
+  };
+
+  /******** STUBS ********/
+  contextPrototype.clip = function() {
+    // TODO: Implement
+  };
+
+  contextPrototype.arcTo = function() {
+    // TODO: Implement
+  };
+
+  contextPrototype.createPattern = function() {
+    return new CanvasPattern_;
+  };
+
+  // Gradient / Pattern Stubs
+  function CanvasGradient_(aType) {
+    this.type_ = aType;
+    this.radius1_ = 0;
+    this.radius2_ = 0;
+    this.colors_ = [];
+    this.focus_ = {x: 0, y: 0};
+  }
+
+  CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
+    aColor = processStyle(aColor);
+    this.colors_.push({offset: 1-aOffset, color: aColor});
+  };
+
+  function CanvasPattern_() {}
+
+  // set up externs
+  G_vmlCanvasManager = G_vmlCanvasManager_;
+  CanvasRenderingContext2D = CanvasRenderingContext2D_;
+  CanvasGradient = CanvasGradient_;
+  CanvasPattern = CanvasPattern_;
+
+})();
+
+} // if

Added: jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/js/chart_img_behaviour.js
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/js/chart_img_behaviour.js	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,33 @@
+/*
+ * chart_behaviour.js
+ *
+ * Helper to make charts more designer friendly.
+ */
+
+Behaviour.register({
+    'img.chart': function(e) {
+        var dim = Element.getDimensions(e);
+        var url = e.src;
+
+        var path  = url;
+        var query = $H();
+
+        if (url.indexOf('?') >= 0) {
+            var path_and_query = url.split('?');
+            path = path_and_query[0];
+
+            var query_params = path_and_query[1].split('&');
+            for (var query_param in query_params) {
+                var key_and_value = query_param.split('=');
+                query[ key_and_value[0] ] = key_and_value[1];
+            }
+        }
+
+        query['width']  = dim.width + 'px';
+        query['height'] = dim.height + 'px';
+
+        url = path + '?' + $H(query).toQueryString();
+
+        e.src = url;
+    }
+});

Added: jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/js/mochikit.noexport.js
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/js/mochikit.noexport.js	Thu Nov 29 12:25:16 2007
@@ -0,0 +1 @@
+MochiKit = {__export__: false};

Added: jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/js/simple_bars.js
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/share/plugins/Jifty/Plugin/Chart/web/static/js/simple_bars.js	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,69 @@
+/*
+ * $Id$
+ * simple_bars.js
+ * by Andrew Sterling Hanenkamp
+ *
+ * Copyright 2007 Boomer Consulting, Inc.
+ *
+ * A custom and extremely simple way of rendering a horizontal bar chart. This
+ * code was custom built for use with Jifty, but could be reused elsewhere.
+ *
+ * This is free software. You may modify or redistribute this code under the
+ * terms of the GNU General Public License or the Artistic license.
+ */
+
+function SimpleBars(table) {
+    var dataset = $H();
+
+    for (var i = 0; i < table.tBodies[0].rows.length; i++) {
+        var table_row = table.tBodies[0].rows[i];
+        dataset[table_row.cells[0].innerHTML] = table_row.cells[1].innerHTML;
+    }
+
+    var max_value = 0;
+    dataset.values().each(function(v,i){max_value=Math.max(max_value, v);});
+
+    var simple_bars = document.createElement('div');
+    simple_bars.id = table.id;
+    simple_bars.className = table.className;
+
+    dataset.keys().each(function(k, i) {
+        var v = dataset[k];
+
+        var row = document.createElement('div');
+        row.className = 'row';
+
+        var row_label = document.createElement('span');
+        row_label.className = 'label';
+        row_label.innerHTML = k;
+        row.appendChild(row_label);
+
+        var row_bar_area = document.createElement('span');
+        row_bar_area.className = 'barArea';
+        row.appendChild(row_bar_area);
+
+        var row_bar = document.createElement('span');
+        row_bar.className = 'bar';
+        row_bar.style.width = Math.round( 100 * v / max_value ) + '%';
+        row_bar.innerHTML = '&nbsp;';
+        row_bar_area.appendChild(row_bar);
+
+        var row_value = document.createElement('span');
+        row_value.className = 'value';
+        row_value.innerHTML = v;
+        row.appendChild(row_value);
+
+        simple_bars.appendChild(row);
+    });
+
+    table.parentNode.insertBefore(simple_bars, table);
+    table.parentNode.removeChild(table);
+
+    return this;
+}
+
+Behaviour.register({
+    'table.simple_bars': function(table) {
+        new SimpleBars(table);
+    }
+});

Modified: jifty/branches/js-refactor/share/plugins/Jifty/Plugin/GoogleMap/web/static/js/google_map.js
==============================================================================
--- jifty/branches/js-refactor/share/plugins/Jifty/Plugin/GoogleMap/web/static/js/google_map.js	(original)
+++ jifty/branches/js-refactor/share/plugins/Jifty/Plugin/GoogleMap/web/static/js/google_map.js	Thu Nov 29 12:25:16 2007
@@ -111,7 +111,6 @@
       };
       element.appendChild(field);
       var submit= document.createElement('input');
-      submit.id = 'blahblah';
       submit.value = 'Query';
       submit.setAttribute('type', 'button');
       submit.onclick = function() { _handle_search(map, field.value) };

Added: jifty/branches/js-refactor/share/plugins/Jifty/Plugin/I18N/web/static/js/loc.js
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/share/plugins/Jifty/Plugin/I18N/web/static/js/loc.js	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,65 @@
+Localization = Object.extend(new Object(), {
+    init: function(params) {
+        this.lang = params.lang || 'en';
+        if (params["dict_path"]) {
+            this.dict_path = params["dict_path"]
+            this.dict = this.load_dict(this.lang)
+        }
+    },
+    switch_dict: function(lang) {
+        this.dict = this.load_dict(lang);
+    },
+    load_dict: function(lang) {
+        var d;
+        new Ajax.Request(
+            this.dict_path + "/" + lang + ".json",
+            {
+                method: 'get',
+                asynchronous: false,
+                onComplete: function(t, obj) {
+                    eval("d = " + t.responseText || "{}");
+                }
+            }
+        );
+        return d;
+    },
+    loc: function(str) {
+        var dict = this.dict;
+        if (dict[str]) {
+            return dict[str];
+        }
+        return str;
+    },
+
+    get_local_time_for_date: function(time) {
+        system_date = new Date(time);
+        user_date = new Date();
+        delta_minutes = Math.floor((user_date - system_date) / (60 * 1000));
+        if (Math.abs(delta_minutes) <= (7*24*60)) {
+            distance = this.distance_of_time_in_words(delta_minutes);
+            if (delta_minutes < 0) {
+                return distance + _(' from now');
+            } else {
+                return distance + _(' ago');
+            }
+        } else {
+            return system_date.toLocaleDateString();
+        }
+    },
+
+    distance_of_time_in_words: function(minutes) {
+        if (minutes.isNaN) return "";
+        minutes = Math.abs(minutes);
+        if (minutes < 1) return _('less than a minute');
+        if (minutes < 50) return _(minutes + ' minute' + (minutes == 1 ? '' : 's'));
+        if (minutes < 90) return _('about one hour');
+        if (minutes < 1080) return (Math.round(minutes / 60) + ' hours');
+        if (minutes < 1440) return _('one day');
+        if (minutes < 2880) return _('about one day');
+        else return (Math.round(minutes / 1440) + _(' days'))
+    }
+
+});
+
+Localization.dict = {};
+_ = Localization.loc.bind(Localization);

Added: jifty/branches/js-refactor/share/plugins/Jifty/Plugin/JQuery/web/static/js/jquery.js
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/share/plugins/Jifty/Plugin/JQuery/web/static/js/jquery.js	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,2344 @@
+// prevent execution of jQuery if included more than once
+if(typeof window.jQuery == "undefined") {
+/*
+ * jQuery 1.1.3.1 - New Wave Javascript
+ *
+ * Copyright (c) 2007 John Resig (jquery.com)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * $Date: 2007-07-05 00:43:24 -0400 (Thu, 05 Jul 2007) $
+ * $Rev: 2243 $
+ */
+
+// Global undefined variable
+window.undefined = window.undefined;
+var jQuery = function(a,c) {
+	// If the context is global, return a new object
+	if ( window == this || !this.init )
+		return new jQuery(a,c);
+	
+	return this.init(a,c);
+};
+
+// Map over the $ in case of overwrite
+if ( typeof $ != "undefined" )
+	jQuery._$ = $;
+	
+// Map the jQuery namespace to the '$' one
+var $ = jQuery;
+
+jQuery.fn = jQuery.prototype = {
+	init: function(a,c) {
+		// Make sure that a selection was provided
+		a = a || document;
+
+		// HANDLE: $(function)
+		// Shortcut for document ready
+		if ( jQuery.isFunction(a) )
+			return new jQuery(document)[ jQuery.fn.ready ? "ready" : "load" ]( a );
+
+		// Handle HTML strings
+		if ( typeof a  == "string" ) {
+			// HANDLE: $(html) -> $(array)
+			var m = /^[^<]*(<(.|\s)+>)[^>]*$/.exec(a);
+			if ( m )
+				a = jQuery.clean( [ m[1] ] );
+
+			// HANDLE: $(expr)
+			else
+				return new jQuery( c ).find( a );
+		}
+
+		return this.setArray(
+			// HANDLE: $(array)
+			a.constructor == Array && a ||
+
+			// HANDLE: $(arraylike)
+			// Watch for when an array-like object is passed as the selector
+			(a.jquery || a.length && a != window && !a.nodeType && a[0] != undefined && a[0].nodeType) && jQuery.makeArray( a ) ||
+
+			// HANDLE: $(*)
+			[ a ] );
+	},
+	jquery: "1.1.3.1",
+
+	size: function() {
+		return this.length;
+	},
+	
+	length: 0,
+
+	get: function( num ) {
+		return num == undefined ?
+
+			// Return a 'clean' array
+			jQuery.makeArray( this ) :
+
+			// Return just the object
+			this[num];
+	},
+	pushStack: function( a ) {
+		var ret = jQuery(a);
+		ret.prevObject = this;
+		return ret;
+	},
+	setArray: function( a ) {
+		this.length = 0;
+		[].push.apply( this, a );
+		return this;
+	},
+	each: function( fn, args ) {
+		return jQuery.each( this, fn, args );
+	},
+	index: function( obj ) {
+		var pos = -1;
+		this.each(function(i){
+			if ( this == obj ) pos = i;
+		});
+		return pos;
+	},
+
+	attr: function( key, value, type ) {
+		var obj = key;
+		
+		// Look for the case where we're accessing a style value
+		if ( key.constructor == String )
+			if ( value == undefined )
+				return this.length && jQuery[ type || "attr" ]( this[0], key ) || undefined;
+			else {
+				obj = {};
+				obj[ key ] = value;
+			}
+		
+		// Check to see if we're setting style values
+		return this.each(function(index){
+			// Set all the styles
+			for ( var prop in obj )
+				jQuery.attr(
+					type ? this.style : this,
+					prop, jQuery.prop(this, obj[prop], type, index, prop)
+				);
+		});
+	},
+
+	css: function( key, value ) {
+		return this.attr( key, value, "curCSS" );
+	},
+
+	text: function(e) {
+		if ( typeof e == "string" )
+			return this.empty().append( document.createTextNode( e ) );
+
+		var t = "";
+		jQuery.each( e || this, function(){
+			jQuery.each( this.childNodes, function(){
+				if ( this.nodeType != 8 )
+					t += this.nodeType != 1 ?
+						this.nodeValue : jQuery.fn.text([ this ]);
+			});
+		});
+		return t;
+	},
+
+	wrap: function() {
+		// The elements to wrap the target around
+		var a, args = arguments;
+
+		// Wrap each of the matched elements individually
+		return this.each(function(){
+			if ( !a )
+				a = jQuery.clean(args, this.ownerDocument);
+
+			// Clone the structure that we're using to wrap
+			var b = a[0].cloneNode(true);
+
+			// Insert it before the element to be wrapped
+			this.parentNode.insertBefore( b, this );
+
+			// Find the deepest point in the wrap structure
+			while ( b.firstChild )
+				b = b.firstChild;
+
+			// Move the matched element to within the wrap structure
+			b.appendChild( this );
+		});
+	},
+	append: function() {
+		return this.domManip(arguments, true, 1, function(a){
+			this.appendChild( a );
+		});
+	},
+	prepend: function() {
+		return this.domManip(arguments, true, -1, function(a){
+			this.insertBefore( a, this.firstChild );
+		});
+	},
+	before: function() {
+		return this.domManip(arguments, false, 1, function(a){
+			this.parentNode.insertBefore( a, this );
+		});
+	},
+	after: function() {
+		return this.domManip(arguments, false, -1, function(a){
+			this.parentNode.insertBefore( a, this.nextSibling );
+		});
+	},
+	end: function() {
+		return this.prevObject || jQuery([]);
+	},
+	find: function(t) {
+		var data = jQuery.map(this, function(a){ return jQuery.find(t,a); });
+		return this.pushStack( /[^+>] [^+>]/.test( t ) || t.indexOf("..") > -1 ?
+			jQuery.unique( data ) : data );
+	},
+	clone: function(deep) {
+		// Need to remove events on the element and its descendants
+		var $this = this.add(this.find("*"));
+		$this.each(function() {
+			this._$events = {};
+			for (var type in this.$events)
+				this._$events[type] = jQuery.extend({},this.$events[type]);
+		}).unbind();
+
+		// Do the clone
+		var r = this.pushStack( jQuery.map( this, function(a){
+			return a.cloneNode( deep != undefined ? deep : true );
+		}) );
+
+		// Add the events back to the original and its descendants
+		$this.each(function() {
+			var events = this._$events;
+			for (var type in events)
+				for (var handler in events[type])
+					jQuery.event.add(this, type, events[type][handler], events[type][handler].data);
+			this._$events = null;
+		});
+
+		// Return the cloned set
+		return r;
+	},
+
+	filter: function(t) {
+		return this.pushStack(
+			jQuery.isFunction( t ) &&
+			jQuery.grep(this, function(el, index){
+				return t.apply(el, [index])
+			}) ||
+
+			jQuery.multiFilter(t,this) );
+	},
+
+	not: function(t) {
+		return this.pushStack(
+			t.constructor == String &&
+			jQuery.multiFilter(t, this, true) ||
+
+			jQuery.grep(this, function(a) {
+				return ( t.constructor == Array || t.jquery )
+					? jQuery.inArray( a, t ) < 0
+					: a != t;
+			})
+		);
+	},
+
+	add: function(t) {
+		return this.pushStack( jQuery.merge(
+			this.get(),
+			t.constructor == String ?
+				jQuery(t).get() :
+				t.length != undefined && (!t.nodeName || t.nodeName == "FORM") ?
+					t : [t] )
+		);
+	},
+	is: function(expr) {
+		return expr ? jQuery.multiFilter(expr,this).length > 0 : false;
+	},
+
+	val: function( val ) {
+		return val == undefined ?
+			( this.length ? this[0].value : null ) :
+			this.attr( "value", val );
+	},
+
+	html: function( val ) {
+		return val == undefined ?
+			( this.length ? this[0].innerHTML : null ) :
+			this.empty().append( val );
+	},
+	domManip: function(args, table, dir, fn){
+		var clone = this.length > 1, a; 
+
+		return this.each(function(){
+			if ( !a ) {
+				a = jQuery.clean(args, this.ownerDocument);
+				if ( dir < 0 )
+					a.reverse();
+			}
+
+			var obj = this;
+
+			if ( table && jQuery.nodeName(this, "table") && jQuery.nodeName(a[0], "tr") )
+				obj = this.getElementsByTagName("tbody")[0] || this.appendChild(document.createElement("tbody"));
+
+			jQuery.each( a, function(){
+				fn.apply( obj, [ clone ? this.cloneNode(true) : this ] );
+			});
+
+		});
+	}
+};
+
+jQuery.extend = jQuery.fn.extend = function() {
+	// copy reference to target object
+	var target = arguments[0], a = 1;
+
+	// extend jQuery itself if only one argument is passed
+	if ( arguments.length == 1 ) {
+		target = this;
+		a = 0;
+	}
+	var prop;
+	while ( (prop = arguments[a++]) != null )
+		// Extend the base object
+		for ( var i in prop ) target[i] = prop[i];
+
+	// Return the modified object
+	return target;
+};
+
+jQuery.extend({
+	noConflict: function() {
+		if ( jQuery._$ )
+			$ = jQuery._$;
+		return jQuery;
+	},
+
+	// This may seem like some crazy code, but trust me when I say that this
+	// is the only cross-browser way to do this. --John
+	isFunction: function( fn ) {
+		return !!fn && typeof fn != "string" && !fn.nodeName && 
+			fn.constructor != Array && /function/i.test( fn + "" );
+	},
+	
+	// check if an element is in a XML document
+	isXMLDoc: function(elem) {
+		return elem.tagName && elem.ownerDocument && !elem.ownerDocument.body;
+	},
+
+	nodeName: function( elem, name ) {
+		return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase();
+	},
+	// args is for internal usage only
+	each: function( obj, fn, args ) {
+		if ( obj.length == undefined )
+			for ( var i in obj )
+				fn.apply( obj[i], args || [i, obj[i]] );
+		else
+			for ( var i = 0, ol = obj.length; i < ol; i++ )
+				if ( fn.apply( obj[i], args || [i, obj[i]] ) === false ) break;
+		return obj;
+	},
+	
+	prop: function(elem, value, type, index, prop){
+			// Handle executable functions
+			if ( jQuery.isFunction( value ) )
+				value = value.call( elem, [index] );
+				
+			// exclude the following css properties to add px
+			var exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i;
+
+			// Handle passing in a number to a CSS property
+			return value && value.constructor == Number && type == "curCSS" && !exclude.test(prop) ?
+				value + "px" :
+				value;
+	},
+
+	className: {
+		// internal only, use addClass("class")
+		add: function( elem, c ){
+			jQuery.each( c.split(/\s+/), function(i, cur){
+				if ( !jQuery.className.has( elem.className, cur ) )
+					elem.className += ( elem.className ? " " : "" ) + cur;
+			});
+		},
+
+		// internal only, use removeClass("class")
+		remove: function( elem, c ){
+			elem.className = c != undefined ?
+				jQuery.grep( elem.className.split(/\s+/), function(cur){
+					return !jQuery.className.has( c, cur );	
+				}).join(" ") : "";
+		},
+
+		// internal only, use is(".class")
+		has: function( t, c ) {
+			return jQuery.inArray( c, (t.className || t).toString().split(/\s+/) ) > -1;
+		}
+	},
+	swap: function(e,o,f) {
+		for ( var i in o ) {
+			e.style["old"+i] = e.style[i];
+			e.style[i] = o[i];
+		}
+		f.apply( e, [] );
+		for ( var i in o )
+			e.style[i] = e.style["old"+i];
+	},
+
+	css: function(e,p) {
+		if ( p == "height" || p == "width" ) {
+			var old = {}, oHeight, oWidth, d = ["Top","Bottom","Right","Left"];
+
+			jQuery.each( d, function(){
+				old["padding" + this] = 0;
+				old["border" + this + "Width"] = 0;
+			});
+
+			jQuery.swap( e, old, function() {
+				if ( jQuery(e).is(':visible') ) {
+					oHeight = e.offsetHeight;
+					oWidth = e.offsetWidth;
+				} else {
+					e = jQuery(e.cloneNode(true))
+						.find(":radio").removeAttr("checked").end()
+						.css({
+							visibility: "hidden", position: "absolute", display: "block", right: "0", left: "0"
+						}).appendTo(e.parentNode)[0];
+
+					var parPos = jQuery.css(e.parentNode,"position") || "static";
+					if ( parPos == "static" )
+						e.parentNode.style.position = "relative";
+
+					oHeight = e.clientHeight;
+					oWidth = e.clientWidth;
+
+					if ( parPos == "static" )
+						e.parentNode.style.position = "static";
+
+					e.parentNode.removeChild(e);
+				}
+			});
+
+			return p == "height" ? oHeight : oWidth;
+		}
+
+		return jQuery.curCSS( e, p );
+	},
+
+	curCSS: function(elem, prop, force) {
+		var ret;
+
+		if (prop == "opacity" && jQuery.browser.msie) {
+			ret = jQuery.attr(elem.style, "opacity");
+			return ret == "" ? "1" : ret;
+		}
+		
+		if (prop.match(/float/i))
+			prop = jQuery.styleFloat;
+
+		if (!force && elem.style[prop])
+			ret = elem.style[prop];
+
+		else if (document.defaultView && document.defaultView.getComputedStyle) {
+
+			if (prop.match(/float/i))
+				prop = "float";
+
+			prop = prop.replace(/([A-Z])/g,"-$1").toLowerCase();
+			var cur = document.defaultView.getComputedStyle(elem, null);
+
+			if ( cur )
+				ret = cur.getPropertyValue(prop);
+			else if ( prop == "display" )
+				ret = "none";
+			else
+				jQuery.swap(elem, { display: "block" }, function() {
+				    var c = document.defaultView.getComputedStyle(this, "");
+				    ret = c && c.getPropertyValue(prop) || "";
+				});
+
+		} else if (elem.currentStyle) {
+			var newProp = prop.replace(/\-(\w)/g,function(m,c){return c.toUpperCase();});
+			ret = elem.currentStyle[prop] || elem.currentStyle[newProp];
+		}
+
+		return ret;
+	},
+	
+	clean: function(a, doc) {
+		var r = [];
+		doc = doc || document;
+
+		jQuery.each( a, function(i,arg){
+			if ( !arg ) return;
+
+			if ( arg.constructor == Number )
+				arg = arg.toString();
+			
+			// Convert html string into DOM nodes
+			if ( typeof arg == "string" ) {
+				// Trim whitespace, otherwise indexOf won't work as expected
+				var s = jQuery.trim(arg).toLowerCase(), div = doc.createElement("div"), tb = [];
+
+				var wrap =
+					// option or optgroup
+					!s.indexOf("<opt") &&
+					[1, "<select>", "</select>"] ||
+					
+					!s.indexOf("<leg") &&
+					[1, "<fieldset>", "</fieldset>"] ||
+					
+					(!s.indexOf("<thead") || !s.indexOf("<tbody") || !s.indexOf("<tfoot") || !s.indexOf("<colg")) &&
+					[1, "<table>", "</table>"] ||
+					
+					!s.indexOf("<tr") &&
+					[2, "<table><tbody>", "</tbody></table>"] ||
+					
+				 	// <thead> matched above
+					(!s.indexOf("<td") || !s.indexOf("<th")) &&
+					[3, "<table><tbody><tr>", "</tr></tbody></table>"] ||
+					
+					!s.indexOf("<col") &&
+					[2, "<table><colgroup>", "</colgroup></table>"] ||
+					
+					[0,"",""];
+
+				// Go to html and back, then peel off extra wrappers
+				div.innerHTML = wrap[1] + arg + wrap[2];
+				
+				// Move to the right depth
+				while ( wrap[0]-- )
+					div = div.firstChild;
+				
+				// Remove IE's autoinserted <tbody> from table fragments
+				if ( jQuery.browser.msie ) {
+					
+					// String was a <table>, *may* have spurious <tbody>
+					if ( !s.indexOf("<table") && s.indexOf("<tbody") < 0 ) 
+						tb = div.firstChild && div.firstChild.childNodes;
+						
+					// String was a bare <thead> or <tfoot>
+					else if ( wrap[1] == "<table>" && s.indexOf("<tbody") < 0 )
+						tb = div.childNodes;
+
+					for ( var n = tb.length-1; n >= 0 ; --n )
+						if ( jQuery.nodeName(tb[n], "tbody") && !tb[n].childNodes.length )
+							tb[n].parentNode.removeChild(tb[n]);
+					
+				}
+				
+				arg = jQuery.makeArray( div.childNodes );
+			}
+
+			if ( 0 === arg.length && (!jQuery.nodeName(arg, "form") && !jQuery.nodeName(arg, "select")) )
+				return;
+
+			if ( arg[0] == undefined || jQuery.nodeName(arg, "form") || arg.options )
+				r.push( arg );
+			else
+				r = jQuery.merge( r, arg );
+
+		});
+
+		return r;
+	},
+	
+	attr: function(elem, name, value){
+		var fix = jQuery.isXMLDoc(elem) ? {} : jQuery.props;
+		
+		// Certain attributes only work when accessed via the old DOM 0 way
+		if ( fix[name] ) {
+			if ( value != undefined ) elem[fix[name]] = value;
+			return elem[fix[name]];
+
+		} else if ( value == undefined && jQuery.browser.msie && jQuery.nodeName(elem, "form") && (name == "action" || name == "method") )
+			return elem.getAttributeNode(name).nodeValue;
+
+		// IE elem.getAttribute passes even for style
+		else if ( elem.tagName ) {
+			
+
+			if ( value != undefined ) elem.setAttribute( name, value );
+			if ( jQuery.browser.msie && /href|src/.test(name) && !jQuery.isXMLDoc(elem) ) 
+				return elem.getAttribute( name, 2 );
+			return elem.getAttribute( name );
+
+		// elem is actually elem.style ... set the style
+		} else {
+			// IE actually uses filters for opacity
+			if ( name == "opacity" && jQuery.browser.msie ) {
+				if ( value != undefined ) {
+					// IE has trouble with opacity if it does not have layout
+					// Force it by setting the zoom level
+					elem.zoom = 1; 
+	
+					// Set the alpha filter to set the opacity
+					elem.filter = (elem.filter || "").replace(/alpha\([^)]*\)/,"") +
+						(parseFloat(value).toString() == "NaN" ? "" : "alpha(opacity=" + value * 100 + ")");
+				}
+	
+				return elem.filter ? 
+					(parseFloat( elem.filter.match(/opacity=([^)]*)/)[1] ) / 100).toString() : "";
+			}
+			name = name.replace(/-([a-z])/ig,function(z,b){return b.toUpperCase();});
+			if ( value != undefined ) elem[name] = value;
+			return elem[name];
+		}
+	},
+	trim: function(t){
+		return t.replace(/^\s+|\s+$/g, "");
+	},
+
+	makeArray: function( a ) {
+		var r = [];
+
+		// Need to use typeof to fight Safari childNodes crashes
+		if ( typeof a != "array" )
+			for ( var i = 0, al = a.length; i < al; i++ )
+				r.push( a[i] );
+		else
+			r = a.slice( 0 );
+
+		return r;
+	},
+
+	inArray: function( b, a ) {
+		for ( var i = 0, al = a.length; i < al; i++ )
+			if ( a[i] == b )
+				return i;
+		return -1;
+	},
+	merge: function(first, second) {
+		// We have to loop this way because IE & Opera overwrite the length
+		// expando of getElementsByTagName
+		for ( var i = 0; second[i]; i++ )
+			first.push(second[i]);
+		return first;
+	},
+	unique: function(first) {
+		var r = [], num = jQuery.mergeNum++;
+
+		for ( var i = 0, fl = first.length; i < fl; i++ )
+			if ( num != first[i].mergeNum ) {
+				first[i].mergeNum = num;
+				r.push(first[i]);
+			}
+
+		return r;
+	},
+
+	mergeNum: 0,
+	grep: function(elems, fn, inv) {
+		// If a string is passed in for the function, make a function
+		// for it (a handy shortcut)
+		if ( typeof fn == "string" )
+			fn = new Function("a","i","return " + fn);
+
+		var result = [];
+
+		// Go through the array, only saving the items
+		// that pass the validator function
+		for ( var i = 0, el = elems.length; i < el; i++ )
+			if ( !inv && fn(elems[i],i) || inv && !fn(elems[i],i) )
+				result.push( elems[i] );
+
+		return result;
+	},
+	map: function(elems, fn) {
+		// If a string is passed in for the function, make a function
+		// for it (a handy shortcut)
+		if ( typeof fn == "string" )
+			fn = new Function("a","return " + fn);
+
+		var result = [];
+
+		// Go through the array, translating each of the items to their
+		// new value (or values).
+		for ( var i = 0, el = elems.length; i < el; i++ ) {
+			var val = fn(elems[i],i);
+
+			if ( val !== null && val != undefined ) {
+				if ( val.constructor != Array ) val = [val];
+				result = result.concat( val );
+			}
+		}
+
+		return result;
+	}
+});
+ 
+/*
+ * Whether the W3C compliant box model is being used.
+ *
+ * @property
+ * @name $.boxModel
+ * @type Boolean
+ * @cat JavaScript
+ */
+new function() {
+	var b = navigator.userAgent.toLowerCase();
+
+	// Figure out what browser is being used
+	jQuery.browser = {
+		version: (b.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/) || [])[1],
+		safari: /webkit/.test(b),
+		opera: /opera/.test(b),
+		msie: /msie/.test(b) && !/opera/.test(b),
+		mozilla: /mozilla/.test(b) && !/(compatible|webkit)/.test(b)
+	};
+
+	// Check to see if the W3C box model is being used
+	jQuery.boxModel = !jQuery.browser.msie || document.compatMode == "CSS1Compat";
+
+	jQuery.styleFloat = jQuery.browser.msie ? "styleFloat" : "cssFloat",
+
+	jQuery.props = {
+		"for": "htmlFor",
+		"class": "className",
+		"float": jQuery.styleFloat,
+		cssFloat: jQuery.styleFloat,
+		styleFloat: jQuery.styleFloat,
+		innerHTML: "innerHTML",
+		className: "className",
+		value: "value",
+		disabled: "disabled",
+		checked: "checked",
+		readonly: "readOnly",
+		selected: "selected",
+		maxlength: "maxLength"
+	};
+};
+
+jQuery.each({
+	parent: "a.parentNode",
+	parents: "jQuery.parents(a)",
+	next: "jQuery.nth(a,2,'nextSibling')",
+	prev: "jQuery.nth(a,2,'previousSibling')",
+	siblings: "jQuery.sibling(a.parentNode.firstChild,a)",
+	children: "jQuery.sibling(a.firstChild)"
+}, function(i,n){
+	jQuery.fn[ i ] = function(a) {
+		var ret = jQuery.map(this,n);
+		if ( a && typeof a == "string" )
+			ret = jQuery.multiFilter(a,ret);
+		return this.pushStack( ret );
+	};
+});
+
+jQuery.each({
+	appendTo: "append",
+	prependTo: "prepend",
+	insertBefore: "before",
+	insertAfter: "after"
+}, function(i,n){
+	jQuery.fn[ i ] = function(){
+		var a = arguments;
+		return this.each(function(){
+			for ( var j = 0, al = a.length; j < al; j++ )
+				jQuery(a[j])[n]( this );
+		});
+	};
+});
+
+jQuery.each( {
+	removeAttr: function( key ) {
+		jQuery.attr( this, key, "" );
+		this.removeAttribute( key );
+	},
+	addClass: function(c){
+		jQuery.className.add(this,c);
+	},
+	removeClass: function(c){
+		jQuery.className.remove(this,c);
+	},
+	toggleClass: function( c ){
+		jQuery.className[ jQuery.className.has(this,c) ? "remove" : "add" ](this, c);
+	},
+	remove: function(a){
+		if ( !a || jQuery.filter( a, [this] ).r.length )
+			this.parentNode.removeChild( this );
+	},
+	empty: function() {
+		while ( this.firstChild )
+			this.removeChild( this.firstChild );
+	}
+}, function(i,n){
+	jQuery.fn[ i ] = function() {
+		return this.each( n, arguments );
+	};
+});
+
+jQuery.each( [ "eq", "lt", "gt", "contains" ], function(i,n){
+	jQuery.fn[ n ] = function(num,fn) {
+		return this.filter( ":" + n + "(" + num + ")", fn );
+	};
+});
+
+jQuery.each( [ "height", "width" ], function(i,n){
+	jQuery.fn[ n ] = function(h) {
+		return h == undefined ?
+			( this.length ? jQuery.css( this[0], n ) : null ) :
+			this.css( n, h.constructor == String ? h : h + "px" );
+	};
+});
+jQuery.extend({
+	expr: {
+		"": "m[2]=='*'||jQuery.nodeName(a,m[2])",
+		"#": "a.getAttribute('id')==m[2]",
+		":": {
+			// Position Checks
+			lt: "i<m[3]-0",
+			gt: "i>m[3]-0",
+			nth: "m[3]-0==i",
+			eq: "m[3]-0==i",
+			first: "i==0",
+			last: "i==r.length-1",
+			even: "i%2==0",
+			odd: "i%2",
+
+			// Child Checks
+			"first-child": "a.parentNode.getElementsByTagName('*')[0]==a",
+			"last-child": "jQuery.nth(a.parentNode.lastChild,1,'previousSibling')==a",
+			"only-child": "!jQuery.nth(a.parentNode.lastChild,2,'previousSibling')",
+
+			// Parent Checks
+			parent: "a.firstChild",
+			empty: "!a.firstChild",
+
+			// Text Check
+			contains: "(a.textContent||a.innerText||'').indexOf(m[3])>=0",
+
+			// Visibility
+			visible: '"hidden"!=a.type&&jQuery.css(a,"display")!="none"&&jQuery.css(a,"visibility")!="hidden"',
+			hidden: '"hidden"==a.type||jQuery.css(a,"display")=="none"||jQuery.css(a,"visibility")=="hidden"',
+
+			// Form attributes
+			enabled: "!a.disabled",
+			disabled: "a.disabled",
+			checked: "a.checked",
+			selected: "a.selected||jQuery.attr(a,'selected')",
+
+			// Form elements
+			text: "'text'==a.type",
+			radio: "'radio'==a.type",
+			checkbox: "'checkbox'==a.type",
+			file: "'file'==a.type",
+			password: "'password'==a.type",
+			submit: "'submit'==a.type",
+			image: "'image'==a.type",
+			reset: "'reset'==a.type",
+			button: '"button"==a.type||jQuery.nodeName(a,"button")',
+			input: "/input|select|textarea|button/i.test(a.nodeName)"
+		},
+		"[": "jQuery.find(m[2],a).length"
+	},
+	
+	// The regular expressions that power the parsing engine
+	parse: [
+		// Match: [@value='test'], [@foo]
+		/^\[ *(@)([\w-]+) *([!*$^~=]*) *('?"?)(.*?)\4 *\]/,
+
+		// Match: [div], [div p]
+		/^(\[)\s*(.*?(\[.*?\])?[^[]*?)\s*\]/,
+
+		// Match: :contains('foo')
+		/^(:)([\w-]+)\("?'?(.*?(\(.*?\))?[^(]*?)"?'?\)/,
+
+		// Match: :even, :last-chlid, #id, .class
+		new RegExp("^([:.#]*)(" + 
+			( jQuery.chars = jQuery.browser.safari && jQuery.browser.version < "3.0.0" ? "\\w" : "(?:[\\w\u0128-\uFFFF*_-]|\\\\.)" ) + "+)")
+	],
+
+	multiFilter: function( expr, elems, not ) {
+		var old, cur = [];
+
+		while ( expr && expr != old ) {
+			old = expr;
+			var f = jQuery.filter( expr, elems, not );
+			expr = f.t.replace(/^\s*,\s*/, "" );
+			cur = not ? elems = f.r : jQuery.merge( cur, f.r );
+		}
+
+		return cur;
+	},
+	find: function( t, context ) {
+		// Quickly handle non-string expressions
+		if ( typeof t != "string" )
+			return [ t ];
+
+		// Make sure that the context is a DOM Element
+		if ( context && !context.nodeType )
+			context = null;
+
+		// Set the correct context (if none is provided)
+		context = context || document;
+
+		// Handle the common XPath // expression
+		if ( !t.indexOf("//") ) {
+			context = context.documentElement;
+			t = t.substr(2,t.length);
+
+		// And the / root expression
+		} else if ( !t.indexOf("/") && !context.ownerDocument ) {
+			context = context.documentElement;
+			t = t.substr(1,t.length);
+			if ( t.indexOf("/") >= 1 )
+				t = t.substr(t.indexOf("/"),t.length);
+		}
+
+		// Initialize the search
+		var ret = [context], done = [], last;
+
+		// Continue while a selector expression exists, and while
+		// we're no longer looping upon ourselves
+		while ( t && last != t ) {
+			var r = [];
+			last = t;
+
+			t = jQuery.trim(t).replace( /^\/\//, "" );
+
+			var foundToken = false;
+
+			// An attempt at speeding up child selectors that
+			// point to a specific element tag
+			var re = new RegExp("^[/>]\\s*(" + jQuery.chars + "+)");
+			var m = re.exec(t);
+
+			if ( m ) {
+				var nodeName = m[1].toUpperCase();
+
+				// Perform our own iteration and filter
+				for ( var i = 0; ret[i]; i++ )
+					for ( var c = ret[i].firstChild; c; c = c.nextSibling )
+						if ( c.nodeType == 1 && (nodeName == "*" || c.nodeName.toUpperCase() == nodeName.toUpperCase()) )
+							r.push( c );
+
+				ret = r;
+				t = t.replace( re, "" );
+				if ( t.indexOf(" ") == 0 ) continue;
+				foundToken = true;
+			} else {
+				re = /^((\/?\.\.)|([>\/+~]))\s*([a-z]*)/i;
+
+				if ( (m = re.exec(t)) != null ) {
+					r = [];
+
+					var nodeName = m[4], mergeNum = jQuery.mergeNum++;
+					m = m[1];
+
+					for ( var j = 0, rl = ret.length; j < rl; j++ )
+						if ( m.indexOf("..") < 0 ) {
+							var n = m == "~" || m == "+" ? ret[j].nextSibling : ret[j].firstChild;
+							for ( ; n; n = n.nextSibling )
+								if ( n.nodeType == 1 ) {
+									if ( m == "~" && n.mergeNum == mergeNum ) break;
+									
+									if (!nodeName || n.nodeName.toUpperCase() == nodeName.toUpperCase() ) {
+										if ( m == "~" ) n.mergeNum = mergeNum;
+										r.push( n );
+									}
+									
+									if ( m == "+" ) break;
+								}
+						} else
+							r.push( ret[j].parentNode );
+
+					ret = r;
+
+					// And remove the token
+					t = jQuery.trim( t.replace( re, "" ) );
+					foundToken = true;
+				}
+			}
+
+			// See if there's still an expression, and that we haven't already
+			// matched a token
+			if ( t && !foundToken ) {
+				// Handle multiple expressions
+				if ( !t.indexOf(",") ) {
+					// Clean the result set
+					if ( context == ret[0] ) ret.shift();
+
+					// Merge the result sets
+					done = jQuery.merge( done, ret );
+
+					// Reset the context
+					r = ret = [context];
+
+					// Touch up the selector string
+					t = " " + t.substr(1,t.length);
+
+				} else {
+					// Optomize for the case nodeName#idName
+					var re2 = new RegExp("^(" + jQuery.chars + "+)(#)(" + jQuery.chars + "+)");
+					var m = re2.exec(t);
+					
+					// Re-organize the results, so that they're consistent
+					if ( m ) {
+					   m = [ 0, m[2], m[3], m[1] ];
+
+					} else {
+						// Otherwise, do a traditional filter check for
+						// ID, class, and element selectors
+						re2 = new RegExp("^([#.]?)(" + jQuery.chars + "*)");
+						m = re2.exec(t);
+					}
+
+					m[2] = m[2].replace(/\\/g, "");
+
+					var elem = ret[ret.length-1];
+
+					// Try to do a global search by ID, where we can
+					if ( m[1] == "#" && elem && elem.getElementById ) {
+						// Optimization for HTML document case
+						var oid = elem.getElementById(m[2]);
+						
+						// Do a quick check for the existence of the actual ID attribute
+						// to avoid selecting by the name attribute in IE
+						// also check to insure id is a string to avoid selecting an element with the name of 'id' inside a form
+						if ( (jQuery.browser.msie||jQuery.browser.opera) && oid && typeof oid.id == "string" && oid.id != m[2] )
+							oid = jQuery('[@id="'+m[2]+'"]', elem)[0];
+
+						// Do a quick check for node name (where applicable) so
+						// that div#foo searches will be really fast
+						ret = r = oid && (!m[3] || jQuery.nodeName(oid, m[3])) ? [oid] : [];
+					} else {
+						// We need to find all descendant elements
+						for ( var i = 0; ret[i]; i++ ) {
+							// Grab the tag name being searched for
+							var tag = m[1] != "" || m[0] == "" ? "*" : m[2];
+
+							// Handle IE7 being really dumb about <object>s
+							if ( tag == "*" && ret[i].nodeName.toLowerCase() == "object" )
+								tag = "param";
+
+							r = jQuery.merge( r, ret[i].getElementsByTagName( tag ));
+						}
+
+						// It's faster to filter by class and be done with it
+						if ( m[1] == "." )
+							r = jQuery.classFilter( r, m[2] );
+
+						// Same with ID filtering
+						if ( m[1] == "#" ) {
+							var tmp = [];
+
+							// Try to find the element with the ID
+							for ( var i = 0; r[i]; i++ )
+								if ( r[i].getAttribute("id") == m[2] ) {
+									tmp = [ r[i] ];
+									break;
+								}
+
+							r = tmp;
+						}
+
+						ret = r;
+					}
+
+					t = t.replace( re2, "" );
+				}
+
+			}
+
+			// If a selector string still exists
+			if ( t ) {
+				// Attempt to filter it
+				var val = jQuery.filter(t,r);
+				ret = r = val.r;
+				t = jQuery.trim(val.t);
+			}
+		}
+
+		// An error occurred with the selector;
+		// just return an empty set instead
+		if ( t )
+			ret = [];
+
+		// Remove the root context
+		if ( ret && context == ret[0] )
+			ret.shift();
+
+		// And combine the results
+		done = jQuery.merge( done, ret );
+
+		return done;
+	},
+
+	classFilter: function(r,m,not){
+		m = " " + m + " ";
+		var tmp = [];
+		for ( var i = 0; r[i]; i++ ) {
+			var pass = (" " + r[i].className + " ").indexOf( m ) >= 0;
+			if ( !not && pass || not && !pass )
+				tmp.push( r[i] );
+		}
+		return tmp;
+	},
+
+	filter: function(t,r,not) {
+		var last;
+
+		// Look for common filter expressions
+		while ( t  && t != last ) {
+			last = t;
+
+			var p = jQuery.parse, m;
+
+			for ( var i = 0; p[i]; i++ ) {
+				m = p[i].exec( t );
+
+				if ( m ) {
+					// Remove what we just matched
+					t = t.substring( m[0].length );
+
+					m[2] = m[2].replace(/\\/g, "");
+					break;
+				}
+			}
+
+			if ( !m )
+				break;
+
+			// :not() is a special case that can be optimized by
+			// keeping it out of the expression list
+			if ( m[1] == ":" && m[2] == "not" )
+				r = jQuery.filter(m[3], r, true).r;
+
+			// We can get a big speed boost by filtering by class here
+			else if ( m[1] == "." )
+				r = jQuery.classFilter(r, m[2], not);
+
+			else if ( m[1] == "@" ) {
+				var tmp = [], type = m[3];
+				
+				for ( var i = 0, rl = r.length; i < rl; i++ ) {
+					var a = r[i], z = a[ jQuery.props[m[2]] || m[2] ];
+					
+					if ( z == null || /href|src/.test(m[2]) )
+						z = jQuery.attr(a,m[2]) || '';
+
+					if ( (type == "" && !!z ||
+						 type == "=" && z == m[5] ||
+						 type == "!=" && z != m[5] ||
+						 type == "^=" && z && !z.indexOf(m[5]) ||
+						 type == "$=" && z.substr(z.length - m[5].length) == m[5] ||
+						 (type == "*=" || type == "~=") && z.indexOf(m[5]) >= 0) ^ not )
+							tmp.push( a );
+				}
+				
+				r = tmp;
+
+			// We can get a speed boost by handling nth-child here
+			} else if ( m[1] == ":" && m[2] == "nth-child" ) {
+				var num = jQuery.mergeNum++, tmp = [],
+					test = /(\d*)n\+?(\d*)/.exec(
+						m[3] == "even" && "2n" || m[3] == "odd" && "2n+1" ||
+						!/\D/.test(m[3]) && "n+" + m[3] || m[3]),
+					first = (test[1] || 1) - 0, last = test[2] - 0;
+
+				for ( var i = 0, rl = r.length; i < rl; i++ ) {
+					var node = r[i], parentNode = node.parentNode;
+
+					if ( num != parentNode.mergeNum ) {
+						var c = 1;
+
+						for ( var n = parentNode.firstChild; n; n = n.nextSibling )
+							if ( n.nodeType == 1 )
+								n.nodeIndex = c++;
+
+						parentNode.mergeNum = num;
+					}
+
+					var add = false;
+
+					if ( first == 1 ) {
+						if ( last == 0 || node.nodeIndex == last )
+							add = true;
+					} else if ( (node.nodeIndex + last) % first == 0 )
+						add = true;
+
+					if ( add ^ not )
+						tmp.push( node );
+				}
+
+				r = tmp;
+
+			// Otherwise, find the expression to execute
+			} else {
+				var f = jQuery.expr[m[1]];
+				if ( typeof f != "string" )
+					f = jQuery.expr[m[1]][m[2]];
+
+				// Build a custom macro to enclose it
+				eval("f = function(a,i){return " + f + "}");
+
+				// Execute it against the current filter
+				r = jQuery.grep( r, f, not );
+			}
+		}
+
+		// Return an array of filtered elements (r)
+		// and the modified expression string (t)
+		return { r: r, t: t };
+	},
+	parents: function( elem ){
+		var matched = [];
+		var cur = elem.parentNode;
+		while ( cur && cur != document ) {
+			matched.push( cur );
+			cur = cur.parentNode;
+		}
+		return matched;
+	},
+	nth: function(cur,result,dir,elem){
+		result = result || 1;
+		var num = 0;
+
+		for ( ; cur; cur = cur[dir] )
+			if ( cur.nodeType == 1 && ++num == result )
+				break;
+
+		return cur;
+	},
+	sibling: function( n, elem ) {
+		var r = [];
+
+		for ( ; n; n = n.nextSibling ) {
+			if ( n.nodeType == 1 && (!elem || n != elem) )
+				r.push( n );
+		}
+
+		return r;
+	}
+});
+/*
+ * A number of helper functions used for managing events.
+ * Many of the ideas behind this code orignated from 
+ * Dean Edwards' addEvent library.
+ */
+jQuery.event = {
+
+	// Bind an event to an element
+	// Original by Dean Edwards
+	add: function(element, type, handler, data) {
+		// For whatever reason, IE has trouble passing the window object
+		// around, causing it to be cloned in the process
+		if ( jQuery.browser.msie && element.setInterval != undefined )
+			element = window;
+		
+		// Make sure that the function being executed has a unique ID
+		if ( !handler.guid )
+			handler.guid = this.guid++;
+			
+		// if data is passed, bind to handler 
+		if( data != undefined ) { 
+        	// Create temporary function pointer to original handler 
+			var fn = handler; 
+
+			// Create unique handler function, wrapped around original handler 
+			handler = function() { 
+				// Pass arguments and context to original handler 
+				return fn.apply(this, arguments); 
+			};
+
+			// Store data in unique handler 
+			handler.data = data;
+
+			// Set the guid of unique handler to the same of original handler, so it can be removed 
+			handler.guid = fn.guid;
+		}
+
+		// Init the element's event structure
+		if (!element.$events)
+			element.$events = {};
+		
+		if (!element.$handle)
+			element.$handle = function() {
+				// returned undefined or false
+				var val;
+
+				// Handle the second event of a trigger and when
+				// an event is called after a page has unloaded
+				if ( typeof jQuery == "undefined" || jQuery.event.triggered )
+				  return val;
+				
+				val = jQuery.event.handle.apply(element, arguments);
+				
+				return val;
+			};
+
+		// Get the current list of functions bound to this event
+		var handlers = element.$events[type];
+
+		// Init the event handler queue
+		if (!handlers) {
+			handlers = element.$events[type] = {};	
+			
+			// And bind the global event handler to the element
+			if (element.addEventListener)
+				element.addEventListener(type, element.$handle, false);
+			else
+				element.attachEvent("on" + type, element.$handle);
+		}
+
+		// Add the function to the element's handler list
+		handlers[handler.guid] = handler;
+
+		// Remember the function in a global list (for triggering)
+		if (!this.global[type])
+			this.global[type] = [];
+		// Only add the element to the global list once
+		if (jQuery.inArray(element, this.global[type]) == -1)
+			this.global[type].push( element );
+	},
+
+	guid: 1,
+	global: {},
+
+	// Detach an event or set of events from an element
+	remove: function(element, type, handler) {
+		var events = element.$events, ret, index;
+
+		if ( events ) {
+			// type is actually an event object here
+			if ( type && type.type ) {
+				handler = type.handler;
+				type = type.type;
+			}
+			
+			if ( !type ) {
+				for ( type in events )
+					this.remove( element, type );
+
+			} else if ( events[type] ) {
+				// remove the given handler for the given type
+				if ( handler )
+					delete events[type][handler.guid];
+				
+				// remove all handlers for the given type
+				else
+					for ( handler in element.$events[type] )
+						delete events[type][handler];
+
+				// remove generic event handler if no more handlers exist
+				for ( ret in events[type] ) break;
+				if ( !ret ) {
+					if (element.removeEventListener)
+						element.removeEventListener(type, element.$handle, false);
+					else
+						element.detachEvent("on" + type, element.$handle);
+					ret = null;
+					delete events[type];
+					
+					// Remove element from the global event type cache
+					while ( this.global[type] && ( (index = jQuery.inArray(element, this.global[type])) >= 0 ) )
+						delete this.global[type][index];
+				}
+			}
+
+			// Remove the expando if it's no longer used
+			for ( ret in events ) break;
+			if ( !ret )
+				element.$handle = element.$events = null;
+		}
+	},
+
+	trigger: function(type, data, element) {
+		// Clone the incoming data, if any
+		data = jQuery.makeArray(data || []);
+
+		// Handle a global trigger
+		if ( !element )
+			jQuery.each( this.global[type] || [], function(){
+				jQuery.event.trigger( type, data, this );
+			});
+
+		// Handle triggering a single element
+		else {
+			var val, ret, fn = jQuery.isFunction( element[ type ] || null );
+			
+			// Pass along a fake event
+			data.unshift( this.fix({ type: type, target: element }) );
+
+			// Trigger the event
+			if ( jQuery.isFunction(element.$handle) && (val = element.$handle.apply( element, data )) !== false )
+				this.triggered = true;
+
+			if ( fn && val !== false && !jQuery.nodeName(element, 'a') )
+				element[ type ]();
+
+			this.triggered = false;
+		}
+	},
+
+	handle: function(event) {
+		// returned undefined or false
+		var val;
+
+		// Empty object is for triggered events with no data
+		event = jQuery.event.fix( event || window.event || {} ); 
+
+		var c = this.$events && this.$events[event.type], args = [].slice.call( arguments, 1 );
+		args.unshift( event );
+
+		for ( var j in c ) {
+			// Pass in a reference to the handler function itself
+			// So that we can later remove it
+			args[0].handler = c[j];
+			args[0].data = c[j].data;
+
+			if ( c[j].apply( this, args ) === false ) {
+				event.preventDefault();
+				event.stopPropagation();
+				val = false;
+			}
+		}
+
+		// Clean up added properties in IE to prevent memory leak
+		if (jQuery.browser.msie)
+			event.target = event.preventDefault = event.stopPropagation =
+				event.handler = event.data = null;
+
+		return val;
+	},
+
+	fix: function(event) {
+		// store a copy of the original event object 
+		// and clone to set read-only properties
+		var originalEvent = event;
+		event = jQuery.extend({}, originalEvent);
+		
+		// add preventDefault and stopPropagation since 
+		// they will not work on the clone
+		event.preventDefault = function() {
+			// if preventDefault exists run it on the original event
+			if (originalEvent.preventDefault)
+				return originalEvent.preventDefault();
+			// otherwise set the returnValue property of the original event to false (IE)
+			originalEvent.returnValue = false;
+		};
+		event.stopPropagation = function() {
+			// if stopPropagation exists run it on the original event
+			if (originalEvent.stopPropagation)
+				return originalEvent.stopPropagation();
+			// otherwise set the cancelBubble property of the original event to true (IE)
+			originalEvent.cancelBubble = true;
+		};
+		
+		// Fix target property, if necessary
+		if ( !event.target && event.srcElement )
+			event.target = event.srcElement;
+				
+		// check if target is a textnode (safari)
+		if (jQuery.browser.safari && event.target.nodeType == 3)
+			event.target = originalEvent.target.parentNode;
+
+		// Add relatedTarget, if necessary
+		if ( !event.relatedTarget && event.fromElement )
+			event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement;
+
+		// Calculate pageX/Y if missing and clientX/Y available
+		if ( event.pageX == null && event.clientX != null ) {
+			var e = document.documentElement, b = document.body;
+			event.pageX = event.clientX + (e && e.scrollLeft || b.scrollLeft);
+			event.pageY = event.clientY + (e && e.scrollTop || b.scrollTop);
+		}
+			
+		// Add which for key events
+		if ( !event.which && (event.charCode || event.keyCode) )
+			event.which = event.charCode || event.keyCode;
+		
+		// Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
+		if ( !event.metaKey && event.ctrlKey )
+			event.metaKey = event.ctrlKey;
+
+		// Add which for click: 1 == left; 2 == middle; 3 == right
+		// Note: button is not normalized, so don't use it
+		if ( !event.which && event.button )
+			event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
+			
+		return event;
+	}
+};
+
+jQuery.fn.extend({
+	bind: function( type, data, fn ) {
+		return type == "unload" ? this.one(type, data, fn) : this.each(function(){
+			jQuery.event.add( this, type, fn || data, fn && data );
+		});
+	},
+	one: function( type, data, fn ) {
+		return this.each(function(){
+			jQuery.event.add( this, type, function(event) {
+				jQuery(this).unbind(event);
+				return (fn || data).apply( this, arguments);
+			}, fn && data);
+		});
+	},
+	unbind: function( type, fn ) {
+		return this.each(function(){
+			jQuery.event.remove( this, type, fn );
+		});
+	},
+	trigger: function( type, data ) {
+		return this.each(function(){
+			jQuery.event.trigger( type, data, this );
+		});
+	},
+	toggle: function() {
+		// Save reference to arguments for access in closure
+		var a = arguments;
+
+		return this.click(function(e) {
+			// Figure out which function to execute
+			this.lastToggle = 0 == this.lastToggle ? 1 : 0;
+			
+			// Make sure that clicks stop
+			e.preventDefault();
+			
+			// and execute the function
+			return a[this.lastToggle].apply( this, [e] ) || false;
+		});
+	},
+	hover: function(f,g) {
+		
+		// A private function for handling mouse 'hovering'
+		function handleHover(e) {
+			// Check if mouse(over|out) are still within the same parent element
+			var p = e.relatedTarget;
+	
+			// Traverse up the tree
+			while ( p && p != this ) try { p = p.parentNode } catch(e) { p = this; };
+			
+			// If we actually just moused on to a sub-element, ignore it
+			if ( p == this ) return false;
+			
+			// Execute the right function
+			return (e.type == "mouseover" ? f : g).apply(this, [e]);
+		}
+		
+		// Bind the function to the two event listeners
+		return this.mouseover(handleHover).mouseout(handleHover);
+	},
+	ready: function(f) {
+		// If the DOM is already ready
+		if ( jQuery.isReady )
+			// Execute the function immediately
+			f.apply( document, [jQuery] );
+			
+		// Otherwise, remember the function for later
+		else
+			// Add the function to the wait list
+			jQuery.readyList.push( function() { return f.apply(this, [jQuery]) } );
+	
+		return this;
+	}
+});
+
+jQuery.extend({
+	/*
+	 * All the code that makes DOM Ready work nicely.
+	 */
+	isReady: false,
+	readyList: [],
+	
+	// Handle when the DOM is ready
+	ready: function() {
+		// Make sure that the DOM is not already loaded
+		if ( !jQuery.isReady ) {
+			// Remember that the DOM is ready
+			jQuery.isReady = true;
+			
+			// If there are functions bound, to execute
+			if ( jQuery.readyList ) {
+				// Execute all of them
+				jQuery.each( jQuery.readyList, function(){
+					this.apply( document );
+				});
+				
+				// Reset the list of functions
+				jQuery.readyList = null;
+			}
+			// Remove event listener to avoid memory leak
+			if ( jQuery.browser.mozilla || jQuery.browser.opera )
+				document.removeEventListener( "DOMContentLoaded", jQuery.ready, false );
+			
+			// Remove script element used by IE hack
+			if( !window.frames.length ) // don't remove if frames are present (#1187)
+				jQuery(window).load(function(){ jQuery("#__ie_init").remove(); });
+		}
+	}
+});
+
+new function(){
+
+	jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
+		"mousedown,mouseup,mousemove,mouseover,mouseout,change,select," + 
+		"submit,keydown,keypress,keyup,error").split(","), function(i,o){
+		
+		// Handle event binding
+		jQuery.fn[o] = function(f){
+			return f ? this.bind(o, f) : this.trigger(o);
+		};
+			
+	});
+	
+	// If Mozilla is used
+	if ( jQuery.browser.mozilla || jQuery.browser.opera )
+		// Use the handy event callback
+		document.addEventListener( "DOMContentLoaded", jQuery.ready, false );
+	
+	// If IE is used, use the excellent hack by Matthias Miller
+	// http://www.outofhanwell.com/blog/index.php?title=the_window_onload_problem_revisited
+	else if ( jQuery.browser.msie ) {
+	
+		// Only works if you document.write() it
+		document.write("<scr" + "ipt id=__ie_init defer=true " + 
+			"src=//:><\/script>");
+	
+		// Use the defer script hack
+		var script = document.getElementById("__ie_init");
+		
+		// script does not exist if jQuery is loaded dynamically
+		if ( script ) 
+			script.onreadystatechange = function() {
+				if ( this.readyState != "complete" ) return;
+				jQuery.ready();
+			};
+	
+		// Clear from memory
+		script = null;
+	
+	// If Safari  is used
+	} else if ( jQuery.browser.safari )
+		// Continually check to see if the document.readyState is valid
+		jQuery.safariTimer = setInterval(function(){
+			// loaded and complete are both valid states
+			if ( document.readyState == "loaded" || 
+				document.readyState == "complete" ) {
+	
+				// If either one are found, remove the timer
+				clearInterval( jQuery.safariTimer );
+				jQuery.safariTimer = null;
+	
+				// and execute any waiting functions
+				jQuery.ready();
+			}
+		}, 10); 
+
+	// A fallback to window.onload, that will always work
+	jQuery.event.add( window, "load", jQuery.ready );
+	
+};
+
+// Clean up after IE to avoid memory leaks
+if (jQuery.browser.msie)
+	jQuery(window).one("unload", function() {
+		var global = jQuery.event.global;
+		for ( var type in global ) {
+			var els = global[type], i = els.length;
+			if ( i && type != 'unload' )
+				do
+					els[i-1] && jQuery.event.remove(els[i-1], type);
+				while (--i);
+		}
+	});
+jQuery.fn.extend({
+	loadIfModified: function( url, params, callback ) {
+		this.load( url, params, callback, 1 );
+	},
+	load: function( url, params, callback, ifModified ) {
+		if ( jQuery.isFunction( url ) )
+			return this.bind("load", url);
+
+		callback = callback || function(){};
+
+		// Default to a GET request
+		var type = "GET";
+
+		// If the second parameter was provided
+		if ( params )
+			// If it's a function
+			if ( jQuery.isFunction( params ) ) {
+				// We assume that it's the callback
+				callback = params;
+				params = null;
+
+			// Otherwise, build a param string
+			} else {
+				params = jQuery.param( params );
+				type = "POST";
+			}
+
+		var self = this;
+
+		// Request the remote document
+		jQuery.ajax({
+			url: url,
+			type: type,
+			data: params,
+			ifModified: ifModified,
+			complete: function(res, status){
+				if ( status == "success" || !ifModified && status == "notmodified" )
+					// Inject the HTML into all the matched elements
+					self.attr("innerHTML", res.responseText)
+					  // Execute all the scripts inside of the newly-injected HTML
+					  .evalScripts()
+					  // Execute callback
+					  .each( callback, [res.responseText, status, res] );
+				else
+					callback.apply( self, [res.responseText, status, res] );
+			}
+		});
+		return this;
+	},
+	serialize: function() {
+		return jQuery.param( this );
+	},
+	evalScripts: function() {
+		return this.find("script").each(function(){
+			if ( this.src )
+				jQuery.getScript( this.src );
+			else
+				jQuery.globalEval( this.text || this.textContent || this.innerHTML || "" );
+		}).end();
+	}
+
+});
+
+// Attach a bunch of functions for handling common AJAX events
+
+jQuery.each( "ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","), function(i,o){
+	jQuery.fn[o] = function(f){
+		return this.bind(o, f);
+	};
+});
+
+jQuery.extend({
+	get: function( url, data, callback, type, ifModified ) {
+		// shift arguments if data argument was ommited
+		if ( jQuery.isFunction( data ) ) {
+			callback = data;
+			data = null;
+		}
+		
+		return jQuery.ajax({
+			type: "GET",
+			url: url,
+			data: data,
+			success: callback,
+			dataType: type,
+			ifModified: ifModified
+		});
+	},
+	getIfModified: function( url, data, callback, type ) {
+		return jQuery.get(url, data, callback, type, 1);
+	},
+	getScript: function( url, callback ) {
+		return jQuery.get(url, null, callback, "script");
+	},
+	getJSON: function( url, data, callback ) {
+		return jQuery.get(url, data, callback, "json");
+	},
+	post: function( url, data, callback, type ) {
+		if ( jQuery.isFunction( data ) ) {
+			callback = data;
+			data = {};
+		}
+
+		return jQuery.ajax({
+			type: "POST",
+			url: url,
+			data: data,
+			success: callback,
+			dataType: type
+		});
+	},
+	ajaxTimeout: function( timeout ) {
+		jQuery.ajaxSettings.timeout = timeout;
+	},
+	ajaxSetup: function( settings ) {
+		jQuery.extend( jQuery.ajaxSettings, settings );
+	},
+
+	ajaxSettings: {
+		global: true,
+		type: "GET",
+		timeout: 0,
+		contentType: "application/x-www-form-urlencoded",
+		processData: true,
+		async: true,
+		data: null
+	},
+	
+	// Last-Modified header cache for next request
+	lastModified: {},
+	ajax: function( s ) {
+		// TODO introduce global settings, allowing the client to modify them for all requests, not only timeout
+		s = jQuery.extend({}, jQuery.ajaxSettings, s);
+
+		// if data available
+		if ( s.data ) {
+			// convert data if not already a string
+			if (s.processData && typeof s.data != "string")
+    			s.data = jQuery.param(s.data);
+			// append data to url for get requests
+			if( s.type.toLowerCase() == "get" ) {
+				// "?" + data or "&" + data (in case there are already params)
+				s.url += ((s.url.indexOf("?") > -1) ? "&" : "?") + s.data;
+				// IE likes to send both get and post data, prevent this
+				s.data = null;
+			}
+		}
+
+		// Watch for a new set of requests
+		if ( s.global && ! jQuery.active++ )
+			jQuery.event.trigger( "ajaxStart" );
+
+		var requestDone = false;
+
+		// Create the request object; Microsoft failed to properly
+		// implement the XMLHttpRequest in IE7, so we use the ActiveXObject when it is available
+		var xml = window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : new XMLHttpRequest();
+
+		// Open the socket
+		xml.open(s.type, s.url, s.async);
+
+		// Set the correct header, if data is being sent
+		if ( s.data )
+			xml.setRequestHeader("Content-Type", s.contentType);
+
+		// Set the If-Modified-Since header, if ifModified mode.
+		if ( s.ifModified )
+			xml.setRequestHeader("If-Modified-Since",
+				jQuery.lastModified[s.url] || "Thu, 01 Jan 1970 00:00:00 GMT" );
+
+		// Set header so the called script knows that it's an XMLHttpRequest
+		xml.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+
+		// Allow custom headers/mimetypes
+		if( s.beforeSend )
+			s.beforeSend(xml);
+			
+		if ( s.global )
+		    jQuery.event.trigger("ajaxSend", [xml, s]);
+
+		// Wait for a response to come back
+		var onreadystatechange = function(isTimeout){
+			// The transfer is complete and the data is available, or the request timed out
+			if ( xml && (xml.readyState == 4 || isTimeout == "timeout") ) {
+				requestDone = true;
+				
+				// clear poll interval
+				if (ival) {
+					clearInterval(ival);
+					ival = null;
+				}
+				
+				var status;
+				try {
+					status = jQuery.httpSuccess( xml ) && isTimeout != "timeout" ?
+						s.ifModified && jQuery.httpNotModified( xml, s.url ) ? "notmodified" : "success" : "error";
+					// Make sure that the request was successful or notmodified
+					if ( status != "error" ) {
+						// Cache Last-Modified header, if ifModified mode.
+						var modRes;
+						try {
+							modRes = xml.getResponseHeader("Last-Modified");
+						} catch(e) {} // swallow exception thrown by FF if header is not available
+	
+						if ( s.ifModified && modRes )
+							jQuery.lastModified[s.url] = modRes;
+	
+						// process the data (runs the xml through httpData regardless of callback)
+						var data = jQuery.httpData( xml, s.dataType );
+	
+						// If a local callback was specified, fire it and pass it the data
+						if ( s.success )
+							s.success( data, status );
+	
+						// Fire the global callback
+						if( s.global )
+							jQuery.event.trigger( "ajaxSuccess", [xml, s] );
+					} else
+						jQuery.handleError(s, xml, status);
+				} catch(e) {
+					status = "error";
+					jQuery.handleError(s, xml, status, e);
+				}
+
+				// The request was completed
+				if( s.global )
+					jQuery.event.trigger( "ajaxComplete", [xml, s] );
+
+				// Handle the global AJAX counter
+				if ( s.global && ! --jQuery.active )
+					jQuery.event.trigger( "ajaxStop" );
+
+				// Process result
+				if ( s.complete )
+					s.complete(xml, status);
+
+				// Stop memory leaks
+				if(s.async)
+					xml = null;
+			}
+		};
+		
+		// don't attach the handler to the request, just poll it instead
+		var ival = setInterval(onreadystatechange, 13); 
+
+		// Timeout checker
+		if ( s.timeout > 0 )
+			setTimeout(function(){
+				// Check to see if the request is still happening
+				if ( xml ) {
+					// Cancel the request
+					xml.abort();
+
+					if( !requestDone )
+						onreadystatechange( "timeout" );
+				}
+			}, s.timeout);
+			
+		// Send the data
+		try {
+			xml.send(s.data);
+		} catch(e) {
+			jQuery.handleError(s, xml, null, e);
+		}
+		
+		// firefox 1.5 doesn't fire statechange for sync requests
+		if ( !s.async )
+			onreadystatechange();
+		
+		// return XMLHttpRequest to allow aborting the request etc.
+		return xml;
+	},
+
+	handleError: function( s, xml, status, e ) {
+		// If a local callback was specified, fire it
+		if ( s.error ) s.error( xml, status, e );
+
+		// Fire the global callback
+		if ( s.global )
+			jQuery.event.trigger( "ajaxError", [xml, s, e] );
+	},
+
+	// Counter for holding the number of active queries
+	active: 0,
+
+	// Determines if an XMLHttpRequest was successful or not
+	httpSuccess: function( r ) {
+		try {
+			return !r.status && location.protocol == "file:" ||
+				( r.status >= 200 && r.status < 300 ) || r.status == 304 ||
+				jQuery.browser.safari && r.status == undefined;
+		} catch(e){}
+		return false;
+	},
+
+	// Determines if an XMLHttpRequest returns NotModified
+	httpNotModified: function( xml, url ) {
+		try {
+			var xmlRes = xml.getResponseHeader("Last-Modified");
+
+			// Firefox always returns 200. check Last-Modified date
+			return xml.status == 304 || xmlRes == jQuery.lastModified[url] ||
+				jQuery.browser.safari && xml.status == undefined;
+		} catch(e){}
+		return false;
+	},
+
+	/* Get the data out of an XMLHttpRequest.
+	 * Return parsed XML if content-type header is "xml" and type is "xml" or omitted,
+	 * otherwise return plain text.
+	 * (String) data - The type of data that you're expecting back,
+	 * (e.g. "xml", "html", "script")
+	 */
+	httpData: function( r, type ) {
+		var ct = r.getResponseHeader("content-type");
+		var data = !type && ct && ct.indexOf("xml") >= 0;
+		data = type == "xml" || data ? r.responseXML : r.responseText;
+
+		// If the type is "script", eval it in global context
+		if ( type == "script" )
+			jQuery.globalEval( data );
+
+		// Get the JavaScript object, if JSON is used.
+		if ( type == "json" )
+			data = eval("(" + data + ")");
+
+		// evaluate scripts within html
+		if ( type == "html" )
+			jQuery("<div>").html(data).evalScripts();
+
+		return data;
+	},
+
+	// Serialize an array of form elements or a set of
+	// key/values into a query string
+	param: function( a ) {
+		var s = [];
+
+		// If an array was passed in, assume that it is an array
+		// of form elements
+		if ( a.constructor == Array || a.jquery )
+			// Serialize the form elements
+			jQuery.each( a, function(){
+				s.push( encodeURIComponent(this.name) + "=" + encodeURIComponent( this.value ) );
+			});
+
+		// Otherwise, assume that it's an object of key/value pairs
+		else
+			// Serialize the key/values
+			for ( var j in a )
+				// If the value is an array then the key names need to be repeated
+				if ( a[j] && a[j].constructor == Array )
+					jQuery.each( a[j], function(){
+						s.push( encodeURIComponent(j) + "=" + encodeURIComponent( this ) );
+					});
+				else
+					s.push( encodeURIComponent(j) + "=" + encodeURIComponent( a[j] ) );
+
+		// Return the resulting serialization
+		return s.join("&");
+	},
+	
+	// evalulates a script in global context
+	// not reliable for safari
+	globalEval: function( data ) {
+		if ( window.execScript )
+			window.execScript( data );
+		else if ( jQuery.browser.safari )
+			// safari doesn't provide a synchronous global eval
+			window.setTimeout( data, 0 );
+		else
+			eval.call( window, data );
+	}
+
+});
+jQuery.fn.extend({
+
+	show: function(speed,callback){
+		return speed ?
+			this.animate({
+				height: "show", width: "show", opacity: "show"
+			}, speed, callback) :
+			
+			this.filter(":hidden").each(function(){
+				this.style.display = this.oldblock ? this.oldblock : "";
+				if ( jQuery.css(this,"display") == "none" )
+					this.style.display = "block";
+			}).end();
+	},
+
+	hide: function(speed,callback){
+		return speed ?
+			this.animate({
+				height: "hide", width: "hide", opacity: "hide"
+			}, speed, callback) :
+			
+			this.filter(":visible").each(function(){
+				this.oldblock = this.oldblock || jQuery.css(this,"display");
+				if ( this.oldblock == "none" )
+					this.oldblock = "block";
+				this.style.display = "none";
+			}).end();
+	},
+
+	// Save the old toggle function
+	_toggle: jQuery.fn.toggle,
+	toggle: function( fn, fn2 ){
+		return jQuery.isFunction(fn) && jQuery.isFunction(fn2) ?
+			this._toggle( fn, fn2 ) :
+			fn ?
+				this.animate({
+					height: "toggle", width: "toggle", opacity: "toggle"
+				}, fn, fn2) :
+				this.each(function(){
+					jQuery(this)[ jQuery(this).is(":hidden") ? "show" : "hide" ]();
+				});
+	},
+	slideDown: function(speed,callback){
+		return this.animate({height: "show"}, speed, callback);
+	},
+	slideUp: function(speed,callback){
+		return this.animate({height: "hide"}, speed, callback);
+	},
+	slideToggle: function(speed, callback){
+		return this.animate({height: "toggle"}, speed, callback);
+	},
+	fadeIn: function(speed, callback){
+		return this.animate({opacity: "show"}, speed, callback);
+	},
+	fadeOut: function(speed, callback){
+		return this.animate({opacity: "hide"}, speed, callback);
+	},
+	fadeTo: function(speed,to,callback){
+		return this.animate({opacity: to}, speed, callback);
+	},
+	animate: function( prop, speed, easing, callback ) {
+		return this.queue(function(){
+			var hidden = jQuery(this).is(":hidden"),
+				opt = jQuery.speed(speed, easing, callback),
+				self = this;
+			
+			for ( var p in prop ) {
+				if ( prop[p] == "hide" && hidden || prop[p] == "show" && !hidden )
+					return jQuery.isFunction(opt.complete) && opt.complete.apply(this);
+
+				if ( p == "height" || p == "width" ) {
+					// Store display property
+					opt.display = jQuery.css(this, "display");
+
+					// Make sure that nothing sneaks out
+					opt.overflow = this.style.overflow;
+				}
+			}
+
+			if ( opt.overflow != null )
+				this.style.overflow = "hidden";
+
+			this.curAnim = jQuery.extend({}, prop);
+			
+			jQuery.each( prop, function(name, val){
+				var e = new jQuery.fx( self, opt, name );
+				if ( val.constructor == Number )
+					e.custom( e.cur(), val );
+				else
+					e[ val == "toggle" ? hidden ? "show" : "hide" : val ]( prop );
+			});
+		});
+	},
+	queue: function(type,fn){
+		if ( !fn ) {
+			fn = type;
+			type = "fx";
+		}
+	
+		return this.each(function(){
+			if ( !this.queue )
+				this.queue = {};
+	
+			if ( !this.queue[type] )
+				this.queue[type] = [];
+	
+			this.queue[type].push( fn );
+		
+			if ( this.queue[type].length == 1 )
+				fn.apply(this);
+		});
+	}
+
+});
+
+jQuery.extend({
+	
+	speed: function(speed, easing, fn) {
+		var opt = speed && speed.constructor == Object ? speed : {
+			complete: fn || !fn && easing || 
+				jQuery.isFunction( speed ) && speed,
+			duration: speed,
+			easing: fn && easing || easing && easing.constructor != Function && easing || (jQuery.easing.swing ? "swing" : "linear")
+		};
+
+		opt.duration = (opt.duration && opt.duration.constructor == Number ? 
+			opt.duration : 
+			{ slow: 600, fast: 200 }[opt.duration]) || 400;
+	
+		// Queueing
+		opt.old = opt.complete;
+		opt.complete = function(){
+			jQuery.dequeue(this, "fx");
+			if ( jQuery.isFunction( opt.old ) )
+				opt.old.apply( this );
+		};
+	
+		return opt;
+	},
+	
+	easing: {
+		linear: function( p, n, firstNum, diff ) {
+			return firstNum + diff * p;
+		},
+		swing: function( p, n, firstNum, diff ) {
+			return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum;
+		}
+	},
+	
+	queue: {},
+	
+	dequeue: function(elem,type){
+		type = type || "fx";
+	
+		if ( elem.queue && elem.queue[type] ) {
+			// Remove self
+			elem.queue[type].shift();
+	
+			// Get next function
+			var f = elem.queue[type][0];
+		
+			if ( f ) f.apply( elem );
+		}
+	},
+
+	timers: [],
+
+	/*
+	 * I originally wrote fx() as a clone of moo.fx and in the process
+	 * of making it small in size the code became illegible to sane
+	 * people. You've been warned.
+	 */
+	
+	fx: function( elem, options, prop ){
+
+		var z = this;
+
+		// The styles
+		var y = elem.style;
+		
+		// Simple function for setting a style value
+		z.a = function(){
+			if ( options.step )
+				options.step.apply( elem, [ z.now ] );
+
+			if ( prop == "opacity" )
+				jQuery.attr(y, "opacity", z.now); // Let attr handle opacity
+			else {
+				y[prop] = parseInt(z.now) + "px";
+				y.display = "block"; // Set display property to block for animation
+			}
+		};
+
+		// Figure out the maximum number to run to
+		z.max = function(){
+			return parseFloat( jQuery.css(elem,prop) );
+		};
+
+		// Get the current size
+		z.cur = function(){
+			var r = parseFloat( jQuery.curCSS(elem, prop) );
+			return r && r > -10000 ? r : z.max();
+		};
+
+		// Start an animation from one number to another
+		z.custom = function(from,to){
+			z.startTime = (new Date()).getTime();
+			z.now = from;
+			z.a();
+
+			jQuery.timers.push(function(){
+				return z.step(from, to);
+			});
+
+			if ( jQuery.timers.length == 1 ) {
+				var timer = setInterval(function(){
+					var timers = jQuery.timers;
+					
+					for ( var i = 0; i < timers.length; i++ )
+						if ( !timers[i]() )
+							timers.splice(i--, 1);
+
+					if ( !timers.length )
+						clearInterval( timer );
+				}, 13);
+			}
+		};
+
+		// Simple 'show' function
+		z.show = function(){
+			if ( !elem.orig ) elem.orig = {};
+
+			// Remember where we started, so that we can go back to it later
+			elem.orig[prop] = jQuery.attr( elem.style, prop );
+
+			options.show = true;
+
+			// Begin the animation
+			z.custom(0, this.cur());
+
+			// Make sure that we start at a small width/height to avoid any
+			// flash of content
+			if ( prop != "opacity" )
+				y[prop] = "1px";
+			
+			// Start by showing the element
+			jQuery(elem).show();
+		};
+
+		// Simple 'hide' function
+		z.hide = function(){
+			if ( !elem.orig ) elem.orig = {};
+
+			// Remember where we started, so that we can go back to it later
+			elem.orig[prop] = jQuery.attr( elem.style, prop );
+
+			options.hide = true;
+
+			// Begin the animation
+			z.custom(this.cur(), 0);
+		};
+
+		// Each step of an animation
+		z.step = function(firstNum, lastNum){
+			var t = (new Date()).getTime();
+
+			if (t > options.duration + z.startTime) {
+				z.now = lastNum;
+				z.a();
+
+				if (elem.curAnim) elem.curAnim[ prop ] = true;
+
+				var done = true;
+				for ( var i in elem.curAnim )
+					if ( elem.curAnim[i] !== true )
+						done = false;
+
+				if ( done ) {
+					if ( options.display != null ) {
+						// Reset the overflow
+						y.overflow = options.overflow;
+					
+						// Reset the display
+						y.display = options.display;
+						if ( jQuery.css(elem, "display") == "none" )
+							y.display = "block";
+					}
+
+					// Hide the element if the "hide" operation was done
+					if ( options.hide )
+						y.display = "none";
+
+					// Reset the properties, if the item has been hidden or shown
+					if ( options.hide || options.show )
+						for ( var p in elem.curAnim )
+							jQuery.attr(y, p, elem.orig[p]);
+				}
+
+				// If a callback was provided, execute it
+				if ( done && jQuery.isFunction( options.complete ) )
+					// Execute the complete function
+					options.complete.apply( elem );
+
+				return false;
+			} else {
+				var n = t - this.startTime;
+				// Figure out where in the animation we are and set the number
+				var p = n / options.duration;
+				
+				// Perform the easing function, defaults to swing
+				z.now = jQuery.easing[options.easing](p, n, firstNum, (lastNum-firstNum), options.duration);
+
+				// Perform the next step of the animation
+				z.a();
+			}
+
+			return true;
+		};
+	
+	}
+});
+}

Added: jifty/branches/js-refactor/share/plugins/Jifty/Plugin/JQuery/web/static/js/noConflict.js
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/share/plugins/Jifty/Plugin/JQuery/web/static/js/noConflict.js	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,4 @@
+/**
+ * noConflict.js - Tell jQuery not to clobber $()
+ */
+jQuery.noConflict();

Modified: jifty/branches/js-refactor/share/po/fr.po
==============================================================================
--- jifty/branches/js-refactor/share/po/fr.po	(original)
+++ jifty/branches/js-refactor/share/po/fr.po	Thu Nov 29 12:25:16 2007
@@ -56,64 +56,79 @@
 "\n"
 "%2\n"
 
-#: lib/Jifty/Action/Record/Search.pm:125
+#: lib/Jifty/Plugin/Authentication/Facebook/View.pm:43
+msgid ""
+" id is 'facebook_link' };\n"
+"        span { _(\"Login to Facebook now to link it with your current account!\") };\n"
+"        a {{ href is $plugin->get_link_url };\n"
+"            img {{ src is 'http://static.ak.facebook.com/images/devsite/facebook_login.gif', border is '0' "
+msgstr ""
+
+#: lib/Jifty/Plugin/Authentication/Facebook/View.pm:26
+msgid ""
+" id is 'facebook_login' };\n"
+"        span { _(\"Login to Facebook now to get started!\") };\n"
+"        a {{ href is $plugin->get_login_url };\n"
+"            img {{ src is 'http://static.ak.facebook.com/images/devsite/facebook_login.gif', border is '0' "
+msgstr ""
+
+#: lib/Jifty/Action/Record/Search.pm:172
 msgid "!=>< allowed"
 msgstr "!=>< autorisés"
 
-#: lib/Jifty/Notification.pm:94
+#: lib/Jifty/Notification.pm:108 lib/Jifty/Notification.pm:125
 #. ($appname, Jifty->config->framework('AdminEmail')
 msgid "%1 <%2>"
 msgstr ""
 
-#: lib/Jifty/Action/Record/Search.pm:115
+#: lib/Jifty/Action/Record/Search.pm:143
 #. ($label)
 msgid "%1 after"
 msgstr "%1 après"
 
-#: lib/Jifty/Action/Record/Search.pm:116
+#: lib/Jifty/Action/Record/Search.pm:146
 #. ($label)
 msgid "%1 before"
 msgstr "%1 avant"
 
-#: lib/Jifty/Action/Record/Search.pm:112
+#: lib/Jifty/Action/Record/Search.pm:133
 #. ($label)
 msgid "%1 contains"
 msgstr "%1 contient"
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:141
-#. ($collection-> count)
+#:
 msgid "%1 entries"
 msgstr "%1 enregistements"
 
-#: lib/Jifty/Action/Record/Search.pm:123
+#: lib/Jifty/Action/Record/Search.pm:166
 #. ($label)
 msgid "%1 greater or equal to"
-msgstr "%1 supérieur ou égal à "
+msgstr "%1 supérieur ou égal à"
 
-#: lib/Jifty/Action/Record/Search.pm:121
+#: lib/Jifty/Action/Record/Search.pm:160
 #. ($label)
 msgid "%1 greater than"
-msgstr "%1 supérieur à "
+msgstr "%1 supérieur à"
 
-#: lib/Jifty/Action/Record/Search.pm:108
+#: lib/Jifty/Action/Record/Search.pm:121
 #. ($label)
 msgid "%1 is not"
 msgstr "%1 n'est pas"
 
-#: lib/Jifty/Action/Record/Search.pm:113
+#: lib/Jifty/Action/Record/Search.pm:136
 #. ($label)
 msgid "%1 lacks"
 msgstr "%1 ne contient pas"
 
-#: lib/Jifty/Action/Record/Search.pm:124
+#: lib/Jifty/Action/Record/Search.pm:169
 #. ($label)
 msgid "%1 less or equal to"
 msgstr "%1 inférieur ou égal"
 
-#: lib/Jifty/Action/Record/Search.pm:122
+#: lib/Jifty/Action/Record/Search.pm:163
 #. ($label)
 msgid "%1 less than"
-msgstr "%1 inférieur à "
+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)
@@ -126,20 +141,24 @@
 msgid "%1 seconds"
 msgstr "%1 secondes"
 
-#: lib/Jifty/Action/Record/Search.pm:117
+#: lib/Jifty/Action/Record/Search.pm:149
 #. ($label)
 msgid "%1 since"
 msgstr "%1 depuis le"
 
-#: lib/Jifty/Action/Record/Search.pm:118
+#: lib/Jifty/Action/Record/Search.pm:152
 #. ($label)
 msgid "%1 until"
 msgstr "%1 depuis"
 
-#: lib/Jifty/Action/Record/Search.pm:77
+#: lib/Jifty/Action/Record/Search.pm:81
 msgid "(any)"
 msgstr ""
 
+#: lib/Jifty/Plugin/AutoReference.pm:95
+msgid "- none -"
+msgstr "- aucun -"
+
 #: lib/Jifty/Plugin/Authentication/Password/Action/ConfirmEmail.pm:58
 msgid ". Your email address has now been confirmed."
 msgstr ". Votre adresse mail est confirmée."
@@ -148,32 +167,28 @@
 msgid "A link to reset your password has been sent to your email account."
 msgstr "Une URL pour changer votre mots de passe, vous a été transmis par mail. "
 
-#: lib/Jifty/Notification.pm:96
+#: lib/Jifty/Notification.pm:110 lib/Jifty/Notification.pm:127
 #. ($appname)
 msgid "A notification from %1!"
-msgstr "Message de %1!"
-
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:443
-msgid "Actions"
-msgstr ""
+msgstr "Message de %1 !"
 
-#: lib/Jifty/Plugin/SkeletonApp/Dispatcher.pm:28
+#: lib/Jifty/Plugin/SkeletonApp/Dispatcher.pm:33
 msgid "Administration"
 msgstr ""
 
-#: lib/Jifty/View/Declare/Helpers.pm:363 share/web/templates/_elements/wrapper:11
+#: lib/Jifty/View/Declare/Page.pm:176 share/web/templates/_elements/wrapper:11
 msgid "Administration mode is enabled."
 msgstr "Mode d'administration activé."
 
-#: lib/Jifty/View/Declare/Helpers.pm:360 share/web/templates/_elements/wrapper:11
+#: lib/Jifty/View/Declare/Page.pm:173 share/web/templates/_elements/wrapper:11
 msgid "Alert"
 msgstr "Alerte"
 
-#: lib/Jifty/Action/Record/Create.pm:82
+#: lib/Jifty/Action/Record/Create.pm:92
 msgid "An error occurred.  Try again later"
 msgstr "Erreur. Ré-essayez plus tard"
 
-#: lib/Jifty/Action/Record/Search.pm:129
+#: lib/Jifty/Action/Record/Search.pm:177
 msgid "Any field contains"
 msgstr "Un champs contient"
 
@@ -181,15 +196,15 @@
 msgid "Anyway, the software has logged this error."
 msgstr "Cette erreur a été enregistrée par le logiciel."
 
-#: lib/Jifty/Plugin/Authentication/Password/Mixin/Model/User.pm:19
+#: lib/Jifty/Plugin/Authentication/Password/Mixin/Model/User.pm:53
 msgid "Authentication token"
 msgstr "Token d'authentification"
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:489 lib/Jifty/Plugin/AdminUI/View-not-yet.pm:59
+#:
 msgid "Back to the admin console"
 msgstr "Retour à l'interface d'admin"
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:461
+#:
 msgid "Back to the application"
 msgstr "Retour à l'application"
 
@@ -202,7 +217,7 @@
 msgid "Calendar"
 msgstr "Calendrier"
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:338
+#: lib/Jifty/View/Declare/CRUD.pm:419
 msgid "Cancel"
 msgstr "Annuler"
 
@@ -214,44 +229,48 @@
 msgid "Close window"
 msgstr "Fermer la fenêtre"
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:382
+#:
 msgid "Confirm delete?"
-msgstr "Confirmer l'effacement ?"
+msgstr "Confirmer la suppresion ?"
 
-#: lib/Jifty/Plugin/Authentication/Password/Action/SendAccountConfirmation.pm:97
+#: lib/Jifty/Plugin/Authentication/Password/Action/SendAccountConfirmation.pm:94
 msgid "Confirmation resent."
 msgstr "Confirmation transmise."
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:213
+#: lib/Jifty/Plugin/OpenID/View.pm:65
+msgid "Continue"
+msgstr "Continuer"
+
+#: lib/Jifty/View/Declare/.CRUD.pm.swp:17 lib/Jifty/View/Declare/CRUD.pm:675
 msgid "Create"
 msgstr "Enregistrer"
 
-#: lib/Jifty/Action/Record/Create.pm:81
+#: lib/Jifty/Action/Record/Create.pm:91
 #. (ref($record)
 msgid "Create of %1 failed: %2"
 msgstr "La création de %1 a échoué : %2"
 
-#: lib/Jifty/Action/Record/Create.pm:105
+#: lib/Jifty/Action/Record/Create.pm:118
 msgid "Created"
-msgstr "Créé"
+msgstr "Création réussie"
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:415
+#:
 msgid "Database Administration"
 msgstr "Administration de la Base de Données"
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:380
+#: lib/Jifty/View/Declare/CRUD.pm:429
 msgid "Delete"
 msgstr "Effacer"
 
-#: lib/Jifty/Action/Record/Delete.pm:76
+#: lib/Jifty/Action/Record/Delete.pm:82
 msgid "Deleted"
-msgstr "Effacé"
+msgstr "Suppression réussie"
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:458 lib/Jifty/Plugin/AdminUI/View-not-yet.pm:486 lib/Jifty/Plugin/AdminUI/View-not-yet.pm:56
+#:
 msgid "Done?"
 msgstr "Fini ?"
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:388 share/web/templates/__jifty/halo:126 share/web/templates/__jifty/halo:20
+#: lib/Jifty/View/Declare/CRUD.pm:349 share/web/templates/__jifty/halo:126 share/web/templates/__jifty/halo:20
 msgid "Edit"
 msgstr "Editer"
 
@@ -264,25 +283,25 @@
 msgid "Email"
 msgstr "Mail"
 
-#: lib/Jifty/Plugin/User/Mixin/Model/User.pm:33
+#: lib/Jifty/Plugin/User/Mixin/Model/User.pm:52
 msgid "Email address"
 msgstr "Adresse mail"
 
-#: lib/Jifty/Plugin/User/Mixin/Model/User.pm:35
+#: lib/Jifty/Plugin/User/Mixin/Model/User.pm:54
 msgid "Email address confirmed?"
 msgstr "Adresse mail confirmée ?"
 
-#: lib/Jifty/Action.pm:1158
+#: lib/Jifty/Action.pm:1312
 msgid "Foo cannot contain -, *, +, or ?."
 msgstr "Cette donnée ne doit pas contenir -, *, +, ou ?."
 
-#: lib/Jifty/Action.pm:1152
+#: lib/Jifty/Action.pm:1306
 msgid "Foo cannot contain uppercase letters."
 msgstr "Cette donnée ne doit pas contenir de majuscule."
 
-#: lib/Jifty/Action.pm:1136
+#: lib/Jifty/Action.pm:1285
 msgid "Foo values are always in lowercase."
-msgstr "Cett donnée doit toujours être en minuscule."
+msgstr "Cette donnée doit toujours être en minuscule."
 
 #: lib/Jifty/Plugin/ErrorTemplates/View.pm:27
 msgid "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 do have is :"
@@ -292,52 +311,79 @@
 msgid "Go back home..."
 msgstr "Retour..."
 
+#: lib/Jifty/Plugin/OpenID/View.pm:35
+msgid "Go for it!"
+msgstr ""
+
 #: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:34
 msgid "Hashed Password"
 msgstr "Mot de passe crypté"
 
 #: lib/Jifty/Plugin/ErrorTemplates/View.pm:44
 msgid "Head on back home"
-msgstr ""
+msgstr "Retour accueil"
 
 #: lib/Jifty/I18N.pm:19 lib/Jifty/I18N.pm:23
 #. ('World')
 msgid "Hello, %1!"
 msgstr "Bonjour, %1"
 
-#: lib/Jifty/Plugin/SkeletonApp/View.pm:31 share/web/templates/_elements/sidebar:5
-#. ($u->$method()
+#: lib/Jifty/Plugin/Authentication/Facebook/Action/LoginFacebookUser.pm:103
+#. (Jifty->web->current_user->user_object->facebook_name)
+msgid "Hi %1!"
+msgstr "Bonjour %1"
+
+#: lib/Jifty/Plugin/I18N/Action/SetLang.pm:44
+#. (DateTime::Locale->load($lang)
+msgid "Hi, we speak %1."
+msgstr "Bonjour, langue disponible : %1."
+
+#: lib/Jifty/Plugin/SkeletonApp/View.pm:31 share/web/templates/_elements/sidebar:4
+#. ($u->username)
+#. (Jifty->web->current_user->username)
 msgid "Hiya, %1."
 msgstr "Bonjour, %1"
 
-#: lib/Jifty/Plugin/SkeletonApp/Dispatcher.pm:23
+#: lib/Jifty/Plugin/SkeletonApp/Dispatcher.pm:26
 msgid "Home"
-msgstr ""
+msgstr "Accueil"
 
-#: lib/Jifty/Plugin/User/Mixin/Model/User.pm:30
+#: lib/Jifty/Plugin/User/Mixin/Model/User.pm:49
 msgid "How should I display your name to other users?"
 msgstr "Nom affiché"
 
-#: lib/Jifty/Action.pm:1090
+#: lib/Jifty/Action.pm:1212
 msgid "I changed $field for you"
-msgstr ""
+msgstr "$field a été changé"
 
 #: lib/Jifty/Plugin/Authentication/Password/Action/ResetLostPassword.pm:64
 msgid "I'm not sure how this happened."
 msgstr ""
 
+#: lib/Jifty/Plugin/OpenID/View.pm:59
+msgid "If the username provided conflicts with an existing username or contains invalid characters, you will have to give us a new one."
+msgstr "Si ce nom d'utilisateur entre en conflit avec un nom existant ou contient des caractères non autorisés, vous devrez le changer."
+
 #: lib/Jifty/Plugin/ErrorTemplates/View.pm:62
 msgid "Internal error"
 msgstr "Erreur interne"
 
-#: lib/Jifty/Plugin/Authentication/Password/Action/SendAccountConfirmation.pm:78 lib/Jifty/Plugin/Authentication/Password/Action/SendPasswordReminder.pm:79
+#: lib/Jifty/Plugin/OpenID/Action/AuthenticateOpenID.pm:59
+msgid "Invalid OpenID URL.  Please check to make sure it is correct.  (@{[$csr->err]})"
+msgstr "URL OpenID incorrecte. Vérifiez la. (@{[$csr->err]}) "
+
+#: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:63 lib/Jifty/Plugin/Authentication/Password/Action/SendAccountConfirmation.pm:75 lib/Jifty/Plugin/Authentication/Password/Action/SendPasswordReminder.pm:79
 msgid "It doesn't look like there's an account by that name."
 msgstr "Il ne semble pas y avoir de compte sous ce nom."
 
-#: lib/Jifty/Plugin/User/Mixin/Model/User.pm:89
+#: lib/Jifty/Plugin/User/Mixin/Model/User.pm:106
 msgid "It looks like somebody else is using that address. Is there a chance you have another account?"
 msgstr "Cette adresse semble utilisée par quelqu'un d'autre. Vous avez peut-être un autre compte ?"
 
+#: lib/Jifty/Plugin/OpenID/Dispatcher.pm:43
+msgid "It looks like someone is already using that OpenID."
+msgstr "Cette URL OpenID est déjà utilisée par un autre utilisateur."
+
 #: lib/Jifty/Plugin/Authentication/Password/Action/Signup.pm:75
 msgid "It looks like you already have an account. Perhaps you want to <a href=\"/login\">log in</a> instead?"
 msgstr "Votre compte existe déjà . Vous voulez peut-être plutôt <a href=\"/login\">vous connecter</a> ?"
@@ -346,84 +392,115 @@
 msgid "It looks like you didn't enter the same password into both boxes. Give it another shot?"
 msgstr "Les mots de passe sont différents. Ré-essayer ?"
 
-#: lib/Jifty/Plugin/Authentication/Password/Action/SendAccountConfirmation.pm:82
+#: lib/Jifty/Plugin/Authentication/Password/Action/SendAccountConfirmation.pm:79
 msgid "It looks like you're already confirmed."
-msgstr "Votre adresse à déjà été confirmée."
+msgstr "Votre adresse a déjà été confirmée."
 
-#: lib/Jifty/View/Declare/Helpers.pm:375 share/web/templates/_elements/wrapper:18
+#: lib/Jifty/Plugin/I18N/Action/SetLang.pm:12
+msgid "Language"
+msgstr "Langue"
+
+#: lib/Jifty/View/Declare/Page.pm:194 share/web/templates/_elements/wrapper:18
 msgid "Loading..."
 msgstr "Chargement..."
 
-#: lib/Jifty/Plugin/Authentication/Password/Dispatcher.pm:106
+#: lib/Jifty/Plugin/Authentication/Password/Dispatcher.pm:132
 msgid "Login"
 msgstr "Identification"
 
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:55
+#: lib/Jifty/Plugin/Authentication/Facebook/View.pm:24
+msgid "Login to Facebook now to get started!"
+msgstr "Connection à Facebook démarrée !"
+
+#: lib/Jifty/Plugin/Authentication/Facebook/View.pm:41
+msgid "Login to Facebook now to link it with your current account!"
+msgstr "Votre connection à Facebook doit maintenant être liée à votre compte courant !"
+
+#: lib/Jifty/Plugin/Authentication/Password/View.pm:82
 msgid "Login with a password"
 msgstr "S'identifier avec un mot de passe"
 
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:40
+#: lib/Jifty/Plugin/OpenID/View.pm:17
+msgid "Login with your OpenID"
+msgstr "S'identifier avec votre OpenID"
+
+#: lib/Jifty/Plugin/Authentication/Password/View.pm:57
 msgid "Login!"
 msgstr "Identification"
 
-#: lib/Jifty/Plugin/Authentication/Password/Dispatcher.pm:123
+#: lib/Jifty/Plugin/Authentication/Password/Dispatcher.pm:151
 msgid "Logout"
 msgstr "Déconnection"
 
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:63
+#: lib/Jifty/Plugin/Authentication/Password/View.pm:90
 msgid "Lost your password?"
 msgstr "Vous avez oublié votre mot de passe ?"
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:46 lib/Jifty/Plugin/AdminUI/View-not-yet.pm:473
-#. ($object_type)
-msgid "Manage records: [_1]"
-msgstr ""
-
 #: share/web/templates/__jifty/error/mason_internal_error:1
 msgid "Mason error"
 msgstr "Erreur Mason"
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:429
+#:
 msgid "Models"
 msgstr "Modèles"
 
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:81
+#: lib/Jifty/Plugin/Authentication/Password/View.pm:114
 msgid "New password"
 msgstr "Nouveau mot de passe"
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:172
+#: lib/Jifty/View/Declare/CRUD.pm:631
 msgid "Next Page"
 msgstr "Page Suivante"
 
-#: lib/Jifty/Plugin/User/Mixin/Model/User.pm:29
+#: lib/Jifty/Plugin/User/Mixin/Model/User.pm:48
 msgid "Nickname"
-msgstr "Surnom"
+msgstr "Identifiant"
 
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:52
+#: lib/Jifty/Plugin/Authentication/Password/View.pm:79
 msgid "No account yet? It's quick and easy. "
 msgstr "Pas encore de compte ? C'est facile et rapide. "
 
-#: lib/Jifty/Action/Record/Search.pm:130
+#: lib/Jifty/Action/Record/Search.pm:178
 msgid "No field contains"
 msgstr "Aucun champs contient"
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:139
+#:
 msgid "No items found"
 msgstr "Aucun enregistrement"
 
-#: lib/Jifty/Web.pm:302
+#: lib/Jifty/View/Declare/CRUD.pm:550
+msgid "No items found."
+msgstr "Aucun enregistrement."
+
+#: lib/Jifty/Web.pm:308
 msgid "No request to handle"
 msgstr "Aucune requête à traiter"
 
+#: lib/Jifty/Plugin/UUID/Widget.pm:37
+msgid "No value yet"
+msgstr "Aucune réponse"
+
 #:
 msgid "Online Documentation"
 msgstr "Documentation en ligne"
 
 #: lib/Jifty/Plugin/OnlineDocs/Dispatcher.pm:26
 msgid "Online docs"
-msgstr ""
+msgstr "Documentation en ligne"
+
+#: lib/Jifty/Plugin/OpenID/Action/AuthenticateOpenID.pm:27
+msgid "OpenID URL"
+msgstr "URL OpenID"
+
+#: lib/Jifty/Plugin/OpenID/Action/VerifyOpenID.pm:54
+msgid "OpenID verification failed.  It looks like you cancelled the OpenID verification request."
+msgstr "Echec de la vérification OpenID. Il semblerait que vous avez annulé votre demande de vérification."
+
+#: lib/Jifty/Plugin/OpenID/Action/VerifyOpenID.pm:63
+msgid "OpenID verification failed: "
+msgstr "Echec de la vérification OpenID: "
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:134
+#: lib/Jifty/View/Declare/CRUD.pm:598
 #. ($page, $collection->pager->last_page)
 msgid "Page %1 of %2"
 msgstr "Page %1 / %2"
@@ -436,15 +513,15 @@
 msgid "Parent"
 msgstr ""
 
-#: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:29 lib/Jifty/Plugin/Authentication/Password/Action/ResetLostPassword.pm:32 lib/Jifty/Plugin/Authentication/Password/Mixin/Model/User.pm:26
+#: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:29 lib/Jifty/Plugin/Authentication/Password/Action/ResetLostPassword.pm:32 lib/Jifty/Plugin/Authentication/Password/Mixin/Model/User.pm:59
 msgid "Password"
 msgstr "Mot de passe"
 
-#: lib/Jifty/Plugin/Authentication/Password/Mixin/Model/User.pm:88
+#: lib/Jifty/Plugin/Authentication/Password/Mixin/Model/User.pm:134
 msgid "Passwords need to be at least six characters long"
 msgstr "Il faut au minimum six caractères"
 
-#: lib/Jifty/Record.pm:272 lib/Jifty/Record.pm:351 lib/Jifty/Record.pm:70
+#: lib/Jifty/Record.pm:505 lib/Jifty/Record.pm:550 lib/Jifty/Record.pm:70
 msgid "Permission denied"
 msgstr "Autorisation refusée"
 
@@ -452,13 +529,17 @@
 msgid "Please email us!"
 msgstr "Envoyez nous un mail !"
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:163
+#: lib/Jifty/View/Declare/CRUD.pm:620
 msgid "Previous Page"
 msgstr "Page précédente"
 
+#: lib/Jifty/View/Declare/CRUD.pm:432
+msgid "Really delete?"
+msgstr "Confirmer l'effacement ?"
+
 #: lib/Jifty/Plugin/Authentication/Password/Action/ResetLostPassword.pm:65
 msgid "Really, really sorry."
-msgstr "Vraiment désolé"
+msgstr "Vraiment désolé."
 
 #:
 msgid "Record created"
@@ -473,11 +554,11 @@
 msgid "Rendered in %1s"
 msgstr ""
 
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:78
+#:
 msgid "Reset lost password"
 msgstr "Changement de mot de passe"
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:53
+#:
 msgid "Run the action"
 msgstr "Exécuter l'action"
 
@@ -485,7 +566,7 @@
 msgid "SQL Statements"
 msgstr ""
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:327
+#: lib/Jifty/View/Declare/CRUD.pm:410
 msgid "Save"
 msgstr "Enregistrer"
 
@@ -493,31 +574,35 @@
 msgid "Schema"
 msgstr "Schéma"
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:304
+#: lib/Jifty/View/Declare/CRUD.pm:298
 msgid "Search"
 msgstr "Chercher"
 
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:119
+#: lib/Jifty/Plugin/Authentication/Password/View.pm:180
 msgid "Send"
 msgstr "Enregistrer"
 
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:97
+#:
 msgid "Send a link to reset your password"
 msgstr "Recevoir un lien pour changer votre mot de passe"
 
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:109 lib/Jifty/Plugin/Authentication/Password/View.pm:114
+#: lib/Jifty/Plugin/Authentication/Password/View.pm:175
 msgid "Send a password reminder"
 msgstr ""
 
-#: lib/Jifty/Plugin/Authentication/Password/Dispatcher.pm:111
+#: lib/Jifty/Plugin/Feedback/View.pm:21
+msgid "Send us feedback!"
+msgstr "Envoyez nous votre avis !"
+
+#: lib/Jifty/Plugin/Authentication/Password/Dispatcher.pm:137 lib/Jifty/Plugin/Authentication/Password/View.pm:42 lib/Jifty/Plugin/Authentication/Password/View.pm:47
 msgid "Sign up"
 msgstr "S'enregistrer"
 
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:53
+#: lib/Jifty/Plugin/Authentication/Password/View.pm:80
 msgid "Sign up for an account!"
 msgstr "Enregistrez vous pour avoir un compte !"
 
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:30 lib/Jifty/Plugin/Authentication/Password/View.pm:35
+#:
 msgid "Signup"
 msgstr "Enregistrement"
 
@@ -538,32 +623,45 @@
 msgid "Sorry about this."
 msgstr "Désolé."
 
+#: lib/Jifty/Plugin/Authentication/Facebook/Action/LoginFacebookUser.pm:62
+msgid "Sorry, something weird happened (we couldn't create a user for you).  Try again later."
+msgstr "Désolé, un problème nous empêche de créer cet utilisateur. Ré-essayer plus tard."
+
 #:
 msgid "Table of Contents"
 msgstr "Table des matières"
 
-#: lib/Jifty/Plugin/User/Mixin/Model/User.pm:82
+#: lib/Jifty/Plugin/Feedback/View.pm:23
+#. (Jifty->config->framework('ApplicationName')
+msgid "Tell us what's good, what's bad, and what else you want %1 to do!"
+msgstr "Dites nous ce qui vous plaît, vous déplaît, ou tout autre avis %1"
+
+#: lib/Jifty/Plugin/User/Mixin/Model/User.pm:99
 #. ($new_email)
 msgid "That %1 doesn't look like an email address."
 msgstr "%1 ne ressemble pas à une adresse mail."
 
-#: lib/Jifty/Action.pm:878
+#: lib/Jifty/Action.pm:975
 msgid "That doesn't look like a correct value"
 msgstr "Valeur incorecte"
 
-#: lib/Jifty/Plugin/Authentication/Password/Action/SendAccountConfirmation.pm:71 lib/Jifty/Plugin/Authentication/Password/Action/SendPasswordReminder.pm:72 lib/Jifty/Plugin/Authentication/Password/Action/Signup.pm:70
+#: lib/Jifty/Plugin/Authentication/Password/Action/SendAccountConfirmation.pm:68 lib/Jifty/Plugin/Authentication/Password/Action/SendPasswordReminder.pm:72 lib/Jifty/Plugin/Authentication/Password/Action/Signup.pm:70
 msgid "That doesn't look like an email address."
 msgstr "Cela ne ressemble pas à une adresse mail."
 
-#: lib/Jifty/Action/Record.pm:249
+#: lib/Jifty/Action/Record.pm:304
 msgid "That doesn't look right, but I don't know why"
 msgstr "Problème"
 
-#: lib/Jifty/Action/Record.pm:181
+#: lib/Jifty/Plugin/OpenID/Dispatcher.pm:48
+msgid "The OpenID '$openid' has been linked to your account."
+msgstr "L'openID '$openid' a été attaché à votre compte."
+
+#: lib/Jifty/Action/Record.pm:205
 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:365
+#: lib/Jifty/Web.pm:429
 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."
 
@@ -573,17 +671,17 @@
 
 #: lib/Jifty/Plugin/ErrorTemplates/View.pm:37
 msgid "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."
-msgstr ""
+msgstr "Il y a de fortes chances pour que ce message d'erreur ne veuille rien dire pour vous, mais nous préférons vous afficher ce laïus plutôt que rien. Nous avons enregistré cette erreur, nous n'avons plus qu'à écrire un message clair pour vous expliquer le problème et comment le régler. "
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:419
+#:
 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."
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:425
+#:
 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)."
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:127
+#: lib/Jifty/View/Declare/CRUD.pm:518
 msgid "Toggle search"
 msgstr "Rechercher"
 
@@ -599,7 +697,7 @@
 msgid "Type that again?"
 msgstr "Confirmez"
 
-#: lib/Jifty/Action/Record/Update.pm:156
+#: lib/Jifty/Action/Record/Update.pm:170
 msgid "Updated"
 msgstr "Modification"
 
@@ -611,6 +709,10 @@
 msgid "W00t"
 msgstr ""
 
+#: lib/Jifty/Plugin/OpenID/View.pm:53
+msgid "We need you to set a username or quickly check the one associated with your OpenID. Your username is what other people will see when you ask questions or make suggestions"
+msgstr "Vous devez préciser un nom d'utilisateur ou vérifier celui associé à votre OpenID. Votre nom d'utilisateur sera présenté lorsque rédigez un commentaire"
+
 #: lib/Jifty/Plugin/Authentication/Password/Action/Signup.pm:114
 msgid "We've sent a confirmation message to your email box."
 msgstr "Nous vous avons envoyé un mail de confirmation."
@@ -620,7 +722,11 @@
 msgid "Welcome %1 to the %2"
 msgstr "Bienvenue %1 sur %2"
 
-#: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:181
+#: lib/Jifty/Plugin/OpenID/Dispatcher.pm:64
+msgid "Welcome back, "
+msgstr "Re-bonjour, "
+
+#: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:182
 #. ($user->name)
 msgid "Welcome back, %1."
 msgstr "Re-bonjour, %1."
@@ -630,13 +736,12 @@
 msgid "Welcome to %1!"
 msgstr "Bienvenue sur %1 !"
 
-#: lib/Jifty/Plugin/Authentication/Password/Action/Signup.pm:113
+#: lib/Jifty/Plugin/Authentication/Password/Action/ConfirmEmail.pm:57 lib/Jifty/Plugin/Authentication/Password/Action/Signup.pm:113
 #. (Jifty->config->framework('ApplicationName')
 msgid "Welcome to %1, %2."
 msgstr "Bienvenue sur %1, %2."
 
-#: lib/Jifty/Plugin/Authentication/Password/Action/ConfirmEmail.pm:57
-#. (Jifty->config->framework('ApplicationName')
+#:
 msgid "Welcome to %1, %2. "
 msgstr "Bienvenue sur %1, %2. "
 
@@ -644,6 +749,14 @@
 msgid "Welcome to your new Jifty application"
 msgstr "Bienvenue sur votre nouvelle application Jifty"
 
+#: lib/Jifty/Plugin/OpenID/Action/CreateOpenIDUser.pm:104
+msgid "Welcome, "
+msgstr "Bienvenue, "
+
+#: lib/Jifty/Plugin/OpenID/View.pm:42
+msgid "You already logged in."
+msgstr "Vous êtes déjà connecté."
+
 #: lib/Jifty/Plugin/Authentication/Password/Action/ResetLostPassword.pm:63
 msgid "You don't exist."
 msgstr "Votre compte n'existe pas."
@@ -660,23 +773,27 @@
 msgid "You have already confirmed your account."
 msgstr "Votre compte à déjà été confirmé."
 
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:98
+#: lib/Jifty/Plugin/Authentication/Password/View.pm:146
 msgid "You lost your password. A link to reset it will be sent to the following email address:"
 msgstr "Vous avez perdu votre mot de passe. Un lien pour changer ce mot de passe sera envoyé à l'adresse suivante :"
 
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:115
+#: lib/Jifty/Plugin/Authentication/Password/View.pm:176
 msgid "You lost your password. A reminder will be send to the following mail:"
 msgstr "Vous avez perdu votre mot de passe. Un lien pour changer ce mot de passe sera envoyé à l'adresse suivante :"
 
-#: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:152
+#:
 msgid "You may have mistyped your email address or password. Give it another shot."
 msgstr "Vous vous êtes trompé dans votre mail ou votre mot de passe. Ré-essayez."
 
-#: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:146
+#: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:146 lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:152
 msgid "You may have mistyped your email or password. Give it another shot."
 msgstr "Vous vous êtes trompé dans votre mail ou votre mot de passe. Ré-essayez."
 
-#: lib/Jifty/Action.pm:865
+#: lib/Jifty/Plugin/Authentication/Facebook/Action/LinkFacebookUser.pm:40
+msgid "You must be logged in to link your user to your Facebook account."
+msgstr "Vous devez être connecté pour lier votre compte d'utilisateur à votre compte Facebook."
+
+#: lib/Jifty/Action.pm:962
 msgid "You need to fill in this field"
 msgstr "Information obligatoire"
 
@@ -685,14 +802,19 @@
 msgid "You said you wanted a pony. (Source %1)"
 msgstr ""
 
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:69
+#: lib/Jifty/Plugin/Authentication/Password/View.pm:96
 msgid "You're already logged in."
 msgstr "Vous êtes déjà connecté."
 
-#: lib/Jifty/Plugin/SkeletonApp/View.pm:34 share/web/templates/_elements/sidebar:7
+#: lib/Jifty/Plugin/SkeletonApp/View.pm:34 share/web/templates/_elements/sidebar:6
 msgid "You're not currently signed in."
 msgstr "Vous n'êtes pas encore connecté."
 
+#: lib/Jifty/Plugin/Authentication/Facebook/Action/LinkFacebookUser.pm:72
+#. (Jifty->web->current_user->user_object->facebook_name)
+msgid "Your account has been successfully linked to your Facebook user %1!"
+msgstr "Votre compte a bien été lié à votre compte Facebook %1"
+
 #: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:38
 msgid "Your browser can remember your login for you"
 msgstr "Votre navigateur peut mémoriser cette identification"
@@ -701,18 +823,10 @@
 msgid "Your password has been reset.  Welcome back."
 msgstr "Votre mot de passe à été changé."
 
-#: lib/Jifty/Plugin/Authentication/Password/Mixin/Model/User.pm:28
+#: lib/Jifty/Plugin/Authentication/Password/Mixin/Model/User.pm:61
 msgid "Your password should be at least six characters"
 msgstr "Votre mot de passe doit contenir au moins six caractères"
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:246
-msgid "asc"
-msgstr ""
-
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:264
-msgid "desc"
-msgstr ""
-
 #: lib/Jifty/Plugin/Authentication/Password/Action/SendAccountConfirmation.pm:31
 msgid "email address"
 msgstr "adresse mail"
@@ -721,6 +835,10 @@
 msgid "for now, and try to forget that we let you down."
 msgstr ""
 
+#: lib/Jifty/Action/Record.pm:253
+msgid "no value"
+msgstr ""
+
 #: lib/Jifty/Manual/PageRegions.pod:188
 msgid "text of the link"
 msgstr ""
@@ -733,6 +851,6 @@
 msgid "type your password again"
 msgstr "confirmez votre mot de passe"
 
-#: lib/Jifty/Action.pm:1050
+#: lib/Jifty/Action.pm:1172
 msgid "warning"
 msgstr ""

Modified: jifty/branches/js-refactor/share/po/ja.po
==============================================================================
--- jifty/branches/js-refactor/share/po/ja.po	(original)
+++ jifty/branches/js-refactor/share/po/ja.po	Thu Nov 29 12:25:16 2007
@@ -1,727 +1,839 @@
-# 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/Plugin/Authentication/Password/Notification/ConfirmLostPassword.pm:38
-#. ($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/Authentication/Password/Notification/ConfirmEmail.pm:41
-#. ($appname,$confirm_url)
-msgid ""
-"\n"
-"You're getting this message because you (or somebody claiming to be you)\n"
-"wants to use %1. \n"
-"\n"
-"We need to make sure that we got your email address right.  Click on the link below to get started:\n"
-"\n"
-"%2\n"
-msgstr ""
-
-#: lib/Jifty/Action/Record/Search.pm:125
-msgid "!=>< allowed"
-msgstr "! = > < は利用できません"
-
-#: lib/Jifty/Notification.pm:94
-#. ($appname, Jifty->config->framework('AdminEmail')
-msgid "%1 <%2>"
-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 を含む"
-
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:141
-#. ($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/Plugin/Authentication/Password/Action/ConfirmEmail.pm:58
-msgid ". Your email address has now been confirmed."
-msgstr ""
-
-#: lib/Jifty/Plugin/Authentication/Password/Action/SendPasswordReminder.pm:96
-msgid "A link to reset your password has been sent to your email account."
-msgstr ""
-
-#: lib/Jifty/Notification.pm:96
-#. ($appname)
-msgid "A notification from %1!"
-msgstr ""
-
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:443
-msgid "Actions"
-msgstr "操作"
-
-#: lib/Jifty/Plugin/SkeletonApp/Dispatcher.pm:28
-msgid "Administration"
-msgstr ""
-
-#: lib/Jifty/View/Declare/Helpers.pm:363 share/web/templates/_elements/wrapper:11
-msgid "Administration mode is enabled."
-msgstr "管理モードが有効になっています"
-
-#: lib/Jifty/View/Declare/Helpers.pm:360 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 "検索したい文字列"
-
-#: lib/Jifty/Plugin/ErrorTemplates/View.pm:131
-msgid "Anyway, the software has logged this error."
-msgstr ""
-
-#: lib/Jifty/Plugin/Authentication/Password/Mixin/Model/User.pm:19
-msgid "Authentication token"
-msgstr ""
-
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:489 lib/Jifty/Plugin/AdminUI/View-not-yet.pm:59
-msgid "Back to the admin console"
-msgstr "管理画面に戻る"
-
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:461
-msgid "Back to the application"
-msgstr "アプリケーションに戻る"
-
-#: share/web/templates/__jifty/halo:117
-msgid "Bindings"
-msgstr ""
-
-#: share/web/templates/helpers/calendar.html:4
-#. (_ &><body class="calpopup"><a href="#" onclick="window.close()
-msgid "Calendar"
-msgstr "カレンダー"
-
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:338
-msgid "Cancel"
-msgstr "キャンセル"
-
-#: share/web/templates/__jifty/halo:81
-msgid "Children"
-msgstr ""
-
-#: share/web/templates/helpers/calendar.html:4
-msgid "Close window"
-msgstr "閉じる"
-
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:382
-msgid "Confirm delete?"
-msgstr "本当に削除しますか"
-
-#: lib/Jifty/Plugin/Authentication/Password/Action/SendAccountConfirmation.pm:97
-msgid "Confirmation resent."
-msgstr ""
-
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:213
-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 "作成しました"
-
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:415
-msgid "Database Administration"
-msgstr "データベース管理"
-
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:380
-msgid "Delete"
-msgstr "削除"
-
-#: lib/Jifty/Action/Record/Delete.pm:76
-msgid "Deleted"
-msgstr "削除しました"
-
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:458 lib/Jifty/Plugin/AdminUI/View-not-yet.pm:486 lib/Jifty/Plugin/AdminUI/View-not-yet.pm:56
-msgid "Done?"
-msgstr ""
-
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:388 share/web/templates/__jifty/halo:126 share/web/templates/__jifty/halo:20
-msgid "Edit"
-msgstr "編集"
-
-#: lib/Jifty/Mason/Halo.pm:104
-#. ($comp_name)
-msgid "Edit %1"
-msgstr "%1 を編集"
-
-#: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:23 lib/Jifty/Plugin/Authentication/Password/Action/SendPasswordReminder.pm:32
-msgid "Email"
-msgstr ""
-
-#: lib/Jifty/Plugin/User/Mixin/Model/User.pm:33
-msgid "Email address"
-msgstr ""
-
-#: lib/Jifty/Plugin/User/Mixin/Model/User.pm:35
-msgid "Email address confirmed?"
-msgstr ""
-
-#: lib/Jifty/Action.pm:1158
-msgid "Foo cannot contain -, *, +, or ?."
-msgstr ""
-
-#: lib/Jifty/Action.pm:1152
-msgid "Foo cannot contain uppercase letters."
-msgstr ""
-
-#: lib/Jifty/Action.pm:1136
-msgid "Foo values are always in lowercase."
-msgstr ""
-
-#: lib/Jifty/Plugin/ErrorTemplates/View.pm:27
-msgid "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 do have is :"
-msgstr ""
-
-#: lib/Jifty/Plugin/ErrorTemplates/View.pm:138 share/web/templates/dhandler:7
-msgid "Go back home..."
-msgstr "ホームページに戻ります…"
-
-#: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:34
-msgid "Hashed Password"
-msgstr ""
-
-#: lib/Jifty/Plugin/ErrorTemplates/View.pm:44
-msgid "Head on back home"
-msgstr ""
-
-#: lib/Jifty/I18N.pm:19 lib/Jifty/I18N.pm:23
-#. ('World')
-msgid "Hello, %1!"
-msgstr ""
-
-#: lib/Jifty/Plugin/SkeletonApp/View.pm:31 share/web/templates/_elements/sidebar:5
-#. ($u->$method()
-msgid "Hiya, %1."
-msgstr "%1 さん、こんにちは"
-
-#: lib/Jifty/Plugin/SkeletonApp/Dispatcher.pm:23
-msgid "Home"
-msgstr ""
-
-#: lib/Jifty/Plugin/User/Mixin/Model/User.pm:30
-msgid "How should I display your name to other users?"
-msgstr ""
-
-#: lib/Jifty/Action.pm:1090
-msgid "I changed $field for you"
-msgstr ""
-
-#: lib/Jifty/Plugin/Authentication/Password/Action/ResetLostPassword.pm:64
-msgid "I'm not sure how this happened."
-msgstr ""
-
-#: lib/Jifty/Plugin/ErrorTemplates/View.pm:62
-msgid "Internal error"
-msgstr ""
-
-#: lib/Jifty/Plugin/Authentication/Password/Action/SendAccountConfirmation.pm:78 lib/Jifty/Plugin/Authentication/Password/Action/SendPasswordReminder.pm:79
-msgid "It doesn't look like there's an account by that name."
-msgstr ""
-
-#: lib/Jifty/Plugin/User/Mixin/Model/User.pm:89
-msgid "It looks like somebody else is using that address. Is there a chance you have another account?"
-msgstr ""
-
-#: lib/Jifty/Plugin/Authentication/Password/Action/Signup.pm:75
-msgid "It looks like you already have an account. Perhaps you want to <a href=\"/login\">log in</a> instead?"
-msgstr ""
-
-#: lib/Jifty/Plugin/Authentication/Password/Action/ResetLostPassword.pm:75
-msgid "It looks like you didn't enter the same password into both boxes. Give it another shot?"
-msgstr ""
-
-#: lib/Jifty/Plugin/Authentication/Password/Action/SendAccountConfirmation.pm:82
-msgid "It looks like you're already confirmed."
-msgstr ""
-
-#:
-msgid "Jifty Developer Documentation Online"
-msgstr "開発者向けオンラインドキュメント"
-
-#:
-msgid "Jifty Pod Online"
-msgstr "オンラインドキュメント"
-
-#: lib/Jifty/View/Declare/Helpers.pm:375 share/web/templates/_elements/wrapper:18
-msgid "Loading..."
-msgstr "読込み中…"
-
-#: lib/Jifty/Plugin/Authentication/Password/Dispatcher.pm:106
-msgid "Login"
-msgstr ""
-
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:55
-msgid "Login with a password"
-msgstr ""
-
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:40
-msgid "Login!"
-msgstr ""
-
-#: lib/Jifty/Plugin/Authentication/Password/Dispatcher.pm:123
-msgid "Logout"
-msgstr ""
-
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:63
-msgid "Lost your password?"
-msgstr ""
-
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:46 lib/Jifty/Plugin/AdminUI/View-not-yet.pm:473
-#. ($object_type)
-msgid "Manage records: [_1]"
-msgstr ""
-
-#: share/web/templates/__jifty/error/mason_internal_error:1
-msgid "Mason error"
-msgstr "Mason エラー"
-
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:429
-msgid "Models"
-msgstr "モデル"
-
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:81
-msgid "New password"
-msgstr ""
-
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:172
-msgid "Next Page"
-msgstr "次のページ"
-
-#: lib/Jifty/Plugin/User/Mixin/Model/User.pm:29
-msgid "Nickname"
-msgstr ""
-
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:52
-msgid "No account yet? It's quick and easy. "
-msgstr ""
-
-#: lib/Jifty/Action/Record/Search.pm:130
-msgid "No field contains"
-msgstr "排除したい文字列"
-
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:139
-msgid "No items found"
-msgstr "見つかりませんでした"
-
-#: lib/Jifty/Web.pm:302
-msgid "No request to handle"
-msgstr "処理できるリクエストがありません"
-
-#:
-msgid "Online Documentation"
-msgstr "オンラインドキュメント"
-
-#: lib/Jifty/Plugin/OnlineDocs/Dispatcher.pm:26
-msgid "Online docs"
-msgstr ""
-
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:134
-#. ($page, $collection->pager->last_page)
-msgid "Page %1 of %2"
-msgstr "%1 / %2 ページ"
-
-#: share/web/templates/__jifty/halo:1
-msgid "Page info"
-msgstr ""
-
-#: share/web/templates/__jifty/halo:72
-msgid "Parent"
-msgstr ""
-
-#: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:29 lib/Jifty/Plugin/Authentication/Password/Action/ResetLostPassword.pm:32 lib/Jifty/Plugin/Authentication/Password/Mixin/Model/User.pm:26
-msgid "Password"
-msgstr ""
-
-#: lib/Jifty/Plugin/Authentication/Password/Mixin/Model/User.pm:88
-msgid "Passwords need to be at least six characters long"
-msgstr ""
-
-#: lib/Jifty/Record.pm:272 lib/Jifty/Record.pm:351 lib/Jifty/Record.pm:70
-msgid "Permission denied"
-msgstr "権限がありません"
-
-#: lib/Jifty/Plugin/Authentication/Password/Action/ResetLostPassword.pm:66
-msgid "Please email us!"
-msgstr ""
-
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:163
-msgid "Previous Page"
-msgstr "前のページ"
-
-#: lib/Jifty/Plugin/Authentication/Password/Action/ResetLostPassword.pm:65
-msgid "Really, really sorry."
-msgstr ""
-
-#: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:37
-msgid "Remember me?"
-msgstr ""
-
-#: share/web/templates/__jifty/halo:69
-#. ($frame->{'render_time'})
-msgid "Rendered in %1s"
-msgstr ""
-
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:78
-msgid "Reset lost password"
-msgstr ""
-
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:53
-msgid "Run the action"
-msgstr "実行"
-
-#: share/web/templates/__jifty/halo:111
-msgid "SQL Statements"
-msgstr "SQL文"
-
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:327
-msgid "Save"
-msgstr "保存"
-
-#:
-msgid "Schema"
-msgstr "スキーマ"
-
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:304
-msgid "Search"
-msgstr "検索"
-
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:119
-msgid "Send"
-msgstr ""
-
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:97
-msgid "Send a link to reset your password"
-msgstr ""
-
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:109 lib/Jifty/Plugin/Authentication/Password/View.pm:114
-msgid "Send a password reminder"
-msgstr ""
-
-#: lib/Jifty/Plugin/Authentication/Password/Dispatcher.pm:111
-msgid "Sign up"
-msgstr ""
-
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:53
-msgid "Sign up for an account!"
-msgstr ""
-
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:30 lib/Jifty/Plugin/Authentication/Password/View.pm:35
-msgid "Signup"
-msgstr ""
-
-#: lib/Jifty/Plugin/Authentication/Password/Action/Signup.pm:108
-#. ($msg)
-msgid "Something bad happened and we couldn't create your account: %1"
-msgstr ""
-
-#: lib/Jifty/Plugin/ErrorTemplates/View.pm:149
-msgid "Something went awry"
-msgstr ""
-
-#: lib/Jifty/Plugin/ErrorTemplates/View.pm:124 share/web/templates/dhandler:1
-msgid "Something's not quite right"
-msgstr "原因不明のエラーです"
-
-#: lib/Jifty/Plugin/ErrorTemplates/View.pm:132
-msgid "Sorry about this."
-msgstr ""
-
-#:
-msgid "Table of Contents"
-msgstr "目次"
-
-#: lib/Jifty/Plugin/User/Mixin/Model/User.pm:82
-#. ($new_email)
-msgid "That %1 doesn't look like an email address."
-msgstr ""
-
-#: lib/Jifty/Action.pm:878
-msgid "That doesn't look like a correct value"
-msgstr "値が不適切なようです"
-
-#: lib/Jifty/Plugin/Authentication/Password/Action/SendAccountConfirmation.pm:71 lib/Jifty/Plugin/Authentication/Password/Action/SendPasswordReminder.pm:72 lib/Jifty/Plugin/Authentication/Password/Action/Signup.pm:70
-msgid "That doesn't look like an email address."
-msgstr ""
-
-#: lib/Jifty/Action/Record.pm:249
-msgid "That doesn't look right, but I don't know why"
-msgstr "原因不明のエラーです"
-
-#: lib/Jifty/Action/Record.pm:181
-msgid "The passwords you typed didn't match each other"
-msgstr "パスワードが一致しません"
-
-#: lib/Jifty/Web.pm:365
-msgid "There was an error completing the request.  Please try again later."
-msgstr "リクエストの処理中にエラーが発生しました。しばらく待ってから再度試してみてください"
-
-#: lib/Jifty/Plugin/Authentication/Password/Action/ResetLostPassword.pm:81
-msgid "There was an error setting your password."
-msgstr ""
-
-#: lib/Jifty/Plugin/ErrorTemplates/View.pm:37
-msgid "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."
-msgstr ""
-
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:419
-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 データベースのレコードを管理できます。下にデータベースの全テーブルが一覧されているはずですので、自由に表示・作成・削除・修正してください。"
-
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:425
-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\" を追加してください。"
-
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:127
-msgid "Toggle search"
-msgstr "検索の切り替え"
-
-#: lib/Jifty/Plugin/ErrorTemplates/View.pm:173 share/web/templates/__jifty/error/mason_internal_error:6
-msgid "Try again"
-msgstr "再度試してみてください"
-
-#: lib/Jifty/Plugin/Authentication/Password/Action/Signup.pm:108
-msgid "Try again later. We're really, really sorry."
-msgstr ""
-
-#: lib/Jifty/Plugin/Authentication/Password/Action/Signup.pm:53
-msgid "Type that again?"
-msgstr ""
-
-#: lib/Jifty/Action/Record/Update.pm:156
-msgid "Updated"
-msgstr "更新しました"
-
-#: share/web/templates/__jifty/halo:93
-msgid "Variables"
-msgstr ""
-
-#: lib/Jifty.pm:27
-msgid "W00t"
-msgstr ""
-
-#: lib/Jifty/Plugin/Authentication/Password/Action/Signup.pm:114
-msgid "We've sent a confirmation message to your email box."
-msgstr ""
-
-#: lib/Jifty/I18N.pm:31
-#. ('Bob', 'World')
-msgid "Welcome %1 to the %2"
-msgstr ""
-
-#: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:181
-#. ($user->name)
-msgid "Welcome back, %1."
-msgstr ""
-
-#: lib/Jifty/Plugin/Authentication/Password/Notification/ConfirmEmail.pm:40
-#. ($appname)
-msgid "Welcome to %1!"
-msgstr ""
-
-#: lib/Jifty/Plugin/Authentication/Password/Action/Signup.pm:113
-#. (Jifty->config->framework('ApplicationName')
-msgid "Welcome to %1, %2."
-msgstr ""
-
-#: lib/Jifty/Plugin/Authentication/Password/Action/ConfirmEmail.pm:57
-#. (Jifty->config->framework('ApplicationName')
-msgid "Welcome to %1, %2. "
-msgstr ""
-
-#: lib/Jifty/Plugin/SkeletonApp/View.pm:75 share/web/templates/index.html:1
-msgid "Welcome to your new Jifty application"
-msgstr "Jifty の新規アプリケーションです"
-
-#: lib/Jifty/Plugin/Authentication/Password/Action/ResetLostPassword.pm:63
-msgid "You don't exist."
-msgstr ""
-
-#: lib/Jifty/Plugin/ErrorTemplates/View.pm:130
-msgid "You got to a page that we don't think exists."
-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/Plugin/Authentication/Password/Action/ConfirmEmail.pm:44
-msgid "You have already confirmed your account."
-msgstr ""
-
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:98
-msgid "You lost your password. A link to reset it will be sent to the following email address:"
-msgstr ""
-
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:115
-msgid "You lost your password. A reminder will be send to the following mail:"
-msgstr ""
-
-#: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:152
-msgid "You may have mistyped your email address or password. Give it another shot."
-msgstr ""
-
-#: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:146
-msgid "You may have mistyped your email or password. Give it another shot."
-msgstr ""
-
-#: lib/Jifty/Action.pm:865
-msgid "You need to fill in this field"
-msgstr "必須項目です"
-
-#: lib/Jifty/Plugin/SkeletonApp/View.pm:75 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)"
-
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:69
-msgid "You're already logged in."
-msgstr ""
-
-#: lib/Jifty/Plugin/SkeletonApp/View.pm:34 share/web/templates/_elements/sidebar:7
-msgid "You're not currently signed in."
-msgstr "ログインしていません"
-
-#: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:38
-msgid "Your browser can remember your login for you"
-msgstr ""
-
-#: lib/Jifty/Plugin/Authentication/Password/Action/ResetLostPassword.pm:87
-msgid "Your password has been reset.  Welcome back."
-msgstr ""
-
-#: lib/Jifty/Plugin/Authentication/Password/Mixin/Model/User.pm:28
-msgid "Your password should be at least six characters"
-msgstr ""
-
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:246
-msgid "asc"
-msgstr "昇順"
-
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:264
-msgid "desc"
-msgstr "降順"
-
-#: lib/Jifty/Plugin/Authentication/Password/Action/SendAccountConfirmation.pm:31
-msgid "email address"
-msgstr ""
-
-#: lib/Jifty/Plugin/ErrorTemplates/View.pm:46
-msgid "for now, and try to forget that we let you down."
-msgstr ""
-
-#: lib/Jifty/Manual/PageRegions.pod:188
-msgid "text of the link"
-msgstr ""
-
-#: lib/Jifty/Manual/PageRegions.pod:225
-msgid "text of the link that hides"
-msgstr ""
-
-#: lib/Jifty/Plugin/Authentication/Password/Action/ResetLostPassword.pm:37
-msgid "type your password again"
-msgstr ""
-
-#: lib/Jifty/Action.pm:1050
-msgid "warning"
-msgstr ""
+# 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/Plugin/Authentication/Password/Notification/ConfirmLostPassword.pm:38
+#. ($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"
+"%1 用のパスワードをリセットします。\n"
+"\n"
+"もしこのメールに心当たりがなく、パスワードをリセットする\n"
+"必要がない場合は無視してください。\n"
+"\n"
+"本当にリセットする場合は下のリンクをクリックしてください。\n"
+"\n"
+"%2\n"
+
+#: lib/Jifty/Plugin/Authentication/Password/Notification/ConfirmEmail.pm:41
+#. ($appname,$confirm_url)
+msgid ""
+"\n"
+"You're getting this message because you (or somebody claiming to be you)\n"
+"wants to use %1. \n"
+"\n"
+"We need to make sure that we got your email address right.  Click on the link below to get started:\n"
+"\n"
+"%2\n"
+msgstr ""
+"\n"
+"%1 へのご登録ありがとうございます。\n"
+"\n"
+"メールアドレスが正しいか確認させていただきます。下のリンクをクリックしてください。\n"
+"\n"
+"%2\n"
+
+#: lib/Jifty/Plugin/Authentication/Facebook/View.pm:43
+msgid ""
+" id is 'facebook_link' };\n"
+"        span { _(\"Login to Facebook now to link it with your current account!\") };\n"
+"        a {{ href is $plugin->get_link_url };\n"
+"            img {{ src is 'http://static.ak.facebook.com/images/devsite/facebook_login.gif', border is '0' "
+msgstr ""
+
+#: lib/Jifty/Plugin/Authentication/Facebook/View.pm:26
+msgid ""
+" id is 'facebook_login' };\n"
+"        span { _(\"Login to Facebook now to get started!\") };\n"
+"        a {{ href is $plugin->get_login_url };\n"
+"            img {{ src is 'http://static.ak.facebook.com/images/devsite/facebook_login.gif', border is '0' "
+msgstr ""
+
+#: lib/Jifty/Action/Record/Search.pm:172
+msgid "!=>< allowed"
+msgstr "! = > < は利用できません"
+
+#: lib/Jifty/Notification.pm:108 lib/Jifty/Notification.pm:125
+#. ($appname, Jifty->config->framework('AdminEmail')
+msgid "%1 <%2>"
+msgstr "%1 <%2>"
+
+#: lib/Jifty/Action/Record/Search.pm:143
+#. ($label)
+msgid "%1 after"
+msgstr "%1 より後"
+
+#: lib/Jifty/Action/Record/Search.pm:146
+#. ($label)
+msgid "%1 before"
+msgstr "%1 より前"
+
+#: lib/Jifty/Action/Record/Search.pm:133
+#. ($label)
+msgid "%1 contains"
+msgstr "%1 を含む"
+
+#:
+msgid "%1 entries"
+msgstr "%1 件"
+
+#: lib/Jifty/Action/Record/Search.pm:166
+#. ($label)
+msgid "%1 greater or equal to"
+msgstr "%1 と同じかそれ以上"
+
+#: lib/Jifty/Action/Record/Search.pm:160
+#. ($label)
+msgid "%1 greater than"
+msgstr "%1 より大きい"
+
+#: lib/Jifty/Action/Record/Search.pm:121
+#. ($label)
+msgid "%1 is not"
+msgstr "%1 以外"
+
+#: lib/Jifty/Action/Record/Search.pm:136
+#. ($label)
+msgid "%1 lacks"
+msgstr "%1 を含まない"
+
+#: lib/Jifty/Action/Record/Search.pm:169
+#. ($label)
+msgid "%1 less or equal to"
+msgstr "%1 と同じかそれ以下"
+
+#: lib/Jifty/Action/Record/Search.pm:163
+#. ($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:149
+#. ($label)
+msgid "%1 since"
+msgstr "%1 と同じかそれ以降"
+
+#: lib/Jifty/Action/Record/Search.pm:152
+#. ($label)
+msgid "%1 until"
+msgstr "%1 と同じかそれ以前"
+
+#: lib/Jifty/Action/Record/Search.pm:81
+msgid "(any)"
+msgstr "(指定なし)"
+
+#: lib/Jifty/Plugin/Authentication/Password/Action/ConfirmEmail.pm:58
+msgid ". Your email address has now been confirmed."
+msgstr "メールアドレスの確認が完了しました。"
+
+#: lib/Jifty/Plugin/Authentication/Password/Action/SendPasswordReminder.pm:96
+msgid "A link to reset your password has been sent to your email account."
+msgstr "お使いのメールアカウントにパスワードをリセットするリンクを送信しました。"
+
+#: lib/Jifty/Notification.pm:110 lib/Jifty/Notification.pm:127
+#. ($appname)
+msgid "A notification from %1!"
+msgstr "%1 からのお知らせです!"
+
+#:
+msgid "Actions"
+msgstr "操作"
+
+#: lib/Jifty/Plugin/SkeletonApp/Dispatcher.pm:33
+msgid "Administration"
+msgstr "管理"
+
+#: lib/Jifty/View/Declare/Page.pm:176 share/web/templates/_elements/wrapper:11
+msgid "Administration mode is enabled."
+msgstr "管理モードが有効になっています。"
+
+#: lib/Jifty/View/Declare/Page.pm:173 share/web/templates/_elements/wrapper:11
+msgid "Alert"
+msgstr "注意"
+
+#: lib/Jifty/Action/Record/Create.pm:92
+msgid "An error occurred.  Try again later"
+msgstr "エラーが発生しました。しばらく待ってから再度試してみてください"
+
+#: lib/Jifty/Action/Record/Search.pm:177
+msgid "Any field contains"
+msgstr "検索したい文字列"
+
+#: lib/Jifty/Plugin/ErrorTemplates/View.pm:131
+msgid "Anyway, the software has logged this error."
+msgstr "ともあれ、エラーは記録しました。"
+
+#: lib/Jifty/Plugin/Authentication/Password/Mixin/Model/User.pm:53
+msgid "Authentication token"
+msgstr "認証トークン"
+
+#:
+msgid "Back to the admin console"
+msgstr "管理画面に戻る"
+
+#:
+msgid "Back to the application"
+msgstr "アプリケーションに戻る"
+
+#: share/web/templates/__jifty/halo:117
+msgid "Bindings"
+msgstr "バインディング"
+
+#: share/web/templates/helpers/calendar.html:4
+#. (_ &><body class="calpopup"><a href="#" onclick="window.close()
+msgid "Calendar"
+msgstr "カレンダー"
+
+#: lib/Jifty/View/Declare/CRUD.pm:419
+msgid "Cancel"
+msgstr "キャンセル"
+
+#: share/web/templates/__jifty/halo:81
+msgid "Children"
+msgstr "子"
+
+#: share/web/templates/helpers/calendar.html:4
+msgid "Close window"
+msgstr "閉じる"
+
+#:
+msgid "Confirm delete?"
+msgstr "本当に削除しますか?"
+
+#: lib/Jifty/Plugin/Authentication/Password/Action/SendAccountConfirmation.pm:94
+msgid "Confirmation resent."
+msgstr "確認メールを再送しました。"
+
+#: lib/Jifty/Plugin/OpenID/View.pm:65
+msgid "Continue"
+msgstr "次"
+
+#: lib/Jifty/View/Declare/CRUD.pm:675
+msgid "Create"
+msgstr "作成"
+
+#: lib/Jifty/Action/Record/Create.pm:91
+#. (ref($record)
+msgid "Create of %1 failed: %2"
+msgstr "%1 の作成に失敗しました: %2"
+
+#: lib/Jifty/Action/Record/Create.pm:118
+msgid "Created"
+msgstr "作成しました"
+
+#:
+msgid "Database Administration"
+msgstr "データベース管理"
+
+#: lib/Jifty/View/Declare/CRUD.pm:429
+msgid "Delete"
+msgstr "削除"
+
+#: lib/Jifty/Action/Record/Delete.pm:82
+msgid "Deleted"
+msgstr "削除しました"
+
+#: lib/Jifty/View/Declare/CRUD.pm:349 share/web/templates/__jifty/halo:126 share/web/templates/__jifty/halo:20
+msgid "Edit"
+msgstr "編集"
+
+#: lib/Jifty/Mason/Halo.pm:104
+#. ($comp_name)
+msgid "Edit %1"
+msgstr "%1 を編集"
+
+#: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:23 lib/Jifty/Plugin/Authentication/Password/Action/SendPasswordReminder.pm:32
+msgid "Email"
+msgstr "メール"
+
+#: lib/Jifty/Plugin/User/Mixin/Model/User.pm:52
+msgid "Email address"
+msgstr "メールアドレス"
+
+#: lib/Jifty/Plugin/User/Mixin/Model/User.pm:54
+msgid "Email address confirmed?"
+msgstr "確認済?"
+
+#: lib/Jifty/Plugin/Authentication/Password/View.pm:209
+msgid "Fill in your address below, and we'll send out another confirmation email to you. "
+msgstr "メールアドレスをご記入ください。再度確認メールを送信します。"
+
+#: lib/Jifty/Action.pm:1312
+msgid "Foo cannot contain -, *, +, or ?."
+msgstr "-、*、+、?は利用できません。"
+
+#: lib/Jifty/Action.pm:1306
+msgid "Foo cannot contain uppercase letters."
+msgstr "大文字は利用できません。"
+
+#: lib/Jifty/Action.pm:1285
+msgid "Foo values are always in lowercase."
+msgstr "かならず小文字で記入してください。"
+
+#: lib/Jifty/Plugin/ErrorTemplates/View.pm:27
+msgid "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 do have is :"
+msgstr "なんらかの理由でページにエラーが発生しました。現時点ではわかりやすい綺麗なエラーメッセージを表示できません。実際のエラーメッセージは次の通りです: "
+
+#: lib/Jifty/Plugin/ErrorTemplates/View.pm:138 share/web/templates/dhandler:7
+msgid "Go back home..."
+msgstr "ホームページに戻る…"
+
+#: lib/Jifty/Plugin/OpenID/View.pm:35
+msgid "Go for it!"
+msgstr "登録する!"
+
+#: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:34
+msgid "Hashed Password"
+msgstr "パスワードハッシュ"
+
+#: lib/Jifty/Plugin/ErrorTemplates/View.pm:44
+msgid "Head on back home"
+msgstr "ホームページに戻る"
+
+#: lib/Jifty/I18N.pm:19 lib/Jifty/I18N.pm:23
+#. ('World')
+msgid "Hello, %1!"
+msgstr "%1 さん、こんにちは!"
+
+#: lib/Jifty/Plugin/Authentication/Facebook/Action/LoginFacebookUser.pm:103
+#. (Jifty->web->current_user->user_object->facebook_name)
+msgid "Hi %1!"
+msgstr "%1 さん、こんにちは!"
+
+#: lib/Jifty/Plugin/SkeletonApp/View.pm:31 share/web/templates/_elements/sidebar:4
+#. ($u->username)
+#. (Jifty->web->current_user->username)
+msgid "Hiya, %1."
+msgstr "%1 さん、こんにちは"
+
+#: lib/Jifty/Plugin/SkeletonApp/Dispatcher.pm:26
+msgid "Home"
+msgstr "ホーム"
+
+#: lib/Jifty/Plugin/User/Mixin/Model/User.pm:49
+msgid "How should I display your name to other users?"
+msgstr "ほかのユーザに公開する名前を決めてください。"
+
+#: lib/Jifty/Action.pm:1212
+msgid "I changed $field for you"
+msgstr "$fieldを変更しました"
+
+#: lib/Jifty/Plugin/Authentication/Password/Action/ResetLostPassword.pm:64
+msgid "I'm not sure how this happened."
+msgstr "原因は不明です。"
+
+#: lib/Jifty/Plugin/OpenID/View.pm:59
+msgid "If the username provided conflicts with an existing username or contains invalid characters, you will have to give us a new one."
+msgstr "ユーザ名が既に登録されている場合、または利用できない文字が含まれている場合は新しいユーザ名を登録していただきます。"
+
+#: lib/Jifty/Plugin/OpenID/View.pm:27
+msgid "If you have a Livejournal or other OpenID account, you don't even need to sign up. Just log in."
+msgstr "LivejournalなどのOpenIDアカウントをお持ちの場合は、サインアップは不要です。ログインしてください。"
+
+#: lib/Jifty/Plugin/ErrorTemplates/View.pm:62
+msgid "Internal error"
+msgstr "内部エラー"
+
+#: lib/Jifty/Plugin/OpenID/Action/AuthenticateOpenID.pm:59
+msgid "Invalid OpenID URL.  Please check to make sure it is correct.  (@{[$csr->err]})"
+msgstr "OpenID URLが正しくありません。ご確認ください。(@{[$csr->err]})"
+
+#: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:63 lib/Jifty/Plugin/Authentication/Password/Action/SendAccountConfirmation.pm:75 lib/Jifty/Plugin/Authentication/Password/Action/SendPasswordReminder.pm:79
+msgid "It doesn't look like there's an account by that name."
+msgstr "そのようなアカウントは存在しないようです。"
+
+#: lib/Jifty/Plugin/User/Mixin/Model/User.pm:106
+msgid "It looks like somebody else is using that address. Is there a chance you have another account?"
+msgstr "そのアドレスは既に使われているようです。別のアカウントをお持ちではありませんか?"
+
+#: lib/Jifty/Plugin/OpenID/Dispatcher.pm:43
+msgid "It looks like someone is already using that OpenID."
+msgstr "そのOpenIDは既に使われているようです。"
+
+#: lib/Jifty/Plugin/Authentication/Password/Action/Signup.pm:75
+msgid "It looks like you already have an account. Perhaps you want to <a href=\"/login\">log in</a> instead?"
+msgstr "既にアカウントをお持ちのようです。<a href=\"/login\">ログイン</a>してください。"
+
+#: lib/Jifty/Plugin/Authentication/Password/Action/ResetLostPassword.pm:75
+msgid "It looks like you didn't enter the same password into both boxes. Give it another shot?"
+msgstr "パスワードが一致しません。もう一度入力してください。"
+
+#: lib/Jifty/Plugin/Authentication/Password/Action/SendAccountConfirmation.pm:79
+msgid "It looks like you're already confirmed."
+msgstr "確認済みのようです。"
+
+#:
+msgid "Jifty Developer Documentation Online"
+msgstr "開発者向けオンラインドキュメント"
+
+#:
+msgid "Jifty Pod Online"
+msgstr "オンラインドキュメント"
+
+#: lib/Jifty/View/Declare/Page.pm:194 share/web/templates/_elements/wrapper:18
+msgid "Loading..."
+msgstr "読込み中…"
+
+#: lib/Jifty/Plugin/Authentication/Password/Dispatcher.pm:132 lib/Jifty/Plugin/Authentication/Password/View.pm:88
+msgid "Login"
+msgstr "ログイン"
+
+#: lib/Jifty/Plugin/Authentication/Facebook/View.pm:24
+msgid "Login to Facebook now to get started!"
+msgstr "Facebookを始めよう!"
+
+#: lib/Jifty/Plugin/Authentication/Facebook/View.pm:41
+msgid "Login to Facebook now to link it with your current account!"
+msgstr "お使いのアカウントをFacebookにリンクさせましょう!"
+
+#: lib/Jifty/Plugin/Authentication/Password/View.pm:82
+msgid "Login with a password"
+msgstr "パスワードでログイン"
+
+#: lib/Jifty/Plugin/OpenID/View.pm:17
+msgid "Login with your OpenID"
+msgstr "OpenIDでログイン"
+
+#: lib/Jifty/Plugin/Authentication/Password/View.pm:57
+msgid "Login!"
+msgstr "ログイン!"
+
+#: lib/Jifty/Plugin/Authentication/Password/Dispatcher.pm:151
+msgid "Logout"
+msgstr "ログアウト"
+
+#: lib/Jifty/Plugin/Authentication/Password/View.pm:90
+msgid "Lost your password?"
+msgstr "パスワードをお忘れですか?"
+
+#: share/web/templates/__jifty/error/mason_internal_error:1
+msgid "Mason error"
+msgstr "Mason エラー"
+
+#:
+msgid "Models"
+msgstr "モデル"
+
+#: lib/Jifty/Plugin/Authentication/Password/View.pm:114
+msgid "New password"
+msgstr "新しいパスワード"
+
+#: lib/Jifty/View/Declare/CRUD.pm:631
+msgid "Next Page"
+msgstr "次のページ"
+
+#: lib/Jifty/Plugin/User/Mixin/Model/User.pm:48
+msgid "Nickname"
+msgstr "ニックネーム"
+
+#: lib/Jifty/Plugin/Authentication/Password/View.pm:79
+msgid "No account yet? It's quick and easy. "
+msgstr "登録はお済みですか?"
+
+#: lib/Jifty/Action/Record/Search.pm:178
+msgid "No field contains"
+msgstr "排除したい文字列"
+
+#:
+msgid "No items found"
+msgstr "見つかりませんでした"
+
+#: lib/Jifty/View/Declare/CRUD.pm:550
+msgid "No items found."
+msgstr "見つかりませんでした。"
+
+#: lib/Jifty/Web.pm:308
+msgid "No request to handle"
+msgstr "処理できるリクエストがありません"
+
+#: lib/Jifty/Plugin/UUID/Widget.pm:37
+msgid "No value yet"
+msgstr "値がありません"
+
+#:
+msgid "Online Documentation"
+msgstr "オンラインドキュメント"
+
+#: lib/Jifty/Plugin/OnlineDocs/Dispatcher.pm:26
+msgid "Online docs"
+msgstr "オンラインドキュメント"
+
+#: lib/Jifty/Plugin/OpenID/Action/AuthenticateOpenID.pm:27
+msgid "OpenID URL"
+msgstr "OpenID URL"
+
+#: lib/Jifty/Plugin/OpenID/Action/VerifyOpenID.pm:54
+msgid "OpenID verification failed.  It looks like you cancelled the OpenID verification request."
+msgstr "OpenIDの確認に失敗しました。確認をキャンセルされませんでしたか。"
+
+#: lib/Jifty/Plugin/OpenID/Action/VerifyOpenID.pm:63
+msgid "OpenID verification failed: "
+msgstr "OpenIDの確認に失敗しました: "
+
+#: lib/Jifty/View/Declare/CRUD.pm:598
+#. ($page, $collection->pager->last_page)
+msgid "Page %1 of %2"
+msgstr "%1 / %2 ページ"
+
+#: share/web/templates/__jifty/halo:1
+msgid "Page info"
+msgstr "ページ情報"
+
+#: share/web/templates/__jifty/halo:72
+msgid "Parent"
+msgstr "親"
+
+#: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:29 lib/Jifty/Plugin/Authentication/Password/Action/ResetLostPassword.pm:32 lib/Jifty/Plugin/Authentication/Password/Mixin/Model/User.pm:59
+msgid "Password"
+msgstr "パスワード"
+
+#: lib/Jifty/Plugin/Authentication/Password/Mixin/Model/User.pm:134
+msgid "Passwords need to be at least six characters long"
+msgstr "パスワードは最低6文字以上にしてください"
+
+#: lib/Jifty/Record.pm:485 lib/Jifty/Record.pm:564 lib/Jifty/Record.pm:71
+msgid "Permission denied"
+msgstr "権限がありません"
+
+#: lib/Jifty/Plugin/Authentication/Password/Action/ResetLostPassword.pm:66
+msgid "Please email us!"
+msgstr "メールをお送りください!"
+
+#: lib/Jifty/View/Declare/CRUD.pm:620
+msgid "Previous Page"
+msgstr "前のページ"
+
+#: lib/Jifty/View/Declare/CRUD.pm:432
+msgid "Really delete?"
+msgstr "本当に削除しますか?"
+
+#: lib/Jifty/Plugin/Authentication/Password/Action/ResetLostPassword.pm:65
+msgid "Really, really sorry."
+msgstr "本当に申し訳ないです。"
+
+#: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:37
+msgid "Remember me?"
+msgstr "記憶しますか?"
+
+#: share/web/templates/__jifty/halo:69
+#. ($frame->{'render_time'})
+msgid "Rendered in %1s"
+msgstr "描画時間: %1秒"
+
+#:
+msgid "Run the action"
+msgstr "実行"
+
+#: share/web/templates/__jifty/halo:111
+msgid "SQL Statements"
+msgstr "SQL文"
+
+#: lib/Jifty/View/Declare/CRUD.pm:410
+msgid "Save"
+msgstr "保存"
+
+#:
+msgid "Schema"
+msgstr "スキーマ"
+
+#: lib/Jifty/View/Declare/CRUD.pm:298
+msgid "Search"
+msgstr "検索"
+
+#: lib/Jifty/Plugin/Authentication/Password/View.pm:150 lib/Jifty/Plugin/Authentication/Password/View.pm:180
+msgid "Send"
+msgstr "送信"
+
+#: lib/Jifty/Plugin/Authentication/Password/View.pm:175
+msgid "Send a password reminder"
+msgstr "通知メールを送信する"
+
+#: lib/Jifty/Plugin/Feedback/View.pm:21
+msgid "Send us feedback!"
+msgstr "ご意見ご感想をお寄せください!"
+
+#: lib/Jifty/Plugin/Authentication/Password/Dispatcher.pm:137 lib/Jifty/Plugin/Authentication/Password/View.pm:42 lib/Jifty/Plugin/Authentication/Password/View.pm:47
+msgid "Sign up"
+msgstr "サインアップ"
+
+#: lib/Jifty/Plugin/Authentication/Password/View.pm:80
+msgid "Sign up for an account!"
+msgstr "サインアップする"
+
+#: lib/Jifty/Plugin/Authentication/Password/Action/Signup.pm:108
+#. ($msg)
+msgid "Something bad happened and we couldn't create your account: %1"
+msgstr "問題が発生したためアカウントを作成できませんでした: %1"
+
+#: lib/Jifty/Plugin/ErrorTemplates/View.pm:149
+msgid "Something went awry"
+msgstr "問題が発生しました"
+
+#: lib/Jifty/Plugin/ErrorTemplates/View.pm:124 share/web/templates/dhandler:1
+msgid "Something's not quite right"
+msgstr "原因不明のエラーです"
+
+#: lib/Jifty/Plugin/ErrorTemplates/View.pm:132
+msgid "Sorry about this."
+msgstr "申し訳ないです。"
+
+#: lib/Jifty/Plugin/Authentication/Facebook/Action/LoginFacebookUser.pm:62
+msgid "Sorry, something weird happened (we couldn't create a user for you).  Try again later."
+msgstr "問題が発生したため、ユーザの作成に失敗しました。お手数ですがあとでもう一度試してみてください。"
+
+#:
+msgid "Table of Contents"
+msgstr "目次"
+
+#: lib/Jifty/Plugin/Feedback/View.pm:23
+#. (Jifty->config->framework('ApplicationName')
+msgid "Tell us what's good, what's bad, and what else you want %1 to do!"
+msgstr "%1へのご意見、ご感想、ご要望をお寄せください。"
+
+#: lib/Jifty/Plugin/User/Mixin/Model/User.pm:99
+#. ($new_email)
+msgid "That %1 doesn't look like an email address."
+msgstr "%1は正しいメールアドレスではないようです。"
+
+#: lib/Jifty/Action.pm:975
+msgid "That doesn't look like a correct value"
+msgstr "値が不適切なようです"
+
+#: lib/Jifty/Plugin/Authentication/Password/Action/SendAccountConfirmation.pm:68 lib/Jifty/Plugin/Authentication/Password/Action/SendPasswordReminder.pm:72 lib/Jifty/Plugin/Authentication/Password/Action/Signup.pm:70
+msgid "That doesn't look like an email address."
+msgstr "正しいメールアドレスではないようです。"
+
+#: lib/Jifty/Action/Record.pm:300
+msgid "That doesn't look right, but I don't know why"
+msgstr "原因不明のエラーです"
+
+#: lib/Jifty/Plugin/OpenID/Dispatcher.pm:48
+msgid "The OpenID '$openid' has been linked to your account."
+msgstr "OpenID '$openid'をお使いのアカウントにリンクさせました。"
+
+#: lib/Jifty/Action/Record.pm:205
+msgid "The passwords you typed didn't match each other"
+msgstr "パスワードが一致しません"
+
+#: lib/Jifty/Web.pm:429
+msgid "There was an error completing the request.  Please try again later."
+msgstr "リクエストの処理中にエラーが発生しました。あとでもう一度試してみてください。"
+
+#: lib/Jifty/Plugin/Authentication/Password/Action/ResetLostPassword.pm:81
+msgid "There was an error setting your password."
+msgstr "パスワードの設定中にエラーが発生しました。"
+
+#: lib/Jifty/Plugin/ErrorTemplates/View.pm:37
+msgid "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."
+msgstr "このエラーメッセージはみなさんにはあまり意味がないかもしれませんが、何もないよりは多少なりとも何が起こっているかお知らせした方が安心していただけると思っています。このエラーは記録しましたので、今度はわかりやすいメッセージを用意したり問題を解決できるようにいたします。"
+
+#:
+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 データベースのレコードを管理できます。下にデータベースの全テーブルが一覧されているはずですので、自由に表示・作成・削除・修正してください。"
+
+#:
+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\" を追加してください。"
+
+#: lib/Jifty/View/Declare/CRUD.pm:518
+msgid "Toggle search"
+msgstr "検索の切り替え"
+
+#: lib/Jifty/Plugin/ErrorTemplates/View.pm:173 share/web/templates/__jifty/error/mason_internal_error:6
+msgid "Try again"
+msgstr "もう一度試してみてください"
+
+#: lib/Jifty/Plugin/Authentication/Password/Action/Signup.pm:108
+msgid "Try again later. We're really, really sorry."
+msgstr "もう一度試してみてください。お手数をおかけして申し訳ないです。"
+
+#: lib/Jifty/Plugin/Authentication/Password/Action/Signup.pm:53
+msgid "Type that again?"
+msgstr "もう一度入力してください。"
+
+#: lib/Jifty/Action/Record/Update.pm:170
+msgid "Updated"
+msgstr "更新しました"
+
+#: share/web/templates/__jifty/halo:93
+msgid "Variables"
+msgstr "変数"
+
+#: lib/Jifty.pm:27
+msgid "W00t"
+msgstr "OK"
+
+#: lib/Jifty/Plugin/OpenID/View.pm:53
+msgid "We need you to set a username or quickly check the one associated with your OpenID. Your username is what other people will see when you ask questions or make suggestions"
+msgstr "ユーザ名を設定するか、お使いのOpenIDに関連づけられたユーザ名をすぐに確認してください。このユーザ名は質問や提案をするときに表示されます"
+
+#: lib/Jifty/Plugin/Authentication/Password/Action/Signup.pm:114
+msgid "We've sent a confirmation message to your email box."
+msgstr "お使いのメールボックスに確認のメールを送信しました。"
+
+#: lib/Jifty/I18N.pm:31
+#. ('Bob', 'World')
+msgid "Welcome %1 to the %2"
+msgstr "%1さん、%2へようこそ"
+
+#: lib/Jifty/Plugin/OpenID/Dispatcher.pm:64
+msgid "Welcome back, "
+msgstr "おかえりなさい"
+
+#: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:182
+#. ($user->name)
+msgid "Welcome back, %1."
+msgstr "%1さん、おかえりなさい"
+
+#: lib/Jifty/Plugin/Authentication/Password/Notification/ConfirmEmail.pm:40
+#. ($appname)
+msgid "Welcome to %1!"
+msgstr "%1へようこそ!"
+
+#: lib/Jifty/Plugin/Authentication/Password/Action/ConfirmEmail.pm:57 lib/Jifty/Plugin/Authentication/Password/Action/Signup.pm:113
+#. (Jifty->config->framework('ApplicationName')
+msgid "Welcome to %1, %2."
+msgstr "%2さん、%1へようこそ"
+
+#: lib/Jifty/Plugin/SkeletonApp/View.pm:81 share/web/templates/index.html:1
+msgid "Welcome to your new Jifty application"
+msgstr "Jifty の新規アプリケーションへようこそ"
+
+#: lib/Jifty/Plugin/OpenID/Action/CreateOpenIDUser.pm:104
+msgid "Welcome, "
+msgstr "ようこそ"
+
+#: lib/Jifty/Plugin/OpenID/View.pm:42
+msgid "You already logged in."
+msgstr "ログイン済みです。"
+
+#: lib/Jifty/Plugin/Authentication/Password/Action/ResetLostPassword.pm:63
+msgid "You don't exist."
+msgstr "そのようなユーザは存在しません。"
+
+#: lib/Jifty/Plugin/ErrorTemplates/View.pm:130
+msgid "You got to a page that we don't think exists."
+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/Plugin/Authentication/Password/Action/ConfirmEmail.pm:44
+msgid "You have already confirmed your account."
+msgstr "アカウントは確認済みです。"
+
+#: lib/Jifty/Plugin/Authentication/Password/View.pm:146
+msgid "You lost your password. A link to reset it will be sent to the following email address:"
+msgstr "パスワードをリセットします。このメールアドレスに通知を送ります:"
+
+#: lib/Jifty/Plugin/Authentication/Password/View.pm:176
+msgid "You lost your password. A reminder will be send to the following mail:"
+msgstr "このメールアドレスに通知を送ります:"
+
+#: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:146 lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:152
+msgid "You may have mistyped your email or password. Give it another shot."
+msgstr "メールアドレスかパスワードが間違っているようです。もう一度入力してください。"
+
+#: lib/Jifty/Plugin/Authentication/Facebook/Action/LinkFacebookUser.pm:40
+msgid "You must be logged in to link your user to your Facebook account."
+msgstr "Facebookアカウントとリンクさせるにはログインする必要があります。"
+
+#: lib/Jifty/Action.pm:962
+msgid "You need to fill in this field"
+msgstr "必須項目です"
+
+#: lib/Jifty/Plugin/SkeletonApp/View.pm:81 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)"
+
+#: lib/Jifty/Plugin/Authentication/Password/View.pm:96
+msgid "You're already logged in."
+msgstr "ログイン済みです。"
+
+#: lib/Jifty/Plugin/SkeletonApp/View.pm:34 share/web/templates/_elements/sidebar:6
+msgid "You're not currently signed in."
+msgstr "サインインしていません。"
+
+#: lib/Jifty/Plugin/Authentication/Facebook/Action/LinkFacebookUser.pm:72
+#. (Jifty->web->current_user->user_object->facebook_name)
+msgid "Your account has been successfully linked to your Facebook user %1!"
+msgstr "アカウントをFacebookのユーザ%1にリンクさせました!"
+
+#: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:38
+msgid "Your browser can remember your login for you"
+msgstr "お使いのブラウザはログインを記憶させることができます"
+
+#: lib/Jifty/Plugin/Authentication/Password/Action/ResetLostPassword.pm:87
+msgid "Your password has been reset.  Welcome back."
+msgstr "パスワードをリセットしました。おかえりなさい"
+
+#: lib/Jifty/Plugin/Authentication/Password/Mixin/Model/User.pm:61
+msgid "Your password should be at least six characters"
+msgstr "パスワードは最低6文字以上にしてください"
+
+#:
+msgid "asc"
+msgstr "昇順"
+
+#:
+msgid "desc"
+msgstr "降順"
+
+#: lib/Jifty/Plugin/Authentication/Password/Action/SendAccountConfirmation.pm:31
+msgid "email address"
+msgstr "メールアドレス"
+
+#: lib/Jifty/Plugin/ErrorTemplates/View.pm:46
+msgid "for now, and try to forget that we let you down."
+msgstr "。ご不便をおかけして申し訳ないです。"
+
+#: lib/Jifty/Manual/PageRegions.pod:188
+msgid "text of the link"
+msgstr "リンクテキスト"
+
+#: lib/Jifty/Manual/PageRegions.pod:225
+msgid "text of the link that hides"
+msgstr "隠すリンクテキスト"
+
+#: lib/Jifty/Plugin/Authentication/Password/Action/ResetLostPassword.pm:37
+msgid "type your password again"
+msgstr "もう一度パスワードを入力してください"
+
+#: lib/Jifty/Action.pm:1172
+msgid "warning"
+msgstr "警告"

Modified: jifty/branches/js-refactor/share/po/zh_cn.po
==============================================================================
--- jifty/branches/js-refactor/share/po/zh_cn.po	(original)
+++ jifty/branches/js-refactor/share/po/zh_cn.po	Thu Nov 29 12:25:16 2007
@@ -7,8 +7,8 @@
 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"
+"PO-Revision-Date: 2007-08-24 10:33+08\n"
+"Last-Translator: sunnavy <sunnavy at gmail.com>\n"
 "Language-Team: Simplified Chinese\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
@@ -338,19 +338,19 @@
 
 #: lib/Jifty/Plugin/User/Mixin/Model/User.pm:89
 msgid "It looks like somebody else is using that address. Is there a chance you have another account?"
-msgstr ""
+msgstr "已经有人使用了这个地址,会不会是因为您还有另外一个账号?"
 
 #: lib/Jifty/Plugin/Authentication/Password/Action/Signup.pm:75
 msgid "It looks like you already have an account. Perhaps you want to <a href=\"/login\">log in</a> instead?"
-msgstr ""
+msgstr "您已经有了一个账号了,或许您是想<a href=\"/login\">登录</a>?"
 
 #: lib/Jifty/Plugin/Authentication/Password/Action/ResetLostPassword.pm:75
 msgid "It looks like you didn't enter the same password into both boxes. Give it another shot?"
-msgstr ""
+msgstr "两次输入的密码不同. 再试一次?"
 
 #: lib/Jifty/Plugin/Authentication/Password/Action/SendAccountConfirmation.pm:82
 msgid "It looks like you're already confirmed."
-msgstr ""
+msgstr "您已经确认过了."
 
 #:
 msgid "Jifty Administrative Console"
@@ -546,11 +546,11 @@
 #: lib/Jifty/Plugin/Authentication/Password/Action/Signup.pm:108
 #. ($msg)
 msgid "Something bad happened and we couldn't create your account: %1"
-msgstr ""
+msgstr "出了一点问题,我们不能创建您的账号: %1"
 
 #: lib/Jifty/Plugin/ErrorTemplates/View.pm:149
 msgid "Something went awry"
-msgstr ""
+msgstr "出了一点岔子"
 
 #: lib/Jifty/Plugin/ErrorTemplates/View.pm:124 share/web/templates/dhandler:1
 msgid "Something's not quite right"
@@ -684,11 +684,11 @@
 
 #: lib/Jifty/Plugin/Authentication/Password/View.pm:98
 msgid "You lost your password. A link to reset it will be sent to the following email address:"
-msgstr ""
+msgstr "您忘记了密码. 重置密码的链接将被发送到以下的电子信箱中:"
 
 #: lib/Jifty/Plugin/Authentication/Password/View.pm:115
 msgid "You lost your password. A reminder will be send to the following mail:"
-msgstr ""
+msgstr "您忘记了密码. 密码提醒将被发送到以下的电子信箱中:"
 
 #: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:152
 msgid "You may have mistyped your email address or password. Give it another shot."

Modified: jifty/branches/js-refactor/share/po/zh_tw.po
==============================================================================
--- jifty/branches/js-refactor/share/po/zh_tw.po	(original)
+++ jifty/branches/js-refactor/share/po/zh_tw.po	Thu Nov 29 12:25:16 2007
@@ -54,61 +54,76 @@
 "\n"
 "%2\n"
 
-#: lib/Jifty/Action/Record/Search.pm:125
+#: lib/Jifty/Plugin/Authentication/Facebook/View.pm:43
+msgid ""
+" id is 'facebook_link' };\n"
+"        span { _(\"Login to Facebook now to link it with your current account!\") };\n"
+"        a {{ href is $plugin->get_link_url };\n"
+"            img {{ src is 'http://static.ak.facebook.com/images/devsite/facebook_login.gif', border is '0' "
+msgstr ""
+
+#: lib/Jifty/Plugin/Authentication/Facebook/View.pm:26
+msgid ""
+" id is 'facebook_login' };\n"
+"        span { _(\"Login to Facebook now to get started!\") };\n"
+"        a {{ href is $plugin->get_login_url };\n"
+"            img {{ src is 'http://static.ak.facebook.com/images/devsite/facebook_login.gif', border is '0' "
+msgstr ""
+
+#: lib/Jifty/Action/Record/Search.pm:172
 msgid "!=>< allowed"
 msgstr "可使用 !=>< 符號"
 
-#: lib/Jifty/Notification.pm:94
+#: lib/Jifty/Notification.pm:108 lib/Jifty/Notification.pm:125
 #. ($appname, Jifty->config->framework('AdminEmail')
 msgid "%1 <%2>"
 msgstr "%1 <%2>"
 
-#: lib/Jifty/Action/Record/Search.pm:115
+#: lib/Jifty/Action/Record/Search.pm:143
 #. ($label)
 msgid "%1 after"
 msgstr "%1 晚於"
 
-#: lib/Jifty/Action/Record/Search.pm:116
+#: lib/Jifty/Action/Record/Search.pm:146
 #. ($label)
 msgid "%1 before"
 msgstr "%1 早於"
 
-#: lib/Jifty/Action/Record/Search.pm:112
+#: lib/Jifty/Action/Record/Search.pm:133
 #. ($label)
 msgid "%1 contains"
 msgstr "%1 包含"
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:141
-#. ($collection-> count)
+#:
 msgid "%1 entries"
 msgstr "共 %1 筆"
 
-#: lib/Jifty/Action/Record/Search.pm:123
+#: lib/Jifty/Action/Record/Search.pm:166
 #. ($label)
 msgid "%1 greater or equal to"
 msgstr "%1 至少為"
 
-#: lib/Jifty/Action/Record/Search.pm:121
+#: lib/Jifty/Action/Record/Search.pm:160
 #. ($label)
 msgid "%1 greater than"
 msgstr "%1 大於"
 
-#: lib/Jifty/Action/Record/Search.pm:108
+#: lib/Jifty/Action/Record/Search.pm:121
 #. ($label)
 msgid "%1 is not"
 msgstr "%1 不等於"
 
-#: lib/Jifty/Action/Record/Search.pm:113
+#: lib/Jifty/Action/Record/Search.pm:136
 #. ($label)
 msgid "%1 lacks"
 msgstr "%1 不包含"
 
-#: lib/Jifty/Action/Record/Search.pm:124
+#: lib/Jifty/Action/Record/Search.pm:169
 #. ($label)
 msgid "%1 less or equal to"
 msgstr "%1 至多為"
 
-#: lib/Jifty/Action/Record/Search.pm:122
+#: lib/Jifty/Action/Record/Search.pm:163
 #. ($label)
 msgid "%1 less than"
 msgstr "%1 小於"
@@ -124,17 +139,17 @@
 msgid "%1 seconds"
 msgstr "%1 秒"
 
-#: lib/Jifty/Action/Record/Search.pm:117
+#: lib/Jifty/Action/Record/Search.pm:149
 #. ($label)
 msgid "%1 since"
 msgstr "%1 自"
 
-#: lib/Jifty/Action/Record/Search.pm:118
+#: lib/Jifty/Action/Record/Search.pm:152
 #. ($label)
 msgid "%1 until"
 msgstr "%1 至"
 
-#: lib/Jifty/Action/Record/Search.pm:77
+#: lib/Jifty/Action/Record/Search.pm:81
 msgid "(any)"
 msgstr "(不限)"
 
@@ -146,32 +161,32 @@
 msgid "A link to reset your password has been sent to your email account."
 msgstr "重設密碼的通知信已送到您的電郵地址."
 
-#: lib/Jifty/Notification.pm:96
+#: lib/Jifty/Notification.pm:110 lib/Jifty/Notification.pm:127
 #. ($appname)
 msgid "A notification from %1!"
 msgstr "%1 捎來的訊息!"
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:443
+#:
 msgid "Actions"
 msgstr "操作"
 
-#: lib/Jifty/Plugin/SkeletonApp/Dispatcher.pm:28
+#: lib/Jifty/Plugin/SkeletonApp/Dispatcher.pm:33
 msgid "Administration"
 msgstr "管理介面"
 
-#: lib/Jifty/View/Declare/Helpers.pm:363 share/web/templates/_elements/wrapper:11
+#: lib/Jifty/View/Declare/Page.pm:176 share/web/templates/_elements/wrapper:11
 msgid "Administration mode is enabled."
 msgstr "系統管理模式開啟中."
 
-#: lib/Jifty/View/Declare/Helpers.pm:360 share/web/templates/_elements/wrapper:11
+#: lib/Jifty/View/Declare/Page.pm:173 share/web/templates/_elements/wrapper:11
 msgid "Alert"
 msgstr "請注意"
 
-#: lib/Jifty/Action/Record/Create.pm:82
+#: lib/Jifty/Action/Record/Create.pm:92
 msgid "An error occurred.  Try again later"
 msgstr "系統無法執行此項操作, 請稍後再試"
 
-#: lib/Jifty/Action/Record/Search.pm:129
+#: lib/Jifty/Action/Record/Search.pm:177
 msgid "Any field contains"
 msgstr "任意欄位包含"
 
@@ -179,15 +194,15 @@
 msgid "Anyway, the software has logged this error."
 msgstr ""
 
-#: lib/Jifty/Plugin/Authentication/Password/Mixin/Model/User.pm:19
+#: lib/Jifty/Plugin/Authentication/Password/Mixin/Model/User.pm:53
 msgid "Authentication token"
 msgstr ""
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:489 lib/Jifty/Plugin/AdminUI/View-not-yet.pm:59
+#:
 msgid "Back to the admin console"
 msgstr "回到管理介面"
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:461
+#:
 msgid "Back to the application"
 msgstr "回到應用程式"
 
@@ -200,7 +215,7 @@
 msgid "Calendar"
 msgstr "日曆"
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:338
+#: lib/Jifty/View/Declare/CRUD.pm:417
 msgid "Cancel"
 msgstr "取消"
 
@@ -212,36 +227,40 @@
 msgid "Close window"
 msgstr "關閉視窗"
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:382
+#:
 msgid "Confirm delete?"
 msgstr "是否確定刪除?"
 
-#: lib/Jifty/Plugin/Authentication/Password/Action/SendAccountConfirmation.pm:97
+#: lib/Jifty/Plugin/Authentication/Password/Action/SendAccountConfirmation.pm:94
 msgid "Confirmation resent."
+msgstr "確認信已寄出."
+
+#: lib/Jifty/Plugin/OpenID/View.pm:65
+msgid "Continue"
 msgstr ""
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:213
+#: lib/Jifty/View/Declare/CRUD.pm:668
 msgid "Create"
 msgstr "建立"
 
-#: lib/Jifty/Action/Record/Create.pm:81
+#: lib/Jifty/Action/Record/Create.pm:91
 #. (ref($record)
 msgid "Create of %1 failed: %2"
 msgstr "無法建立 %1: %2"
 
-#: lib/Jifty/Action/Record/Create.pm:105
+#: lib/Jifty/Action/Record/Create.pm:118
 msgid "Created"
 msgstr "成功建立項目."
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:415
+#:
 msgid "Database Administration"
 msgstr "資料庫管理"
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:380
+#: lib/Jifty/View/Declare/CRUD.pm:427
 msgid "Delete"
 msgstr "刪除"
 
-#: lib/Jifty/Action/Record/Delete.pm:76
+#: lib/Jifty/Action/Record/Delete.pm:82
 msgid "Deleted"
 msgstr "成功刪除項目."
 
@@ -249,11 +268,11 @@
 msgid "Dismiss"
 msgstr "關閉"
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:458 lib/Jifty/Plugin/AdminUI/View-not-yet.pm:486 lib/Jifty/Plugin/AdminUI/View-not-yet.pm:56
+#:
 msgid "Done?"
 msgstr "設定完畢?"
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:388 share/web/templates/__jifty/halo:126 share/web/templates/__jifty/halo:20
+#: lib/Jifty/View/Declare/CRUD.pm:347 share/web/templates/__jifty/halo:126 share/web/templates/__jifty/halo:20
 msgid "Edit"
 msgstr "編輯"
 
@@ -266,23 +285,27 @@
 msgid "Email"
 msgstr "電郵"
 
-#: lib/Jifty/Plugin/User/Mixin/Model/User.pm:33
+#: lib/Jifty/Plugin/User/Mixin/Model/User.pm:52
 msgid "Email address"
 msgstr "電郵地址"
 
-#: lib/Jifty/Plugin/User/Mixin/Model/User.pm:35
+#: lib/Jifty/Plugin/User/Mixin/Model/User.pm:54
 msgid "Email address confirmed?"
 msgstr "已確認電郵地址?"
 
-#: lib/Jifty/Action.pm:1158
+#: lib/Jifty/Plugin/Authentication/Password/View.pm:209
+msgid "Fill in your address below, and we'll send out another confirmation email to you. "
+msgstr "請填入您的電郵,我們會再寄一封確認信給您."
+
+#: lib/Jifty/Action.pm:1312
 msgid "Foo cannot contain -, *, +, or ?."
 msgstr ""
 
-#: lib/Jifty/Action.pm:1152
+#: lib/Jifty/Action.pm:1306
 msgid "Foo cannot contain uppercase letters."
 msgstr ""
 
-#: lib/Jifty/Action.pm:1136
+#: lib/Jifty/Action.pm:1285
 msgid "Foo values are always in lowercase."
 msgstr ""
 
@@ -294,6 +317,10 @@
 msgid "Go back home..."
 msgstr "回首頁..."
 
+#: lib/Jifty/Plugin/OpenID/View.pm:35
+msgid "Go for it!"
+msgstr ""
+
 #: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:34
 msgid "Hashed Password"
 msgstr ""
@@ -307,20 +334,26 @@
 msgid "Hello, %1!"
 msgstr "%1 您好!"
 
-#: lib/Jifty/Plugin/SkeletonApp/View.pm:31 share/web/templates/_elements/sidebar:5
-#. ($u->$method()
+#: lib/Jifty/Plugin/Authentication/Facebook/Action/LoginFacebookUser.pm:103
+#. (Jifty->web->current_user->user_object->facebook_name)
+msgid "Hi %1!"
+msgstr ""
+
+#: lib/Jifty/Plugin/SkeletonApp/View.pm:31 share/web/templates/_elements/sidebar:4
+#. ($u->username)
+#. (Jifty->web->current_user->username)
 msgid "Hiya, %1."
 msgstr "%1 您好."
 
-#: lib/Jifty/Plugin/SkeletonApp/Dispatcher.pm:23
+#: lib/Jifty/Plugin/SkeletonApp/Dispatcher.pm:26
 msgid "Home"
 msgstr "首頁"
 
-#: lib/Jifty/Plugin/User/Mixin/Model/User.pm:30
+#: lib/Jifty/Plugin/User/Mixin/Model/User.pm:49
 msgid "How should I display your name to other users?"
 msgstr "您在本系統的代號"
 
-#: lib/Jifty/Action.pm:1090
+#: lib/Jifty/Action.pm:1212
 msgid "I changed $field for you"
 msgstr ""
 
@@ -328,29 +361,45 @@
 msgid "I'm not sure how this happened."
 msgstr ""
 
+#: lib/Jifty/Plugin/OpenID/View.pm:59
+msgid "If the username provided conflicts with an existing username or contains invalid characters, you will have to give us a new one."
+msgstr ""
+
+#: lib/Jifty/Plugin/OpenID/View.pm:27
+msgid "If you have a Livejournal or other OpenID account, you don't even need to sign up. Just log in."
+msgstr ""
+
 #: lib/Jifty/Plugin/ErrorTemplates/View.pm:62
 msgid "Internal error"
 msgstr ""
 
-#: lib/Jifty/Plugin/Authentication/Password/Action/SendAccountConfirmation.pm:78 lib/Jifty/Plugin/Authentication/Password/Action/SendPasswordReminder.pm:79
-msgid "It doesn't look like there's an account by that name."
+#: lib/Jifty/Plugin/OpenID/Action/AuthenticateOpenID.pm:59
+msgid "Invalid OpenID URL.  Please check to make sure it is correct.  (@{[$csr->err]})"
 msgstr ""
 
-#: lib/Jifty/Plugin/User/Mixin/Model/User.pm:89
+#: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:63 lib/Jifty/Plugin/Authentication/Password/Action/SendAccountConfirmation.pm:75 lib/Jifty/Plugin/Authentication/Password/Action/SendPasswordReminder.pm:79
+msgid "It doesn't look like there's an account by that name."
+msgstr "沒有這個帳號."
+
+#: lib/Jifty/Plugin/User/Mixin/Model/User.pm:106
 msgid "It looks like somebody else is using that address. Is there a chance you have another account?"
 msgstr ""
 
+#: lib/Jifty/Plugin/OpenID/Dispatcher.pm:43
+msgid "It looks like someone is already using that OpenID."
+msgstr ""
+
 #: lib/Jifty/Plugin/Authentication/Password/Action/Signup.pm:75
 msgid "It looks like you already have an account. Perhaps you want to <a href=\"/login\">log in</a> instead?"
-msgstr ""
+msgstr "您似乎已經有帳號了.按這裡<a href=\"/login\">登入</a>."
 
 #: lib/Jifty/Plugin/Authentication/Password/Action/ResetLostPassword.pm:75
 msgid "It looks like you didn't enter the same password into both boxes. Give it another shot?"
-msgstr ""
+msgstr "兩組密碼不同.請再試一次."
 
-#: lib/Jifty/Plugin/Authentication/Password/Action/SendAccountConfirmation.pm:82
+#: lib/Jifty/Plugin/Authentication/Password/Action/SendAccountConfirmation.pm:79
 msgid "It looks like you're already confirmed."
-msgstr ""
+msgstr "您的帳號已通過認證."
 
 #:
 msgid "Jifty Administrative Console"
@@ -364,27 +413,39 @@
 msgid "Jifty Pod Online"
 msgstr "Jifty 線上 POD 文件"
 
-#: lib/Jifty/View/Declare/Helpers.pm:375 share/web/templates/_elements/wrapper:18
+#: lib/Jifty/View/Declare/Page.pm:194 share/web/templates/_elements/wrapper:18
 msgid "Loading..."
 msgstr "請稍候..."
 
-#: lib/Jifty/Plugin/Authentication/Password/Dispatcher.pm:106
+#: lib/Jifty/Plugin/Authentication/Password/Dispatcher.pm:132 lib/Jifty/Plugin/Authentication/Password/View.pm:88
 msgid "Login"
 msgstr "登入"
 
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:55
+#: lib/Jifty/Plugin/Authentication/Facebook/View.pm:24
+msgid "Login to Facebook now to get started!"
+msgstr ""
+
+#: lib/Jifty/Plugin/Authentication/Facebook/View.pm:41
+msgid "Login to Facebook now to link it with your current account!"
+msgstr ""
+
+#: lib/Jifty/Plugin/Authentication/Password/View.pm:82
 msgid "Login with a password"
 msgstr "使用密碼登入"
 
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:40
+#: lib/Jifty/Plugin/OpenID/View.pm:17
+msgid "Login with your OpenID"
+msgstr ""
+
+#: lib/Jifty/Plugin/Authentication/Password/View.pm:57
 msgid "Login!"
 msgstr "登入!"
 
-#: lib/Jifty/Plugin/Authentication/Password/Dispatcher.pm:123
+#: lib/Jifty/Plugin/Authentication/Password/Dispatcher.pm:151
 msgid "Logout"
 msgstr "登出"
 
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:63
+#: lib/Jifty/Plugin/Authentication/Password/View.pm:90
 msgid "Lost your password?"
 msgstr "忘記密碼了?"
 
@@ -396,47 +457,50 @@
 msgid "Manage records:"
 msgstr "管理記錄:"
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:46 lib/Jifty/Plugin/AdminUI/View-not-yet.pm:473
-#. ($object_type)
-msgid "Manage records: [_1]"
-msgstr ""
-
 #: share/web/templates/__jifty/error/mason_internal_error:1
 msgid "Mason error"
 msgstr "Mason 系統錯誤"
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:429
+#:
 msgid "Models"
 msgstr "模型"
 
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:81
+#: lib/Jifty/Plugin/Authentication/Password/View.pm:114
 msgid "New password"
 msgstr "新的密碼"
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:172
+#: lib/Jifty/View/Declare/CRUD.pm:628
 msgid "Next Page"
 msgstr "下一頁"
 
-#: lib/Jifty/Plugin/User/Mixin/Model/User.pm:29
+#: lib/Jifty/Plugin/User/Mixin/Model/User.pm:48
 msgid "Nickname"
 msgstr "暱稱"
 
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:52
+#: lib/Jifty/Plugin/Authentication/Password/View.pm:79
 msgid "No account yet? It's quick and easy. "
 msgstr "還沒有註冊帳號嗎? "
 
-#: lib/Jifty/Action/Record/Search.pm:130
+#: lib/Jifty/Action/Record/Search.pm:178
 msgid "No field contains"
 msgstr "沒有欄位包含"
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:139
+#:
 msgid "No items found"
 msgstr "未找到記錄"
 
-#: lib/Jifty/Web.pm:302
+#: lib/Jifty/View/Declare/CRUD.pm:547
+msgid "No items found."
+msgstr ""
+
+#: lib/Jifty/Web.pm:307
 msgid "No request to handle"
 msgstr "沒有可處理的要求"
 
+#: lib/Jifty/Plugin/UUID/Widget.pm:37
+msgid "No value yet"
+msgstr ""
+
 #:
 msgid "Online Documentation"
 msgstr "線上文件"
@@ -445,7 +509,19 @@
 msgid "Online docs"
 msgstr "線上文件"
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:134
+#: lib/Jifty/Plugin/OpenID/Action/AuthenticateOpenID.pm:27
+msgid "OpenID URL"
+msgstr ""
+
+#: lib/Jifty/Plugin/OpenID/Action/VerifyOpenID.pm:54
+msgid "OpenID verification failed.  It looks like you cancelled the OpenID verification request."
+msgstr ""
+
+#: lib/Jifty/Plugin/OpenID/Action/VerifyOpenID.pm:63
+msgid "OpenID verification failed: "
+msgstr ""
+
+#: lib/Jifty/View/Declare/CRUD.pm:595
 #. ($page, $collection->pager->last_page)
 msgid "Page %1 of %2"
 msgstr "第 %1 頁, 共 %2 頁"
@@ -458,15 +534,15 @@
 msgid "Parent"
 msgstr "上層元件"
 
-#: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:29 lib/Jifty/Plugin/Authentication/Password/Action/ResetLostPassword.pm:32 lib/Jifty/Plugin/Authentication/Password/Mixin/Model/User.pm:26
+#: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:29 lib/Jifty/Plugin/Authentication/Password/Action/ResetLostPassword.pm:32 lib/Jifty/Plugin/Authentication/Password/Mixin/Model/User.pm:59
 msgid "Password"
 msgstr "密碼"
 
-#: lib/Jifty/Plugin/Authentication/Password/Mixin/Model/User.pm:88
+#: lib/Jifty/Plugin/Authentication/Password/Mixin/Model/User.pm:134
 msgid "Passwords need to be at least six characters long"
 msgstr "密碼至少需要有六個字"
 
-#: lib/Jifty/Record.pm:272 lib/Jifty/Record.pm:351 lib/Jifty/Record.pm:70
+#: lib/Jifty/Record.pm:485 lib/Jifty/Record.pm:564 lib/Jifty/Record.pm:71
 msgid "Permission denied"
 msgstr "權限不足."
 
@@ -474,10 +550,14 @@
 msgid "Please email us!"
 msgstr "請寫信通知我們!"
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:163
+#: lib/Jifty/View/Declare/CRUD.pm:617
 msgid "Previous Page"
 msgstr "上一頁"
 
+#: lib/Jifty/View/Declare/CRUD.pm:430
+msgid "Really delete?"
+msgstr ""
+
 #: lib/Jifty/Plugin/Authentication/Password/Action/ResetLostPassword.pm:65
 msgid "Really, really sorry."
 msgstr "十二萬分抱歉."
@@ -495,11 +575,11 @@
 msgid "Rendered in %1s"
 msgstr "頁面繪製時間: %1 秒"
 
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:78
+#:
 msgid "Reset lost password"
 msgstr "重設密碼"
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:53
+#:
 msgid "Run the action"
 msgstr "執行動作"
 
@@ -507,7 +587,7 @@
 msgid "SQL Statements"
 msgstr "SQL 陳述式"
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:327
+#: lib/Jifty/View/Declare/CRUD.pm:408
 msgid "Save"
 msgstr "儲存"
 
@@ -515,31 +595,35 @@
 msgid "Schema"
 msgstr "綱要"
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:304
+#: lib/Jifty/View/Declare/CRUD.pm:298
 msgid "Search"
 msgstr "搜尋"
 
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:119
+#: lib/Jifty/Plugin/Authentication/Password/View.pm:150 lib/Jifty/Plugin/Authentication/Password/View.pm:180
 msgid "Send"
 msgstr "寄出"
 
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:97
+#:
 msgid "Send a link to reset your password"
 msgstr "寄送重設密碼的鏈結"
 
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:109 lib/Jifty/Plugin/Authentication/Password/View.pm:114
+#: lib/Jifty/Plugin/Authentication/Password/View.pm:175
 msgid "Send a password reminder"
 msgstr "重設密碼"
 
-#: lib/Jifty/Plugin/Authentication/Password/Dispatcher.pm:111
+#: lib/Jifty/Plugin/Feedback/View.pm:21
+msgid "Send us feedback!"
+msgstr ""
+
+#: lib/Jifty/Plugin/Authentication/Password/Dispatcher.pm:137 lib/Jifty/Plugin/Authentication/Password/View.pm:42 lib/Jifty/Plugin/Authentication/Password/View.pm:47
 msgid "Sign up"
 msgstr "註冊"
 
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:53
+#: lib/Jifty/Plugin/Authentication/Password/View.pm:80
 msgid "Sign up for an account!"
 msgstr "現在就註冊!"
 
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:30 lib/Jifty/Plugin/Authentication/Password/View.pm:35
+#:
 msgid "Signup"
 msgstr "註冊"
 
@@ -560,32 +644,45 @@
 msgid "Sorry about this."
 msgstr "非常抱歉."
 
+#: lib/Jifty/Plugin/Authentication/Facebook/Action/LoginFacebookUser.pm:62
+msgid "Sorry, something weird happened (we couldn't create a user for you).  Try again later."
+msgstr ""
+
 #:
 msgid "Table of Contents"
 msgstr "目錄"
 
-#: lib/Jifty/Plugin/User/Mixin/Model/User.pm:82
+#: lib/Jifty/Plugin/Feedback/View.pm:23
+#. (Jifty->config->framework('ApplicationName')
+msgid "Tell us what's good, what's bad, and what else you want %1 to do!"
+msgstr ""
+
+#: lib/Jifty/Plugin/User/Mixin/Model/User.pm:99
 #. ($new_email)
 msgid "That %1 doesn't look like an email address."
 msgstr ""
 
-#: lib/Jifty/Action.pm:878
+#: lib/Jifty/Action.pm:975
 msgid "That doesn't look like a correct value"
 msgstr "欄位格式錯誤."
 
-#: lib/Jifty/Plugin/Authentication/Password/Action/SendAccountConfirmation.pm:71 lib/Jifty/Plugin/Authentication/Password/Action/SendPasswordReminder.pm:72 lib/Jifty/Plugin/Authentication/Password/Action/Signup.pm:70
+#: lib/Jifty/Plugin/Authentication/Password/Action/SendAccountConfirmation.pm:68 lib/Jifty/Plugin/Authentication/Password/Action/SendPasswordReminder.pm:72 lib/Jifty/Plugin/Authentication/Password/Action/Signup.pm:70
 msgid "That doesn't look like an email address."
 msgstr ""
 
-#: lib/Jifty/Action/Record.pm:249
+#: lib/Jifty/Action/Record.pm:300
 msgid "That doesn't look right, but I don't know why"
 msgstr "欄位內容錯誤."
 
-#: lib/Jifty/Action/Record.pm:181
+#: lib/Jifty/Plugin/OpenID/Dispatcher.pm:48
+msgid "The OpenID '$openid' has been linked to your account."
+msgstr ""
+
+#: lib/Jifty/Action/Record.pm:205
 msgid "The passwords you typed didn't match each other"
 msgstr "兩組密碼不符合."
 
-#: lib/Jifty/Web.pm:365
+#: lib/Jifty/Web.pm:428
 msgid "There was an error completing the request.  Please try again later."
 msgstr "系統執行錯誤, 請稍候再試."
 
@@ -597,15 +694,15 @@
 msgid "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."
 msgstr ""
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:419
+#:
 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 "您可利用此介面來管理資料庫的內容. 請點選表格名稱, 進行增刪及編輯."
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:425
+#:
 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\" 即可."
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:127
+#: lib/Jifty/View/Declare/CRUD.pm:515
 msgid "Toggle search"
 msgstr "切換搜尋畫面"
 
@@ -621,7 +718,7 @@
 msgid "Type that again?"
 msgstr "請再輸入一次"
 
-#: lib/Jifty/Action/Record/Update.pm:156
+#: lib/Jifty/Action/Record/Update.pm:168
 msgid "Updated"
 msgstr "成功更新項目."
 
@@ -633,6 +730,10 @@
 msgid "W00t"
 msgstr "喔耶"
 
+#: lib/Jifty/Plugin/OpenID/View.pm:53
+msgid "We need you to set a username or quickly check the one associated with your OpenID. Your username is what other people will see when you ask questions or make suggestions"
+msgstr ""
+
 #: lib/Jifty/Plugin/Authentication/Password/Action/Signup.pm:114
 msgid "We've sent a confirmation message to your email box."
 msgstr "系統已將確認信寄送到您的電郵信箱."
@@ -642,7 +743,11 @@
 msgid "Welcome %1 to the %2"
 msgstr "歡迎 %1 蒞臨 %2"
 
-#: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:181
+#: lib/Jifty/Plugin/OpenID/Dispatcher.pm:64
+msgid "Welcome back, "
+msgstr ""
+
+#: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:182
 #. ($user->name)
 msgid "Welcome back, %1."
 msgstr "歡迎回來, %1."
@@ -652,20 +757,27 @@
 msgid "Welcome to %1!"
 msgstr "歡迎光臨 %1!"
 
-#: lib/Jifty/Plugin/Authentication/Password/Action/Signup.pm:113
+#: lib/Jifty/Plugin/Authentication/Password/Action/ConfirmEmail.pm:57 lib/Jifty/Plugin/Authentication/Password/Action/Signup.pm:113
 #. (Jifty->config->framework('ApplicationName')
 msgid "Welcome to %1, %2."
 msgstr "歡迎光臨 %1, %2!"
 
-#: lib/Jifty/Plugin/Authentication/Password/Action/ConfirmEmail.pm:57
-#. (Jifty->config->framework('ApplicationName')
+#:
 msgid "Welcome to %1, %2. "
 msgstr "歡迎光臨 %1, %2. "
 
-#: lib/Jifty/Plugin/SkeletonApp/View.pm:75 share/web/templates/index.html:1
+#: lib/Jifty/Plugin/SkeletonApp/View.pm:76 share/web/templates/index.html:1
 msgid "Welcome to your new Jifty application"
 msgstr "歡迎光臨您嶄新的 Jifty 應用程式"
 
+#: lib/Jifty/Plugin/OpenID/Action/CreateOpenIDUser.pm:104
+msgid "Welcome, "
+msgstr ""
+
+#: lib/Jifty/Plugin/OpenID/View.pm:42
+msgid "You already logged in."
+msgstr ""
+
 #: lib/Jifty/Plugin/Authentication/Password/Action/ResetLostPassword.pm:63
 msgid "You don't exist."
 msgstr "您不存在."
@@ -682,39 +794,44 @@
 msgid "You have already confirmed your account."
 msgstr "您已經確認過這個帳號了."
 
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:98
+#: lib/Jifty/Plugin/Authentication/Password/View.pm:146
 msgid "You lost your password. A link to reset it will be sent to the following email address:"
-msgstr ""
+msgstr "您忘記密碼了.我們會寄送一個重設密碼的連結到下面的電郵地址:"
 
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:115
+#: lib/Jifty/Plugin/Authentication/Password/View.pm:176
 msgid "You lost your password. A reminder will be send to the following mail:"
-msgstr ""
-
-#: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:152
-msgid "You may have mistyped your email address or password. Give it another shot."
-msgstr ""
+msgstr "您忘記密碼了.我們會寄送一份提示到下面的電郵地址:"
 
-#: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:146
+#: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:146 lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:152
 msgid "You may have mistyped your email or password. Give it another shot."
+msgstr "你可以打錯了電郵或密碼.請再試一次"
+
+#: lib/Jifty/Plugin/Authentication/Facebook/Action/LinkFacebookUser.pm:40
+msgid "You must be logged in to link your user to your Facebook account."
 msgstr ""
 
-#: lib/Jifty/Action.pm:865
+#: lib/Jifty/Action.pm:962
 msgid "You need to fill in this field"
 msgstr "此欄位不能留空."
 
-#: lib/Jifty/Plugin/SkeletonApp/View.pm:75 share/web/templates/index.html:3
+#: lib/Jifty/Plugin/SkeletonApp/View.pm:76 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)"
 
-#: lib/Jifty/Plugin/Authentication/Password/View.pm:69
+#: lib/Jifty/Plugin/Authentication/Password/View.pm:96
 msgid "You're already logged in."
 msgstr "您已經登入了."
 
-#: lib/Jifty/Plugin/SkeletonApp/View.pm:34 share/web/templates/_elements/sidebar:7
+#: lib/Jifty/Plugin/SkeletonApp/View.pm:34 share/web/templates/_elements/sidebar:6
 msgid "You're not currently signed in."
 msgstr "您目前尚未登入."
 
+#: lib/Jifty/Plugin/Authentication/Facebook/Action/LinkFacebookUser.pm:72
+#. (Jifty->web->current_user->user_object->facebook_name)
+msgid "Your account has been successfully linked to your Facebook user %1!"
+msgstr ""
+
 #: lib/Jifty/Plugin/Authentication/Password/Action/Login.pm:38
 msgid "Your browser can remember your login for you"
 msgstr "下次開啟瀏覽器時, 是否保留您的登入狀態?"
@@ -723,15 +840,15 @@
 msgid "Your password has been reset.  Welcome back."
 msgstr "成功重設您的密碼. 歡迎回來."
 
-#: lib/Jifty/Plugin/Authentication/Password/Mixin/Model/User.pm:28
+#: lib/Jifty/Plugin/Authentication/Password/Mixin/Model/User.pm:61
 msgid "Your password should be at least six characters"
 msgstr "您的密碼必須至少有六個字"
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:246
+#:
 msgid "asc"
 msgstr "遞增"
 
-#: lib/Jifty/Plugin/AdminUI/View-not-yet.pm:264
+#:
 msgid "desc"
 msgstr "遞減"
 
@@ -755,6 +872,6 @@
 msgid "type your password again"
 msgstr "請再次鍵入您的密碼"
 
-#: lib/Jifty/Action.pm:1050
+#: lib/Jifty/Action.pm:1172
 msgid "warning"
 msgstr ""

Modified: jifty/branches/js-refactor/share/web/static/css/autocomplete.css
==============================================================================
--- jifty/branches/js-refactor/share/web/static/css/autocomplete.css	(original)
+++ jifty/branches/js-refactor/share/web/static/css/autocomplete.css	Thu Nov 29 12:25:16 2007
@@ -22,6 +22,7 @@
     color: white;
 }
 
+div.inline .autocomplete .hidden_value,
 .autocomplete .hidden_value {
     display: none;
 }

Modified: jifty/branches/js-refactor/share/web/static/css/notices.css
==============================================================================
--- jifty/branches/js-refactor/share/web/static/css/notices.css	(original)
+++ jifty/branches/js-refactor/share/web/static/css/notices.css	Thu Nov 29 12:25:16 2007
@@ -37,3 +37,24 @@
     text-indent: -9999em;
 }
 
+#jifty-result-popup {
+    bottom: 0.6em;
+    position: fixed;
+    right: 0;
+    width: 180px;
+    z-index: 43;
+}
+
+#jifty-result-popup .popup_notification {
+     background: #A9B7FF url(/static/images/silk/information.png) no-repeat scroll 5px center;
+     border: 1px solid #004E75;
+     overflow: hidden;
+     padding: 0.5em 0.5em 0.5em 27px;
+}
+
+#jifty-result-popup .result-error {
+    background: #FFA8A8 url(/static/images/silk/error.png) no-repeat scroll 5px center;
+    border: 1px solid #B71111;
+    color: #B71111;
+}
+

Modified: jifty/branches/js-refactor/share/web/static/js/calendar.js
==============================================================================
--- jifty/branches/js-refactor/share/web/static/js/calendar.js	(original)
+++ jifty/branches/js-refactor/share/web/static/js/calendar.js	Thu Nov 29 12:25:16 2007
@@ -65,7 +65,7 @@
         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.changePageEvent.subscribe( function() { setTimeout( function() { Jifty.Calendar._blurredCalendar = null; }, 75 ) }, null, false );
         
         cal.render();
 

Modified: jifty/branches/js-refactor/share/web/static/js/jifty.js
==============================================================================
--- jifty/branches/js-refactor/share/web/static/js/jifty.js	(original)
+++ jifty/branches/js-refactor/share/web/static/js/jifty.js	Thu Nov 29 12:25:16 2007
@@ -17,20 +17,20 @@
 function _get_named_args(args) {
     var result = {};
     for (var i = 0; i < args.length; i+=2) {
-	result[args[i]] = args[i+1];
+        result[args[i]] = args[i+1];
     }
     return result;
 
 }
 
 function _get_onclick(action_hash, name, args, path) {
-    var onclick = 'if(event.ctrlKey||event.metaKey||event.altKey||event.shiftKey) return true; return update('
+    var onclick = 'if(event.ctrlKey||event.metaKey||event.altKey||event.shiftKey) return true; return Jifty.update('
     + JSON.stringify({'continuation': {},
-		      'actions': action_hash,
-		      'fragments': [{'mode': 'Replace', 'args': args, 'region': name, 'path': path}]})
+                      'actions': action_hash,
+                      'fragments': [{'mode': 'Replace', 'args': args, 'region': name, 'path': path}]})
     +', this)';
     onclick = onclick.replace(/"/g, "'"); //"' )# grr emacs!
-	return onclick;
+        return onclick;
 }
 // XXX
 var hyperlink  = function() {
@@ -38,9 +38,9 @@
     var current_region = Jifty.Web.current_region;
     var onclick = _get_onclick({}, current_region.name, current_region.args, args.onclick[0].replace_with);
     outs( a(function() { attr(function()
-			      {return ['onclick', onclick, 'href', '#']});
-	    return args.label
-		}));
+                              {return ['onclick', onclick, 'href', '#']});
+            return args.label
+                }));
 }
 
 var render_param = function(a, field) { outs(a.render_param(field)) };
@@ -57,27 +57,27 @@
     var current_region = Jifty.Web.current_region;
     var onclick = _get_onclick(action_hash, current_region.name, current_region.args, current_region.path);
     outs(
-	 div(function() {
-		 attr(function() { return ['class', 'submit_button'] });
-		 return input(function() { attr(function()
-						{return ['type', 'submit',
-							 'onclick', onclick,
-							 'class', 'widget button',
-							 'id', 'S' + (++SERIAL + SERIAL_postfix),
-							 'value', args.label,
-							 'name', 'J:V-region-__page-signup_widget=_signup|J:ACTIONS=signupnow'] })});
-		     }));
+         div(function() {
+                 attr(function() { return ['class', 'submit_button'] });
+                 return input(function() { attr(function()
+                                                {return ['type', 'submit',
+                                                         'onclick', onclick,
+                                                         'class', 'widget button',
+                                                         'id', 'S' + (++SERIAL + SERIAL_postfix),
+                                                         'value', args.label,
+                                                         'name', 'J:V-region-__page-signup_widget=_signup|J:ACTIONS=signupnow'] })});
+                     }));
 
 };
 
 function register_action(a) {
     outs(div(function() {
-		attr(function() { return ['class', 'hidden'] });
-		return input(function() { attr(function() {
-				return ['type', 'hidden',
-					'name', a.register_name(),
-					'id', a.register_name(),
-					'value', a.actionClass] }) } ) } ));
+                attr(function() { return ['class', 'hidden'] });
+                return input(function() { attr(function() {
+                                return ['type', 'hidden',
+                                        'name', a.register_name(),
+                                        'id', a.register_name(),
+                                        'value', a.actionClass] }) } ) } ));
     /* XXX: fallback values */
 }
 
@@ -260,11 +260,13 @@
                                     if (field.nodeName == 'error' || field.nodeName == 'warning') {
                                         var err_div = document.getElementById(field.getAttribute("id"));
                                         if (err_div != null) {
+                                            Element.show(err_div);
                                             err_div.innerHTML = field.firstChild.data;
                                         }
                                     } else if (field.nodeName == 'ok') {
                                         var err_div = document.getElementById(field.getAttribute("id"));
                                         if (err_div != null) {
+                                            Element.hide(err_div);
                                             err_div.innerHTML = '';
                                         }
                                     }
@@ -276,6 +278,7 @@
                                     if (field.nodeName == 'canonicalization_note')  {
                                         var note_div= document.getElementById(field.getAttribute("id"));
                                         if (note_div != null) {
+                                            Element.show(note_div);
                                             note_div.innerHTML = field.firstChild.data;
                                         }
                                     }
@@ -308,7 +311,7 @@
         hide_wait_message();
     },
 
-    disable_input_fields: function() {
+    disable_input_fields: function(disabled_elements) {
         var disable = function() {
             var elt = arguments[0];
             // Disabling hidden elements seems to  make IE sad for some reason
@@ -316,6 +319,7 @@
                 // Triggers https://bugzilla.mozilla.org/show_bug.cgi?id=236791
                 elt.blur();
                 elt.disabled = true;
+                disabled_elements.push(elt);
             }
         };
         this.fields().each(disable);
@@ -331,23 +335,23 @@
 
     /* client side logic extracted from Jifty::Action */
     _action_spec: function() {
-	if (!this.s_a) {
-	    /* XXX: make REST client accessible */
-	    var Todo = new AsynapseRecord('todo');
-	    this.s_a = $H(Todo.eval_ajax_get('/=/action/'+this.actionClass+'.js'));
-	}
-	
-	return this.s_a
+        if (!this.s_a) {
+            /* XXX: make REST client accessible */
+            var Todo = new AsynapseRecord('todo');
+            this.s_a = $H(Todo.eval_ajax_get('/=/action/'+this.actionClass+'.js'));
+        }
+        
+        return this.s_a
     },
     argument_names: function() {
-	return this._action_spec().keys();
+        return this._action_spec().keys();
     },
 
     render_param: function(field) {
-	var a_s = this._action_spec();
-	var type = 'text';
-	var f = new ActionField(field, a_s[field], this);
-	return f.render();
+        var a_s = this._action_spec();
+        var type = 'text';
+        var f = new ActionField(field, a_s[field], this);
+        return f.render();
     },
     register_name: function() { return this.register.id }
 
@@ -358,110 +362,110 @@
 ActionField = Class.create();
 ActionField.prototype = {
  initialize: function(name, args, action) {
-	this.name = name;
-	this.label = args.label;
-	this.hints = args.hints;
-	this.mandatory = args.mandatory;
-	this.ajax_validates = args.ajax_validates;
-	this.current_value = action.data_structure().fields[name].value;
+        this.name = name;
+        this.label = args.label;
+        this.hints = args.hints;
+        this.mandatory = args.mandatory;
+        this.ajax_validates = args.ajax_validates;
+        this.current_value = action.data_structure().fields[name].value;
         this.error = action.result.field_error[name];
-	this.action = action;
-	if (!this.render_mode) this.render_mode = 'update';
-	this.type = 'text';
+        this.action = action;
+        if (!this.render_mode) this.render_mode = 'update';
+        this.type = 'text';
     },
 
  render: function() {
-	if (this.render_mode == 'read')
-	    return this.render_wrapper
-		(this.render_preamble,
-		 this.render_label,
-		 this.render_value);
-	else
-	    return this.render_wrapper
-	    (this.render_preamble,
-	     this.render_label,
-	     this.render_widget,
-	     this.render_autocomplete_div,
-	     this.render_inline_javascript,
-	     this.render_hints,
-	     this.render_errors,
-	     this.render_warnings,
-	     this.render_canonicalization_notes);
+        if (this.render_mode == 'read')
+            return this.render_wrapper
+                (this.render_preamble,
+                 this.render_label,
+                 this.render_value);
+        else
+            return this.render_wrapper
+            (this.render_preamble,
+             this.render_label,
+             this.render_widget,
+             this.render_autocomplete_div,
+             this.render_inline_javascript,
+             this.render_hints,
+             this.render_errors,
+             this.render_warnings,
+             this.render_canonicalization_notes);
     },
  render_wrapper: function () {
-	var classes = ['form_field'];
-	if (this.mandatory) classes.push('mandatory');
-	if (this.name) classes.push('argument-'+this.name);
-	var args = arguments;
-	var tthis = this;
-	return div(function() {
-		attr(function(){return ['class', classes.join(' ')]});
-		var buf = new Array;
-		for (var i = 0; i < args.length; ++i) {
-		    buf.push(typeof(args[i]) == 'function' ? args[i].apply(tthis) : args[i]);
-		}
-		return buf.join('');
-	    });
+        var classes = ['form_field'];
+        if (this.mandatory) classes.push('mandatory');
+        if (this.name) classes.push('argument-'+this.name);
+        var args = arguments;
+        var tthis = this;
+        return div(function() {
+                attr(function(){return ['class', classes.join(' ')]});
+                var buf = new Array;
+                for (var i = 0; i < args.length; ++i) {
+                    buf.push(typeof(args[i]) == 'function' ? args[i].apply(tthis) : args[i]);
+                }
+                return buf.join('');
+            });
     },
     render_preamble: function() {
-	var tthis = this;
-	return span(function(){attr(function(){return ['class', "preamble"]});
-		return tthis.preamble });
+        var tthis = this;
+        return span(function(){attr(function(){return ['class', "preamble"]});
+                return tthis.preamble });
     },
 
     render_label: function() {
-	var tthis = this;
-	if(this.render_mode == 'update')
-	    return label(function(){attr(function(){return['class', "label", 'for', tthis.element_id()]});
-		    return tthis.label });
-	else
-	    return span(function(){attr(function(){return['class', "label" ]});
-		    return tthis.label });
+        var tthis = this;
+        if(this.render_mode == 'update')
+            return label(function(){attr(function(){return['class', "label", 'for', tthis.element_id()]});
+                    return tthis.label });
+        else
+            return span(function(){attr(function(){return['class', "label" ]});
+                    return tthis.label });
     },
  input_name: function() {
-	return ['J:A:F', this.name, this.action.moniker].join('-');
+        return ['J:A:F', this.name, this.action.moniker].join('-');
     },
  render_hints: function() {
-	var tthis = this;
-	return span(function(){attr(function(){return ['class', "hints"]});
-		return tthis.hints });
+        var tthis = this;
+        return span(function(){attr(function(){return ['class', "hints"]});
+                return tthis.hints });
     },
 
  render_errors: function() {
-	if (!this.action) return '';
-	var tthis = this;
-	// XXX: post-request handler needs to extract field error messages
-	return span(function(){attr(function(){return ['class', "error", 'id', 'errors-'+tthis.input_name()]});
-		return tthis.error });
+        if (!this.action) return '';
+        var tthis = this;
+        // XXX: post-request handler needs to extract field error messages
+        return span(function(){attr(function(){return ['class', "error", 'id', 'errors-'+tthis.input_name()]});
+                return tthis.error });
     },
 
  render_widget: function () {
-	var tthis = this;
-	return input(function(){
-		    attr(function(){
-			    var fields = ['type', tthis.type];
-			    if (tthis.input_name) fields.push('name', tthis.input_name());
-			    fields.push('id', tthis.element_id());
-			    if (tthis.current_value) fields.push('value', tthis.current_value);
-			    fields.push('class', tthis._widget_class().join(' '));
-			    if (tthis.max_length) fields.push('size', tthis.max_length, 'maxlength', tthis.max_length);
-			    if (tthis.disable_autocomplete) fields.push('autocomplete', "off");
-			    //" " .$self->other_widget_properties;
-			    return fields;
-			})});
+        var tthis = this;
+        return input(function(){
+                    attr(function(){
+                            var fields = ['type', tthis.type];
+                            if (tthis.input_name) fields.push('name', tthis.input_name());
+                            fields.push('id', tthis.element_id());
+                            if (tthis.current_value) fields.push('value', tthis.current_value);
+                            fields.push('class', tthis._widget_class().join(' '));
+                            if (tthis.max_length) fields.push('size', tthis.max_length, 'maxlength', tthis.max_length);
+                            if (tthis.disable_autocomplete) fields.push('autocomplete', "off");
+                            //" " .$self->other_widget_properties;
+                            return fields;
+                        })});
     },
  _widget_class: function() {
-	var classes = ['form_field'];
-	if (this.mandatory)      classes.push('mandatory');
-	if (this.name)           classes.push('argument-'+this.name);
-	if (this.ajax_validates) classes.push('ajaxvalidation');
-	return classes;
+        var classes = ['form_field'];
+        if (this.mandatory)      classes.push('mandatory');
+        if (this.name)           classes.push('argument-'+this.name);
+        if (this.ajax_validates) classes.push('ajaxvalidation');
+        return classes;
     },
 
  element_id: function() { if(!this._element_id) this._element_id = this.input_name() + '-S' + (++SERIAL + SERIAL_postfix);
-			  return this._element_id; },
+                          return this._element_id; },
  __noSuchMethod__: function(name) {
-	return '<!-- '+name+' not implemented yet -->';
+        return '<!-- '+name+' not implemented yet -->';
     }
 
 };
@@ -515,9 +519,9 @@
     getAction: function (element) {
         element = $(element);    
         var moniker = Form.Element.getMoniker(element);
-	if (!current_actions[moniker])
-	    current_actions[moniker] = new Action(moniker);
-	return current_actions[moniker];
+        if (!current_actions.get(moniker))
+            current_actions.set(moniker, new Action(moniker));
+        return current_actions.get(moniker);
     },
 
     // Returns the name of the field
@@ -596,20 +600,23 @@
          && ((element.nodeName != 'A')     || (! element.getAttribute("name"))))
             return $H();
 
+        if (element.getAttribute("name").length == 0)
+            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];
+            extras.set(bits[0], bits[1]);
         }
         return extras;
     },
 
     buttonActions: function(element) {
         element = $(element);
-        var actions = Form.Element.buttonArguments(element)['J:ACTIONS'];
+        var actions = Form.Element.buttonArguments(element).get('J:ACTIONS');
         if(actions) {
             return actions.split(",");
         } else {
@@ -627,7 +634,7 @@
             var e = document.createElement("input");
             e.setAttribute("type", "hidden");
             e.setAttribute("name", keys[i]);
-            e.setAttribute("value", args[keys[i]]);
+            e.setAttribute("value", args.get(keys[i]));
             e['virtualform'] = Form.Element.getForm(element);
             extras.push(e);
         }
@@ -712,6 +719,9 @@
 
             Element.addClassName( e, "jifty_enter_handler_attached" );
         }
+    },
+    '.form_field .error, .form_field .warning, .form_field .canonicalization_note': function(e) {
+        if ( e.innerHTML == "" ) Element.hide(e);
     }
 });
 
@@ -725,8 +735,8 @@
         this.name = name;
         this.args = $H(args);
         this.path = path;
-        this.parent = parent ? fragments[parent] : null;
-        if (fragments[name]) {
+        this.parent = parent ? fragments.get(parent) : null;
+        if (fragments.get(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
@@ -735,12 +745,12 @@
                 var k = keys[i];
                 var parsed = k.match(/^(.*?)\.(.*)/);
                 if ((parsed != null) && (parsed.length == 3) && (parsed[1] == this.name)) {
-                    delete current_args[k];
+                    current_args.unset(k);
                 }
             }
         }
 
-        fragments[name] = this;
+        fragments.set(name, this);
     },
 
     setPath: function(supplied) {
@@ -749,7 +759,7 @@
         for (var i = 0; i < keys.length; i++) {
             var k = keys[i];
             if (k == this.name) {
-                this.path = current_args[k];
+                this.path = current_args.get(k);
             }
         }
 
@@ -759,7 +769,7 @@
         }
         
         // Propagate back to current args
-        current_args[this.name] = this.path;
+        current_args.set(this.name, this.path);
 
         // Return new value
         return this.path;
@@ -773,7 +783,7 @@
             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];
+                this.args.set(parsed[2], current_args.get(k));
             }
         }
 
@@ -784,7 +794,7 @@
         keys = supplied.keys();
         for (var i = 0; i < keys.length; i++) {
             var k = keys[i];
-            current_args[this.name+'.'+k] = supplied[k];
+            current_args.set(this.name+'.'+k, supplied.get(k));
         }
         
         // Return new values
@@ -842,12 +852,12 @@
         // If we're removing the element, do it now
         // XXX TODO: Effects on this?
         if (f['mode'] == "Delete") {
-            fragments[name] = null;
+            fragments.set(name, null);
             Element.remove(element);
             return;
         }
 
-        f['is_new'] = (fragments[name] ? false : true);
+        f['is_new'] = (fragments.get(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
@@ -866,14 +876,14 @@
 
             // 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)) {
+        } else if ((f['path'] != null) && f['toggle'] && (f['path'] == fragments.get(name).path)) {
             // If they set the 'toggle' flag, and clicking wouldn't change the path
             Element.update(element, '');
-            fragments[name].path = null;
+            fragments.get(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;
+            f['path'] == fragments.get(name).path;
         }
 
     return f;    
@@ -888,7 +898,7 @@
          child = child.nextSibling) {
         var name = child.nodeName.toLowerCase();
         if (table[name])
-	    table[name](child);
+            table[name](child);
     }
 }
 
@@ -902,14 +912,14 @@
             } else if (fragment_bit.firstChild) {
                 textContent = fragment_bit.firstChild.nodeValue;
             } 
-	    try {
-		var cache_func = eval(textContent);
-		CACHE[f['path']] = { 'type': c_type, 'content': cache_func };
-	    }
-	    catch(e) { 
-		alert(e);
-		alert(textContent);
-	    }
+            try {
+                var cache_func = eval(textContent);
+                CACHE[f['path']] = { 'type': c_type, 'content': cache_func };
+            }
+            catch(e) { 
+                alert(e);
+                alert(textContent);
+            }
         }
     });
 };
@@ -919,67 +929,69 @@
 //   - f: fragment spec
 var apply_fragment_updates = function(fragment, f) {
     // We found the right fragment
-    var dom_fragment = fragments[f['region']];
+    var dom_fragment = fragments.get(f['region']);
     var new_dom_args = $H();
 
     var element = f['element'];
     walk_node(fragment,
     { argument: function(fragment_bit) {
-	    // 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;
-	},
+            // 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.set(fragment_bit.getAttribute("name"), textContent);
+        },
       content: function(fragment_bit) {
-	    var textContent = '';
-	    if (fragment_bit.textContent) {
-		textContent = fragment_bit.textContent;
-	    } else if (fragment_bit.firstChild) {
-		textContent = fragment_bit.firstChild.nodeValue;
-	    }
+            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
+            // 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
         YAHOO.util.Event.onAvailable(element.id, function() {
             (function() { this.evalScripts() }).bind(textContent)();
         });
         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
-	}
+        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
+//  - 'action_arguments' is a hash of action monikers to hashes of arguments which should override any arguments coming from form fields
+//        the hash keys for 'action_arguments' are the values of the 'actions' array
 //  - '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
@@ -987,7 +999,7 @@
 //     - '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() {
+Jifty.update = function () {
     // loads
     if(!Ajax.getTransport()) return true;
     // XXX: prevent default behavior in IE
@@ -1001,8 +1013,11 @@
     // The YAML/JSON data structure that will be sent
     var request = $H();
 
+    // Keep track of disabled elements
+    var disabled_elements = $A();
+
     // Set request base path
-    request['path'] = '/__jifty/webservices/xml';
+    request.set('path', '/__jifty/webservices/xml');
 
     // Grab extra arguments (from a button)
     var button_args = Form.Element.buttonFormElements(trigger);
@@ -1019,14 +1034,14 @@
     }
     var optional_fragments;
     if (form && form['J:CALL']) 
-	optional_fragments = [ prepare_element_for_update({'mode':'Replace','args':{},'region':'__page','path': null}) ];
+        optional_fragments = [ prepare_element_for_update({'mode':'Replace','args':{},'region':'__page','path': null}) ];
     // Build actions structure
     var has_request = 0;
-    request['actions'] = $H();
+    request.set('actions', $H());
     for (var moniker in named_args['actions']) {
         var disable = named_args['actions'][moniker];
         var a = new Action(moniker, button_args);
-	current_actions[moniker] = a; // XXX: how do i make this bloody singleton?
+            current_actions.set(moniker, a); // XXX: how do i make this bloody singleton?
         // Special case for Redirect, allow optional, implicit __page
         // from the response to be used.
         if (a.actionClass == 'Jifty::Action::Redirect')
@@ -1036,15 +1051,26 @@
             if (a.hasUpload())
                 return true;
             if(disable) {
-                a.disable_input_fields();
+                a.disable_input_fields(disabled_elements);
             }
-            request['actions'][moniker] = a.data_structure();
+            var param = a.data_structure();
+            var fields = param.fields;
+            var override = named_args['action_arguments'][param.moniker] || {};
+            for (var argname in override) {
+                if (fields[argname]) {
+                    fields[argname].value = override[argname];
+                }
+                else {
+                    fields[argname] = { value: override[argname] };
+                }
+            }
+            request.get('actions').set(moniker, param);
             ++has_request;
         }
 
     }
 
-    request['fragments'] = $H();
+    request.set('fragments', $H());
     var update_from_cache = new Array;
 
     // Build fragments structure
@@ -1057,11 +1083,11 @@
         if (cached && cached['type'] == 'static') {
             var my_fragment = document.createElement('fragment');
             var content_node = document.createElement('content');
-	    var cached_result;
+            var cached_result;
 
-	    Jifty.Web.current_region = fragments[f['region']];
-	    try { cached_result = apply_cached_for_action(cached['content'], []) }
-	    catch (e) { alert(e) }
+            Jifty.Web.current_region = fragments.get(f['region']);
+            try { cached_result = apply_cached_for_action(cached['content'], []) }
+            catch (e) { alert(e) }
 
             content_node.textContent = cached_result;
             my_fragment.appendChild(content_node);
@@ -1071,49 +1097,49 @@
  } );
             continue;
         }
-	else if (cached && cached['type'] == 'action') {
+        else if (cached && cached['type'] == 'action') {
             var my_fragment = document.createElement('fragment');
             var content_node = document.createElement('content');
 
             my_fragment.appendChild(content_node);
             my_fragment.setAttribute('id', f['region']);
             update_from_cache.push(function(){
-		    var cached_result;
-		    Jifty.Web.current_region = fragments[f['region']];
-		    try {
-			cached_result = apply_cached_for_action(cached['content'], Form.getActions(form));
-		    }
-		    catch (e) { alert(e); throw e }
-		    content_node.textContent = cached_result;
-		    apply_fragment_updates(my_fragment, f);
+                    var cached_result;
+                    Jifty.Web.current_region = fragments.get(f['region']);
+                    try {
+                        cached_result = apply_cached_for_action(cached['content'], Form.getActions(form));
+                    }
+                    catch (e) { alert(e); throw e }
+                    content_node.textContent = cached_result;
+                    apply_fragment_updates(my_fragment, f);
  } );
             continue;
-	}
+        }
         else if (cached && cached['type'] == 'crudview') {
-	    try { // XXX: get model class etc as metadata in cache 
-		// XXX: kill dup code
-	    var Todo = new AsynapseRecord('todo');
-	    var record = Todo.find(f['args']['id']);
+            try { // XXX: get model class etc as metadata in cache 
+                // XXX: kill dup code
+            var Todo = new AsynapseRecord('todo');
+            var record = Todo.find(f['args']['id']);
             var my_fragment = document.createElement('fragment');
             var content_node = document.createElement('content');
             content_node.textContent = cached['content'](record);
             my_fragment.appendChild(content_node);
             my_fragment.setAttribute('id', f['region']);
             update_from_cache.push(function(){ apply_fragment_updates(my_fragment, f); } );
-	    } catch (e) { alert(e) };
-	    continue;
-	}
+            } catch (e) { alert(e) };
+            continue;
+        }
 
         // Update with all new values
         var name = f['region'];
-        var fragment_request = fragments[name].data_structure(f['path'], f['args']);
+        var fragment_request = fragments.get(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;
+        request.get('fragments').set(name, fragment_request);
         ++has_request;
     }
 
@@ -1130,23 +1156,28 @@
         // Grab the XML response
         var response = transport.responseXML.documentElement;
 
-	// Get action results
+        // Get action results
         walk_node(response,
-	{ result: function(result) {
-		var moniker = result.getAttribute("moniker");
-		walk_node(result,
-			  { field: function(field) {
-				  var error = field.getElementsByTagName('error')[0];
-				  if (error) {
-				      var text = error.textContent
-					  ? error.textContent
-					  : (error.firstChild ? error.firstChild.nodeValue : '');
-				      var action = current_actions[moniker];
-				      action.result.field_error[field.getAttribute("name")] = text;
-				      }
-			      }});
-	    }});
-	// empty known action. XXX: we should only need to discard actions being submitted
+        { result: function(result) {
+                var moniker = result.getAttribute("moniker");
+                walk_node(result,
+                          { field: function(field) {
+                                  var error = field.getElementsByTagName('error')[0];
+                                  if (error) {
+                                      var text = error.textContent
+                                          ? error.textContent
+                                          : (error.firstChild ? error.firstChild.nodeValue : '');
+                                      var action = current_actions.get(moniker);
+                                      action.result.field_error[field.getAttribute("name")] = text;
+                                      }
+                              }});
+            }});
+
+        for ( var i = 0; i < disabled_elements.length; i++ ) {
+            disabled_elements[i].disabled = false;
+        }
+
+        // empty known action. XXX: we should only need to discard actions being submitted
 
         // Loop through the result looking for it
         var expected_fragments = optional_fragments ? optional_fragments : named_args['fragments'];
@@ -1165,20 +1196,20 @@
             extract_cacheable(response_fragment, f);
         }
 
-	update_from_cache.each(function(x) { x() });
+        update_from_cache.each(function(x) { x() });
 
         walk_node(response,
-	{ result: function(result) {
+        { result: function(result) {
                 for (var key = result.firstChild;
                      key != null;
                      key = key.nextSibling) {
                     show_action_result(result.getAttribute("moniker"),key);
                 }
             },
-	  redirect: function(redirect) {
+          redirect: function(redirect) {
                 document.location =  redirect.firstChild.firstChild.nodeValue;
-	}});
-	current_actions = $H();
+        }});
+        current_actions = $H();
     };
     var onFailure = function(transport, object) {
         hide_wait_message_now();
@@ -1187,23 +1218,21 @@
 
         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();
+        for ( var i = 0; i < disabled_elements.length; i++ ) {
+            disabled_elements[i].disabled = false;
         }
     };
 
     // Build variable structure
-    request['variables'] = $H();
+    request.set('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];
+        request.get('variables').set('region-'+k, current_args.get(k));
     }
 
     // Build continuation structure
-    request['continuation'] = named_args['continuation'];
+    request.set('continuation', named_args['continuation']);
 
     // Push any state variables which we set into the forms
     for (var i = 0; i < document.forms.length; i++) {
@@ -1212,20 +1241,20 @@
         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];
+                form['J:V-region-'+n].value = current_args.get(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]);
+                hidden.setAttribute('value', current_args.get(n));
                 form.appendChild(hidden);
             }
         }
     }
 
     // Set up our options
-    var options = { postBody: JSON.stringify(request),
+    var options = { postBody: request.toJSON(), //JSON.stringify(request.toObject),
                     onSuccess: onSuccess,
                     onException: onFailure,
                     onFailure: onFailure,
@@ -1240,6 +1269,11 @@
     return false;
 }
 
+function update ( named_args, trigger ) {
+    alert( 'please use Jifty.update instead of update.' );
+    return Jifty.update( named_args, trigger );
+}
+
 function trace( msg ){
   if( typeof( jsTrace ) != 'undefined' ){
     jsTrace.send( msg );
@@ -1380,10 +1414,10 @@
   getUpdatedChoices: function() {
       var request = { path: this.url, actions: {} };
 
-      var a = $H();
+      var a = {}; //$H();
       a['moniker'] = 'autocomplete';
       a['class']   = 'Jifty::Action::Autocomplete';
-      a['fields']  = $H();
+      a['fields']  = {}; //$H();
       a['fields']['moniker']  = this.action.moniker;
       a['fields']['argument'] = Form.Element.getField(this.field);
       request['actions']['autocomplete'] = a;
@@ -1474,3 +1508,49 @@
         return true;
     }
 }
+
+function _sp_submit_form(elt, event, submit_to) {
+    var form = Form.Element.getForm(elt);
+    var elements = Form.getElements(form);
+
+    // Three things need to get merged -- hidden defaults, defaults
+    // from buttons, and form values.  Hence, we build up three lists
+    // and then merge them.
+    var hiddens = $H();
+    var buttons = $H();
+    var inputs = $H();
+    for (var i = 0; i < elements.length; i++) {
+        var e = elements[i];
+        var parsed = e.getAttribute("name").match(/^J:V-region-__page\.(.*)/);
+        var extras = Form.Element.buttonArguments(e);
+        if (extras.keys().length > 1) {
+            // Button with values
+            for (var j = 0; j < extras.keys().length; j++) {
+                if ( extras.keys()[j] == 'extend' ) continue;
+                // Might also have J:V mappings on it
+                parsed = extras.keys()[j].match(/^J:V-region-__page\.(.*)/);
+                if ((parsed != null) && (parsed.length == 2)) {
+                    buttons.set(parsed[1], extras.values()[j]);
+                } else if (extras.keys()[j].length > 0) {
+                    inputs.set(extras.keys()[j], extras.values()[j]);
+                }
+                
+            }
+        } else if ((parsed != null) && (parsed.length == 2)) {
+            // Hidden default
+            hiddens.set(parsed[1], $F(e));
+        } else if (e.name.length > 0) {
+            // Straight up values
+            inputs.set(e.name, $F(e));
+        }
+    }
+
+    var args = hiddens.merge(buttons.merge(inputs));
+
+    /* we want to feed a common object instead of a Hash to Jifty.update */ 
+    var args_object = {};
+    args.each( function( pair ) { args_object[pair.key] = pair.value; } );
+
+    if(event.ctrlKey||event.metaKey||event.altKey||event.shiftKey) return true;
+    return Jifty.update( {'continuation':{},'actions':null,'fragments':[{'mode':'Replace','args':args_object,'region':'__page','path': submit_to}]}, elt );
+}

Modified: jifty/branches/js-refactor/share/web/static/js/prototype.js
==============================================================================
--- jifty/branches/js-refactor/share/web/static/js/prototype.js	(original)
+++ jifty/branches/js-refactor/share/web/static/js/prototype.js	Thu Nov 29 12:25:16 2007
@@ -1,52 +1,111 @@
-/*  Prototype JavaScript framework, version 1.5.1
+/*  Prototype JavaScript framework, version 1.6.0
  *  (c) 2005-2007 Sam Stephenson
  *
  *  Prototype is freely distributable under the terms of an MIT-style license.
  *  For details, see the Prototype web site: http://www.prototypejs.org/
  *
-/*--------------------------------------------------------------------------*/
+ *--------------------------------------------------------------------------*/
 
 var Prototype = {
-  Version: '1.5.1',
+  Version: '1.6.0',
 
   Browser: {
     IE:     !!(window.attachEvent && !window.opera),
     Opera:  !!window.opera,
     WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
-    Gecko:  navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1
+    Gecko:  navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1,
+    MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)
   },
 
   BrowserFeatures: {
     XPath: !!document.evaluate,
     ElementExtensions: !!window.HTMLElement,
     SpecificElementExtensions:
-      (document.createElement('div').__proto__ !==
-       document.createElement('form').__proto__)
+      document.createElement('div').__proto__ &&
+      document.createElement('div').__proto__ !==
+        document.createElement('form').__proto__
   },
 
-  ScriptFragment: '<script[^>]*>([\u0001-\uFFFF]*?)</script>',
-  JSONFilter: /^\/\*-secure-\s*(.*)\s*\*\/\s*$/,
+  ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
+  JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
 
   emptyFunction: function() { },
   K: function(x) { return x }
-}
+};
+
+if (Prototype.Browser.MobileSafari)
+  Prototype.BrowserFeatures.SpecificElementExtensions = false;
 
+if (Prototype.Browser.WebKit)
+  Prototype.BrowserFeatures.XPath = false;
+
+/* Based on Alex Arnell's inheritance implementation. */
 var Class = {
   create: function() {
-    return function() {
+    var parent = null, properties = $A(arguments);
+    if (Object.isFunction(properties[0]))
+      parent = properties.shift();
+
+    function klass() {
       this.initialize.apply(this, arguments);
     }
+
+    Object.extend(klass, Class.Methods);
+    klass.superclass = parent;
+    klass.subclasses = [];
+
+    if (parent) {
+      var subclass = function() { };
+      subclass.prototype = parent.prototype;
+      klass.prototype = new subclass;
+      parent.subclasses.push(klass);
+    }
+
+    for (var i = 0; i < properties.length; i++)
+      klass.addMethods(properties[i]);
+
+    if (!klass.prototype.initialize)
+      klass.prototype.initialize = Prototype.emptyFunction;
+
+    klass.prototype.constructor = klass;
+
+    return klass;
   }
-}
+};
 
-var Abstract = new Object();
+Class.Methods = {
+  addMethods: function(source) {
+    var ancestor   = this.superclass && this.superclass.prototype;
+    var properties = Object.keys(source);
+
+    if (!Object.keys({ toString: true }).length)
+      properties.push("toString", "valueOf");
+
+    for (var i = 0, length = properties.length; i < length; i++) {
+      var property = properties[i], value = source[property];
+      if (ancestor && Object.isFunction(value) &&
+          value.argumentNames().first() == "$super") {
+        var method = value, value = Object.extend((function(m) {
+          return function() { return ancestor[m].apply(this, arguments) };
+        })(property).wrap(method), {
+          valueOf:  function() { return method },
+          toString: function() { return method.toString() }
+        });
+      }
+      this.prototype[property] = value;
+    }
+
+    return this;
+  }
+};
+
+var Abstract = { };
 
 Object.extend = function(destination, source) {
-  for (var property in source) {
+  for (var property in source)
     destination[property] = source[property];
-  }
   return destination;
-}
+};
 
 Object.extend(Object, {
   inspect: function(object) {
@@ -62,24 +121,35 @@
 
   toJSON: function(object) {
     var type = typeof object;
-    switch(type) {
+    switch (type) {
       case 'undefined':
       case 'function':
       case 'unknown': return;
       case 'boolean': return object.toString();
     }
+
     if (object === null) return 'null';
     if (object.toJSON) return object.toJSON();
-    if (object.ownerDocument === document) return;
+    if (Object.isElement(object)) return;
+
     var results = [];
     for (var property in object) {
       var value = Object.toJSON(object[property]);
       if (value !== undefined)
         results.push(property.toJSON() + ': ' + value);
     }
+
     return '{' + results.join(', ') + '}';
   },
 
+  toQueryString: function(object) {
+    return $H(object).toQueryString();
+  },
+
+  toHTML: function(object) {
+    return object && object.toHTML ? object.toHTML() : String.interpret(object);
+  },
+
   keys: function(object) {
     var keys = [];
     for (var property in object)
@@ -95,36 +165,87 @@
   },
 
   clone: function(object) {
-    return Object.extend({}, object);
+    return Object.extend({ }, object);
+  },
+
+  isElement: function(object) {
+    return object && object.nodeType == 1;
+  },
+
+  isArray: function(object) {
+    return object && object.constructor === Array;
+  },
+
+  isHash: function(object) {
+    return object instanceof Hash;
+  },
+
+  isFunction: function(object) {
+    return typeof object == "function";
+  },
+
+  isString: function(object) {
+    return typeof object == "string";
+  },
+
+  isNumber: function(object) {
+    return typeof object == "number";
+  },
+
+  isUndefined: function(object) {
+    return typeof object == "undefined";
   }
 });
 
-Function.prototype.bind = function() {
-  var __method = this, args = $A(arguments), object = args.shift();
-  return function() {
-    return __method.apply(object, args.concat($A(arguments)));
-  }
-}
+Object.extend(Function.prototype, {
+  argumentNames: function() {
+    var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip");
+    return names.length == 1 && !names[0] ? [] : names;
+  },
 
-Function.prototype.bindAsEventListener = function(object) {
-  var __method = this, args = $A(arguments), object = args.shift();
-  return function(event) {
-    return __method.apply(object, [event || window.event].concat(args));
-  }
-}
+  bind: function() {
+    if (arguments.length < 2 && arguments[0] === undefined) return this;
+    var __method = this, args = $A(arguments), object = args.shift();
+    return function() {
+      return __method.apply(object, args.concat($A(arguments)));
+    }
+  },
 
-Object.extend(Number.prototype, {
-  toColorPart: function() {
-    return this.toPaddedString(2, 16);
+  bindAsEventListener: function() {
+    var __method = this, args = $A(arguments), object = args.shift();
+    return function(event) {
+      return __method.apply(object, [event || window.event].concat(args));
+    }
   },
 
-  succ: function() {
-    return this + 1;
+  curry: function() {
+    if (!arguments.length) return this;
+    var __method = this, args = $A(arguments);
+    return function() {
+      return __method.apply(this, args.concat($A(arguments)));
+    }
   },
 
-  times: function(iterator) {
-    $R(0, this, true).each(iterator);
-    return this;
+  delay: function() {
+    var __method = this, args = $A(arguments), timeout = args.shift() * 1000;
+    return window.setTimeout(function() {
+      return __method.apply(__method, args);
+    }, timeout);
+  },
+
+  wrap: function(wrapper) {
+    var __method = this;
+    return function() {
+      return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));
+    }
+  },
+
+  methodize: function() {
+    if (this._methodized) return this._methodized;
+    var __method = this;
+    return this._methodized = function() {
+      return __method.apply(null, [this].concat($A(arguments)));
+    };
   },
 
   toPaddedString: function(length, radix) {
@@ -137,13 +258,15 @@
   }
 });
 
+Function.prototype.defer = Function.prototype.delay.curry(0.01);
+
 Date.prototype.toJSON = function() {
-  return '"' + this.getFullYear() + '-' +
-    (this.getMonth() + 1).toPaddedString(2) + '-' +
-    this.getDate().toPaddedString(2) + 'T' +
-    this.getHours().toPaddedString(2) + ':' +
-    this.getMinutes().toPaddedString(2) + ':' +
-    this.getSeconds().toPaddedString(2) + '"';
+  return '"' + this.getUTCFullYear() + '-' +
+    (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
+    this.getUTCDate().toPaddedString(2) + 'T' +
+    this.getUTCHours().toPaddedString(2) + ':' +
+    this.getUTCMinutes().toPaddedString(2) + ':' +
+    this.getUTCSeconds().toPaddedString(2) + 'Z"';
 };
 
 var Try = {
@@ -155,17 +278,22 @@
       try {
         returnValue = lambda();
         break;
-      } catch (e) {}
+      } catch (e) { }
     }
 
     return returnValue;
   }
-}
+};
+
+RegExp.prototype.match = RegExp.prototype.test;
+
+RegExp.escape = function(str) {
+  return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
+};
 
 /*--------------------------------------------------------------------------*/
 
-var PeriodicalExecuter = Class.create();
-PeriodicalExecuter.prototype = {
+var PeriodicalExecuter = Class.create({
   initialize: function(callback, frequency) {
     this.callback = callback;
     this.frequency = frequency;
@@ -178,6 +306,10 @@
     this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
   },
 
+  execute: function() {
+    this.callback(this);
+  },
+
   stop: function() {
     if (!this.timer) return;
     clearInterval(this.timer);
@@ -188,13 +320,26 @@
     if (!this.currentlyExecuting) {
       try {
         this.currentlyExecuting = true;
-        this.callback(this);
+        this.execute();
       } finally {
         this.currentlyExecuting = false;
       }
     }
   }
-}
+});
+Object.extend(String, {
+  interpret: function(value) {
+    return value == null ? '' : String(value);
+  },
+  specialChar: {
+    '\b': '\\b',
+    '\t': '\\t',
+    '\n': '\\n',
+    '\f': '\\f',
+    '\r': '\\r',
+    '\\': '\\\\'
+  }
+});
 Object.extend(String, {
   interpret: function(value) {
     return value == null ? '' : String(value);
@@ -238,14 +383,14 @@
 
   scan: function(pattern, iterator) {
     this.gsub(pattern, iterator);
-    return this;
+    return String(this);
   },
 
   truncate: function(length, truncation) {
     length = length || 30;
     truncation = truncation === undefined ? '...' : truncation;
     return this.length > length ?
-      this.slice(0, length - truncation.length) + truncation : this;
+      this.slice(0, length - truncation.length) + truncation : String(this);
   },
 
   strip: function() {
@@ -279,7 +424,7 @@
   },
 
   unescapeHTML: function() {
-    var div = document.createElement('div');
+    var div = new Element('div');
     div.innerHTML = this.stripTags();
     return div.childNodes[0] ? (div.childNodes.length > 1 ?
       $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) :
@@ -288,16 +433,16 @@
 
   toQueryParams: function(separator) {
     var match = this.strip().match(/([^?#]*)(#.*)?$/);
-    if (!match) return {};
+    if (!match) return { };
 
-    return match[1].split(separator || '&').inject({}, function(hash, pair) {
+    return match[1].split(separator || '&').inject({ }, function(hash, pair) {
       if ((pair = pair.split('='))[0]) {
         var key = decodeURIComponent(pair.shift());
         var value = pair.length > 1 ? pair.join('=') : pair[0];
         if (value != undefined) value = decodeURIComponent(value);
 
         if (key in hash) {
-          if (hash[key].constructor != Array) hash[key] = [hash[key]];
+          if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
           hash[key].push(value);
         }
         else hash[key] = value;
@@ -316,9 +461,7 @@
   },
 
   times: function(count) {
-    var result = '';
-    for (var i = 0; i < count; i++) result += this;
-    return result;
+    return count < 1 ? '' : new Array(count + 1).join(this);
   },
 
   camelize: function() {
@@ -364,11 +507,15 @@
     return this.sub(filter || Prototype.JSONFilter, '#{1}');
   },
 
+  isJSON: function() {
+    var str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
+    return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
+  },
+
   evalJSON: function(sanitize) {
     var json = this.unfilterJSON();
     try {
-      if (!sanitize || (/^("(\\.|[^"\\\n\r])*?"|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+?$/.test(json)))
-        return eval('(' + json + ')');
+      if (!sanitize || json.isJSON()) return eval('(' + json + ')');
     } catch (e) { }
     throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
   },
@@ -392,6 +539,10 @@
 
   blank: function() {
     return /^\s*$/.test(this);
+  },
+
+  interpolate: function(object, pattern) {
+    return new Template(this, pattern).evaluate(object);
   }
 });
 
@@ -405,10 +556,10 @@
 });
 
 String.prototype.gsub.prepareReplacement = function(replacement) {
-  if (typeof replacement == 'function') return replacement;
+  if (Object.isFunction(replacement)) return replacement;
   var template = new Template(replacement);
   return function(match) { return template.evaluate(match) };
-}
+};
 
 String.prototype.parseQuery = String.prototype.toQueryParams;
 
@@ -419,28 +570,46 @@
 
 with (String.prototype.escapeHTML) div.appendChild(text);
 
-var Template = Class.create();
-Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
-Template.prototype = {
+var Template = Class.create({
   initialize: function(template, pattern) {
     this.template = template.toString();
-    this.pattern  = pattern || Template.Pattern;
+    this.pattern = pattern || Template.Pattern;
   },
 
   evaluate: function(object) {
+    if (Object.isFunction(object.toTemplateReplacements))
+      object = object.toTemplateReplacements();
+
     return this.template.gsub(this.pattern, function(match) {
-      var before = match[1];
+      if (object == null) return '';
+
+      var before = match[1] || '';
       if (before == '\\') return match[2];
-      return before + String.interpret(object[match[3]]);
-    });
+
+      var ctx = object, expr = match[3];
+      var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/, match = pattern.exec(expr);
+      if (match == null) return before;
+
+      while (match != null) {
+        var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1];
+        ctx = ctx[comp];
+        if (null == ctx || '' == match[3]) break;
+        expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
+        match = pattern.exec(expr);
+      }
+
+      return before + String.interpret(ctx);
+    }.bind(this));
   }
-}
+});
+Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
 
-var $break = {}, $continue = new Error('"throw $continue" is deprecated, use "return" instead');
+var $break = { };
 
 var Enumerable = {
-  each: function(iterator) {
+  each: function(iterator, context) {
     var index = 0;
+    iterator = iterator.bind(context);
     try {
       this._each(function(value) {
         iterator(value, index++);
@@ -451,32 +620,36 @@
     return this;
   },
 
-  eachSlice: function(number, iterator) {
+  eachSlice: function(number, iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
     var index = -number, slices = [], array = this.toArray();
     while ((index += number) < array.length)
       slices.push(array.slice(index, index+number));
-    return slices.map(iterator);
+    return slices.collect(iterator, context);
   },
 
-  all: function(iterator) {
+  all: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
     var result = true;
     this.each(function(value, index) {
-      result = result && !!(iterator || Prototype.K)(value, index);
+      result = result && !!iterator(value, index);
       if (!result) throw $break;
     });
     return result;
   },
 
-  any: function(iterator) {
+  any: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
     var result = false;
     this.each(function(value, index) {
-      if (result = !!(iterator || Prototype.K)(value, index))
+      if (result = !!iterator(value, index))
         throw $break;
     });
     return result;
   },
 
-  collect: function(iterator) {
+  collect: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
     var results = [];
     this.each(function(value, index) {
       results.push((iterator || Prototype.K)(value, index));
@@ -484,7 +657,8 @@
     return results;
   },
 
-  detect: function(iterator) {
+  detect: function(iterator, context) {
+    iterator = iterator.bind(context);
     var result;
     this.each(function(value, index) {
       if (iterator(value, index)) {
@@ -495,7 +669,8 @@
     return result;
   },
 
-  findAll: function(iterator) {
+  findAll: function(iterator, context) {
+    iterator = iterator.bind(context);
     var results = [];
     this.each(function(value, index) {
       if (iterator(value, index))
@@ -504,17 +679,24 @@
     return results;
   },
 
-  grep: function(pattern, iterator) {
+  grep: function(filter, iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
     var results = [];
+
+    if (Object.isString(filter))
+      filter = new RegExp(filter);
+
     this.each(function(value, index) {
-      var stringValue = value.toString();
-      if (stringValue.match(pattern))
-        results.push((iterator || Prototype.K)(value, index));
-    })
+      if (filter.match(value))
+        results.push(iterator(value, index));
+    });
     return results;
   },
 
   include: function(object) {
+    if (Object.isFunction(this.indexOf))
+      if (this.indexOf(object) != -1) return true;
+
     var found = false;
     this.each(function(value) {
       if (value == object) {
@@ -533,7 +715,8 @@
     });
   },
 
-  inject: function(memo, iterator) {
+  inject: function(memo, iterator, context) {
+    iterator = iterator.bind(context);
     this.each(function(value, index) {
       memo = iterator(memo, value, index);
     });
@@ -547,30 +730,33 @@
     });
   },
 
-  max: function(iterator) {
+  max: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
     var result;
     this.each(function(value, index) {
-      value = (iterator || Prototype.K)(value, index);
+      value = iterator(value, index);
       if (result == undefined || value >= result)
         result = value;
     });
     return result;
   },
 
-  min: function(iterator) {
+  min: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
     var result;
     this.each(function(value, index) {
-      value = (iterator || Prototype.K)(value, index);
+      value = iterator(value, index);
       if (result == undefined || value < result)
         result = value;
     });
     return result;
   },
 
-  partition: function(iterator) {
+  partition: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
     var trues = [], falses = [];
     this.each(function(value, index) {
-      ((iterator || Prototype.K)(value, index) ?
+      (iterator(value, index) ?
         trues : falses).push(value);
     });
     return [trues, falses];
@@ -578,13 +764,14 @@
 
   pluck: function(property) {
     var results = [];
-    this.each(function(value, index) {
+    this.each(function(value) {
       results.push(value[property]);
     });
     return results;
   },
 
-  reject: function(iterator) {
+  reject: function(iterator, context) {
+    iterator = iterator.bind(context);
     var results = [];
     this.each(function(value, index) {
       if (!iterator(value, index))
@@ -593,7 +780,8 @@
     return results;
   },
 
-  sortBy: function(iterator) {
+  sortBy: function(iterator, context) {
+    iterator = iterator.bind(context);
     return this.map(function(value, index) {
       return {value: value, criteria: iterator(value, index)};
     }).sort(function(left, right) {
@@ -608,7 +796,7 @@
 
   zip: function() {
     var iterator = Prototype.K, args = $A(arguments);
-    if (typeof args.last() == 'function')
+    if (Object.isFunction(args.last()))
       iterator = args.pop();
 
     var collections = [this].concat(args).map($A);
@@ -624,46 +812,42 @@
   inspect: function() {
     return '#<Enumerable:' + this.toArray().inspect() + '>';
   }
-}
+};
 
 Object.extend(Enumerable, {
   map:     Enumerable.collect,
   find:    Enumerable.detect,
   select:  Enumerable.findAll,
+  filter:  Enumerable.findAll,
   member:  Enumerable.include,
-  entries: Enumerable.toArray
+  entries: Enumerable.toArray,
+  every:   Enumerable.all,
+  some:    Enumerable.any
 });
-var $A = Array.from = function(iterable) {
+function $A(iterable) {
   if (!iterable) return [];
-  if (iterable.toArray) {
-    return iterable.toArray();
-  } else {
-    var results = [];
-    for (var i = 0, length = iterable.length; i < length; i++)
-      results.push(iterable[i]);
-    return results;
-  }
+  if (iterable.toArray) return iterable.toArray();
+  var length = iterable.length, results = new Array(length);
+  while (length--) results[length] = iterable[length];
+  return results;
 }
 
 if (Prototype.Browser.WebKit) {
-  $A = Array.from = function(iterable) {
+  function $A(iterable) {
     if (!iterable) return [];
-    if (!(typeof iterable == 'function' && iterable == '[object NodeList]') &&
-      iterable.toArray) {
-      return iterable.toArray();
-    } else {
-      var results = [];
-      for (var i = 0, length = iterable.length; i < length; i++)
-        results.push(iterable[i]);
-      return results;
-    }
+    if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') &&
+        iterable.toArray) return iterable.toArray();
+    var length = iterable.length, results = new Array(length);
+    while (length--) results[length] = iterable[length];
+    return results;
   }
 }
 
+Array.from = $A;
+
 Object.extend(Array.prototype, Enumerable);
 
-if (!Array.prototype._reverse)
-  Array.prototype._reverse = Array.prototype.reverse;
+if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse;
 
 Object.extend(Array.prototype, {
   _each: function(iterator) {
@@ -692,7 +876,7 @@
 
   flatten: function() {
     return this.inject([], function(array, value) {
-      return array.concat(value && value.constructor == Array ?
+      return array.concat(Object.isArray(value) ?
         value.flatten() : [value]);
     });
   },
@@ -704,12 +888,6 @@
     });
   },
 
-  indexOf: function(object) {
-    for (var i = 0, length = this.length; i < length; i++)
-      if (this[i] == object) return i;
-    return -1;
-  },
-
   reverse: function(inline) {
     return (inline !== false ? this : this.toArray())._reverse();
   },
@@ -726,6 +904,12 @@
     });
   },
 
+  intersect: function(array) {
+    return this.uniq().findAll(function(item) {
+      return array.detect(function(value) { return item === value });
+    });
+  },
+
   clone: function() {
     return [].concat(this);
   },
@@ -748,9 +932,29 @@
   }
 });
 
+// use native browser JS 1.6 implementation if available
+if (Object.isFunction(Array.prototype.forEach))
+  Array.prototype._each = Array.prototype.forEach;
+
+if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) {
+  i || (i = 0);
+  var length = this.length;
+  if (i < 0) i = length + i;
+  for (; i < length; i++)
+    if (this[i] === item) return i;
+  return -1;
+};
+
+if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) {
+  i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
+  var n = this.slice(0, i).reverse().indexOf(item);
+  return (n < 0) ? n : i - n - 1;
+};
+
 Array.prototype.toArray = Array.prototype.clone;
 
 function $w(string) {
+  if (!Object.isString(string)) return [];
   string = string.strip();
   return string ? string.split(/\s+/) : [];
 }
@@ -760,7 +964,7 @@
     var array = [];
     for (var i = 0, length = this.length; i < length; i++) array.push(this[i]);
     for (var i = 0, length = arguments.length; i < length; i++) {
-      if (arguments[i].constructor == Array) {
+      if (Object.isArray(arguments[i])) {
         for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
           array.push(arguments[i][j]);
       } else {
@@ -768,136 +972,160 @@
       }
     }
     return array;
-  }
+  };
 }
-var Hash = function(object) {
-  if (object instanceof Hash) this.merge(object);
-  else Object.extend(this, object || {});
-};
-
-Object.extend(Hash, {
-  toQueryString: function(obj) {
-    var parts = [];
-    parts.add = arguments.callee.addPair;
-
-    this.prototype._each.call(obj, function(pair) {
-      if (!pair.key) return;
-      var value = pair.value;
-
-      if (value && typeof value == 'object') {
-        if (value.constructor == Array) value.each(function(value) {
-          parts.add(pair.key, value);
-        });
-        return;
-      }
-      parts.add(pair.key, value);
-    });
+Object.extend(Number.prototype, {
+  toColorPart: function() {
+    return this.toPaddedString(2, 16);
+  },
 
-    return parts.join('&');
+  succ: function() {
+    return this + 1;
   },
 
-  toJSON: function(object) {
-    var results = [];
-    this.prototype._each.call(object, function(pair) {
-      var value = Object.toJSON(pair.value);
-      if (value !== undefined) results.push(pair.key.toJSON() + ': ' + value);
-    });
-    return '{' + results.join(', ') + '}';
+  times: function(iterator) {
+    $R(0, this, true).each(iterator);
+    return this;
+  },
+
+  toPaddedString: function(length, radix) {
+    var string = this.toString(radix || 10);
+    return '0'.times(length - string.length) + string;
+  },
+
+  toJSON: function() {
+    return isFinite(this) ? this.toString() : 'null';
   }
 });
 
-Hash.toQueryString.addPair = function(key, value, prefix) {
-  key = encodeURIComponent(key);
-  if (value === undefined) this.push(key);
-  else this.push(key + '=' + (value == null ? '' : encodeURIComponent(value)));
-}
-
-Object.extend(Hash.prototype, Enumerable);
-Object.extend(Hash.prototype, {
-  _each: function(iterator) {
-    for (var key in this) {
-      var value = this[key];
-      if (value && value == Hash.prototype[key]) continue;
+$w('abs round ceil floor').each(function(method){
+  Number.prototype[method] = Math[method].methodize();
+});
+function $H(object) {
+  return new Hash(object);
+};
 
-      var pair = [key, value];
-      pair.key = key;
-      pair.value = value;
-      iterator(pair);
+var Hash = Class.create(Enumerable, (function() {
+  if (function() {
+    var i = 0, Test = function(value) { this.key = value };
+    Test.prototype.key = 'foo';
+    for (var property in new Test('bar')) i++;
+    return i > 1;
+  }()) {
+    function each(iterator) {
+      var cache = [];
+      for (var key in this._object) {
+        var value = this._object[key];
+        if (cache.include(key)) continue;
+        cache.push(key);
+        var pair = [key, value];
+        pair.key = key;
+        pair.value = value;
+        iterator(pair);
+      }
+    }
+  } else {
+    function each(iterator) {
+      for (var key in this._object) {
+        var value = this._object[key], pair = [key, value];
+        pair.key = key;
+        pair.value = value;
+        iterator(pair);
+      }
     }
   },
 
-  keys: function() {
-    return this.pluck('key');
-  },
+  toJSON: function() {
+    return Hash.toJSON(this);
+  }
 
-  values: function() {
-    return this.pluck('value');
-  },
+  function toQueryPair(key, value) {
+    if (Object.isUndefined(value)) return key;
+    return key + '=' + encodeURIComponent(String.interpret(value));
+  }
 
-  merge: function(hash) {
-    return $H(hash).inject(this, function(mergedHash, pair) {
-      mergedHash[pair.key] = pair.value;
-      return mergedHash;
-    });
-  },
+  return {
+    initialize: function(object) {
+      this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
+    },
 
-  remove: function() {
-    var result;
-    for(var i = 0, length = arguments.length; i < length; i++) {
-      var value = this[arguments[i]];
-      if (value !== undefined){
-        if (result === undefined) result = value;
-        else {
-          if (result.constructor != Array) result = [result];
-          result.push(value)
+    _each: each,
+
+    set: function(key, value) {
+      return this._object[key] = value;
+    },
+
+    get: function(key) {
+      return this._object[key];
+    },
+
+    unset: function(key) {
+      var value = this._object[key];
+      delete this._object[key];
+      return value;
+    },
+
+    toObject: function() {
+      return Object.clone(this._object);
+    },
+
+    keys: function() {
+      return this.pluck('key');
+    },
+
+    values: function() {
+      return this.pluck('value');
+    },
+
+    index: function(value) {
+      var match = this.detect(function(pair) {
+        return pair.value === value;
+      });
+      return match && match.key;
+    },
+
+    merge: function(object) {
+      return this.clone().update(object);
+    },
+
+    update: function(object) {
+      return new Hash(object).inject(this, function(result, pair) {
+        result.set(pair.key, pair.value);
+        return result;
+      });
+    },
+
+    toQueryString: function() {
+      return this.map(function(pair) {
+        var key = encodeURIComponent(pair.key), values = pair.value;
+
+        if (values && typeof values == 'object') {
+          if (Object.isArray(values))
+            return values.map(toQueryPair.curry(key)).join('&');
         }
-      }
-      delete this[arguments[i]];
-    }
-    return result;
-  },
+        return toQueryPair(key, values);
+      }).join('&');
+    },
 
-  toQueryString: function() {
-    return Hash.toQueryString(this);
-  },
+    inspect: function() {
+      return '#<Hash:{' + this.map(function(pair) {
+        return pair.map(Object.inspect).join(': ');
+      }).join(', ') + '}>';
+    },
 
-  inspect: function() {
-    return '#<Hash:{' + this.map(function(pair) {
-      return pair.map(Object.inspect).join(': ');
-    }).join(', ') + '}>';
-  },
+    toJSON: function() {
+      return Object.toJSON(this.toObject());
+    },
 
-  toJSON: function() {
-    return Hash.toJSON(this);
+    clone: function() {
+      return new Hash(this);
+    }
   }
-});
-
-function $H(object) {
-  if (object instanceof Hash) return object;
-  return new Hash(object);
-};
+})());
 
-// Safari iterates over shadowed properties
-if (function() {
-  var i = 0, Test = function(value) { this.key = value };
-  Test.prototype.key = 'foo';
-  for (var property in new Test('bar')) i++;
-  return i > 1;
-}()) Hash.prototype._each = function(iterator) {
-  var cache = [];
-  for (var key in this) {
-    var value = this[key];
-    if ((value && value == Hash.prototype[key]) || cache.include(key)) continue;
-    cache.push(key);
-    var pair = [key, value];
-    pair.key = key;
-    pair.value = value;
-    iterator(pair);
-  }
-};
-ObjectRange = Class.create();
-Object.extend(ObjectRange.prototype, Enumerable);
-Object.extend(ObjectRange.prototype, {
+Hash.prototype.toTemplateReplacements = Hash.prototype.toObject;
+Hash.from = $H;
+var ObjectRange = Class.create(Enumerable, {
   initialize: function(start, end, exclusive) {
     this.start = start;
     this.end = end;
@@ -923,7 +1151,7 @@
 
 var $R = function(start, end, exclusive) {
   return new ObjectRange(start, end, exclusive);
-}
+};
 
 var Ajax = {
   getTransport: function() {
@@ -935,7 +1163,7 @@
   },
 
   activeRequestCount: 0
-}
+};
 
 Ajax.Responders = {
   responders: [],
@@ -955,10 +1183,10 @@
 
   dispatch: function(callback, request, transport, json) {
     this.each(function(responder) {
-      if (typeof responder[callback] == 'function') {
+      if (Object.isFunction(responder[callback])) {
         try {
           responder[callback].apply(responder, [request, transport, json]);
-        } catch (e) {}
+        } catch (e) { }
       }
     });
   }
@@ -967,42 +1195,35 @@
 Object.extend(Ajax.Responders, Enumerable);
 
 Ajax.Responders.register({
-  onCreate: function() {
-    Ajax.activeRequestCount++;
-  },
-  onComplete: function() {
-    Ajax.activeRequestCount--;
-  }
+  onCreate:   function() { Ajax.activeRequestCount++ },
+  onComplete: function() { Ajax.activeRequestCount-- }
 });
 
-Ajax.Base = function() {};
-Ajax.Base.prototype = {
-  setOptions: function(options) {
+Ajax.Base = Class.create({
+  initialize: function(options) {
     this.options = {
       method:       'post',
       asynchronous: true,
       contentType:  'application/x-www-form-urlencoded',
       encoding:     'UTF-8',
-      parameters:   ''
-    }
-    Object.extend(this.options, options || {});
+      parameters:   '',
+      evalJSON:     true,
+      evalJS:       true
+    };
+    Object.extend(this.options, options || { });
 
     this.options.method = this.options.method.toLowerCase();
-    if (typeof this.options.parameters == 'string')
+    if (Object.isString(this.options.parameters))
       this.options.parameters = this.options.parameters.toQueryParams();
   }
-}
-
-Ajax.Request = Class.create();
-Ajax.Request.Events =
-  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
+});
 
-Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
+Ajax.Request = Class.create(Ajax.Base, {
   _complete: false,
 
-  initialize: function(url, options) {
+  initialize: function($super, url, options) {
+    $super(options);
     this.transport = Ajax.getTransport();
-    this.setOptions(options);
     this.request(url);
   },
 
@@ -1019,7 +1240,7 @@
 
     this.parameters = params;
 
-    if (params = Hash.toQueryString(params)) {
+    if (params = Object.toQueryString(params)) {
       // when GET, append parameters to URL
       if (this.method == 'get')
         this.url += (this.url.include('?') ? '&' : '?') + params;
@@ -1028,14 +1249,14 @@
     }
 
     try {
-      if (this.options.onCreate) this.options.onCreate(this.transport);
-      Ajax.Responders.dispatch('onCreate', this, this.transport);
+      var response = new Ajax.Response(this);
+      if (this.options.onCreate) this.options.onCreate(response);
+      Ajax.Responders.dispatch('onCreate', this, response);
 
       this.transport.open(this.method.toUpperCase(), this.url,
         this.options.asynchronous);
 
-      if (this.options.asynchronous)
-        setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10);
+      if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);
 
       this.transport.onreadystatechange = this.onStateChange.bind(this);
       this.setRequestHeaders();
@@ -1067,61 +1288,74 @@
     };
 
     if (this.method == 'post') {
-      headers['Content-type'] = this.options.contentType +
-        (this.options.encoding ? '; charset=' + this.options.encoding : '');
+          headers['Content-Type'] = this.options.contentType +
+            (this.options.encoding ? '; charset=' + this.options.encoding : '');
 
       /* Force "Connection: close" for older Mozilla browsers to work
        * around a bug where XMLHttpRequest sends an incorrect
        * Content-length header. See Mozilla Bugzilla #246651.
        */
       if (this.transport.overrideMimeType &&
-          (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
+          (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) {
             headers['Connection'] = 'close';
+          }
     }
 
     // user-defined headers
     if (typeof this.options.requestHeaders == 'object') {
       var extras = this.options.requestHeaders;
 
-      if (typeof extras.push == 'function')
-        for (var i = 0, length = extras.length; i < length; i += 2)
+      if (Object.isFunction(extras.push))
+        for (var i = 0, length = extras.length; i < length; i += 2) {
           headers[extras[i]] = extras[i+1];
+        }
       else
-        $H(extras).each(function(pair) { headers[pair.key] = pair.value });
+        $H(extras).each(function(pair) {
+            headers[pair.key] = pair.value 
+        });
+    }
+
+    for (var name in headers) {
+        if ( typeof headers[name] == "string" ) 
+            this.transport.setRequestHeader(name, headers[name]);
     }
 
-    for (var name in headers)
-      this.transport.setRequestHeader(name, headers[name]);
   },
 
   success: function() {
-    return !this.transport.status
-        || (this.transport.status >= 200 && this.transport.status < 300);
+    var status = this.getStatus();
+    return !status || (status >= 200 && status < 300);
+  },
+
+  getStatus: function() {
+    try {
+      return this.transport.status || 0;
+    } catch (e) { return 0 }
   },
 
   respondToReadyState: function(readyState) {
-    var state = Ajax.Request.Events[readyState];
-    var transport = this.transport, json = this.evalJSON();
+    var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);
 
     if (state == 'Complete') {
       try {
         this._complete = true;
-        (this.options['on' + this.transport.status]
+        (this.options['on' + response.status]
          || this.options['on' + (this.success() ? 'Success' : 'Failure')]
-         || Prototype.emptyFunction)(transport, json);
+         || Prototype.emptyFunction)(response, response.headerJSON);
       } catch (e) {
         this.dispatchException(e);
       }
 
-      var contentType = this.getHeader('Content-type');
-      if (contentType && contentType.strip().
-        match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i))
-          this.evalResponse();
+      var contentType = response.getHeader('Content-type');
+      if (this.options.evalJS == 'force'
+          || (this.options.evalJS && contentType
+          && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
+        this.evalResponse();
     }
 
     try {
-      (this.options['on' + state] || Prototype.emptyFunction)(transport, json);
-      Ajax.Responders.dispatch('on' + state, this, transport, json);
+      (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);
+      Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);
     } catch (e) {
       this.dispatchException(e);
     }
@@ -1138,13 +1372,6 @@
     } catch (e) { return null }
   },
 
-  evalJSON: function() {
-    try {
-      var json = this.getHeader('X-JSON');
-      return json ? json.evalJSON() : null;
-    } catch (e) { return null }
-  },
-
   evalResponse: function() {
     try {
       return eval((this.transport.responseText || '').unfilterJSON());
@@ -1159,57 +1386,129 @@
   }
 });
 
-Ajax.Updater = Class.create();
-
-Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
-  initialize: function(container, url, options) {
+Ajax.Request.Events =
+  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
+
+Ajax.Response = Class.create({
+  initialize: function(request){
+    this.request = request;
+    var transport  = this.transport  = request.transport,
+        readyState = this.readyState = transport.readyState;
+
+    if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {
+      this.status       = this.getStatus();
+      this.statusText   = this.getStatusText();
+      this.responseText = String.interpret(transport.responseText);
+      this.headerJSON   = this._getHeaderJSON();
+    }
+
+    if(readyState == 4) {
+      var xml = transport.responseXML;
+      this.responseXML  = xml === undefined ? null : xml;
+      this.responseJSON = this._getResponseJSON();
+    }
+  },
+
+  status:      0,
+  statusText: '',
+
+  getStatus: Ajax.Request.prototype.getStatus,
+
+  getStatusText: function() {
+    try {
+      return this.transport.statusText || '';
+    } catch (e) { return '' }
+  },
+
+  getHeader: Ajax.Request.prototype.getHeader,
+
+  getAllHeaders: function() {
+    try {
+      return this.getAllResponseHeaders();
+    } catch (e) { return null }
+  },
+
+  getResponseHeader: function(name) {
+    return this.transport.getResponseHeader(name);
+  },
+
+  getAllResponseHeaders: function() {
+    return this.transport.getAllResponseHeaders();
+  },
+
+  _getHeaderJSON: function() {
+    var json = this.getHeader('X-JSON');
+    if (!json) return null;
+    json = decodeURIComponent(escape(json));
+    try {
+      return json.evalJSON(this.request.options.sanitizeJSON);
+    } catch (e) {
+      this.request.dispatchException(e);
+    }
+  },
+
+  _getResponseJSON: function() {
+    var options = this.request.options;
+    if (!options.evalJSON || (options.evalJSON != 'force' &&
+      !(this.getHeader('Content-type') || '').include('application/json')))
+        return null;
+    try {
+      return this.transport.responseText.evalJSON(options.sanitizeJSON);
+    } catch (e) {
+      this.request.dispatchException(e);
+    }
+  }
+});
+
+Ajax.Updater = Class.create(Ajax.Request, {
+  initialize: function($super, container, url, options) {
     this.container = {
       success: (container.success || 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, param) {
-      this.updateContent();
-      onComplete(transport, param);
+    options = options || { };
+    var onComplete = options.onComplete;
+    options.onComplete = (function(response, param) {
+      this.updateContent(response.responseText);
+      if (Object.isFunction(onComplete)) onComplete(response, param);
     }).bind(this);
 
-    this.request(url);
+    $super(url, options);
   },
 
-  updateContent: function() {
-    var receiver = this.container[this.success() ? 'success' : 'failure'];
-    var response = this.transport.responseText;
+  updateContent: function(responseText) {
+    var receiver = this.container[this.success() ? 'success' : 'failure'],
+        options = this.options;
 
-    if (!this.options.evalScripts) response = response.stripScripts();
+    if (!options.evalScripts) responseText = responseText.stripScripts();
 
     if (receiver = $(receiver)) {
-      if (this.options.insertion)
-        new this.options.insertion(receiver, response);
-      else
-        receiver.update(response);
+      if (options.insertion) {
+        if (Object.isString(options.insertion)) {
+          var insertion = { }; insertion[options.insertion] = responseText;
+          receiver.insert(insertion);
+        }
+        else options.insertion(receiver, responseText);
+      }
+      else receiver.update(responseText);
     }
 
     if (this.success()) {
-      if (this.onComplete)
-        setTimeout(this.onComplete.bind(this), 10);
+      if (this.onComplete) this.onComplete.bind(this).defer();
     }
   }
 });
 
-Ajax.PeriodicalUpdater = Class.create();
-Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
-  initialize: function(container, url, options) {
-    this.setOptions(options);
+Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
+  initialize: function($super, container, url, options) {
+    $super(options);
     this.onComplete = this.options.onComplete;
 
     this.frequency = (this.options.frequency || 2);
     this.decay = (this.options.decay || 1);
 
-    this.updater = {};
+    this.updater = { };
     this.container = container;
     this.url = url;
 
@@ -1227,15 +1526,14 @@
     (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
   },
 
-  updateComplete: function(request) {
+  updateComplete: function(response) {
     if (this.options.decay) {
-      this.decay = (request.responseText == this.lastText ?
+      this.decay = (response.responseText == this.lastText ?
         this.decay * this.options.decay : 1);
 
-      this.lastText = request.responseText;
+      this.lastText = response.responseText;
     }
-    this.timer = setTimeout(this.onTimerEvent.bind(this),
-      this.decay * this.frequency * 1000);
+    this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);
   },
 
   onTimerEvent: function() {
@@ -1248,7 +1546,7 @@
       elements.push($(arguments[i]));
     return elements;
   }
-  if (typeof element == 'string')
+  if (Object.isString(element))
     element = document.getElementById(element);
   return Element.extend(element);
 }
@@ -1259,65 +1557,51 @@
     var query = document.evaluate(expression, $(parentElement) || document,
       null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
     for (var i = 0, length = query.snapshotLength; i < length; i++)
-      results.push(query.snapshotItem(i));
+      results.push(Element.extend(query.snapshotItem(i)));
     return results;
   };
-
-  document.getElementsByClassName = function(className, parentElement) {
-    var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]";
-    return document._getElementsByXPath(q, parentElement);
-  }
-
-} else document.getElementsByClassName = function(className, parentElement) {
-  var children = ($(parentElement) || document.body).getElementsByTagName('*');
-  var elements = [], child;
-  for (var i = 0, length = children.length; i < length; i++) {
-    child = children[i];
-    if (Element.hasClassName(child, className))
-      elements.push(Element.extend(child));
-  }
-  return elements;
-};
+}
 
 /*--------------------------------------------------------------------------*/
 
-if (!window.Element) var Element = {};
+if (!window.Node) var Node = { };
 
-Element.extend = function(element) {
-  var F = Prototype.BrowserFeatures;
-  if (!element || !element.tagName || element.nodeType == 3 ||
-   element._extended || F.SpecificElementExtensions || element == window)
-    return element;
-
-  var methods = {}, tagName = element.tagName, cache = Element.extend.cache,
-   T = Element.Methods.ByTag;
-
-  // extend methods for all tags (Safari doesn't need this)
-  if (!F.ElementExtensions) {
-    Object.extend(methods, Element.Methods),
-    Object.extend(methods, Element.Methods.Simulated);
-  }
-
-  // extend methods for specific tags
-  if (T[tagName]) Object.extend(methods, T[tagName]);
-
-  for (var property in methods) {
-    var value = methods[property];
-    if (typeof value == 'function' && !(property in element))
-      element[property] = cache.findOrStore(value);
-  }
-
-  element._extended = Prototype.emptyFunction;
-  return element;
-};
+if (!Node.ELEMENT_NODE) {
+  // DOM level 2 ECMAScript Language Binding
+  Object.extend(Node, {
+    ELEMENT_NODE: 1,
+    ATTRIBUTE_NODE: 2,
+    TEXT_NODE: 3,
+    CDATA_SECTION_NODE: 4,
+    ENTITY_REFERENCE_NODE: 5,
+    ENTITY_NODE: 6,
+    PROCESSING_INSTRUCTION_NODE: 7,
+    COMMENT_NODE: 8,
+    DOCUMENT_NODE: 9,
+    DOCUMENT_TYPE_NODE: 10,
+    DOCUMENT_FRAGMENT_NODE: 11,
+    NOTATION_NODE: 12
+  });
+}
 
-Element.extend.cache = {
-  findOrStore: function(value) {
-    return this[value] = this[value] || function() {
-      return value.apply(null, [this].concat($A(arguments)));
+(function() {
+  var element = this.Element;
+  this.Element = function(tagName, attributes) {
+    attributes = attributes || { };
+    tagName = tagName.toLowerCase();
+    var cache = Element.cache;
+    if (Prototype.Browser.IE && attributes.name) {
+      tagName = '<' + tagName + ' name="' + attributes.name + '">';
+      delete attributes.name;
+      return Element.writeAttribute(document.createElement(tagName), attributes);
     }
-  }
-};
+    if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
+    return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
+  };
+  Object.extend(this.Element, element || { });
+}).call(window);
+
+Element.cache = { };
 
 Element.Methods = {
   visible: function(element) {
@@ -1346,28 +1630,76 @@
     return element;
   },
 
-  update: function(element, html) {
-    html = typeof html == 'undefined' ? '' : html.toString();
-    $(element).innerHTML = html.stripScripts();
-    setTimeout(function() {html.evalScripts()}, 10);
+  update: function(element, content) {
+    element = $(element);
+    if (content && content.toElement) content = content.toElement();
+    if (Object.isElement(content)) return element.update().insert(content);
+    content = Object.toHTML(content);
+    element.innerHTML = content.stripScripts();
+    content.evalScripts.bind(content).defer();
+    return element;
     return element;
   },
 
-  replace: function(element, html) {
+  replace: function(element, content) {
     element = $(element);
-    html = typeof html == 'undefined' ? '' : html.toString();
-    if (element.outerHTML) {
-      element.outerHTML = html.stripScripts();
-    } else {
+    if (content && content.toElement) content = content.toElement();
+    else if (!Object.isElement(content)) {
+      content = Object.toHTML(content);
       var range = element.ownerDocument.createRange();
-      range.selectNodeContents(element);
-      element.parentNode.replaceChild(
-        range.createContextualFragment(html.stripScripts()), element);
+      range.selectNode(element);
+      content.evalScripts.bind(content).defer();
+      content = range.createContextualFragment(content.stripScripts());
+    }
+    element.parentNode.replaceChild(content, element);
+    return element;
+  },
+
+  insert: function(element, insertions) {
+    element = $(element);
+
+    if (Object.isString(insertions) || Object.isNumber(insertions) ||
+        Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
+          insertions = {bottom:insertions};
+
+    var content, t, range;
+
+    for (position in insertions) {
+      if ( position == 'extend' ) continue;
+      content  = insertions[position];
+      position = position.toLowerCase();
+      t = Element._insertionTranslations[position];
+
+      if (content && content.toElement) content = content.toElement();
+      if (Object.isElement(content)) {
+        t.insert(element, content);
+        continue;
+      }
+
+      content = Object.toHTML(content);
+
+      range = element.ownerDocument.createRange();
+      t.initializeRange(element, range);
+      t.insert(element, range.createContextualFragment(content.stripScripts()));
+
+      content.evalScripts.bind(content).defer();
     }
-    setTimeout(function() {html.evalScripts()}, 10);
+
     return element;
   },
 
+  wrap: function(element, wrapper, attributes) {
+    element = $(element);
+    if (Object.isElement(wrapper))
+      $(wrapper).writeAttribute(attributes || { });
+    else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);
+    else wrapper = new Element('div', wrapper);
+    if (element.parentNode)
+      element.parentNode.replaceChild(wrapper, element);
+    wrapper.appendChild(element);
+    return wrapper;
+  },
+
   inspect: function(element) {
     element = $(element);
     var result = '<' + element.tagName.toLowerCase();
@@ -1423,7 +1755,7 @@
   },
 
   match: function(element, selector) {
-    if (typeof selector == 'string')
+    if (Object.isString(selector))
       selector = new Selector(selector);
     return selector.match($(element));
   },
@@ -1460,28 +1792,58 @@
       nextSiblings[index || 0];
   },
 
-  getElementsBySelector: function() {
+  select: function() {
     var args = $A(arguments), element = $(args.shift());
     return Selector.findChildElements(element, args);
   },
 
-  getElementsByClassName: function(element, className) {
-    return document.getElementsByClassName(className, element);
+  adjacent: function() {
+    var args = $A(arguments), element = $(args.shift());
+    return Selector.findChildElements(element.parentNode, args).without(element);
+  },
+
+  identify: function(element) {
+    element = $(element);
+    var id = element.readAttribute('id'), self = arguments.callee;
+    if (id) return id;
+    do { id = 'anonymous_element_' + self.counter++ } while ($(id));
+    element.writeAttribute('id', id);
+    return id;
   },
 
   readAttribute: function(element, name) {
     element = $(element);
     if (Prototype.Browser.IE) {
-      if (!element.attributes) return null;
-      var t = Element._attributeTranslations;
+      var t = Element._attributeTranslations.read;
       if (t.values[name]) return t.values[name](element, name);
-      if (t.names[name])  name = t.names[name];
-      var attribute = element.attributes[name];
-      return attribute ? attribute.nodeValue : null;
+      if (t.names[name]) name = t.names[name];
+      if (name.include(':')) {
+        return (!element.attributes || !element.attributes[name]) ? null :
+         element.attributes[name].value;
+      }
     }
     return element.getAttribute(name);
   },
 
+  writeAttribute: function(element, name, value) {
+    element = $(element);
+    var attributes = { }, t = Element._attributeTranslations.write;
+
+    if (typeof name == 'object') attributes = name;
+    else attributes[name] = value === undefined ? true : value;
+
+    for (var attr in attributes) {
+      var name = t.names[attr] || attr, value = attributes[attr];
+      if (t.values[attr]) name = t.values[attr](element, value);
+      if (value === false || value === null)
+        element.removeAttribute(name);
+      else if (value === true)
+        element.setAttribute(name, name);
+      else element.setAttribute(name, value);
+    }
+    return element;
+  },
+
   getHeight: function(element) {
     return $(element).getDimensions().height;
   },
@@ -1497,39 +1859,28 @@
   hasClassName: function(element, className) {
     if (!(element = $(element))) return;
     var elementClassName = element.className;
-    if (elementClassName.length == 0) return false;
-    if (elementClassName == className ||
-        elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
-      return true;
-    return false;
+    return (elementClassName.length > 0 && (elementClassName == className ||
+      new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
   },
 
   addClassName: function(element, className) {
     if (!(element = $(element))) return;
-    Element.classNames(element).add(className);
+    if (!element.hasClassName(className))
+      element.className += (element.className ? ' ' : '') + className;
     return element;
   },
 
   removeClassName: function(element, className) {
     if (!(element = $(element))) return;
-    Element.classNames(element).remove(className);
+    element.className = element.className.replace(
+      new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();
     return element;
   },
 
   toggleClassName: function(element, className) {
     if (!(element = $(element))) return;
-    Element.classNames(element)[element.hasClassName(className) ? 'remove' : 'add'](className);
-    return element;
-  },
-
-  observe: function() {
-    Event.observe.apply(Event, arguments);
-    return $A(arguments).first();
-  },
-
-  stopObserving: function() {
-    Event.stopObserving.apply(Event, arguments);
-    return $A(arguments).first();
+    return element[element.hasClassName(className) ?
+      'removeClassName' : 'addClassName'](className);
   },
 
   // removes whitespace-only text node children
@@ -1551,6 +1902,20 @@
 
   descendantOf: function(element, ancestor) {
     element = $(element), ancestor = $(ancestor);
+
+    if (element.compareDocumentPosition)
+      return (element.compareDocumentPosition(ancestor) & 8) === 8;
+
+    if (element.sourceIndex && !Prototype.Browser.Opera) {
+      var e = element.sourceIndex, a = ancestor.sourceIndex,
+       nextAncestor = ancestor.nextSibling;
+      if (!nextAncestor) {
+        do { ancestor = ancestor.parentNode; }
+        while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode);
+      }
+      if (nextAncestor) return (e > a && e < nextAncestor.sourceIndex);
+    }
+
     while (element = element.parentNode)
       if (element == ancestor) return true;
     return false;
@@ -1558,7 +1923,7 @@
 
   scrollTo: function(element) {
     element = $(element);
-    var pos = Position.cumulativeOffset(element);
+    var pos = element.cumulativeOffset();
     window.scrollTo(pos[0], pos[1]);
     return element;
   },
@@ -1579,16 +1944,20 @@
     return $(element).getStyle('opacity');
   },
 
-  setStyle: function(element, styles, camelized) {
+  setStyle: function(element, styles) {
     element = $(element);
-    var elementStyle = element.style;
-
+    var elementStyle = element.style, match;
+    if (Object.isString(styles)) {
+      element.style.cssText += ';' + styles;
+      return styles.include('opacity') ?
+        element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element;
+    }
     for (var property in styles)
-      if (property == 'opacity') element.setOpacity(styles[property])
+      if (property == 'opacity') element.setOpacity(styles[property]);
       else
         elementStyle[(property == 'float' || property == 'cssFloat') ?
           (elementStyle.styleFloat === undefined ? 'cssFloat' : 'styleFloat') :
-          (camelized ? property : property.camelize())] = styles[property];
+            property] = styles[property];
 
     return element;
   },
@@ -1655,8 +2024,8 @@
   makeClipping: function(element) {
     element = $(element);
     if (element._overflow) return element;
-    element._overflow = element.style.overflow || 'auto';
-    if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden')
+    element._overflow = Element.getStyle(element, 'overflow') || 'auto';
+    if (element._overflow !== 'hidden')
       element.style.overflow = 'hidden';
     return element;
   },
@@ -1667,14 +2036,216 @@
     element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
     element._overflow = null;
     return element;
+  },
+
+  cumulativeOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      element = element.offsetParent;
+    } while (element);
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  positionedOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      element = element.offsetParent;
+      if (element) {
+        if (element.tagName == 'BODY') break;
+        var p = Element.getStyle(element, 'position');
+        if (p == 'relative' || p == 'absolute') break;
+      }
+    } while (element);
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  absolutize: function(element) {
+    element = $(element);
+    if (element.getStyle('position') == 'absolute') return;
+    // Position.prepare(); // To be done manually by Scripty when it needs it.
+
+    var offsets = element.positionedOffset();
+    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';
+    return element;
+  },
+
+  relativize: function(element) {
+    element = $(element);
+    if (element.getStyle('position') == 'relative') return;
+    // Position.prepare(); // To be done manually by Scripty when it needs it.
+
+    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;
+    return element;
+  },
+
+  cumulativeScrollOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.scrollTop  || 0;
+      valueL += element.scrollLeft || 0;
+      element = element.parentNode;
+    } while (element);
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  getOffsetParent: 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);
+  },
+
+  viewportOffset: 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 &&
+        Element.getStyle(element, 'position') == 'absolute') break;
+
+    } while (element = element.offsetParent);
+
+    element = forElement;
+    do {
+      if (!Prototype.Browser.Opera || element.tagName == 'BODY') {
+        valueT -= element.scrollTop  || 0;
+        valueL -= element.scrollLeft || 0;
+      }
+    } while (element = element.parentNode);
+
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  clonePosition: function(element, source) {
+    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 = source.viewportOffset();
+
+    // find coordinate system to use
+    element = $(element);
+    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(element, 'position') == 'absolute') {
+      parent = element.getOffsetParent();
+      delta = parent.viewportOffset();
+    }
+
+    // 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)   element.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
+    if (options.setTop)    element.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
+    if (options.setWidth)  element.style.width = source.offsetWidth + 'px';
+    if (options.setHeight) element.style.height = source.offsetHeight + 'px';
+    return element;
   }
 };
 
+Element.Methods.identify.counter = 1;
+
 Object.extend(Element.Methods, {
-  childOf: Element.Methods.descendantOf,
+  getElementsBySelector: Element.Methods.select,
   childElements: Element.Methods.immediateDescendants
 });
 
+Element._attributeTranslations = {
+  write: {
+    names: {
+      className: 'class',
+      htmlFor:   'for'
+    },
+    values: { }
+  }
+};
+
+
+if (!document.createRange || Prototype.Browser.Opera) {
+  Element.Methods.insert = function(element, insertions) {
+    element = $(element);
+
+    if (Object.isString(insertions) || Object.isNumber(insertions) ||
+        Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
+          insertions = { bottom: insertions };
+
+    var t = Element._insertionTranslations, content, position, pos, tagName;
+
+    for (position in insertions) {
+      content  = insertions[position];
+      position = position.toLowerCase();
+      pos      = t[position];
+
+      if (content && content.toElement) content = content.toElement();
+      if (Object.isElement(content)) {
+        pos.insert(element, content);
+        continue;
+      }
+
+      content = Object.toHTML(content);
+      tagName = ((position == 'before' || position == 'after')
+        ? element.parentNode : element).tagName.toUpperCase();
+
+      if (t.tags[tagName]) {
+        var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
+        if (position == 'top' || position == 'after') fragments.reverse();
+        fragments.each(pos.insert.curry(element));
+      }
+      else element.insertAdjacentHTML(pos.adjacency, content.stripScripts());
+
+      content.evalScripts.bind(content).defer();
+    }
+
+    return element;
+  };
+}
+
 if (Prototype.Browser.Opera) {
   Element.Methods._getStyle = Element.Methods.getStyle;
   Element.Methods.getStyle = function(element, style) {
@@ -1687,8 +2258,28 @@
       default: return Element._getStyle(element, style);
     }
   };
+  Element.Methods._readAttribute = Element.Methods.readAttribute;
+  Element.Methods.readAttribute = function(element, attribute) {
+    if (attribute == 'title') return element.title;
+    return Element._readAttribute(element, attribute);
+  };
 }
+
 else if (Prototype.Browser.IE) {
+  $w('positionedOffset getOffsetParent viewportOffset').each(function(method) {
+    Element.Methods[method] = Element.Methods[method].wrap(
+      function(proceed, element) {
+        element = $(element);
+        var position = element.getStyle('position');
+        if (position != 'static') return proceed(element);
+        element.setStyle({ position: 'relative' });
+        var value = proceed(element);
+        element.setStyle({ position: position });
+        return value;
+      }
+    );
+  });
+
   Element.Methods.getStyle = function(element, style) {
     element = $(element);
     style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
@@ -1703,56 +2294,118 @@
 
     if (value == 'auto') {
       if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))
-        return element['offset'+style.capitalize()] + 'px';
+        return element['offset' + style.capitalize()] + 'px';
       return null;
     }
     return value;
   };
 
   Element.Methods.setOpacity = function(element, value) {
+    function stripAlpha(filter){
+      return filter.replace(/alpha\([^\)]*\)/gi,'');
+    }
     element = $(element);
+    var currentStyle = element.currentStyle;
+    if ((currentStyle && !currentStyle.hasLayout) ||
+      (!currentStyle && element.style.zoom == 'normal'))
+        element.style.zoom = 1;
+
     var filter = element.getStyle('filter'), style = element.style;
     if (value == 1 || value === '') {
-      style.filter = filter.replace(/alpha\([^\)]*\)/gi,'');
+      (filter = stripAlpha(filter)) ?
+        style.filter = filter : style.removeAttribute('filter');
       return element;
     } else if (value < 0.00001) value = 0;
-    style.filter = filter.replace(/alpha\([^\)]*\)/gi, '') +
+    style.filter = stripAlpha(filter) +
       'alpha(opacity=' + (value * 100) + ')';
     return element;
   };
 
-  // IE is missing .innerHTML support for TABLE-related elements
-  Element.Methods.update = function(element, html) {
-    element = $(element);
-    html = typeof html == 'undefined' ? '' : html.toString();
-    var tagName = element.tagName.toUpperCase();
-    if (['THEAD','TBODY','TR','TD'].include(tagName)) {
-      var div = document.createElement('div');
-      switch (tagName) {
-        case 'THEAD':
-        case 'TBODY':
-          div.innerHTML = '<table><tbody>' +  html.stripScripts() + '</tbody></table>';
-          depth = 2;
-          break;
-        case 'TR':
-          div.innerHTML = '<table><tbody><tr>' +  html.stripScripts() + '</tr></tbody></table>';
-          depth = 3;
-          break;
-        case 'TD':
-          div.innerHTML = '<table><tbody><tr><td>' +  html.stripScripts() + '</td></tr></tbody></table>';
-          depth = 4;
+  Element._attributeTranslations = {
+    read: {
+      names: {
+        'class': 'className',
+        'for':   'htmlFor'
+      },
+      values: {
+        _getAttr: function(element, attribute) {
+          return element.getAttribute(attribute, 2);
+        },
+        _getAttrNode: function(element, attribute) {
+          var node = element.getAttributeNode(attribute);
+          return node ? node.value : "";
+        },
+        _getEv: function(element, attribute) {
+          var attribute = element.getAttribute(attribute);
+          return attribute ? attribute.toString().slice(23, -2) : null;
+        },
+        _flag: function(element, attribute) {
+          return $(element).hasAttribute(attribute) ? attribute : null;
+        },
+        style: function(element) {
+          return element.style.cssText.toLowerCase();
+        },
+        title: function(element) {
+          return element.title;
+        }
       }
-      $A(element.childNodes).each(function(node) { element.removeChild(node) });
-      depth.times(function() { div = div.firstChild });
-      $A(div.childNodes).each(function(node) { element.appendChild(node) });
-    } else {
-      element.innerHTML = html.stripScripts();
     }
-    setTimeout(function() { html.evalScripts() }, 10);
-    return element;
-  }
+  };
+
+  Element._attributeTranslations.write = {
+    names: Object.clone(Element._attributeTranslations.read.names),
+    values: {
+      checked: function(element, value) {
+        element.checked = !!value;
+      },
+
+      style: function(element, value) {
+        element.style.cssText = value ? value : '';
+      }
+    }
+  };
+
+  Element._attributeTranslations.has = {};
+
+  $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +
+      'encType maxLength readOnly longDesc').each(function(attr) {
+    Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;
+    Element._attributeTranslations.has[attr.toLowerCase()] = attr;
+  });
+
+  (function(v) {
+    Object.extend(v, {
+      href:        v._getAttr,
+      src:         v._getAttr,
+      type:        v._getAttr,
+      action:      v._getAttrNode,
+      disabled:    v._flag,
+      checked:     v._flag,
+      readonly:    v._flag,
+      multiple:    v._flag,
+      onload:      v._getEv,
+      onunload:    v._getEv,
+      onclick:     v._getEv,
+      ondblclick:  v._getEv,
+      onmousedown: v._getEv,
+      onmouseup:   v._getEv,
+      onmouseover: v._getEv,
+      onmousemove: v._getEv,
+      onmouseout:  v._getEv,
+      onfocus:     v._getEv,
+      onblur:      v._getEv,
+      onkeypress:  v._getEv,
+      onkeydown:   v._getEv,
+      onkeyup:     v._getEv,
+      onsubmit:    v._getEv,
+      onreset:     v._getEv,
+      onselect:    v._getEv,
+      onchange:    v._getEv
+    });
+  })(Element._attributeTranslations.read.values);
 }
-else if (Prototype.Browser.Gecko) {
+
+else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) {
   Element.Methods.setOpacity = function(element, value) {
     element = $(element);
     element.style.opacity = (value == 1) ? 0.999999 :
@@ -1761,68 +2414,219 @@
   };
 }
 
-Element._attributeTranslations = {
-  names: {
-    colspan:   "colSpan",
-    rowspan:   "rowSpan",
-    valign:    "vAlign",
-    datetime:  "dateTime",
-    accesskey: "accessKey",
-    tabindex:  "tabIndex",
-    enctype:   "encType",
-    maxlength: "maxLength",
-    readonly:  "readOnly",
-    longdesc:  "longDesc"
-  },
-  values: {
-    _getAttr: function(element, attribute) {
-      return element.getAttribute(attribute, 2);
-    },
-    _flag: function(element, attribute) {
-      return $(element).hasAttribute(attribute) ? attribute : null;
-    },
-    style: function(element) {
-      return element.style.cssText.toLowerCase();
-    },
-    title: function(element) {
-      var node = element.getAttributeNode('title');
-      return node.specified ? node.nodeValue : null;
+else if (Prototype.Browser.WebKit) {
+  Element.Methods.setOpacity = function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1 || value === '') ? '' :
+      (value < 0.00001) ? 0 : value;
+
+    if (value == 1)
+      if(element.tagName == 'IMG' && element.width) {
+        element.width++; element.width--;
+      } else try {
+        var n = document.createTextNode(' ');
+        element.appendChild(n);
+        element.removeChild(n);
+      } catch (e) { }
+
+    return element;
+  };
+
+  // Safari returns margins on body which is incorrect if the child is absolutely
+  // positioned.  For performance reasons, redefine Position.cumulativeOffset for
+  // KHTML/WebKit only.
+  Element.Methods.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 Element._returnOffset(valueL, valueT);
+  };
+}
+
+if (Prototype.Browser.IE || Prototype.Browser.Opera) {
+  // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements
+  Element.Methods.update = function(element, content) {
+    element = $(element);
+
+    if (content && content.toElement) content = content.toElement();
+    if (Object.isElement(content)) return element.update().insert(content);
+
+    content = Object.toHTML(content);
+    var tagName = element.tagName.toUpperCase();
+
+    if (tagName in Element._insertionTranslations.tags) {
+      $A(element.childNodes).each(function(node) { element.removeChild(node) });
+      Element._getContentFromAnonymousElement(tagName, content.stripScripts())
+        .each(function(node) { element.appendChild(node) });
+    }
+    else element.innerHTML = content.stripScripts();
+
+    content.evalScripts.bind(content).defer();
+    return element;
+  };
+}
+
+if (document.createElement('div').outerHTML) {
+  Element.Methods.replace = function(element, content) {
+    element = $(element);
+
+    if (content && content.toElement) content = content.toElement();
+    if (Object.isElement(content)) {
+      element.parentNode.replaceChild(content, element);
+      return element;
+    }
+
+    content = Object.toHTML(content);
+    var parent = element.parentNode, tagName = parent.tagName.toUpperCase();
+
+    if (Element._insertionTranslations.tags[tagName]) {
+      var nextSibling = element.next();
+      var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
+      parent.removeChild(element);
+      if (nextSibling)
+        fragments.each(function(node) { parent.insertBefore(node, nextSibling) });
+      else
+        fragments.each(function(node) { parent.appendChild(node) });
+    }
+    else element.outerHTML = content.stripScripts();
+
+    content.evalScripts.bind(content).defer();
+    return element;
+  };
+}
+
+Element._returnOffset = function(l, t) {
+  var result = [l, t];
+  result.left = l;
+  result.top = t;
+  return result;
+};
+
+Element._getContentFromAnonymousElement = function(tagName, html) {
+  var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
+  div.innerHTML = t[0] + html + t[1];
+  t[2].times(function() { div = div.firstChild });
+  return $A(div.childNodes);
+};
+
+Element._insertionTranslations = {
+  before: {
+    adjacency: 'beforeBegin',
+    insert: function(element, node) {
+      element.parentNode.insertBefore(node, element);
+    },
+    initializeRange: function(element, range) {
+      range.setStartBefore(element);
+    }
+  },
+  top: {
+    adjacency: 'afterBegin',
+    insert: function(element, node) {
+      element.insertBefore(node, element.firstChild);
+    },
+    initializeRange: function(element, range) {
+      range.selectNodeContents(element);
+      range.collapse(true);
+    }
+  },
+  bottom: {
+    adjacency: 'beforeEnd',
+    insert: function(element, node) {
+      element.appendChild(node);
+    }
+  },
+  after: {
+    adjacency: 'afterEnd',
+    insert: function(element, node) {
+      element.parentNode.insertBefore(node, element.nextSibling);
+    },
+    initializeRange: function(element, range) {
+      range.setStartAfter(element);
     }
+  },
+  tags: {
+    TABLE:  ['<table>',                '</table>',                   1],
+    TBODY:  ['<table><tbody>',         '</tbody></table>',           2],
+    TR:     ['<table><tbody><tr>',     '</tr></tbody></table>',      3],
+    TD:     ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
+    SELECT: ['<select>',               '</select>',                  1]
   }
 };
 
 (function() {
-  Object.extend(this, {
-    href: this._getAttr,
-    src:  this._getAttr,
-    type: this._getAttr,
-    disabled: this._flag,
-    checked:  this._flag,
-    readonly: this._flag,
-    multiple: this._flag
+  this.bottom.initializeRange = this.top.initializeRange;
+  Object.extend(this.tags, {
+    THEAD: this.tags.TBODY,
+    TFOOT: this.tags.TBODY,
+    TH:    this.tags.TD
   });
-}).call(Element._attributeTranslations.values);
+}).call(Element._insertionTranslations);
 
 Element.Methods.Simulated = {
   hasAttribute: function(element, attribute) {
-    var t = Element._attributeTranslations, node;
-    attribute = t.names[attribute] || attribute;
-    node = $(element).getAttributeNode(attribute);
+    attribute = Element._attributeTranslations.has[attribute] || attribute;
+    var node = $(element).getAttributeNode(attribute);
     return node && node.specified;
   }
 };
 
-Element.Methods.ByTag = {};
+Element.Methods.ByTag = { };
 
 Object.extend(Element, Element.Methods);
 
 if (!Prototype.BrowserFeatures.ElementExtensions &&
- document.createElement('div').__proto__) {
-  window.HTMLElement = {};
+    document.createElement('div').__proto__) {
+  window.HTMLElement = { };
   window.HTMLElement.prototype = document.createElement('div').__proto__;
   Prototype.BrowserFeatures.ElementExtensions = true;
 }
 
+Element.extend = (function() {
+  if (Prototype.BrowserFeatures.SpecificElementExtensions)
+    return Prototype.K;
+
+  var Methods = { }, ByTag = Element.Methods.ByTag;
+
+  var extend = Object.extend(function(element) {
+    if (!element || element._extendedByPrototype ||
+        element.nodeType != 1 || element == window) return element;
+
+    var methods = Object.clone(Methods),
+      tagName = element.tagName, property, value;
+
+    // extend methods for specific tags
+    if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);
+
+    for (property in methods) {
+      value = methods[property];
+      if (Object.isFunction(value) && !(property in element))
+        element[property] = value.methodize();
+    }
+
+    element._extendedByPrototype = Prototype.emptyFunction;
+    return element;
+
+  }, {
+    refresh: function() {
+      // extend methods for all tags (Safari doesn't need this)
+      if (!Prototype.BrowserFeatures.ElementExtensions) {
+        Object.extend(Methods, Element.Methods);
+        Object.extend(Methods, Element.Methods.Simulated);
+      }
+    }
+  });
+
+  extend.refresh();
+  return extend;
+})();
+
 Element.hasAttribute = function(element, attribute) {
   if (element.hasAttribute) return element.hasAttribute(attribute);
   return Element.Methods.Simulated.hasAttribute(element, attribute);
@@ -1847,26 +2651,27 @@
     methods = arguments[1];
   }
 
-  if (!tagName) Object.extend(Element.Methods, methods || {});
+  if (!tagName) Object.extend(Element.Methods, methods || { });
   else {
-    if (tagName.constructor == Array) tagName.each(extend);
+    if (Object.isArray(tagName)) tagName.each(extend);
     else extend(tagName);
   }
 
   function extend(tagName) {
     tagName = tagName.toUpperCase();
     if (!Element.Methods.ByTag[tagName])
-      Element.Methods.ByTag[tagName] = {};
+      Element.Methods.ByTag[tagName] = { };
     Object.extend(Element.Methods.ByTag[tagName], methods);
   }
 
   function copy(methods, destination, onlyIfAbsent) {
     onlyIfAbsent = onlyIfAbsent || false;
-    var cache = Element.extend.cache;
     for (var property in methods) {
       var value = methods[property];
+      // don't copy update, temporarily 
+      if (!Object.isFunction(value) || property == 'update') continue;
       if (!onlyIfAbsent || !(property in destination))
-        destination[property] = cache.findOrStore(value);
+        destination[property] = value.methodize();
     }
   }
 
@@ -1890,7 +2695,7 @@
     klass = 'HTML' + tagName.capitalize() + 'Element';
     if (window[klass]) return window[klass];
 
-    window[klass] = {};
+    window[klass] = { };
     window[klass].prototype = document.createElement(tagName).__proto__;
     return window[klass];
   }
@@ -1903,153 +2708,48 @@
   if (F.SpecificElementExtensions) {
     for (var tag in Element.Methods.ByTag) {
       var klass = findDOMClass(tag);
-      if (typeof klass == "undefined") continue;
+      if (Object.isUndefined(klass)) continue;
       copy(T[tag], klass.prototype);
     }
   }
 
   Object.extend(Element, Element.Methods);
   delete Element.ByTag;
-};
-
-var 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) {
-        var tagName = this.element.tagName.toUpperCase();
-        if (['TBODY', 'TR'].include(tagName)) {
-          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);
-  },
+  if (Element.extend.refresh) Element.extend.refresh();
+  Element.cache = { };
+};
 
-  set: function(className) {
-    this.element.className = className;
+document.viewport = {
+  getDimensions: function() {
+    var dimensions = { };
+    $w('width height').each(function(d) {
+      var D = d.capitalize();
+      dimensions[d] = self['inner' + D] ||
+       (document.documentElement['client' + D] || document.body['client' + D]);
+    });
+    return dimensions;
   },
 
-  add: function(classNameToAdd) {
-    if (this.include(classNameToAdd)) return;
-    this.set($A(this).concat(classNameToAdd).join(' '));
+  getWidth: function() {
+    return this.getDimensions().width;
   },
 
-  remove: function(classNameToRemove) {
-    if (!this.include(classNameToRemove)) return;
-    this.set($A(this).without(classNameToRemove).join(' '));
+  getHeight: function() {
+    return this.getDimensions().height;
   },
 
-  toString: function() {
-    return $A(this).join(' ');
+  getScrollOffsets: function() {
+    return Element._returnOffset(
+      window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
+      window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
   }
 };
-
-Object.extend(Element.ClassNames.prototype, Enumerable);
 /* Portions of the Selector class are derived from Jack Slocum’s DomQuery,
  * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
  * license.  Please see http://www.yui-ext.com/ for more information. */
 
-var Selector = Class.create();
-
-Selector.prototype = {
+var Selector = Class.create({
   initialize: function(expression) {
     this.expression = expression.strip();
     this.compileMatcher();
@@ -2057,15 +2757,17 @@
 
   compileMatcher: function() {
     // Selectors with namespaced attributes can't use the XPath version
-    if (Prototype.BrowserFeatures.XPath && !(/\[[\w-]*?:/).test(this.expression))
+    if (Prototype.BrowserFeatures.XPath && !(/(\[[\w-]*?:|:checked)/).test(this.expression))
       return this.compileXPathMatcher();
 
     var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
         c = Selector.criteria, le, p, m;
 
     if (Selector._cache[e]) {
-      this.matcher = Selector._cache[e]; return;
+      this.matcher = Selector._cache[e];
+      return;
     }
+
     this.matcher = ["this.matcher = function(root) {",
                     "var r = root, h = Selector.handlers, c = false, n;"];
 
@@ -2074,7 +2776,7 @@
       for (var i in ps) {
         p = ps[i];
         if (m = e.match(p)) {
-          this.matcher.push(typeof c[i] == 'function' ? c[i](m) :
+          this.matcher.push(Object.isFunction(c[i]) ? c[i](m) :
     	      new Template(c[i]).evaluate(m));
           e = e.replace(m[0], '');
           break;
@@ -2089,7 +2791,7 @@
 
   compileXPathMatcher: function() {
     var e = this.expression, ps = Selector.patterns,
-        x = Selector.xpath, le,  m;
+        x = Selector.xpath, le, m;
 
     if (Selector._cache[e]) {
       this.xpath = Selector._cache[e]; return;
@@ -2100,7 +2802,7 @@
       le = e;
       for (var i in ps) {
         if (m = e.match(ps[i])) {
-          this.matcher.push(typeof x[i] == 'function' ? x[i](m) :
+          this.matcher.push(Object.isFunction(x[i]) ? x[i](m) :
             new Template(x[i]).evaluate(m));
           e = e.replace(m[0], '');
           break;
@@ -2119,7 +2821,39 @@
   },
 
   match: function(element) {
-    return this.findElements(document).include(element);
+    this.tokens = [];
+
+    var e = this.expression, ps = Selector.patterns, as = Selector.assertions;
+    var le, p, m;
+
+    while (e && le !== e && (/\S/).test(e)) {
+      le = e;
+      for (var i in ps) {
+        p = ps[i];
+        if (m = e.match(p)) {
+          // use the Selector.assertions methods unless the selector
+          // is too complex.
+          if (as[i]) {
+            this.tokens.push([i, Object.clone(m)]);
+            e = e.replace(m[0], '');
+          } else {
+            // reluctantly do a document-wide search
+            // and look for a match in the array
+            return this.findElements(document).include(element);
+          }
+        }
+      }
+    }
+
+    var match = true, name, matches;
+    for (var i = 0, token; token = this.tokens[i]; i++) {
+      name = token[0], matches = token[1];
+      if (!Selector.assertions[name](element, matches)) {
+        match = false; break;
+      }
+    }
+
+    return match;
   },
 
   toString: function() {
@@ -2129,10 +2863,10 @@
   inspect: function() {
     return "#<Selector:" + this.expression.inspect() + ">";
   }
-};
+});
 
 Object.extend(Selector, {
-  _cache: {},
+  _cache: { },
 
   xpath: {
     descendant:   "//*",
@@ -2154,7 +2888,7 @@
     pseudo: function(m) {
       var h = Selector.xpath.pseudos[m[1]];
       if (!h) return '';
-      if (typeof h === 'function') return h(m);
+      if (Object.isFunction(h)) return h(m);
       return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
     },
     operators: {
@@ -2183,7 +2917,7 @@
           le = e;
           for (var i in p) {
             if (m = e.match(p[i])) {
-              v = typeof x[i] == 'function' ? x[i](m) : new Template(x[i]).evaluate(m);
+              v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m);
               exclusion.push("(" + v.substring(1, v.length - 1) + ")");
               e = e.replace(m[0], '');
               break;
@@ -2241,7 +2975,7 @@
       m[3] = (m[5] || m[6]);
       return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m);
     },
-    pseudo:       function(m) {
+    pseudo: function(m) {
       if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
       return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);
     },
@@ -2263,9 +2997,33 @@
     tagName:      /^\s*(\*|[\w\-]+)(\b|$)?/,
     id:           /^#([\w\-\*]+)(\b|$)/,
     className:    /^\.([\w\-\*]+)(\b|$)/,
-    pseudo:       /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|\s|(?=:))/,
+    pseudo:       /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s)|(?=:))/,
     attrPresence: /^\[([\w]+)\]/,
-    attr:         /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\]]*?)\4|([^'"][^\]]*?)))?\]/
+    attr:         /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
+  },
+
+  // for Selector.match and Element#match
+  assertions: {
+    tagName: function(element, matches) {
+      return matches[1].toUpperCase() == element.tagName.toUpperCase();
+    },
+
+    className: function(element, matches) {
+      return Element.hasClassName(element, matches[1]);
+    },
+
+    id: function(element, matches) {
+      return element.id === matches[1];
+    },
+
+    attrPresence: function(element, matches) {
+      return Element.hasAttribute(element, matches[1]);
+    },
+
+    attr: function(element, matches) {
+      var nodeValue = Element.readAttribute(element, matches[1]);
+      return Selector.operators[matches[2]](nodeValue, matches[3]);
+    }
   },
 
   handlers: {
@@ -2297,7 +3055,7 @@
       parentNode._counted = true;
       if (reverse) {
         for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
-          node = nodes[i];
+          var node = nodes[i];
           if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
         }
       } else {
@@ -2384,7 +3142,8 @@
 
     id: function(nodes, root, id, combinator) {
       var targetNode = $(id), h = Selector.handlers;
-      if (!nodes && root == document) return targetNode ? [targetNode] : [];
+      if (!targetNode) return [];
+      if (!nodes && root == document) return [targetNode];
       if (nodes) {
         if (combinator) {
           if (combinator == 'child') {
@@ -2424,6 +3183,7 @@
     },
 
     attrPresence: function(nodes, root, attr) {
+      if (!nodes) nodes = root.getElementsByTagName("*");
       var results = [];
       for (var i = 0, node; node = nodes[i]; i++)
         if (Element.hasAttribute(node, attr)) results.push(node);
@@ -2592,7 +3352,7 @@
   },
 
   findElement: function(elements, expression, index) {
-    if (typeof expression == 'number') {
+    if (Object.isNumber(expression)) {
       index = expression; expression = false;
     }
     return Selector.matchElements(elements, expression || '*')[index || 0];
@@ -2621,13 +3381,19 @@
     return form;
   },
 
-  serializeElements: function(elements, getHash) {
-    var data = elements.inject({}, function(result, element) {
+  serializeElements: function(elements, options) {
+    if (typeof options != 'object') options = { hash: !!options };
+    else if (options.hash === undefined) options.hash = true;
+    var key, value, submitted = false, submit = options.submit;
+
+    var data = elements.inject({ }, function(result, element) {
       if (!element.disabled && element.name) {
-        var key = element.name, value = $(element).getValue();
-        if (value != null) {
-         	if (key in result) {
-            if (result[key].constructor != Array) result[key] = [result[key]];
+        key = element.name; value = $(element).getValue();
+        if (value != null && (element.type != 'submit' || (!submitted &&
+            submit !== false && (!submit || key == submit) && (submitted = true)))) {
+          if (key in result) {
+            // a key is already present; construct an array of values
+            if (!Object.isArray(result[key])) result[key] = [result[key]];
             result[key].push(value);
           }
           else result[key] = value;
@@ -2636,13 +3402,14 @@
       return result;
     });
 
-    return getHash ? data : Hash.toQueryString(data);
+    return options.hash ? data : Object.toQueryString(data);
   }
 };
+  },
 
 Form.Methods = {
-  serialize: function(form, getHash) {
-    return Form.serializeElements(Form.getElements(form), getHash);
+  serialize: function(form, options) {
+    return Form.serializeElements(Form.getElements(form), options);
   },
 
   getElements: function(form) {
@@ -2655,9 +3422,216 @@
     );
   },
 
-  getInputs: function(form, typeName, name) {
-    form = $(form);
-    var inputs = form.getElementsByTagName('input');
+  pseudos: {
+    'first-child': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        if (Selector.handlers.previousElementSibling(node)) continue;
+          results.push(node);
+      }
+      return results;
+    },
+    'last-child': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        if (Selector.handlers.nextElementSibling(node)) continue;
+          results.push(node);
+      }
+      return results;
+    },
+    'only-child': function(nodes, value, root) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
+          results.push(node);
+      return results;
+    },
+    'nth-child':        function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root);
+    },
+    'nth-last-child':   function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, true);
+    },
+    'nth-of-type':      function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, false, true);
+    },
+    'nth-last-of-type': function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, true, true);
+    },
+    'first-of-type':    function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, "1", root, false, true);
+    },
+    'last-of-type':     function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, "1", root, true, true);
+    },
+    'only-of-type':     function(nodes, formula, root) {
+      var p = Selector.pseudos;
+      return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
+    },
+
+    // handles the an+b logic
+    getIndices: function(a, b, total) {
+      if (a == 0) return b > 0 ? [b] : [];
+      return $R(1, total).inject([], function(memo, i) {
+        if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);
+        return memo;
+      });
+    },
+
+    // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type
+    nth: function(nodes, formula, root, reverse, ofType) {
+      if (nodes.length == 0) return [];
+      if (formula == 'even') formula = '2n+0';
+      if (formula == 'odd')  formula = '2n+1';
+      var h = Selector.handlers, results = [], indexed = [], m;
+      h.mark(nodes);
+      for (var i = 0, node; node = nodes[i]; i++) {
+        if (!node.parentNode._counted) {
+          h.index(node.parentNode, reverse, ofType);
+          indexed.push(node.parentNode);
+        }
+      }
+      if (formula.match(/^\d+$/)) { // just a number
+        formula = Number(formula);
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node.nodeIndex == formula) results.push(node);
+      } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
+        if (m[1] == "-") m[1] = -1;
+        var a = m[1] ? Number(m[1]) : 1;
+        var b = m[2] ? Number(m[2]) : 0;
+        var indices = Selector.pseudos.getIndices(a, b, nodes.length);
+        for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {
+          for (var j = 0; j < l; j++)
+            if (node.nodeIndex == indices[j]) results.push(node);
+        }
+      }
+      h.unmark(nodes);
+      h.unmark(indexed);
+      return results;
+    },
+
+    'empty': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        // IE treats comments as element nodes
+        if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue;
+        results.push(node);
+      }
+      return results;
+    },
+
+    'not': function(nodes, selector, root) {
+      var h = Selector.handlers, selectorType, m;
+      var exclusions = new Selector(selector).findElements(root);
+      h.mark(exclusions);
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!node._counted) results.push(node);
+      h.unmark(exclusions);
+      return results;
+    },
+
+    'enabled': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!node.disabled) results.push(node);
+      return results;
+    },
+
+    'disabled': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (node.disabled) results.push(node);
+      return results;
+    },
+
+    'checked': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (node.checked) results.push(node);
+      return results;
+    }
+  },
+
+  operators: {
+    '=':  function(nv, v) { return nv == v; },
+    '!=': function(nv, v) { return nv != v; },
+    '^=': function(nv, v) { return nv.startsWith(v); },
+    '$=': function(nv, v) { return nv.endsWith(v); },
+    '*=': function(nv, v) { return nv.include(v); },
+    '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
+    '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); }
+  },
+
+  matchElements: function(elements, expression) {
+    var matches = new Selector(expression).findElements(), h = Selector.handlers;
+    h.mark(matches);
+    for (var i = 0, results = [], element; element = elements[i]; i++)
+      if (element._counted) results.push(element);
+    h.unmark(matches);
+    return results;
+  },
+
+  findElement: function(elements, expression, index) {
+    if (typeof expression == 'number') {
+      index = expression; expression = false;
+    }
+    return Selector.matchElements(elements, expression || '*')[index || 0];
+  },
+
+  findChildElements: function(element, expressions) {
+    var exprs = expressions.join(','), expressions = [];
+    exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
+      expressions.push(m[1].strip());
+    });
+    var results = [], h = Selector.handlers;
+    for (var i = 0, l = expressions.length, selector; i < l; i++) {
+      selector = new Selector(expressions[i].strip());
+      h.concat(results, selector.findElements(element));
+    }
+    return (l > 1) ? h.unique(results) : results;
+  }
+});
+
+function $$() {
+  return Selector.findChildElements(document, $A(arguments));
+}
+var Form = {
+  reset: function(form) {
+    $(form).reset();
+    return form;
+  },
+
+  serializeElements: function(elements, getHash) {
+    var data = elements.inject({}, function(result, element) {
+      if (!element.disabled && element.name) {
+        var key = element.name, value = $(element).getValue();
+        if (value != null) {
+         	if (key in result) {
+            if (result[key].constructor != Array) result[key] = [result[key]];
+            result[key].push(value);
+          }
+          else result[key] = value;
+        }
+      }
+      return result;
+    });
+
+    return getHash ? data : Hash.toQueryString(data);
+  }
+};
+
+Form.Methods = {
+  serialize: function(form, getHash) {
+    return Form.serializeElements(Form.getElements(form), getHash);
+  },
+
+  getElements: function(form) {
+    return $A($(form).getElementsByTagName('*')).inject([],
+      function(elements, child) {
+        if (Form.Element.Serializers[child.tagName.toLowerCase()])
+          elements.push(Element.extend(child));
+        return elements;
+      }
+    );
+  },
+
+  getInputs: function(form, typeName, name) {
+    form = $(form);
+    var inputs = form.getElementsByTagName('input');
 
     if (!typeName && !name) return $A(inputs).map(Element.extend);
 
@@ -2684,9 +3658,15 @@
   },
 
   findFirstElement: function(form) {
-    return $(form).getElements().find(function(element) {
-      return element.type != 'hidden' && !element.disabled &&
-        ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
+    var elements = $(form).getElements().findAll(function(element) {
+      return 'hidden' != element.type && !element.disabled;
+    });
+    var firstByIndex = elements.findAll(function(element) {
+      return element.hasAttribute('tabIndex') && element.tabIndex >= 0;
+    }).sortBy(function(element) { return element.tabIndex }).first();
+
+    return firstByIndex ? firstByIndex : elements.find(function(element) {
+      return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
     });
   },
 
@@ -2697,22 +3677,23 @@
   },
 
   request: function(form, options) {
-    form = $(form), options = Object.clone(options || {});
+    form = $(form), options = Object.clone(options || { });
 
-    var params = options.parameters;
+    var params = options.parameters, action = form.readAttribute('action') || '';
+    if (action.blank()) action = window.location.href;
     options.parameters = form.serialize(true);
 
     if (params) {
-      if (typeof params == 'string') params = params.toQueryParams();
+      if (Object.isString(params)) params = params.toQueryParams();
       Object.extend(options.parameters, params);
     }
 
     if (form.hasAttribute('method') && !options.method)
       options.method = form.method;
 
-    return new Ajax.Request(form.readAttribute('action'), options);
+    return new Ajax.Request(action, options);
   }
-}
+};
 
 /*--------------------------------------------------------------------------*/
 
@@ -2726,7 +3707,7 @@
     $(element).select();
     return element;
   }
-}
+};
 
 Form.Element.Methods = {
   serialize: function(element) {
@@ -2734,9 +3715,9 @@
     if (!element.disabled && element.name) {
       var value = element.getValue();
       if (value != undefined) {
-        var pair = {};
+        var pair = { };
         pair[element.name] = value;
-        return Hash.toQueryString(pair);
+        return Object.toQueryString(pair);
       }
     }
     return '';
@@ -2748,6 +3729,13 @@
     return Form.Element.Serializers[method](element);
   },
 
+  setValue: function(element, value) {
+    element = $(element);
+    var method = element.tagName.toLowerCase();
+    Form.Element.Serializers[method](element, value);
+    return element;
+  },
+
   clear: function(element) {
     $(element).value = '';
     return element;
@@ -2762,9 +3750,9 @@
     try {
       element.focus();
       if (element.select && (element.tagName.toLowerCase() != 'input' ||
-        !['button', 'reset', 'submit'].include(element.type)))
+          !['button', 'reset', 'submit'].include(element.type)))
         element.select();
-    } catch (e) {}
+    } catch (e) { }
     return element;
   },
 
@@ -2780,7 +3768,7 @@
     element.disabled = false;
     return element;
   }
-}
+};
 
 /*--------------------------------------------------------------------------*/
 
@@ -2790,27 +3778,46 @@
 /*--------------------------------------------------------------------------*/
 
 Form.Element.Serializers = {
-  input: function(element) {
+  input: function(element, value) {
     switch (element.type.toLowerCase()) {
       case 'checkbox':
       case 'radio':
-        return Form.Element.Serializers.inputSelector(element);
+        return Form.Element.Serializers.inputSelector(element, value);
+      default:
+        return Form.Element.Serializers.textarea(element, value);
       default:
         return Form.Element.Serializers.textarea(element);
     }
   },
 
-  inputSelector: function(element) {
-    return element.checked ? element.value : null;
-  },
-
-  textarea: function(element) {
-    return element.value;
-  },
-
-  select: function(element) {
-    return this[element.type == 'select-one' ?
-      'selectOne' : 'selectMany'](element);
+  inputSelector: function(element, value) {
+    if (value === undefined) return element.checked ? element.value : null;
+    else element.checked = !!value;
+  },
+
+  textarea: function(element, value) {
+    if (value === undefined) return element.value;
+    else element.value = value;
+  },
+
+  select: function(element, index) {
+    if (index === undefined)
+      return this[element.type == 'select-one' ?
+        'selectOne' : 'selectMany'](element);
+    else {
+      var opt, value, single = !Object.isArray(index);
+      for (var i = 0, length = element.length; i < length; i++) {
+        opt = element.options[i];
+        value = this.optionValue(opt);
+        if (single) {
+          if (value == index) {
+            opt.selected = true;
+            return;
+          }
+        }
+        else opt.selected = index.include(value);
+      }
+    }
   },
 
   selectOne: function(element) {
@@ -2833,45 +3840,34 @@
     // extend element because hasAttribute may not be native
     return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
   }
-}
+};
 
 /*--------------------------------------------------------------------------*/
 
-Abstract.TimedObserver = function() {}
-Abstract.TimedObserver.prototype = {
-  initialize: function(element, frequency, callback) {
-    this.frequency = frequency;
+Abstract.TimedObserver = Class.create(PeriodicalExecuter, {
+  initialize: function($super, element, frequency, callback) {
+    $super(callback, 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() {
+  execute: function() {
     var value = this.getValue();
-    var changed = ('string' == typeof this.lastValue && 'string' == typeof value
-      ? this.lastValue != value : String(this.lastValue) != String(value));
-    if (changed) {
+    if (Object.isString(this.lastValue) && Object.isString(value) ?
+        this.lastValue != value : String(this.lastValue) != String(value)) {
       this.callback(this.element, value);
       this.lastValue = value;
     }
   }
-}
+});
 
-Form.Element.Observer = Class.create();
-Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
+Form.Element.Observer = Class.create(Abstract.TimedObserver, {
   getValue: function() {
     return Form.Element.getValue(this.element);
   }
 });
 
-Form.Observer = Class.create();
-Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
+Form.Observer = Class.create(Abstract.TimedObserver, {
   getValue: function() {
     return Form.serialize(this.element);
   }
@@ -2879,8 +3875,7 @@
 
 /*--------------------------------------------------------------------------*/
 
-Abstract.EventObserver = function() {}
-Abstract.EventObserver.prototype = {
+Abstract.EventObserver = Class.create({
   initialize: function(element, callback) {
     this.element  = $(element);
     this.callback = callback;
@@ -2901,7 +3896,7 @@
   },
 
   registerFormCallbacks: function() {
-    Form.getElements(this.element).each(this.registerCallback.bind(this));
+    Form.getElements(this.element).each(this.registerCallback, this);
   },
 
   registerCallback: function(element) {
@@ -2917,24 +3912,20 @@
       }
     }
   }
-}
+});
 
-Form.Element.EventObserver = Class.create();
-Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
+Form.Element.EventObserver = Class.create(Abstract.EventObserver, {
   getValue: function() {
     return Form.Element.getValue(this.element);
   }
 });
 
-Form.EventObserver = Class.create();
-Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
+Form.EventObserver = Class.create(Abstract.EventObserver, {
   getValue: function() {
     return Form.serialize(this.element);
   }
 });
-if (!window.Event) {
-  var Event = new Object();
-}
+if (!window.Event) var Event = { };
 
 Object.extend(Event, {
   KEY_BACKSPACE: 8,
@@ -2950,100 +3941,335 @@
   KEY_END:      35,
   KEY_PAGEUP:   33,
   KEY_PAGEDOWN: 34,
+  KEY_INSERT:   45,
 
-  element: function(event) {
-    return $(event.target || event.srcElement);
-  },
+  cache: { },
 
-  isLeftClick: function(event) {
-    return (((event.which) && (event.which == 1)) ||
-            ((event.button) && (event.button == 1)));
-  },
+  relatedTarget: function(event) {
+    var element;
+    switch(event.type) {
+      case 'mouseover': element = event.fromElement; break;
+      case 'mouseout':  element = event.toElement;   break;
+      default: return null;
+    }
+    return Element.extend(element);
+  }
+});
 
-  pointerX: function(event) {
-    return event.pageX || (event.clientX +
-      (document.documentElement.scrollLeft || document.body.scrollLeft));
-  },
+Event.Methods = (function() {
+  var isButton;
 
-  pointerY: function(event) {
-    return event.pageY || (event.clientY +
-      (document.documentElement.scrollTop || document.body.scrollTop));
-  },
+  if (Prototype.Browser.IE) {
+    var buttonMap = { 0: 1, 1: 4, 2: 2 };
+    isButton = function(event, code) {
+      return event.button == buttonMap[code];
+    };
+
+  } else if (Prototype.Browser.WebKit) {
+    isButton = function(event, code) {
+      switch (code) {
+        case 0: return event.which == 1 && !event.metaKey;
+        case 1: return event.which == 1 && event.metaKey;
+        default: return false;
+      }
+    };
+
+  } else {
+    isButton = function(event, code) {
+      return event.which ? (event.which === code + 1) : (event.button === code);
+    };
+  }
+
+  return {
+    isLeftClick:   function(event) { return isButton(event, 0) },
+    isMiddleClick: function(event) { return isButton(event, 1) },
+    isRightClick:  function(event) { return isButton(event, 2) },
+
+    element: function(event) {
+      var node = Event.extend(event).target;
+      return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node);
+    },
+
+    findElement: function(event, expression) {
+      var element = Event.element(event);
+      return element.match(expression) ? element : element.up(expression);
+    },
 
-  stop: function(event) {
-    if (event.preventDefault) {
+    pointer: function(event) {
+      return {
+        x: event.pageX || (event.clientX +
+          (document.documentElement.scrollLeft || document.body.scrollLeft)),
+        y: event.pageY || (event.clientY +
+          (document.documentElement.scrollTop || document.body.scrollTop))
+      };
+    },
+
+    pointerX: function(event) { return Event.pointer(event).x },
+    pointerY: function(event) { return Event.pointer(event).y },
+
+    stop: function(event) {
+      Event.extend(event);
       event.preventDefault();
       event.stopPropagation();
-    } else {
-      event.returnValue = false;
-      event.cancelBubble = true;
+      event.stopped = 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;
-  },
+Event.extend = (function() {
+  var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
+    m[name] = Event.Methods[name].methodize();
+    return m;
+  });
 
-  observers: false,
+  if (Prototype.Browser.IE) {
+    Object.extend(methods, {
+      stopPropagation: function() { this.cancelBubble = true },
+      preventDefault:  function() { this.returnValue = false },
+      inspect: function() { return "[object Event]" }
+    });
+
+    return function(event) {
+      if (!event) return false;
+      if (event._extendedByPrototype) return event;
+
+      event._extendedByPrototype = Prototype.emptyFunction;
+      var pointer = Event.pointer(event);
+      Object.extend(event, {
+        target: event.srcElement,
+        relatedTarget: Event.relatedTarget(event),
+        pageX:  pointer.x,
+        pageY:  pointer.y
+      });
+      return Object.extend(event, methods);
+    };
 
-  _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);
+  } else {
+    Event.prototype = Event.prototype || document.createEvent("HTMLEvents").__proto__;
+    Object.extend(Event.prototype, methods);
+    return Prototype.K;
+  }
+})();
+
+Object.extend(Event, (function() {
+  var cache = Event.cache;
+
+  function getEventID(element) {
+    if (element._eventID) return element._eventID;
+    arguments.callee.id = arguments.callee.id || 1;
+    return element._eventID = ++arguments.callee.id;
+  }
+
+  function getDOMEventName(eventName) {
+    if (eventName && eventName.include(':')) return "dataavailable";
+    return eventName;
+  }
+
+  function getCacheForID(id) {
+    return cache[id] = cache[id] || { };
+  }
+
+  function getWrappersForEventName(id, eventName) {
+    var c = getCacheForID(id);
+    return c[eventName] = c[eventName] || [];
+  }
+
+  function createWrapper(element, eventName, handler) {
+    var id = getEventID(element);
+    var c = getWrappersForEventName(id, eventName);
+    if (c.pluck("handler").include(handler)) return false;
+
+    var wrapper = function(event) {
+      if (!Event || !Event.extend ||
+        (event.eventName && event.eventName != eventName))
+          return false;
+
+      Event.extend(event);
+      handler.call(element, event)
+    };
+
+    wrapper.handler = handler;
+    c.push(wrapper);
+    return wrapper;
+  }
+
+  function findWrapper(id, eventName, handler) {
+    var c = getWrappersForEventName(id, eventName);
+    return c.find(function(wrapper) { return wrapper.handler == handler });
+  }
+
+  function destroyWrapper(id, eventName, handler) {
+    var c = getCacheForID(id);
+    if (!c[eventName]) return false;
+    c[eventName] = c[eventName].without(findWrapper(id, eventName, handler));
+  }
+
+  function destroyCache() {
+    for (var id in cache)
+      for (var eventName in cache[id])
+        cache[id][eventName] = null;
+  }
+
+  if (window.attachEvent) {
+    window.attachEvent("onunload", destroyCache);
+  }
+
+  return {
+    observe: function(element, eventName, handler) {
+      element = $(element);
+      var name = getDOMEventName(eventName);
+
+      var wrapper = createWrapper(element, eventName, handler);
+      if (!wrapper) return element;
+
+      if (element.addEventListener) {
+        element.addEventListener(name, wrapper, false);
+      } else {
+        element.attachEvent("on" + name, wrapper);
+      }
+
+      return element;
+    },
+
+    stopObserving: function(element, eventName, handler) {
+      element = $(element);
+      var id = getEventID(element), name = getDOMEventName(eventName);
+
+      if (!handler && eventName) {
+        getWrappersForEventName(id, eventName).each(function(wrapper) {
+          element.stopObserving(eventName, wrapper.handler);
+        });
+        return element;
+
+      } else if (!eventName) {
+        Object.keys(getCacheForID(id)).each(function(eventName) {
+          element.stopObserving(eventName);
+        });
+        return element;
+      }
+
+      var wrapper = findWrapper(id, eventName, handler);
+      if (!wrapper) return element;
+
+      if (element.removeEventListener) {
+        element.removeEventListener(name, wrapper, false);
+      } else {
+        element.detachEvent("on" + name, wrapper);
+      }
+
+      destroyWrapper(id, eventName, handler);
+
+      return element;
+    },
+
+    fire: function(element, eventName, memo) {
+      element = $(element);
+      if (element == document && document.createEvent && !element.dispatchEvent)
+        element = document.documentElement;
+
+      if (document.createEvent) {
+        var event = document.createEvent("HTMLEvents");
+        event.initEvent("dataavailable", true, true);
+      } else {
+        var event = document.createEventObject();
+        event.eventType = "ondataavailable";
+      }
+
+      event.eventName = eventName;
+      event.memo = memo || { };
+
+      if (document.createEvent) {
+        element.dispatchEvent(event);
+      } else {
+        element.fireEvent(event.eventType, event);
+      }
+
+      return event;
     }
-  },
+  };
+})());
+
+Object.extend(Event, Event.Methods);
 
-  unloadCache: function() {
-    if (!Event.observers) return;
-    for (var i = 0, length = Event.observers.length; i < length; i++) {
-      Event.stopObserving.apply(this, Event.observers[i]);
-      Event.observers[i][0] = null;
+Element.addMethods({
+  fire:          Event.fire,
+  observe:       Event.observe,
+  stopObserving: Event.stopObserving
+});
+
+Object.extend(document, {
+  fire:          Element.Methods.fire.methodize(),
+  observe:       Element.Methods.observe.methodize(),
+  stopObserving: Element.Methods.stopObserving.methodize()
+});
+
+(function() {
+  /* Support for the DOMContentLoaded event is based on work by Dan Webb,
+     Matthias Miller, Dean Edwards and John Resig. */
+
+  var timer, fired = false;
+
+  function fireContentLoadedEvent() {
+    if (fired) return;
+    if (timer) window.clearInterval(timer);
+    document.fire("dom:loaded");
+    fired = true;
+  }
+
+  if (document.addEventListener) {
+    if (Prototype.Browser.WebKit) {
+      timer = window.setInterval(function() {
+        if (/loaded|complete/.test(document.readyState))
+          fireContentLoadedEvent();
+      }, 0);
+
+      Event.observe(window, "load", fireContentLoadedEvent);
+
+    } else {
+      document.addEventListener("DOMContentLoaded",
+        fireContentLoadedEvent, false);
     }
-    Event.observers = false;
-  },
 
-  observe: function(element, name, observer, useCapture) {
-    element = $(element);
-    useCapture = useCapture || false;
+  } else {
+    document.write("<script id=__onDOMContentLoaded defer src=//:><\/script>");
+    $("__onDOMContentLoaded").onreadystatechange = function() {
+      if (this.readyState == "complete") {
+        this.onreadystatechange = null;
+        fireContentLoadedEvent();
+      }
+    };
+  }
+})();
+/*------------------------------- DEPRECATED -------------------------------*/
 
-    if (name == 'keypress' &&
-      (Prototype.Browser.WebKit || element.attachEvent))
-      name = 'keydown';
+Hash.toQueryString = Object.toQueryString;
 
-    Event._observeAndCache(element, name, observer, useCapture);
+var Toggle = { display: Element.toggle };
+
+Element.Methods.childOf = Element.Methods.descendantOf;
+
+var Insertion = {
+  Before: function(element, content) {
+    return Element.insert(element, {before:content});
   },
 
-  stopObserving: function(element, name, observer, useCapture) {
-    element = $(element);
-    useCapture = useCapture || false;
+  Top: function(element, content) {
+    return Element.insert(element, {top:content});
+  },
 
-    if (name == 'keypress' &&
-        (Prototype.Browser.WebKit || element.attachEvent))
-      name = 'keydown';
+  Bottom: function(element, content) {
+    return Element.insert(element, {bottom:content});
+  },
 
-    if (element.removeEventListener) {
-      element.removeEventListener(name, observer, useCapture);
-    } else if (element.detachEvent) {
-      try {
-        element.detachEvent('on' + name, observer);
-      } catch (e) {}
-    }
+  After: function(element, content) {
+    return Element.insert(element, {after:content});
   }
-});
+};
 
-/* prevent memory leaks in IE */
-if (Prototype.Browser.IE)
-  Event.observe(window, 'unload', Event.unloadCache, false);
+var $continue = new Error('"throw $continue" is deprecated, use "return" instead');
+
+// This should be moved to script.aculo.us; notice the deprecated methods
+// further below, that map to the newer Element methods.
 var Position = {
   // set to true if needed, warning: firefox performance problems
   // NOT neeeded for page scrolling, only if draggable contained in
@@ -3063,59 +4289,13 @@
                 || 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) {
-        if(element.tagName=='BODY') break;
-        var 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);
+    this.offset = Element.cumulativeOffset(element);
 
     return (y >= this.offset[1] &&
             y <  this.offset[1] + element.offsetHeight &&
@@ -3124,11 +4304,11 @@
   },
 
   withinIncludingScrolloffsets: function(element, x, y) {
-    var offsetcache = this.realOffset(element);
+    var offsetcache = Element.cumulativeScrollOffset(element);
 
     this.xcomp = x + offsetcache[0] - this.deltaX;
     this.ycomp = y + offsetcache[1] - this.deltaY;
-    this.offset = this.cumulativeOffset(element);
+    this.offset = Element.cumulativeOffset(element);
 
     return (this.ycomp >= this.offset[1] &&
             this.ycomp <  this.offset[1] + element.offsetHeight &&
@@ -3147,125 +4327,104 @@
         element.offsetWidth;
   },
 
-  page: function(forElement) {
-    var valueT = 0, valueL = 0;
+  // Deprecation layer -- use newer Element methods now (1.5.2).
 
-    var element = forElement;
-    do {
-      valueT += element.offsetTop  || 0;
-      valueL += element.offsetLeft || 0;
+  cumulativeOffset: Element.Methods.cumulativeOffset,
 
-      // Safari fix
-      if (element.offsetParent == document.body)
-        if (Element.getStyle(element,'position')=='absolute') break;
+  positionedOffset: Element.Methods.positionedOffset,
 
-    } while (element = element.offsetParent);
-
-    element = forElement;
-    do {
-      if (!window.opera || element.tagName=='BODY') {
-        valueT -= element.scrollTop  || 0;
-        valueL -= element.scrollLeft || 0;
-      }
-    } while (element = element.parentNode);
+  absolutize: function(element) {
+    Position.prepare();
+    return Element.absolutize(element);
+  },
 
-    return [valueL, valueT];
+  relativize: function(element) {
+    Position.prepare();
+    return Element.relativize(element);
   },
 
-  clone: function(source, target) {
-    var options = Object.extend({
-      setLeft:    true,
-      setTop:     true,
-      setWidth:   true,
-      setHeight:  true,
-      offsetTop:  0,
-      offsetLeft: 0
-    }, arguments[2] || {})
+  realOffset: Element.Methods.cumulativeScrollOffset,
 
-    // find page position of source
-    source = $(source);
-    var p = Position.page(source);
+  offsetParent: Element.Methods.getOffsetParent,
 
-    // 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);
-    }
+  page: Element.Methods.viewportOffset,
 
-    // correct by body offsets (fixes Safari)
-    if (parent == document.body) {
-      delta[0] -= document.body.offsetLeft;
-      delta[1] -= document.body.offsetTop;
+  clone: function(source, target, options) {
+    options = options || { };
+    return Element.clonePosition(target, source, options);
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){
+  function iter(name) {
+    return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]";
+  }
+
+  instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?
+  function(element, className) {
+    className = className.toString().strip();
+    var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className);
+    return cond ? document._getElementsByXPath('.//*' + cond, element) : [];
+  } : function(element, className) {
+    className = className.toString().strip();
+    var elements = [], classNames = (/\s/.test(className) ? $w(className) : null);
+    if (!classNames && !className) return elements;
+
+    var nodes = $(element).getElementsByTagName('*');
+    className = ' ' + className + ' ';
+
+    for (var i = 0, child, cn; child = nodes[i]; i++) {
+      if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||
+          (classNames && classNames.all(function(name) {
+            return !name.toString().blank() && cn.include(' ' + name + ' ');
+          }))))
+        elements.push(Element.extend(child));
     }
+    return elements;
+  };
 
-    // 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';
-  },
+  return function(className, parentElement) {
+    return $(parentElement || document.body).getElementsByClassName(className);
+  };
+}(Element.Methods);
 
-  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.ClassNames = Class.create();
+Element.ClassNames.prototype = {
+  initialize: function(element) {
+    this.element = $(element);
+  },
 
-    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;
+  _each: function(iterator) {
+    this.element.className.split(/\s+/).select(function(name) {
+      return name.length > 0;
+    })._each(iterator);
+  },
 
-    element.style.position = 'absolute';
-    element.style.top    = top + 'px';
-    element.style.left   = left + 'px';
-    element.style.width  = width + 'px';
-    element.style.height = height + 'px';
+  set: function(className) {
+    this.element.className = className;
   },
 
-  relativize: function(element) {
-    element = $(element);
-    if (element.style.position == 'relative') return;
-    Position.prepare();
+  add: function(classNameToAdd) {
+    if (this.include(classNameToAdd)) return;
+    this.set($A(this).concat(classNameToAdd).join(' '));
+  },
 
-    element.style.position = 'relative';
-    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
-    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
+  remove: function(classNameToRemove) {
+    if (!this.include(classNameToRemove)) return;
+    this.set($A(this).without(classNameToRemove).join(' '));
+  },
 
-    element.style.top    = top + 'px';
-    element.style.left   = left + 'px';
-    element.style.height = element._originalHeight;
-    element.style.width  = element._originalWidth;
+  toString: function() {
+    return $A(this).join(' ');
   }
-}
-
-// 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 (Prototype.Browser.WebKit) {
-  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);
+Object.extend(Element.ClassNames.prototype, Enumerable);
 
-    return [valueL, valueT];
-  }
-}
+/*--------------------------------------------------------------------------*/
 
-Element.addMethods();
\ No newline at end of file
+Element.addMethods();

Modified: jifty/branches/js-refactor/share/web/static/js/scriptaculous/effects.js
==============================================================================
--- jifty/branches/js-refactor/share/web/static/js/scriptaculous/effects.js	(original)
+++ jifty/branches/js-refactor/share/web/static/js/scriptaculous/effects.js	Thu Nov 29 12:25:16 2007
@@ -231,10 +231,10 @@
   get: function(queueName) {
     if(typeof queueName != 'string') return queueName;
     
-    if(!this.instances[queueName])
-      this.instances[queueName] = new Effect.ScopedQueue();
+    if(!this.instances.get(queueName))
+      this.instances.set(queueName, new Effect.ScopedQueue());
       
-    return this.instances[queueName];
+    return this.instances.get(queueName);
   }
 }
 Effect.Queue = Effect.Queues.get('global');

Modified: jifty/branches/js-refactor/share/web/templates/autohandler
==============================================================================
--- jifty/branches/js-refactor/share/web/templates/autohandler	(original)
+++ jifty/branches/js-refactor/share/web/templates/autohandler	Thu Nov 29 12:25:16 2007
@@ -1,9 +1,11 @@
 <%init>
 $r->content_type('text/html; charset=utf-8');
 
-if ($m->base_comp->path =~ m|/_elements/|) {
+# XXX FIXME See Jifty::View::Mason::Handler for the source of this puke.
+use vars qw/ $jifty_internal_request /;
+if (!$jifty_internal_request && $m->base_comp->path =~ m|/_elements/|) {
     # Requesting an internal component by hand -- naughty
-    $m->redirect("/errors/requested_private_component");
+    $m->redirect('/__jifty/error/requested_private_component');
 }
 $m->comp('/_elements/nav');
 $m->call_next();

Modified: jifty/branches/js-refactor/t/13-sessions.t
==============================================================================
--- jifty/branches/js-refactor/t/13-sessions.t	(original)
+++ jifty/branches/js-refactor/t/13-sessions.t	Thu Nov 29 12:25:16 2007
@@ -7,9 +7,9 @@
 
 =cut
 
-use Jifty::Test tests => 31;
+use Jifty::Test tests => 19;
 
-my ($first_id, $second_id, $third_id);
+my ($first_id, $third_id);
 
 {
     my $session = Jifty::Web::Session->new();
@@ -44,7 +44,6 @@
     $session->load_by_kv(user => 'second');
     ok($session->id, "got a session");
     isnt($session->id, $first_id, "NOT same session as before");
-    $second_id = $session->id;
 
     is($session->get('hello'),  undef, "different value gives different session");
     is($session->get('number'), undef, "different value gives different session");
@@ -63,36 +62,36 @@
     is($session->get('number'), '1st', "even though the two sessions have some overlapping keys, the one that matters doesn't overlap");
 }
 
-# the three-arg form
-
-{
-    my $session = Jifty::Web::Session->new();
-    $session->load_by_kv('user', 'first', sub { $_[0] =~ /^f/ } );
-    ok($session->id, "got a session");
-    is($session->id, $first_id, "first session again");
-    is($session->get('number'), '1st');
-}
-
+# test null char
+TODO:
 {
-    my $session = Jifty::Web::Session->new();
-    $session->load_by_kv('user', 'third', sub { $_[0] =~ /\b(thi|3)rd\b/ } );
-    ok($session->id, "got a session");
-    $third_id = $session->id;
-
-    isnt($session->id, $first_id,  "not first session");
-    isnt($session->id, $second_id, "not second session");
-    is($session->get('number'), undef);
-    $session->set(number => '3rd');
-    is($session->get('number'), '3rd');
-}
-
-{
-    my $session = Jifty::Web::Session->new();
-    $session->load_by_kv('user', 'third', sub { $_[0] =~ /\b(thi|3)rd\b/ } );
-    ok($session->id, "got a session");
-    isnt($session->id, $first_id,  "not first session");
-    isnt($session->id, $second_id, "not second session");
-    is($session->id,   $third_id, "third session again");
-    is($session->get('number'), '3rd');
+    local $TODO = "something doesn't like \\0, and it generates a lot of warnings, so the tests here are commented out instead of todo'd";
+    ok(0, $TODO);
 }
 
+#{
+#    my $session = Jifty::Web::Session->new();
+#    $session->load_by_kv(user => "first\0sneaky!");
+#    ok($session->id, "got a session");
+#    isnt($session->id, $first_id, "'first\\0sneaky!' different from 'first'");
+#    is($session->get('hello'), undef, "first\\0sneaky has no session data yet");
+#}
+#
+#{
+#    my $session = Jifty::Web::Session->new();
+#    $session->load_by_kv(user => "\0third");
+#    ok($session->id, "got a session");
+#    $third_id = $session->id;
+#
+#    $session->set(a => 'apple');
+#}
+#
+#{
+#    my $session = Jifty::Web::Session->new();
+#    $session->load_by_kv(user => "\0third");
+#    ok($session->id, "got a session");
+#    is($session->id, $third_id, "same session as before");
+#
+#    is($session->get('a'), 'apple', "'set', destroy, 'get' works");
+#}
+#

Modified: jifty/branches/js-refactor/t/99-pod-coverage.t
==============================================================================
--- jifty/branches/js-refactor/t/99-pod-coverage.t	(original)
+++ jifty/branches/js-refactor/t/99-pod-coverage.t	Thu Nov 29 12:25:16 2007
@@ -1,6 +1,7 @@
 use Test::More;
 eval "use Test::Pod::Coverage 1.00";
 plan skip_all => "Test::Pod::Coverage 1.00 required for testing POD coverage" if $@;
+plan skip_all => "Coverage tests only run for authors" unless (-d 'inc/.author');
 all_pod_coverage_ok( );
 
 # Workaround for dumb bug (fixed in 5.8.7) where Test::Builder thinks that

Modified: jifty/branches/js-refactor/t/DateTime.t
==============================================================================
--- jifty/branches/js-refactor/t/DateTime.t	(original)
+++ jifty/branches/js-refactor/t/DateTime.t	Thu Nov 29 12:25:16 2007
@@ -1,9 +1,27 @@
 #!/usr/bin/perl -w
 
-use Jifty::Test tests => 2;
+use Jifty::Test tests => 8;
 
 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;
+
+is $date->friendly_date, '2006-05-03';
+
+$date = Jifty::DateTime->now;
+is $date->friendly_date, 'today';
+
+$date = Jifty::DateTime->now->subtract(days => 1);
+is $date->friendly_date, 'yesterday';
+
+$date = Jifty::DateTime->now->subtract(days => 2);
+like $date->friendly_date, qr/^\d\d\d\d-\d\d-\d\d$/;
+
+$date = Jifty::DateTime->now->add(days => 1);
+is $date->friendly_date, 'tomorrow';
+
+$date = Jifty::DateTime->now->add(days => 2);
+like $date->friendly_date, qr/^\d\d\d\d-\d\d-\d\d$/;
+

Modified: jifty/branches/js-refactor/t/TestApp-Plugin-Chart/lib/TestApp/Plugin/Chart/View.pm
==============================================================================
--- jifty/branches/js-refactor/t/TestApp-Plugin-Chart/lib/TestApp/Plugin/Chart/View.pm	(original)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-Chart/lib/TestApp/Plugin/Chart/View.pm	Thu Nov 29 12:25:16 2007
@@ -7,8 +7,8 @@
 template '/graphit' => page {
     Jifty->web->chart(
         type   => 'Pie',
-        width  => 400,
-        height => 300,
+        width  => '100%',
+        height => 500,
         data   => sub {
             [
                 [ 2004, 2005, 2006, 2007 ],

Modified: jifty/branches/js-refactor/t/TestApp-Plugin-Chart/t/chart.t
==============================================================================
--- jifty/branches/js-refactor/t/TestApp-Plugin-Chart/t/chart.t	(original)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-Chart/t/chart.t	Thu Nov 29 12:25:16 2007
@@ -4,10 +4,28 @@
 
 use lib 't/lib';
 
+# XXX FIXME This is here to prevent a segfault on my machine during testing.
+#   -- sterling
+use Test::More;
+eval "use GD; use Chart::pie; 1";
+if ($@) {
+    plan skip_all => 'Chart is not installed.';
+}
+else {
+    plan tests => 9;
+}
+
 use Jifty::SubTest;
-use Jifty::Test tests => 9;
+use Jifty::Test;
 use Jifty::Test::WWW::Mechanize;
 
+use Jifty::Plugin::Chart::Renderer::Chart;
+
+my $chart_plugin = (Jifty->find_plugin('Jifty::Plugin::Chart'))[0];
+$chart_plugin->renderer(
+    $chart_plugin->init_renderer('Jifty::Plugin::Chart::Renderer::Chart')
+);
+
 my $server = Jifty::Test->make_server;
 ok($server, 'got a server');
 
@@ -16,13 +34,10 @@
 my $mech = Jifty::Test::WWW::Mechanize->new;
 
 $mech->get_ok($url . '/graphit', 'try getting /graphit');
-my $img_match = qr{<img src="(/chart/S\d+)" width="400" height="300"/>};
+my $img_match = qr{<img src="(/chart/chart/S\d+)" };
 $mech->content_like($img_match, 'has an img tag');
 my ($chart_path) = $mech->content =~ $img_match;
 
-TODO: {
-local $TODO = "Always fail because eval 'use Chart::*' breaks file handles?";
-
 $mech->get_ok($url . $chart_path, 'try getting ' . $chart_path);
 
 my $response = $mech->response;
@@ -39,7 +54,6 @@
 
     is($info->{file_ext}, 'png', 'it is a png file');
     is($info->{width}, 400, 'it is 400 pixels wide');
-    is($info->{height}, 300, 'it is 300 pixels tall');
+    is($info->{height}, 500, 'it is 500 pixels tall');
 };
 
-};

Added: jifty/branches/js-refactor/t/TestApp-Plugin-Chart/t/gd_graph.t
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-Chart/t/gd_graph.t	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,59 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+
+use lib 't/lib';
+
+# XXX FIXME This is here to prevent a segfault on my machine during testing.
+#   -- sterling
+use Test::More;
+eval "use GD; use GD::Graph::pie; 1";
+if ($@) {
+    plan skip_all => 'GD::Graph is not installed.';
+}
+else {
+    plan tests => 9;
+}
+
+use Jifty::SubTest;
+use Jifty::Test;
+use Jifty::Test::WWW::Mechanize;
+
+use Jifty::Plugin::Chart::Renderer::GD::Graph;
+
+my $chart_plugin = (Jifty->find_plugin('Jifty::Plugin::Chart'))[0];
+$chart_plugin->renderer(
+    $chart_plugin->init_renderer('Jifty::Plugin::Chart::Renderer::GD::Graph')
+);
+
+my $server = Jifty::Test->make_server;
+ok($server, 'got a server');
+
+my $url = $server->started_ok;
+
+my $mech = Jifty::Test::WWW::Mechanize->new;
+
+$mech->get_ok($url . '/graphit', 'try getting /graphit');
+my $img_match = qr{<img src="(/chart/gd_graph/S\d+)" };
+$mech->content_like($img_match, 'has an img tag');
+my ($chart_path) = $mech->content =~ $img_match;
+
+$mech->get_ok($url . $chart_path, 'try getting ' . $chart_path);
+
+my $response = $mech->response;
+is($response->header('Content-type'), 'image/png', 'content type set to png');
+
+SKIP: {
+    eval "use Image::Info qw/ image_info /";
+    skip "Image::Info is not installed", 3 if $@;
+
+    my $imgdata = $mech->content;
+    my $info = image_info(\$imgdata);
+
+    diag($info->{error}) if $info->{error};
+
+    is($info->{file_ext}, 'png', 'it is a png file');
+    is($info->{width}, 400, 'it is 400 pixels wide');
+    is($info->{height}, 500, 'it is 500 pixels tall');
+};
+

Added: jifty/branches/js-refactor/t/TestApp-Plugin-CompressedCSSandJS/Makefile.PL
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-CompressedCSSandJS/Makefile.PL	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,7 @@
+use inc::Module::Install;
+
+name        'TestApp::Plugin::JQuery';
+version     '0.01';
+requires    'Jifty' => '0.70129';
+
+WriteAll;

Added: jifty/branches/js-refactor/t/TestApp-Plugin-CompressedCSSandJS/bin/jifty
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-CompressedCSSandJS/bin/jifty	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,11 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+use File::Basename qw(dirname); 
+use UNIVERSAL::require;
+
+use Jifty;
+use Jifty::Script;
+
+local $SIG{INT} = sub { warn "Stopped\n"; exit; };
+Jifty::Script->dispatch();

Added: jifty/branches/js-refactor/t/TestApp-Plugin-CompressedCSSandJS/etc/config.yml
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-CompressedCSSandJS/etc/config.yml	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,55 @@
+--- 
+framework: 
+  AdminMode: 1
+  ApplicationClass: TestApp::Plugin::CompressedCSSandJS
+  ApplicationName: TestApp::Plugin::CompressedCSSandJS
+  ApplicationUUID: DC3B58E4-4F3C-11DC-9ECB-E5DB6F105773
+  ConfigFileVersion: 2
+  Database: 
+    CheckSchema: 1
+    Database: testapp_plugin_singlepage
+    Driver: SQLite
+    Host: localhost
+    Password: ''
+    RecordBaseClass: Jifty::DBI::Record::Cachable
+    RecordUUIDs: active
+    User: ''
+    Version: 0.0.1
+  DevelMode: 1
+  L10N: 
+    PoDir: share/po
+  LogLevel: DEBUG
+  Mailer: Sendmail
+  MailerArgs: []
+
+  Plugins: 
+    - LetMe: {}
+    - SkeletonApp: {}
+    - REST: {}
+    - Halo: {}
+    - ErrorTemplates: {}
+    - OnlineDocs: {}
+    - CompressedCSSandJS: {}
+    - AdminUI: {}
+    - User: {}
+    - CompressedCSSandJS: {}
+
+  PubSub: 
+    Backend: Memcached
+    Enable: ~
+  SkipAccessControl: 0
+  TemplateClass: TestApp::Plugin::CompressedCSSandJS::View
+  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/js-refactor/t/TestApp-Plugin-CompressedCSSandJS/t/css.t
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-CompressedCSSandJS/t/css.t	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,20 @@
+use strict;
+use warnings;
+use lib 't/lib';
+use Jifty::SubTest;
+use Jifty::Test tests => 7;
+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/static/css/main.css","Got main.css");
+$mech->content_contains('@import "combobox.css"');
+$mech->get_ok("$URL");
+ok($mech->content =~ m{<link rel="stylesheet" type="text/css" href="/__jifty/css/(.*)" /});
+my $css_file = $1;
+
+$mech->get_ok("$URL/__jifty/css/$css_file");
+$mech->content_contains('End of combobox.css', 'squished');
+

Added: jifty/branches/js-refactor/t/TestApp-Plugin-JQuery/Makefile.PL
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-JQuery/Makefile.PL	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,7 @@
+use inc::Module::Install;
+
+name        'TestApp::Plugin::JQuery';
+version     '0.01';
+requires    'Jifty' => '0.70129';
+
+WriteAll;

Added: jifty/branches/js-refactor/t/TestApp-Plugin-JQuery/bin/jifty
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-JQuery/bin/jifty	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,11 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+use File::Basename qw(dirname); 
+use UNIVERSAL::require;
+
+use Jifty;
+use Jifty::Script;
+
+local $SIG{INT} = sub { warn "Stopped\n"; exit; };
+Jifty::Script->dispatch();

Added: jifty/branches/js-refactor/t/TestApp-Plugin-JQuery/etc/config.yml
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-JQuery/etc/config.yml	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,54 @@
+--- 
+framework: 
+  AdminMode: 1
+  ApplicationClass: TestApp::Plugin::JQuery
+  ApplicationName: TestApp::Plugin::JQuery
+  ApplicationUUID: DC3B58E4-4F3C-11DC-9ECB-E5DB6F105772
+  ConfigFileVersion: 2
+  Database: 
+    CheckSchema: 1
+    Database: testapp_plugin_jquery
+    Driver: SQLite
+    Host: localhost
+    Password: ''
+    RecordBaseClass: Jifty::DBI::Record::Cachable
+    RecordUUIDs: active
+    User: ''
+    Version: 0.0.1
+  DevelMode: 1
+  L10N: 
+    PoDir: share/po
+  LogLevel: INFO
+  Mailer: Sendmail
+  MailerArgs: []
+
+  Plugins: 
+    - LetMe: {}
+    - SkeletonApp: {}
+    - REST: {}
+    - Halo: {}
+    - ErrorTemplates: {}
+    - OnlineDocs: {}
+    - CompressedCSSandJS: {}
+    - AdminUI: {}
+    - JQuery: {}
+
+  PubSub: 
+    Backend: Memcached
+    Enable: ~
+  SkipAccessControl: 0
+  TemplateClass: TestApp::Plugin::JQuery::View
+  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/js-refactor/t/TestApp-Plugin-JQuery/t/jquery.t
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-JQuery/t/jquery.t	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,28 @@
+use strict;
+use warnings;
+
+use lib 't/lib';
+use Jifty::SubTest;
+use Jifty::Test tests => 9;
+use Jifty::Test::WWW::Mechanize;
+
+my $server = Jifty::Test->make_server;
+ok($server, 'got a server');
+
+my $url = $server->started_ok;
+
+my $mech = Jifty::Test::WWW::Mechanize->new;
+
+$mech->get_ok($url);
+my ($script) = $mech->content =~ m{<script type="text/javascript" src="([^"]+)"></script>};
+ok($script, "Found the script tag.");
+
+$mech->get_ok($url . $script, "Fetched $url$script");
+$mech->content_like(qr/^ \* jQuery (?:[\d\.]+) - New Wave Javascript/m,
+    "Found the start of the jQuery script");
+$mech->content_like(qr/^var jQuery = function\(/m, 
+    "Found the main jQuery declaration");
+$mech->content_like(qr/^ \* noConflict.js/m, 
+    "Found the start of the noConflict script");
+$mech->content_like(qr/^jQuery\.noConflict\(\);/m, 
+    "Found the call to noConflict()");

Added: jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/Makefile.PL
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/Makefile.PL	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,7 @@
+use inc::Module::Install;
+
+name        'TestApp::Plugin::OAuth';
+version     '0.01';
+requires    'Jifty' => '0.70824';
+
+WriteAll;

Added: jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/bin/jifty
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/bin/jifty	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,11 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+use File::Basename qw(dirname); 
+use UNIVERSAL::require;
+
+use Jifty;
+use Jifty::Script;
+
+local $SIG{INT} = sub { warn "Stopped\n"; exit; };
+Jifty::Script->dispatch();

Added: jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/etc/config.yml
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/etc/config.yml	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,51 @@
+--- 
+framework: 
+  AdminMode: 1
+  ApplicationClass: TestApp::Plugin::OAuth
+  ApplicationName: TestApp-Plugin-OAuth
+  ApplicationUUID: B5461398-7DC0-11DC-83A6-036B06D64C5E
+  ConfigFileVersion: 2
+  Database: 
+    CheckSchema: 1
+    Database: testapp_plugin_oauth
+    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: 
+    - REST: {}
+    - Halo: {}
+    - CompressedCSSandJS: {}
+    - AdminUI: {}
+    - OAuth: {}
+    - Authentication::Password: {}
+    - SkeletonApp: {}
+
+  PubSub: 
+    Backend: Memcached
+    Enable: ~
+  SkipAccessControl: 0
+  TemplateClass: TestApp::Plugin::OAuth::View
+  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/js-refactor/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Dispatcher.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Dispatcher.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,26 @@
+package TestApp::Plugin::OAuth::Dispatcher;
+use strict;
+use warnings;
+use Jifty::Dispatcher -base;
+
+my @login_required = qw{
+    oauth/authorize
+    nuke/?
+};
+
+my $login_required = join '|', map {"^$_"} @login_required;
+$login_required = qr/$login_required/;
+
+before '**' => run {
+    if (Jifty->web->current_user->id) {
+        my $top = Jifty->web->navigation;
+        $top->child( _('Pick!')    => url => '/pick' );
+        $top->child( _('Choices')  => url => '/choices' );
+    }
+    elsif ($1 =~ $login_required) {
+        tangent '/login';
+    }
+};
+
+1;
+

Added: jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Model/User.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Model/User.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,18 @@
+use strict;
+use warnings;
+
+package TestApp::Plugin::OAuth::Model::User;
+use Jifty::DBI::Schema;
+
+use TestApp::Plugin::OAuth::Record schema {
+    column 'tasty' =>
+        type is 'boolean',
+        default is 'f';
+
+};
+
+use Jifty::Plugin::User::Mixin::Model::User;
+use Jifty::Plugin::Authentication::Password::Mixin::Model::User;
+
+1;
+

Added: jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Test.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/Test.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,285 @@
+#!/usr/bin/env perl
+package TestApp::Plugin::OAuth::Test;
+use strict;
+use warnings;
+use base qw/Jifty::Test/;
+
+use MIME::Base64;
+use Crypt::OpenSSL::RSA;
+use Digest::HMAC_SHA1 'hmac_sha1';
+use Jifty::Test::WWW::Mechanize;
+
+our @EXPORT = qw($timestamp $url $umech $cmech $pubkey $seckey $token_obj
+                 $server $URL response_is sign get_latest_token allow_ok deny_ok
+                 _authorize_request_token get_request_token get_authorized_token
+                 get_access_token);
+
+our $timestamp = 0;
+our $url;
+our $umech;
+our $cmech;
+our $pubkey = slurp('t/id_rsa.pub');
+our $seckey = slurp('t/id_rsa');
+our $token_obj;
+our $server;
+our $URL;
+
+sub setup {
+    my $class = shift;
+    $class->SUPER::setup;
+    $class->export_to_level(1);
+
+    $server  = Jifty::Test->make_server;
+    $URL     = $server->started_ok;
+    $umech   = Jifty::Test::WWW::Mechanize->new();
+    $cmech   = Jifty::Test::WWW::Mechanize->new();
+    $url     = $URL . '/oauth/request_token';
+}
+
+sub response_is {
+    ++$timestamp;
+
+    my %params = (
+        oauth_timestamp        => $timestamp,
+        oauth_nonce            => scalar(reverse $timestamp),
+        oauth_signature_method => 'HMAC-SHA1',
+        oauth_version          => '1.0',
+
+        code                   => 400,
+        testname               => "",
+        method                 => 'POST',
+        token_secret           => '',
+        @_,
+    );
+
+    local $url = $URL . delete $params{url}
+        if $params{url};
+
+    for (grep {!defined $params{$_}} keys %params) {
+        delete $params{$_};
+    }
+
+    my $code            = delete $params{code};
+    my $testname        = delete $params{testname} || "Response was $code";
+    my $method          = delete $params{method};
+    my $token_secret    = delete $params{token_secret};
+    my $consumer_secret = delete $params{consumer_secret}
+        or die "consumer_secret not passed to response_is!";
+
+    if ($url !~ /request_token/) {
+        $token_secret ||= $token_obj->secret;
+        $params{oauth_token} ||= $token_obj->token;
+    }
+
+    $params{oauth_signature} ||= sign($method, $token_secret, $consumer_secret, %params);
+
+    my $r;
+
+    if ($method eq 'POST') {
+        $r = $cmech->post($url, [%params]);
+    }
+    else {
+        my $query = join '&',
+                    map { "$_=" . Jifty->web->escape_uri($params{$_}||'') }
+                    keys %params;
+        $r = $cmech->get("$url?$query");
+    }
+
+    local $Test::Builder::Level = $Test::Builder::Level + 1;
+    main::is($r->code, $code, $testname);
+
+    if ($url =~ /oauth/) {
+        undef $token_obj;
+        get_latest_token();
+        if ($code == 200) {
+            main::ok($token_obj, "Successfully loaded a token object with token ".$token_obj->token.".");
+        }
+        else {
+            main::ok(!$token_obj, "Did not get a token");
+        }
+    }
+
+    return $cmech->content;
+}
+
+sub sign {
+    my ($method, $token_secret, $consumer_secret, %params) = @_;
+
+    local $url = delete $params{sign_url} || $url;
+
+    my $key = delete $params{signature_key};
+    my $sig_method = $params{oauth_signature_method} || delete $params{_signature_method};
+
+    delete $params{oauth_signature};
+
+    if ($sig_method eq 'PLAINTEXT') {
+        my $signature = join '&',
+                        map { Jifty->web->escape_uri($_||'') }
+                            $consumer_secret,
+                            $token_secret;
+        return $signature;
+    }
+
+    my $normalized_request_parameters
+        = join '&',
+          map { "$_=" . Jifty->web->escape_uri($params{$_}||'') }
+          sort keys %params;
+
+    my $signature_base_string
+        = join '&',
+          map { Jifty->web->escape_uri($_||'') }
+              uc($method),
+              $url,
+              $normalized_request_parameters,
+              $consumer_secret,
+              $token_secret;
+
+    my $signature;
+
+    if ($sig_method eq 'RSA-SHA1') {
+        my $pubkey = Crypt::OpenSSL::RSA->new_private_key($key);
+        $signature = encode_base64($pubkey->sign($signature_base_string), "");
+    }
+    elsif ($sig_method eq 'HMAC-SHA1') {
+        my $key = join '&',
+          map { Jifty->web->escape_uri($_||'') }
+              $consumer_secret,
+              $token_secret;
+        my $hmac = Digest::HMAC_SHA1->new($key);
+        $hmac->add($signature_base_string);
+        $signature = $hmac->b64digest;
+    }
+
+    return ($signature, $signature_base_string, $normalized_request_parameters)
+        if wantarray;
+    return $signature;
+
+}
+
+sub slurp {
+    no warnings 'once';
+    my $file = shift;
+    local $/;
+    local @ARGV = $file;
+    my $contents = scalar <>
+        or die "Unable to slurp $file";
+    return $contents;
+}
+
+sub get_latest_token {
+    my $content = $cmech->content;
+
+    $content =~ s/\boauth_token=(\w+)//
+        or return;
+    my $token = $1;
+
+    $content =~ s/\boauth_token_secret=(\w+)//
+        or return;
+    my $secret = $1;
+
+    local $Test::Builder::Level = $Test::Builder::Level + 1;
+    main::is($content, '&', "the output was exactly oauth_token=...&oauth_secret=...");
+
+    my $package = 'Jifty::Plugin::OAuth::Model::';
+
+    if ($cmech->uri =~ /request_token/) {
+        $package .= 'RequestToken';
+    }
+    elsif ($cmech->uri =~ /access_token/) {
+        $package .= 'AccessToken';
+    }
+    else {
+        Jifty->log->error("Called get_latest_token, but I cannot grok the URI " . $cmech->uri);
+        return;
+    }
+
+    $token_obj = $package->new(current_user => Jifty::CurrentUser->superuser);
+    $token_obj->load_by_cols(token => $token);
+
+    if (!$token_obj->id) {
+        Jifty->log->error("Could not find a $package with token $token");
+        return;
+    }
+
+    return $token_obj;
+}
+
+sub allow_ok {
+    local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+    my $error = _authorize_request_token('Allow');
+    ok(0, $error), return if $error;
+
+    my $name = $token_obj->consumer->name;
+    $umech->content_contains("Allowing $name to access your stuff");
+}
+
+sub deny_ok {
+    local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+    my $error = _authorize_request_token('Deny');
+    ok(0, $error), return if $error;
+
+    my $name = $token_obj->consumer->name;
+    $umech->content_contains("Denying $name the right to access your stuff");
+}
+
+sub _authorize_request_token {
+    local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+    my $which_button = shift
+        or die "You did not specify a button to click to _authorize_request_token";
+
+    my $token = shift || $token_obj->token;
+    $token = $token->token if ref $token;
+
+    $umech->get('/oauth/authorize')
+        or return "Unable to navigate to /oauth/authorize";;
+    $umech->content =~ /If you trust this application/
+        or return "Content did not much qr/If you trust this application/";
+    my $moniker = $umech->moniker_for('TestApp::Plugin::OAuth::Action::AuthorizeRequestToken')
+        or return "Unable to find moniker for AuthorizeRequestToken";
+    $umech->fill_in_action($moniker, token => $token)
+        or return "Unable to fill in the AuthorizeRequestToken action";
+    $umech->click_button(value => $which_button)
+        or return "Unable to click $which_button button";
+    return;
+}
+
+sub get_request_token {
+    local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+    response_is(
+        url                    => '/oauth/request_token',
+        code                   => 200,
+        testname               => "200 - plaintext signature",
+        consumer_secret        => 'bar',
+        oauth_consumer_key     => 'foo',
+        oauth_signature_method => 'PLAINTEXT',
+        @_,
+    );
+    return $token_obj;
+}
+
+sub get_authorized_token {
+    local $Test::Builder::Level = $Test::Builder::Level + 1;
+    get_request_token(@_);
+    allow_ok();
+    return $token_obj;
+}
+
+sub get_access_token {
+    local $Test::Builder::Level = $Test::Builder::Level + 1;
+    get_authorized_token();
+    response_is(
+        url                    => '/oauth/access_token',
+        code                   => 200,
+        testname               => "200 - plaintext signature",
+        consumer_secret        => 'bar',
+        oauth_consumer_key     => 'foo',
+        oauth_signature_method => 'PLAINTEXT',
+    );
+}
+
+1;
+

Added: jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/View.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/lib/TestApp/Plugin/OAuth/View.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,12 @@
+#!/usr/bin/env perl
+package TestApp::Plugin::OAuth::View;
+use strict;
+use warnings;
+use Jifty::View::Declare -base;
+
+template '/nuke/the/whales' => page {
+    h1 { "Press the shiny red button." }
+};
+
+1;
+

Added: jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/t/00-test-setup.t
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/t/00-test-setup.t	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,69 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+
+use Test::More;
+BEGIN {
+    if (eval { require Net::OAuth::Request; require Crypt::OpenSSL::RSA; 1 }) {
+        plan tests => 10;
+    }
+    else {
+        plan skip_all => "Net::OAuth isn't installed";
+    }
+}
+
+use lib 't/lib';
+use Jifty::SubTest;
+use TestApp::Plugin::OAuth::Test;
+
+# sign PLAINTEXT {{{
+is(sign('POST', 'jjd999tj88uiths3', 'djr9rjt0jd78jf88',
+        oauth_signature_method => 'PLAINTEXT'),
+    'djr9rjt0jd78jf88&jjd999tj88uiths3', 'PLAINTEXT example 1 works');
+is(sign('POST', 'jjd99$tj88uiths3', 'djr9rjt0jd78jf88',
+        oauth_signature_method => 'PLAINTEXT'),
+    'djr9rjt0jd78jf88&jjd99%24tj88uiths3', 'PLAINTEXT example 2 works');
+is(sign('POST', undef, 'djr9rjt0jd78jf88',
+        oauth_signature_method => 'PLAINTEXT'),
+    'djr9rjt0jd78jf88&', 'PLAINTEXT example 2 works');
+# }}}
+# sign HMAC-SHA1 {{{
+my ($sig, $sbs, $nrp) = sign(
+    'GET',
+    'pfkkdhi9sl3r4s00',
+    'kd94hf93k423kf44',
+    sign_url => 'http://photos.example.net/photos',
+    oauth_consumer_key => 'dpf43f3p2l4k3l03',
+    oauth_signature_method => 'HMAC-SHA1',
+    oauth_timestamp => '1191242096',
+    oauth_nonce => 'kllo9940pd9333jh',
+    oauth_token => 'nnch734d00sl2jdk',
+    file => 'vacation.jpg',
+    size => 'original',
+    oauth_version => '1.0');
+
+is($nrp, 'file=vacation.jpg&oauth_consumer_key=dpf43f3p2l4k3l03&oauth_nonce=kllo9940pd9333jh&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1191242096&oauth_token=nnch734d00sl2jdk&oauth_version=1.0&size=original', 'HMAC-SHA1 normalized request paramaters correct');
+is($sbs, 'GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26size%3Doriginal&kd94hf93k423kf44&pfkkdhi9sl3r4s00', 'HMAC-SHA1 signature-base-string correct');
+is($sig, 'Gcg/323lvAsQ707p+y41y14qWfY', 'HMAC-SHA1 signature correct');
+# }}}
+# sign RSA-SHA1 {{{
+($sig, $sbs, $nrp) = sign(
+    'GET',
+    'pfkkdhi9sl3r4s00',
+    'kd94hf93k423kf44',
+    sign_url => 'http://photos.example.net/photos',
+    signature_key => $seckey,
+    oauth_consumer_key => 'dpf43f3p2l4k3l03',
+    oauth_signature_method => 'RSA-SHA1',
+    oauth_timestamp => '1191242096',
+    oauth_nonce => 'kllo9940pd9333jh',
+    oauth_token => 'nnch734d00sl2jdk',
+    file => 'vacation.jpg',
+    size => 'original',
+    oauth_version => '1.0');
+
+is($nrp, 'file=vacation.jpg&oauth_consumer_key=dpf43f3p2l4k3l03&oauth_nonce=kllo9940pd9333jh&oauth_signature_method=RSA-SHA1&oauth_timestamp=1191242096&oauth_token=nnch734d00sl2jdk&oauth_version=1.0&size=original', 'RSA-SHA1 normalized request paramaters correct');
+is($sbs, 'GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26oauth_signature_method%3DRSA-SHA1%26oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26size%3Doriginal&kd94hf93k423kf44&pfkkdhi9sl3r4s00', 'RSA-SHA1 signature-base-string correct');
+is($sig, 'oSjbUzMjD4E+LeHMaYzYx1KyULDwuR6V9oeNgTLoO9m90iJh4d01J/8SzvHKT8N0y2vs1o8s72z19Eicj6l+mEmH5Rp0cwWOE9UdvC+JdFSIA1bmlwVPCFL7jDQqRSBJsXEiT44T5j9P+Dh5Z5WUjEgCExQyNP38Z3nMnYYOCRM=', 'RSA-SHA1 signature correct');
+# }}}
+

Added: jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/t/01-basic.t
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/t/01-basic.t	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,36 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+
+use Test::More;
+BEGIN {
+    if (eval { require Net::OAuth::Request; require Crypt::OpenSSL::RSA; 1 }) {
+        plan tests => 9;
+    }
+    else {
+        plan skip_all => "Net::OAuth isn't installed";
+    }
+}
+
+use lib 't/lib';
+use Jifty::SubTest;
+
+use Jifty::Test;
+
+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 . '/oauth');
+$mech->content_like(qr{/oauth/request_token}, "oauth page mentions request_token URL");
+$mech->content_like(qr{/oauth/authorize}, "oauth page mentions authorize URL");
+$mech->content_like(qr{/oauth/access_token}, "oauth page mentions access_token URL");
+
+$mech->content_like(qr{http://oauth\.net/}, "oauth page mentions OAuth homepage");
+
+$mech->get_ok($URL . '/oauth/authorize');
+$mech->content_unlike(qr{If you trust this application}, "/oauth/authorize requires being logged in");
+

Added: jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/t/02-request-token.t
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/t/02-request-token.t	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,278 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+
+use Test::More;
+BEGIN {
+    if (eval { require Net::OAuth::Request; require Crypt::OpenSSL::RSA; 1 }) {
+        plan tests => 58;
+    }
+    else {
+        plan skip_all => "Net::OAuth isn't installed";
+    }
+}
+
+use lib 't/lib';
+use Jifty::SubTest;
+
+use TestApp::Plugin::OAuth::Test;
+
+# create some consumers {{{
+my $consumer = Jifty::Plugin::OAuth::Model::Consumer->new(current_user => Jifty::CurrentUser->superuser);
+my ($ok, $msg) = $consumer->create(
+    consumer_key => 'foo',
+    secret       => 'bar',
+    name         => 'FooBar industries',
+    url          => 'http://foo.bar.example.com',
+    rsa_key      => $pubkey,
+);
+ok($ok, $msg);
+
+my $rsaless = Jifty::Plugin::OAuth::Model::Consumer->new(current_user => Jifty::CurrentUser->superuser);
+($ok, $msg) = $rsaless->create(
+    consumer_key => 'foo2',
+    secret       => 'bar2',
+    name         => 'Backwater.org',
+    url          => 'http://backwater.org',
+);
+ok($ok, $msg);
+# }}}
+
+# success modes
+
+# get a request token as a known consumer (PLAINTEXT) {{{
+response_is(
+    code                   => 200,
+    testname               => "200 - plaintext signature",
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+    oauth_signature_method => 'PLAINTEXT',
+);
+# }}}
+# get a request token as a known consumer (HMAC-SHA1) {{{
+$timestamp = 100; # set timestamp to test different consumers' timestamps
+response_is(
+    code                   => 200,
+    testname               => "200 - HMAC-SHA1 signature",
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+    oauth_signature_method => 'HMAC-SHA1',
+);
+# }}}
+# get a request token as a known consumer (RSA-SHA1) {{{
+response_is(
+    code                   => 200,
+    testname               => "200 - RSA-SHA1 signature",
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+    signature_key          => $seckey,
+    oauth_signature_method => 'RSA-SHA1',
+);
+# }}}
+# same timestamp, different nonce {{{
+--$timestamp;
+response_is(
+    code                   => 200,
+    testname               => "200 - RSA-SHA1 signature",
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+    oauth_nonce            => 'kjfh',
+    signature_key          => $seckey,
+    oauth_signature_method => 'RSA-SHA1',
+);
+# }}}
+# same nonce, different timestamp {{{
+response_is(
+    code                   => 200,
+    testname               => "200 - RSA-SHA1 signature",
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+    oauth_nonce            => 'kjfh',
+    signature_key          => $seckey,
+    oauth_signature_method => 'RSA-SHA1',
+);
+# }}}}
+
+# get a request token as an RSA-less consumer (PLAINTEXT) {{{
+
+# consumer 1 has a timestamp of about 101 now. if this gives a timestamp error,
+# then timestamps must be globally increasing, which is wrong. they must only
+# be increasing per consumer
+$timestamp = 50;
+
+response_is(
+    code                   => 200,
+    testname               => "200 - plaintext signature",
+    consumer_secret        => 'bar2',
+    oauth_consumer_key     => 'foo2',
+    oauth_signature_method => 'PLAINTEXT',
+);
+# }}}
+# get a request token as an RSA-less consumer (HMAC-SHA1) {{{
+response_is(
+    code                   => 200,
+    testname               => "200 - HMAC-SHA1 signature",
+    consumer_secret        => 'bar2',
+    oauth_consumer_key     => 'foo2',
+    oauth_signature_method => 'HMAC-SHA1',
+);
+# }}}
+
+# failure modes
+
+# request a request token as an RSA-less consumer (RSA-SHA1) {{{
+response_is(
+    code                   => 400,
+    testname               => "400 - RSA-SHA1 signature, without registering RSA key!",
+    consumer_secret        => 'bar2',
+    oauth_consumer_key     => 'foo2',
+    signature_key          => $seckey,
+    oauth_signature_method => 'RSA-SHA1',
+);
+# }}}
+# unknown consumer {{{
+# we're back to the first consumer, so we need a locally larger timestamp
+$timestamp = 200;
+response_is(
+    code                   => 401,
+    testname               => "401 - unknown consumer",
+    consumer_secret        => 'zzz',
+    oauth_consumer_key     => 'whoami',
+);
+# }}}
+# wrong consumer secret {{{
+response_is (
+    code                   => 401,
+    testname               => "401 - wrong consumer secret",
+    consumer_secret        => 'not bar!',
+    oauth_consumer_key     => 'foo',
+);
+# }}}
+# wrong signature {{{
+response_is(
+    code                   => 401,
+    testname               => "401 - wrong signature",
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+    oauth_signature        => 'hello ^____^',
+);
+# }}}
+# unknown signature method {{{
+response_is(
+    code                   => 400,
+    testname               => "400 - unknown signature method",
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+    oauth_signature_method => 'Peaches. Peaches FOR YOU',
+);
+# }}}
+# missing parameters {{{
+# oauth_consumer_key {{{
+response_is(
+    code                   => 400,
+    testname               => "400 - missing parameter oauth_consumer_key",
+    consumer_secret        => 'bar',
+    oauth_signature_method => 'PLAINTEXT',
+);
+# }}}
+# oauth_nonce {{{
+response_is(
+    code                   => 400,
+    testname               => "400 - missing parameter oauth_nonce",
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+    oauth_nonce            => undef,
+    oauth_signature_method => 'PLAINTEXT',
+);
+# }}}
+# oauth_timestamp {{{
+response_is(
+    code                   => 400,
+    testname               => "400 - missing parameter oauth_timestamp",
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+    oauth_timestamp        => undef,
+    oauth_signature_method => 'PLAINTEXT',
+);
+# }}}
+# oauth_signature_method {{{
+response_is(
+    code                   => 400,
+    testname               => "400 - missing parameter oauth_signature_method",
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+    oauth_signature_method => undef,
+    _signature_method       => 'PLAINTEXT', # so we get a real signature
+);
+# }}}
+# }}}
+# unsupported parameter {{{
+response_is(
+    code                   => 400,
+    testname               => "400 - unsupported parameter oauth_candy",
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+    oauth_signature_method => 'PLAINTEXT',
+    oauth_candy            => 'yummy',
+);
+# }}}
+# invalid timestamp (noninteger) {{{
+response_is(
+    code                   => 400,
+    testname               => "400 - malformed timestamp (noninteger)",
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+    oauth_signature_method => 'PLAINTEXT',
+    oauth_timestamp        => 'half past nine',
+);
+# }}}
+# invalid timestamp (smaller than previous request) {{{
+$timestamp = 1000;
+# first make a good request with a large timestamp {{{
+response_is(
+    code                   => 200,
+    testname               => "200 - setting up a future test",
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+);
+# }}}
+$timestamp = 500;
+# then a new request with a smaller timestamp {{{
+response_is(
+    code                   => 401,
+    testname               => "401 - timestamp smaller than a previous timestamp",
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+    oauth_signature_method => 'PLAINTEXT',
+);
+# }}}
+$timestamp = 2000;
+# }}}
+# GET not POST {{{
+response_is(
+    code                   => 404,
+    testname               => "404 - GET not supported for request_token",
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+    oauth_signature_method => 'PLAINTEXT',
+    method                 => 'GET',
+);
+# }}}
+
+# duplicate timestamp and nonce {{{
+response_is(
+    code                   => 200,
+    testname               => "200 - plaintext signature",
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+    oauth_signature_method => 'PLAINTEXT',
+);
+--$timestamp;
+response_is(
+    code                   => 401,
+    testname               => "401 - duplicate timestamp and nonce",
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+    oauth_signature_method => 'PLAINTEXT',
+);
+# }}}

Added: jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/t/03-authorize.t
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/t/03-authorize.t	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,225 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+
+use Test::More;
+BEGIN {
+    if (eval { require Net::OAuth::Request; require Crypt::OpenSSL::RSA; 1 }) {
+        plan tests => 85;
+    }
+    else {
+        plan skip_all => "Net::OAuth isn't installed";
+    }
+}
+
+use lib 't/lib';
+use Jifty::SubTest;
+
+use TestApp::Plugin::OAuth::Test;
+
+use Jifty::Test::WWW::Mechanize;
+
+# create some consumers {{{
+my $consumer = Jifty::Plugin::OAuth::Model::Consumer->new(current_user => Jifty::CurrentUser->superuser);
+my ($ok, $msg) = $consumer->create(
+    consumer_key => 'foo',
+    secret       => 'bar',
+    name         => 'FooBar Industries',
+    url          => 'http://foo.bar.example.com',
+    rsa_key      => $pubkey,
+);
+ok($ok, $msg);
+
+my $rsaless = Jifty::Plugin::OAuth::Model::Consumer->new(current_user => Jifty::CurrentUser->superuser);
+($ok, $msg) = $rsaless->create(
+    consumer_key => 'foo2',
+    secret       => 'bar2',
+    name         => 'Backwater.org',
+    url          => 'http://backwater.org',
+);
+ok($ok, $msg);
+# }}}
+
+# try to navigate to protected pages while not logged in {{{
+$umech->get_ok($URL . '/oauth/authorize');
+$umech->content_unlike(qr/If you trust this application/);
+
+$umech->get_ok('/oauth/authorized');
+$umech->content_unlike(qr/If you trust this application/);
+
+$umech->get_ok('/nuke/the/whales');
+$umech->content_unlike(qr/Press the shiny red button/);
+# }}}
+# log in {{{
+my $u = TestApp::Plugin::OAuth::Model::User->new(current_user => TestApp::Plugin::OAuth::CurrentUser->superuser);
+$u->create( name => 'You Zer', email => 'youzer at example.com', password => 'secret', email_confirmed => 1);
+ok($u->id, "New user has valid id set");
+
+$umech->get_ok('/login');
+$umech->fill_in_action_ok($umech->moniker_for('TestApp::Plugin::OAuth::Action::Login'), email => 'youzer at example.com', password => 'secret');
+$umech->submit;
+$umech->content_contains('Logout');
+# }}}
+# try to navigate to protected pages while logged in {{{
+$umech->get_ok('/oauth/authorize');
+$umech->content_like(qr/If you trust this application/);
+
+$umech->get_ok('/oauth/authorized');
+$umech->content_like(qr/If you trust this application/);
+
+$umech->get_ok('/nuke/the/whales');
+$umech->content_like(qr/Press the shiny red button/);
+# }}}
+# deny an unknown access token {{{
+my $error = _authorize_request_token('Deny', 'deadbeef');
+if ($error) {
+    ok(0, $error);
+}
+else {
+    $umech->content_contains("I don't know of that request token.");
+}
+# }}}
+# allow an unknown access token {{{
+$error = _authorize_request_token('Allow', 'hamburger');
+if ($error) {
+    ok(0, $error);
+}
+else {
+    $umech->content_contains("I don't know of that request token.");
+}
+# }}}
+# deny request token {{{
+get_request_token();
+deny_ok();
+# }}}
+# try to use the denied request token {{{
+$error = _authorize_request_token('Deny');
+if ($error) {
+    ok(0, $error);
+}
+else {
+    $umech->content_contains("I don't know of that request token.");
+}
+# }}}
+# allow request token {{{
+get_request_token();
+allow_ok();
+# }}}
+# try to allow again {{{
+$error = _authorize_request_token('Allow');
+if ($error) {
+    ok(0, $error);
+}
+else {
+    $umech->content_contains("I don't know of that request token.");
+}
+# }}}
+# expire a token, try to allow it {{{
+get_request_token();
+
+my $late = Jifty::DateTime->now(time_zone => 'GMT')->subtract(minutes => 10);
+$token_obj->set_valid_until($late);
+
+$error = _authorize_request_token('Allow');
+if ($error) {
+    ok(0, $error);
+}
+else {
+    $umech->content_contains("This request token has expired.");
+}
+# }}}
+# try again, it should be deleted {{{
+$error = _authorize_request_token('Allow');
+if ($error) {
+    ok(0, $error);
+}
+else {
+    $umech->content_contains("I don't know of that request token.");
+}
+# }}}
+
+# deny token with a request parameter {{{
+get_request_token();
+$umech->get_ok('/oauth/authorize?oauth_token=' . $token_obj->token);
+$umech->content_like(qr/If you trust this application/);
+$umech->content_unlike(qr/should have provided it/, "token hint doesn't show up if we already have it");
+
+$umech->form_number(1);
+$umech->click_button(value => 'Deny');
+
+$umech->content_contains("Denying FooBar Industries the right to access your stuff");
+$umech->content_contains("click here");
+$umech->content_contains("http://foo.bar.example.com?oauth_token=" . $token_obj->token);
+$umech->content_contains("To return to");
+$umech->content_contains("FooBar Industries");
+# }}}
+# allow token with a request parameter {{{
+get_request_token();
+$umech->get_ok('/oauth/authorize?oauth_token=' . $token_obj->token);
+$umech->content_like(qr/If you trust this application/);
+$umech->content_unlike(qr/should have provided it/, "token hint doesn't show up if we already have it");
+
+$umech->form_number(1);
+$umech->click_button(value => 'Allow');
+
+$umech->content_contains("Allowing FooBar Industries to access your stuff");
+$umech->content_contains("click here");
+$umech->content_contains("http://foo.bar.example.com?oauth_token=" . $token_obj->token);
+$umech->content_contains("To return to");
+$umech->content_contains("FooBar Industries");
+# }}}
+# deny token with a callback {{{
+get_request_token();
+$umech->get_ok('/oauth/authorize?oauth_callback=http%3A%2f%2fgoogle.com');
+$umech->content_like(qr/If you trust this application/);
+
+$umech->fill_in_action_ok($umech->moniker_for('TestApp::Plugin::OAuth::Action::AuthorizeRequestToken'), token => $token_obj->token);
+$umech->click_button(value => 'Deny');
+
+$umech->content_contains("Denying FooBar Industries the right to access your stuff");
+$umech->content_contains("click here");
+$umech->content_contains("http://google.com?oauth_token=" . $token_obj->token);
+$umech->content_contains("To return to");
+$umech->content_contains("FooBar Industries");
+# }}}
+# deny it with a callback + request params {{{
+get_request_token();
+$umech->get_ok('/oauth/authorize?oauth_token='.$token_obj->token.'&oauth_callback=http%3A%2F%2Fgoogle.com%2F%3Ffoo%3Dbar');
+$umech->content_like(qr/If you trust this application/);
+$umech->content_unlike(qr/should have provided it/, "token hint doesn't show up if we already have it");
+
+$umech->form_number(1);
+$umech->click_button(value => 'Deny');
+
+$umech->content_contains("Denying FooBar Industries the right to access your stuff");
+$umech->content_contains("click here");
+my $token = $token_obj->token;
+$umech->content_like(qr{http://google\.com/\?foo=bar&(?:amp;|#38;)?oauth_token=$token});
+$umech->content_contains("To return to");
+$umech->content_contains("FooBar Industries");
+# }}}
+
+# authorizing a token refreshes its valid_until {{{
+get_request_token();
+my $in_ten = DateTime->now(time_zone => "GMT")->add(minutes => 10);
+$token_obj->set_valid_until($in_ten->clone);
+
+my $id = $token_obj->id;
+undef $token_obj;
+$token_obj = Jifty::Plugin::OAuth::Model::RequestToken->new(current_user => Jifty::CurrentUser->superuser);
+$token_obj->load($id);
+
+allow_ok();
+
+undef $token_obj;
+$token_obj = Jifty::Plugin::OAuth::Model::RequestToken->new(current_user => Jifty::CurrentUser->superuser);
+$token_obj->load($id);
+
+my $difference = $token_obj->valid_until - $in_ten;
+
+TODO: {
+    local $TODO = "some kind of caching issue, serverside it works fine";
+    ok($difference->minutes > 15, "valid for more than 15 minutes");
+}
+# }}}
+

Added: jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/t/04-access-token.t
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/t/04-access-token.t	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,186 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+
+use Test::More;
+BEGIN {
+    if (eval { require Net::OAuth::Request; require Crypt::OpenSSL::RSA; 1 }) {
+        plan tests => 70;
+    }
+    else {
+        plan skip_all => "Net::OAuth isn't installed";
+    }
+}
+
+use lib 't/lib';
+use Jifty::SubTest;
+
+use TestApp::Plugin::OAuth::Test;
+
+use Jifty::Test::WWW::Mechanize;
+
+# setup {{{
+# create two consumers {{{
+my $consumer = Jifty::Plugin::OAuth::Model::Consumer->new(current_user => Jifty::CurrentUser->superuser);
+my ($ok, $msg) = $consumer->create(
+    consumer_key => 'foo',
+    secret       => 'bar',
+    name         => 'FooBar Industries',
+    url          => 'http://foo.bar.example.com',
+    rsa_key      => $pubkey,
+);
+ok($ok, $msg);
+
+my $rsaless = Jifty::Plugin::OAuth::Model::Consumer->new(current_user => Jifty::CurrentUser->superuser);
+($ok, $msg) = $rsaless->create(
+    consumer_key => 'foo2',
+    secret       => 'bar2',
+    name         => 'Backwater.org',
+    url          => 'http://backwater.org',
+);
+ok($ok, $msg);
+# }}}
+# create user and log in {{{
+my $u = TestApp::Plugin::OAuth::Model::User->new(current_user => TestApp::Plugin::OAuth::CurrentUser->superuser);
+$u->create( name => 'You Zer', email => 'youzer at example.com', password => 'secret', email_confirmed => 1);
+ok($u->id, "New user has valid id set");
+
+$umech->get_ok($URL . '/login');
+$umech->fill_in_action_ok($umech->moniker_for('TestApp::Plugin::OAuth::Action::Login'), email => 'youzer at example.com', password => 'secret');
+$umech->submit;
+$umech->content_contains('Logout');
+# }}}
+# }}}
+# basic working access token {{{
+get_authorized_token();
+my $request_token = $token_obj->token;
+response_is(
+    url                    => '/oauth/access_token',
+    code                   => 200,
+    testname               => "200 - plaintext signature",
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+    oauth_signature_method => 'PLAINTEXT',
+);
+isnt($token_obj->token, $request_token, "different token for request and access");
+# }}}
+# try to get an access token from denied request token {{{
+get_request_token();
+deny_ok();
+response_is(
+    url                    => '/oauth/access_token',
+    code                   => 401,
+    testname               => "401 - denied token",
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+    oauth_signature_method => 'PLAINTEXT',
+);
+# }}}
+# try to get an access token as a different consumer {{{
+get_authorized_token();
+$request_token = $token_obj;
+response_is(
+    url                    => '/oauth/access_token',
+    code                   => 401,
+    testname               => "401 - denied token",
+    consumer_secret        => 'bar2',
+    oauth_consumer_key     => 'foo2',
+    oauth_signature_method => 'PLAINTEXT',
+);
+# }}}
+# get that same access token as the original consumer {{{
+$token_obj = $request_token;
+response_is(
+    url                    => '/oauth/access_token',
+    code                   => 200,
+    testname               => "200 - got token",
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+    oauth_signature_method => 'PLAINTEXT',
+);
+# }}}
+# same timestamp, different nonce {{{
+get_authorized_token();
+--$timestamp;
+response_is(
+    url                    => '/oauth/access_token',
+    code                   => 200,
+    testname               => "200 - plaintext signature",
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+    oauth_signature_method => 'PLAINTEXT',
+    oauth_nonce            => 'kjfh',
+);
+# }}}
+# different timestamp, same nonce {{{
+get_authorized_token();
+response_is(
+    url                    => '/oauth/access_token',
+    code                   => 200,
+    testname               => "200 - plaintext signature",
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+    oauth_signature_method => 'PLAINTEXT',
+    oauth_nonce            => 'kjfh',
+);
+# }}}
+# duplicate timestamp and nonce as previous access token {{{
+get_authorized_token();
+$timestamp -= 2;
+response_is(
+    url                    => '/oauth/access_token',
+    code                   => 401,
+    testname               => "401 - duplicate ts/nonce as previous access",
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+    oauth_signature_method => 'PLAINTEXT',
+);
+$timestamp += 100;
+# }}}
+# duplicate timestamp and nonce as request token {{{
+get_authorized_token();
+--$timestamp;
+response_is(
+    url                    => '/oauth/access_token',
+    code                   => 401,
+    testname               => "401 - duplicate ts/nonce for request token",
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+    oauth_signature_method => 'PLAINTEXT',
+);
+# }}}
+# same request token {{{
+$token_obj = $request_token;
+response_is(
+    url                    => '/oauth/access_token',
+    code                   => 401,
+    testname               => "401 - already used",
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+    oauth_signature_method => 'PLAINTEXT',
+);
+# }}}
+# expired request token {{{
+get_authorized_token();
+$token_obj->set_valid_until(DateTime->now(time_zone => "GMT")->subtract(days => 1));
+response_is(
+    url                    => '/oauth/access_token',
+    code                   => 401,
+    testname               => "401 - expired",
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+    oauth_signature_method => 'PLAINTEXT',
+);
+# }}}
+# wrong consumer secret {{{
+get_authorized_token();
+response_is(
+    url                    => '/oauth/access_token',
+    code                   => 401,
+    testname               => "401 - wrong secret",
+    consumer_secret        => 'bah!',
+    oauth_consumer_key     => 'foo',
+    oauth_signature_method => 'PLAINTEXT',
+);
+# }}}
+

Added: jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/t/05-protected-resource.t
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/t/05-protected-resource.t	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,66 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+
+use Test::More;
+BEGIN {
+    if (eval { require Net::OAuth::Request; require Crypt::OpenSSL::RSA; 1 }) {
+        plan tests => 16;
+    }
+    else {
+        plan skip_all => "Net::OAuth isn't installed";
+    }
+}
+
+use lib 't/lib';
+use Jifty::SubTest;
+
+use TestApp::Plugin::OAuth::Test;
+
+use Jifty::Test::WWW::Mechanize;
+
+# setup {{{
+# create two consumers {{{
+my $consumer = Jifty::Plugin::OAuth::Model::Consumer->new(current_user => Jifty::CurrentUser->superuser);
+my ($ok, $msg) = $consumer->create(
+    consumer_key => 'foo',
+    secret       => 'bar',
+    name         => 'FooBar Industries',
+    url          => 'http://foo.bar.example.com',
+    rsa_key      => $pubkey,
+);
+ok($ok, $msg);
+
+my $rsaless = Jifty::Plugin::OAuth::Model::Consumer->new(current_user => Jifty::CurrentUser->superuser);
+($ok, $msg) = $rsaless->create(
+    consumer_key => 'foo2',
+    secret       => 'bar2',
+    name         => 'Backwater.org',
+    url          => 'http://backwater.org',
+);
+ok($ok, $msg);
+# }}}
+# create user and log in {{{
+my $u = TestApp::Plugin::OAuth::Model::User->new(current_user => TestApp::Plugin::OAuth::CurrentUser->superuser);
+$u->create( name => 'You Zer', email => 'youzer at example.com', password => 'secret', email_confirmed => 1);
+ok($u->id, "New user has valid id set");
+
+$umech->get_ok($URL . '/login');
+$umech->fill_in_action_ok($umech->moniker_for('TestApp::Plugin::OAuth::Action::Login'), email => 'youzer at example.com', password => 'secret');
+$umech->submit;
+$umech->content_contains('Logout');
+# }}}
+# }}}
+# basic protected request {{{
+get_access_token();
+response_is(
+    url                    => '/nuke/the/whales',
+    code                   => 200,
+    testname               => "200 - protected resource request",
+    consumer_secret        => 'bar',
+    oauth_consumer_key     => 'foo',
+    oauth_signature_method => 'PLAINTEXT',
+);
+$cmech->content_contains("Press the shiny red button", "got to a protected page");
+# }}}
+

Added: jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/t/id_rsa
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/t/id_rsa	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQC1ekM402pEiZ6MyaG0RzDNrw0digCV0e45mCgaQs2F0q4v2O8C
+xjl9pbsuf2qz1jHKGdJIXuhaW1XRqCOE2ZHc/n/+s2s8TUIcBve3B2glKxJhgyV8
+nDpZkjOEctef8uFPU3Alfm382kj0THcXdgsQ+jreLJ1VCS5xNcU6VpXa4QIDAQAB
+AoGAOHsl4tDB2TTvuKekgURK5ykdLt1dk0N0Hk7B5HJ4HrdUaSXeNYHWMMnc+PrF
+DdWTR3BD5yxKqpyUmBz5eQZyA8vVKzEVmYCkA+EO6TQeo6xveH/9xaFbTtXpwtvS
+N9m3kwEfmfudJvQRFb3q79I+17/g8rWbZlDYK7CKyfVs17UCQQDxdMOz/Q7xpP+f
+sXTHxvhtw4FFvAZEOEQA1a+uHGSmz+Vq0SIOpwZwri4aFG1YVUS2FUGHuhSpsuUJ
+Pg3kY1N3AkEAwGiiObgemFQLvCVigP8YcZyt98a+vE2Joq3iJyd/4DnEqvN98WNm
+5zaSDEXAJzC1ZuqnMUFVbiYBt2W4InBqZwJAEpgyZg8L8pIJWYv5+VSaVyGiN/OV
+6/UFT6clI1xuZ+ZEvagjXkuAlHbld/6wuQfABeG3LTOoWbU8LC0KNtdrWwJAF0gR
+6R4IRbJVwSxc4PL9CDJHMqYPykUvlEmqBcbXyE/1JiJUaPL4Lp4Byg5ek99m888M
+7/7R0YQzzPc38qLbnQJBAMbs/L0td6AponlpHCLmhHd7dka6GNIdyaALLNSVefD+
++MLQ7dATQne1y5n08vswMX9QnNTxFnlK59gWk/0gow4=
+-----END RSA PRIVATE KEY-----

Added: jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/t/id_rsa.pub
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-OAuth/t/id_rsa.pub	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,5 @@
+-----BEGIN RSA PUBLIC KEY-----
+MIGJAoGBALV6QzjTakSJnozJobRHMM2vDR2KAJXR7jmYKBpCzYXSri/Y7wLGOX2l
+uy5/arPWMcoZ0khe6FpbVdGoI4TZkdz+f/6zazxNQhwG97cHaCUrEmGDJXycOlmS
+M4Ry15/y4U9TcCV+bfzaSPRMdxd2CxD6Ot4snVUJLnE1xTpWldrhAgMBAAE=
+-----END RSA PUBLIC KEY-----

Added: jifty/branches/js-refactor/t/TestApp-Plugin-OnClick/Makefile.PL
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-OnClick/Makefile.PL	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,7 @@
+use inc::Module::Install;
+
+name        'TestApp::Plugin::OnClick';
+version     '0.01';
+requires    'Jifty' => '0.70824';
+
+WriteAll;

Added: jifty/branches/js-refactor/t/TestApp-Plugin-OnClick/bin/jifty
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-OnClick/bin/jifty	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,11 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+use File::Basename qw(dirname); 
+use UNIVERSAL::require;
+
+use Jifty;
+use Jifty::Script;
+
+local $SIG{INT} = sub { warn "Stopped\n"; exit; };
+Jifty::Script->dispatch();

Added: jifty/branches/js-refactor/t/TestApp-Plugin-OnClick/etc/config.yml
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-OnClick/etc/config.yml	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,59 @@
+--- 
+framework: 
+  AdminMode: 1
+  ApplicationClass: TestApp::Plugin::OnClick
+  ApplicationName: TestApp-Plugin-OnClick
+  ApplicationUUID: 45E1B0FE-820A-11DC-9905-76B28F38D863
+  ConfigFileVersion: 2
+  Database: 
+    CheckSchema: 1
+    Database: testapp_plugin_onclick
+    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: 
+    - LetMe: {}
+
+    - SkeletonApp: {}
+
+    - REST: {}
+
+    - Halo: {}
+
+    - ErrorTemplates: {}
+
+    - OnlineDocs: {}
+
+    - CompressedCSSandJS: {}
+
+    - AdminUI: {}
+
+  PubSub: 
+    Backend: Memcached
+    Enable: ~
+  SkipAccessControl: 0
+  TemplateClass: TestApp::Plugin::OnClick::View
+  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/js-refactor/t/TestApp-Plugin-OnClick/share/web/templates/content.html
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-OnClick/share/web/templates/content.html	Thu Nov 29 12:25:16 2007
@@ -0,0 +1 @@
+original content

Added: jifty/branches/js-refactor/t/TestApp-Plugin-OnClick/share/web/templates/content1.html
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-OnClick/share/web/templates/content1.html	Thu Nov 29 12:25:16 2007
@@ -0,0 +1 @@
+yatta

Added: jifty/branches/js-refactor/t/TestApp-Plugin-OnClick/share/web/templates/onclick.html
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-OnClick/share/web/templates/onclick.html	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,26 @@
+<&|/_elements/wrapper, title => _('Welcome to your new Jifty application') &>
+
+args:<% Jifty->web->request->arguments->{'J:V-region-content'} || ''%>
+
+<% Jifty->web->link(
+               label   => 'replace content',
+               id => 'replace_content',
+               onclick => {
+                   replace_with => '/content1.html',
+                   region => 'content',
+               },
+           ) %>
+
+<% Jifty->web->region( 
+    name     => 'content', 
+    path     => '/content.html',
+) %>
+
+
+<a id="original_content" 
+href="/onclick.html?J:V-region-content=%2Fcontent.html"
+onclick="return update(
+{'continuation':{},'actions':{},'fragments':[{'mode':'Replace','args':{},'region':'content','path':'/content.html'}],'action_arguments':{}},
+this );">revert content</a>
+
+</&>

Added: jifty/branches/js-refactor/t/TestApp-Plugin-OnClick/t/onclick.t
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-OnClick/t/onclick.t	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,34 @@
+use strict;
+use warnings;
+use lib 't/lib';
+use Jifty::SubTest;
+use Jifty::Test tests => 10;
+use Jifty::Test::WWW::Selenium;
+use utf8;
+
+my $server = Jifty::Test->make_server;
+my $sel    = Jifty::Test::WWW::Selenium->rc_ok($server);
+my $URL    = $server->started_ok;
+
+$sel->open_ok("/onclick.html");
+$sel->click_ok("//a[\@id='replace_content']");
+
+sleep 2; # in case the click returning slowly
+
+my $html = $sel->get_html_source;
+
+
+like( $html, qr/yatta/, 'replace content correctly' );
+unlike( $html, qr{args:/content1\.html}, 'replaced by javascript' );
+
+$sel->click_ok("//a[\@id='original_content']");
+is( $sel->get_alert,
+    'please use Jifty.update instead of update.',
+    'bare update is deprecated'
+);
+$html = $sel->get_html_source;
+like( $html, qr/original content/, 'replace content correctly' );
+unlike( $html, qr{args:/content\.html}, 'replaced by javascript' );
+
+$sel->stop;
+

Added: jifty/branches/js-refactor/t/TestApp-Plugin-SinglePage/Makefile.PL
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-SinglePage/Makefile.PL	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,7 @@
+use inc::Module::Install;
+
+name        'TestApp::Plugin::JQuery';
+version     '0.01';
+requires    'Jifty' => '0.70129';
+
+WriteAll;

Added: jifty/branches/js-refactor/t/TestApp-Plugin-SinglePage/bin/jifty
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-SinglePage/bin/jifty	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,11 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+use File::Basename qw(dirname); 
+use UNIVERSAL::require;
+
+use Jifty;
+use Jifty::Script;
+
+local $SIG{INT} = sub { warn "Stopped\n"; exit; };
+Jifty::Script->dispatch();

Added: jifty/branches/js-refactor/t/TestApp-Plugin-SinglePage/etc/config.yml
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-SinglePage/etc/config.yml	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,55 @@
+--- 
+framework: 
+  AdminMode: 1
+  ApplicationClass: TestApp::Plugin::SinglePage
+  ApplicationName: TestApp::Plugin::SinglePage
+  ApplicationUUID: DC3B58E4-4F3C-11DC-9ECB-E5DB6F105773
+  ConfigFileVersion: 2
+  Database: 
+    CheckSchema: 1
+    Database: testapp_plugin_singlepage
+    Driver: SQLite
+    Host: localhost
+    Password: ''
+    RecordBaseClass: Jifty::DBI::Record::Cachable
+    RecordUUIDs: active
+    User: ''
+    Version: 0.0.1
+  DevelMode: 1
+  L10N: 
+    PoDir: share/po
+  LogLevel: INFO
+  Mailer: Sendmail
+  MailerArgs: []
+
+  Plugins: 
+    - LetMe: {}
+    - SkeletonApp: {}
+    - REST: {}
+    - Halo: {}
+    - ErrorTemplates: {}
+    - OnlineDocs: {}
+    - CompressedCSSandJS: {}
+    - AdminUI: {}
+    - User: {}
+    - SinglePage: {}
+
+  PubSub: 
+    Backend: Memcached
+    Enable: ~
+  SkipAccessControl: 0
+  TemplateClass: TestApp::Plugin::SinglePage::View
+  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/js-refactor/t/TestApp-Plugin-SinglePage/lib/TestApp/Plugin/SinglePage/Model/User.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-SinglePage/lib/TestApp/Plugin/SinglePage/Model/User.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,14 @@
+package TestApp::Plugin::SinglePage::Model::User;
+use warnings;
+use strict;
+use base qw/TestApp::Plugin::SinglePage::Record/;
+use Jifty::DBI::Schema;
+
+
+use TestApp::Plugin::SinglePage::Record schema {
+};
+
+use Jifty::Plugin::User::Mixin::Model::User;
+
+1;
+

Added: jifty/branches/js-refactor/t/TestApp-Plugin-SinglePage/lib/TestApp/Plugin/SinglePage/View.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-SinglePage/lib/TestApp/Plugin/SinglePage/View.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,53 @@
+package TestApp::Plugin::SinglePage::View;
+use strict;
+use Jifty::View::Declare -base;
+use Jifty::View::Declare::Helpers;
+
+
+template '/index.html' =>
+    page { title => 'test' }
+    content {
+	with (submit_to => '/page2'),
+	form {
+	    select { { name is 'foo' };
+		     option { { value is $_ }; $_ } for 1..10;
+		 };
+	    form_submit( label => 'Next' );
+	};
+	render_region('random');
+    };
+
+template '/page2' =>
+    page { title => 'page2' }
+    content {
+	my $foo = get('foo');
+	my $create = Jifty->web->new_action( class => 'CreateUser', moniker => 'create_user',
+					     arguments => { name => $foo });
+	my $redir = new_action(class     => "Jifty::Action::Redirect",
+			       arguments => { url => '/index.html' });
+	render_region( 'foo' );
+	hyperlink( label => 'foo', onclick => { region => 'foo', replace_with => '_r_foo', args => { foo => '123123' } } );
+
+	form {
+	    Jifty->web->form->register_action($redir);
+	    render_action($create);
+	    form_submit( label   => 'Next' );
+	}
+    };
+
+template 'page3' =>
+    page { title => 'page3' }
+    content {
+	h1 { 'yatta' };
+	form {
+	    hyperlink(label => 'Back', url => '/');
+	}
+    };
+
+template '_r_foo' => sub {
+    my $foo = get('foo');
+    h1 { $foo };
+};
+
+1;
+

Added: jifty/branches/js-refactor/t/TestApp-Plugin-SinglePage/t/statevar.t
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp-Plugin-SinglePage/t/statevar.t	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,38 @@
+use strict;
+use warnings;
+use lib 't/lib';
+use Jifty::SubTest;
+use Jifty::Test tests => 6;
+use Jifty::Test::WWW::Selenium;
+use utf8;
+
+my $server  = Jifty::Test->make_server;
+my $sel = Jifty::Test::WWW::Selenium->rc_ok( $server );
+my $URL = $server->started_ok;
+diag $URL;
+
+$sel->open_ok("/");
+$sel->select_ok("foo", "label=4");
+$sel->click_ok("//input[\@value='Next']");
+
+my $html = $sel->get_html_source;
+
+unlike($html, qr'name="J:V-region-__page."');
+diag $html;
+
+$sel->stop;
+
+#$SIG{INT} = sub { exit };
+
+#sleep 100 while 1;
+
+
+exit;
+__END__
+$sel->value_is("J:A:F-name-create_user", "4");
+$sel->type_ok("J:A:F-email-create_user", "orz\@orz.org");
+$sel->open_ok("/");
+$sel->select_ok("foo", "label=4");
+$sel->click_ok("//input[\@value='Next']");
+$sel->value_is("J:A:F-name-create_user", "4");
+$sel->type_ok("J:A:F-email-create_user", "orz\@orz.org");

Added: jifty/branches/js-refactor/t/TestApp/etc/config.yml
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp/etc/config.yml	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,5 @@
+---
+application:
+    ThisConfigFile: etc/config.yml
+    EtcConfig: 1
+

Added: jifty/branches/js-refactor/t/TestApp/etc/site_config.yml
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp/etc/site_config.yml	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,5 @@
+---
+application:
+    ThisConfigFile: etc/site_config.yml
+    EtcSiteConfig: 1
+

Added: jifty/branches/js-refactor/t/TestApp/lib/TestApp/Action/SayHi.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp/lib/TestApp/Action/SayHi.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,18 @@
+package TestApp::Action::SayHi;
+
+use Jifty::Param::Schema;
+use Jifty::Action schema {
+
+    param 'name';
+    param 'greeting';
+
+
+};
+
+sub take_action {
+    my $self = shift;
+
+    $self->result->message($self->argument_value('name').', '. $self->argument_value('greeting'));
+}
+
+1;

Added: jifty/branches/js-refactor/t/TestApp/lib/TestApp/Model/OtherThingy.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp/lib/TestApp/Model/OtherThingy.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,17 @@
+use strict;
+use warnings;
+
+package TestApp::Model::OtherThingy;
+use Jifty::DBI::Schema;
+
+use TestApp::Record schema {
+
+  column value => type is 'text',  is mandatory;
+  column user_id => refers_to TestApp::Model::User;
+
+};
+
+use Jifty::RightsFrom column => 'user_id';
+
+1;
+

Added: jifty/branches/js-refactor/t/TestApp/lib/TestApp/Model/Thingy.pm
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp/lib/TestApp/Model/Thingy.pm	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,17 @@
+use strict;
+use warnings;
+
+package TestApp::Model::Thingy;
+use Jifty::DBI::Schema;
+
+use TestApp::Record schema {
+
+  column value => type is 'text',  is mandatory;
+  column user_id => refers_to TestApp::Model::User;
+
+};
+
+use Jifty::RightsFrom column => 'user';
+
+1;
+

Modified: jifty/branches/js-refactor/t/TestApp/lib/TestApp/Model/User.pm
==============================================================================
--- jifty/branches/js-refactor/t/TestApp/lib/TestApp/Model/User.pm	(original)
+++ jifty/branches/js-refactor/t/TestApp/lib/TestApp/Model/User.pm	Thu Nov 29 12:25:16 2007
@@ -33,12 +33,34 @@
   type is 'datetime',
   is immutable,
   default is defer { DateTime->now },
-  filters are 'Jifty::DBI::Filter::DateTime';
+  filters are qw(Jifty::Filter::DateTime Jifty::DBI::Filter::Date);
 column 'uuid' => is UUID;
 };
 
 
 # Your model-specific methods go here.
 
+sub current_time
+{
+    Jifty::DateTime->now();
+}
+
+sub time_zone
+{
+    'America/Anchorage'
+}
+
+sub current_user_can {
+    my $self = shift;
+    my $right = shift;
+    my %args = @_;
+
+    return 1 if $self->SUPER::current_user_can($right => %args);
+    
+    return 1 if $self->current_user->id and $self->id and $self->current_user->id == $self->id;
+
+    return 0;
+}
+
 1;
 

Modified: jifty/branches/js-refactor/t/TestApp/lib/TestApp/View.pm
==============================================================================
--- jifty/branches/js-refactor/t/TestApp/lib/TestApp/View.pm	(original)
+++ jifty/branches/js-refactor/t/TestApp/lib/TestApp/View.pm	Thu Nov 29 12:25:16 2007
@@ -4,6 +4,28 @@
 
 use Jifty::View::Declare -base;
 
+__PACKAGE__->use_mason_wrapper;
+
+template 'say_hi' => page {
+    my $a = Jifty->web->new_action( class => 'SayHi' );
+    form {
+
+        #render_param($a => 'name');
+        render_param( $a => 'greeting' );
+        Jifty->web->form->submit(
+            label   => _('Create'),
+            onclick => [
+                {   submit => {
+                        action    => $a,
+                        arguments => { name => 'dave' }
+                    }
+                }
+            ]
+        );
+
+    };
+};
+
 template 'concrete2.html' => sub {
     html {
         body {
@@ -103,4 +125,12 @@
 
     h1 { 'redirected ok'};
 };
+
+template 'use_mason_wrapper' => page {
+    h1 { 'In a Mason Wrapper?' };
+};
+
+
+
+
 1;

Added: jifty/branches/js-refactor/t/TestApp/share/web/templates/_elements/wrapper
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp/share/web/templates/_elements/wrapper	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,38 @@
+<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 id="custom-stuff">Custom Wrapper</div>
+  </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>

Modified: jifty/branches/js-refactor/t/TestApp/t/00-model-User.t
==============================================================================
--- jifty/branches/js-refactor/t/TestApp/t/00-model-User.t	(original)
+++ jifty/branches/js-refactor/t/TestApp/t/00-model-User.t	Thu Nov 29 12:25:16 2007
@@ -11,7 +11,8 @@
 use lib 't/lib';
 use Jifty::SubTest;
 
-use Jifty::Test tests => 12;
+use Jifty::Test tests => 24;
+Jifty::Test->web; # initialize for use with the as_*_action tests
 # Make sure we can load the model
 use_ok('TestApp::Model::User');
 
@@ -27,6 +28,22 @@
 is($o->id, $id, "Create returned the right id");
 is($o->name, $$, "Created object has the right name");
 
+# Test the as_foo_action methods
+my $action = $o->as_create_action( moniker => 'test1' );
+isa_ok($action, 'TestApp::Action::CreateUser');
+is($action->moniker, 'test1', 'create action moniker is test1');
+$action = $o->as_update_action( moniker => 'test2' );
+isa_ok($action, 'TestApp::Action::UpdateUser');
+is($action->record->id, $o->id, 'update action ID is correct');
+is($action->moniker, 'test2', 'update action moniker is test2');
+$action = $o->as_delete_action( moniker => 'test3' );
+isa_ok($action, 'TestApp::Action::DeleteUser');
+is($action->record->id, $o->id, 'delete action ID is correct');
+is($action->moniker, 'test3', 'delete action moniker is test3');
+$action = $o->as_search_action( moniker => 'test4' );
+isa_ok($action, 'TestApp::Action::SearchUser');
+is($action->moniker, 'test4', 'search action moniker is test4');
+
 # And another
 $o->create( name => $$, email => $$, password => $$ );
 ok($o->id, "User create returned another value");
@@ -37,6 +54,11 @@
 $collection->unlimit;
 is($collection->count, 2, "Finds two records");
 
+# Check the as_search_action method
+$action = $collection->as_search_action( moniker => 'test5' );
+isa_ok($action, 'TestApp::Action::SearchUser');
+is($action->moniker, 'test5', 'search action moniker is test5');
+
 # Searches in specific
 $collection->limit(column => 'id', value => $o->id);
 is($collection->count, 1, "Finds one record with specific id");

Modified: jifty/branches/js-refactor/t/TestApp/t/11-current_user.t
==============================================================================
--- jifty/branches/js-refactor/t/TestApp/t/11-current_user.t	(original)
+++ jifty/branches/js-refactor/t/TestApp/t/11-current_user.t	Thu Nov 29 12:25:16 2007
@@ -11,7 +11,7 @@
 use lib 't/lib';
 use Jifty::SubTest;
 
-use Jifty::Test tests => 19;
+use Jifty::Test tests => 33;
 use Jifty::Test::WWW::Mechanize;
 
 use_ok('TestApp::Model::User');
@@ -23,6 +23,7 @@
 
 # Make it so that all users have full access
 TestApp::Model::User->add_trigger( before_access => sub { 'allow' } );
+TestApp::Model::User->finalize_triggers if TestApp::Model::User->can('finalize_triggers');
 
 # Create two users
 my $o = TestApp::Model::User->new(current_user => $system_user);
@@ -34,6 +35,18 @@
             password => 'secret2', tasty => 1 );
 ok($o->id, "New user has valid id set");
 ok($o->tasty, "User is tasty");
+like($o->created_on->time_zone, qr/Floating/, "User's created_on date is in the floating timezone");
+like($o->current_time->time_zone, qr/UTC/, "Jifty::DateTime::now defaults to UTC (superuser has no user_object)");
+
+my $now = $o->current_time->clone;
+$now->set_current_user_timezone('America/Chicago');
+like($now->time_zone, , qr{America::Chicago}, "set_current_user_timezone defaults to the passed in timezone");
+$now->set_current_user_timezone();
+like($now->time_zone, , qr{UTC}, "set_current_user_timezone defaults to UTC if no passed in timezone");
+
+is($o->email, 'bob at example.com', 'email initially set correctly');
+$o->set_email('bob+jifty at example.com');
+is($o->email, 'bob+jifty at example.com', 'email updated correctly');
 
 # Create a CurrentUser
 my $bob = TestApp::CurrentUser->new( name => 'Bob' );
@@ -42,6 +55,23 @@
 ok($bob->user_object->tasty, "The CurrentUser is tasty");
 ok($bob->is_superuser, "CurrentUser is a superuser");
 
+is($bob->user_object->email, 'bob+jifty at example.com', 'email from before');
+$bob->user_object->set_email('bob+test at example.com');
+is($bob->user_object->email, 'bob+test at example.com', 'email updated correctly');
+like($bob->user_object->created_on->time_zone, qr/Floating/, "User's created_on date is in the floating timezone");
+like($bob->user_object->current_time->time_zone, qr{America::Anchorage}, "Jifty::DateTime::now correctly peers into current_user->user_object->time_zone");
+
+$now = $bob->user_object->current_time->clone;
+$now->set_time_zone('America/New_York');
+like($now->time_zone, qr{America::New_York}, "setting up other tests");
+$now->set_current_user_timezone();
+like($now->time_zone, qr{America::Anchorage}, "set_current_user_timezone correctly gets the user's timezone");
+$now->set_current_user_timezone('America/Chicago');
+like($now->time_zone, , qr{America::Anchorage}, "set_current_user_timezone uses the user's in timezone even if one is passed in");
+
+my $dt = Jifty::DateTime->from_epoch(epoch => time);
+like($now->time_zone, qr{America::Anchorage}, "from_epoch correctly gets the user's timezone");
+
 my $server = Jifty::Test->make_server;
 isa_ok($server, 'Jifty::Server');
 

Added: jifty/branches/js-refactor/t/TestApp/t/18-test-www-declare.t
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp/t/18-test-www-declare.t	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,30 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+
+use lib 't/lib';
+use Jifty::SubTest;
+use Test::More;
+
+BEGIN {
+    unless (eval { require Test::WWW::Declare }) {
+        plan skip_all => "Test::WWW::Declare isn't installed";
+    }
+}
+
+use Jifty::Test::WWW::Declare tests => 2;
+
+# this is a duplication of t/TestApp/t/17-template-region-internal-redirect.t
+# if the user sees failures here, then he either saw failures in t/17 OR
+# J:T:W:D is broken
+
+session user => run {
+    flow "region with internal redirects" => check {
+        get "region-with-internal-redirect";
+        content should match qr/redirected ok/;
+        content should match qr/other region/;
+        content should match qr/still going/;
+        content shouldnt match qr/sorry/;
+    };
+};
+

Added: jifty/branches/js-refactor/t/TestApp/t/19-rightsfrom.t
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp/t/19-rightsfrom.t	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,88 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+
+=head1 DESCRIPTION
+
+Test the RightsFrom mixin.
+
+=cut
+
+use lib 't/lib';
+use Jifty::SubTest;
+
+use Jifty::Test no_plan => 1;;
+
+use_ok('TestApp::Model::User');
+use_ok('TestApp::Model::Thingy');
+use_ok('TestApp::Model::OtherThingy');
+use_ok('TestApp::CurrentUser');
+
+# Get a system user
+my $system_user = TestApp::CurrentUser->superuser;
+ok($system_user, "Found a system user");
+
+# Create users
+my $one = TestApp::Model::User->new(current_user => $system_user);
+$one->create( name => 'A User', email => 'auser at example.com', 
+            password => 'secret', tasty => 0 );
+ok($one->id, "New user has valid id set");
+is($one->name, "A User", "Has the right name");
+my $two = TestApp::Model::User->new(current_user => $system_user);
+$two->create( name => 'Bob', email => 'bob at example.com', 
+            password => 'secret2', tasty => 0 );
+ok($two->id, "New user has valid id set");
+
+# Create a CurrentUser
+my $one_user = TestApp::CurrentUser->new( id => $one->id );
+ok($one_user->id, "Loaded the current user");
+is($one_user->id, $one->id, "Has the right id");
+is($one_user->user_object->id, $one->id, "User object is right");
+is($one_user->user_object->name, $one->name, "Name is consistent");
+
+my $two_by_one = TestApp::Model::User->new( current_user => $one_user );
+$two_by_one->load( $two->id );
+ok($two_by_one->id, "Has an id");
+is($two_by_one->id, $two->id, "Has the right id");
+ok(!$two_by_one->current_user_can("read"), "Can read the remote user");
+ok(!$two_by_one->name, "Can't read their name");
+
+# And a thingy and otherthingy, one from each user; thingy has
+# rights_from 'user', otherthingy has rights from 'user_id';
+for my $class (qw/TestApp::Model::Thingy TestApp::Model::OtherThingy/) {
+    my $mine = $class->new(current_user => $system_user);
+    $mine->create( user_id => $one->id, value => "Whee" );
+    ok( $mine->id, "New object has a valid id");
+    is( $mine->user_id, $one->id, "Has right user" );
+    my $theirs = $class->new(current_user => $system_user);
+    $theirs->create( user_id => $two->id, value => "Not whee" );
+    ok( $theirs->id, "New object has a valid id");
+    is( $theirs->user_id, $two->id, "Has right user" );
+
+    my $access = $class->new( current_user => $one_user );
+    $access->load( $mine->id );
+    ok( $access->id, "Object has an id" );
+    is( $access->id, $mine->id, "Has the right id" );
+    ok( $access->current_user_can("read"), "I can read it");
+    ok( $access->value, "Has a value" );
+    is( $access->value, "Whee", "Can read the value" );
+    isa_ok( $access->user, "TestApp::Model::User", "Has a user" );
+    ok( $access->user_id, "Can read the user_id" );
+    ok( $access->user->id, "Can read the user->id" );
+    is( $access->user->id, $one->id, "Has the right user" );
+
+    $access->load( $theirs->id );
+    ok( $access->id, "Object has an id" );
+    is( $access->id, $theirs->id, "Has the right id" );
+    ok( !$access->current_user_can("read"), "I can't read it");
+    ok( !$access->value, "Can't read the value" );
+    isa_ok( $access->user, "TestApp::Model::User", "Has a user" );
+    ok( !$access->user_id, "Can't read the user_id" );
+    TODO:
+    {
+        local $TODO = "ACLs should apply to object refs, but can't";
+        # Except the problem is that Jifty current_user_can's often
+        # call their object refs, which would cause recursion.
+        ok( !$access->user->id, "Can't read the user->id" );
+    }
+}

Modified: jifty/branches/js-refactor/t/TestApp/t/before_access.t
==============================================================================
--- jifty/branches/js-refactor/t/TestApp/t/before_access.t	(original)
+++ jifty/branches/js-refactor/t/TestApp/t/before_access.t	Thu Nov 29 12:25:16 2007
@@ -22,6 +22,7 @@
     }
     return 'ignore';
 });
+TestApp::Model::User->finalize_triggers if TestApp::Model::User->can('finalize_triggers');
 
 # Try creating non-bob, which will be denied
 my $o = TestApp::Model::User->new(current_user => $system_user);

Added: jifty/branches/js-refactor/t/TestApp/t/config/01-basic.t
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp/t/config/01-basic.t	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,33 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+
+use lib 't/lib';
+use Jifty::SubTest;
+
+use Jifty::Test;
+
+my %option_from_file = (
+    EtcConfig         => 'etc/config.yml',
+    EtcSiteConfig     => 'etc/site_config.yml',
+    TTestConfig       => 't/test_config.yml',
+    TConfigTestConfig => 't/config/test_config.yml',
+);
+
+my %no_option_from_file = (
+    IndividualFile    => 't/config/02-individual.t-config.yml',
+);
+
+plan tests => 2 + keys(%option_from_file) + keys %no_option_from_file;
+
+ok(Jifty->config->framework('Web')->{'Port'} >= 10000, "default test config still exists");
+is(Jifty->config->app('ThisConfigFile'), 't/config/test_config.yml', "the same value merges correctly");
+
+while (my ($option, $file) = each %option_from_file) {
+    is(Jifty->config->app($option), '1', "options from $file loaded");
+}
+
+while (my ($option, $file) = each %no_option_from_file) {
+    is(Jifty->config->app($option), undef, "options from $file NOT loaded");
+}
+

Added: jifty/branches/js-refactor/t/TestApp/t/config/02-individual.t
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp/t/config/02-individual.t	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,26 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+
+use lib 't/lib';
+use Jifty::SubTest;
+
+use Jifty::Test;
+
+my %option_from_file = (
+    EtcConfig         => 'etc/config.yml',
+    EtcSiteConfig     => 'etc/site_config.yml',
+    TTestConfig       => 't/test_config.yml',
+    TConfigTestConfig => 't/config/test_config.yml',
+    IndividualFile    => 't/config/02-individual.t-config.tml ',
+);
+
+plan tests => 2 + keys %option_from_file;
+
+ok(Jifty->config->framework('Web')->{'Port'} >= 10000, "default test config still exists");
+is(Jifty->config->app('ThisConfigFile'), 't/config/02-individual.t-config.yml', "the same value merges correctly");
+
+while (my ($option, $file) = each %option_from_file) {
+    is(Jifty->config->app($option), '1', "options from $file loaded");
+}
+

Added: jifty/branches/js-refactor/t/TestApp/t/config/02-individual.t-config.yml
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp/t/config/02-individual.t-config.yml	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,5 @@
+---
+application:
+    ThisConfigFile: t/config/02-individual.t-config.yml
+    IndividualFile: 1
+

Added: jifty/branches/js-refactor/t/TestApp/t/config/03-nosubtest.t
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp/t/config/03-nosubtest.t	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,30 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+
+use Jifty::Test;
+
+my %option_from_file = (
+    TTestConfig       => 't/test_config.yml',
+    TConfigTestConfig => 't/config/test_config.yml',
+);
+
+my %no_option_from_file = (
+    EtcConfig         => 'etc/config.yml',
+    EtcSiteConfig     => 'etc/site_config.yml',
+    IndividualFile    => 't/config/02-individual.t-config.yml',
+);
+
+plan tests => 2 + keys(%option_from_file) + keys %no_option_from_file;
+
+ok(Jifty->config->framework('Web')->{'Port'} >= 10000, "default test config still exists");
+is(Jifty->config->app('ThisConfigFile'), 't/config/test_config.yml', "the same value merges correctly");
+
+while (my ($option, $file) = each %option_from_file) {
+    is(Jifty->config->app($option), '1', "options from $file loaded");
+}
+
+while (my ($option, $file) = each %no_option_from_file) {
+    is(Jifty->config->app($option), undef, "options from $file NOT loaded");
+}
+

Added: jifty/branches/js-refactor/t/TestApp/t/config/test_config.yml
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp/t/config/test_config.yml	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,5 @@
+---
+application:
+    ThisConfigFile: t/config/test_config.yml
+    TConfigTestConfig: 1
+

Added: jifty/branches/js-refactor/t/TestApp/t/crud.t
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp/t/crud.t	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,20 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+
+use lib 't/lib';
+use Jifty::SubTest;
+
+use Jifty::Test tests => 3;
+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/crud/User");
+
+# TODO FIXME XXX Surely more tests are needed... and don't call me Shirley.

Added: jifty/branches/js-refactor/t/TestApp/t/test_config.yml
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp/t/test_config.yml	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,5 @@
+---
+application:
+    ThisConfigFile: t/test_config.yml
+    TTestConfig: 1
+

Added: jifty/branches/js-refactor/t/TestApp/t/use_mason_wrapper.t
==============================================================================
--- (empty file)
+++ jifty/branches/js-refactor/t/TestApp/t/use_mason_wrapper.t	Thu Nov 29 12:25:16 2007
@@ -0,0 +1,21 @@
+#!/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;
+my $url = $server->started_ok;
+
+my $mech = Jifty::Test::WWW::Mechanize->new;
+$mech->get_ok( $url . '/use_mason_wrapper', 'grab a page' );
+
+$mech->content_contains( 'In a Mason Wrapper?', 'got the right template' );
+$mech->content_contains( 'Custom Wrapper', 'used the custom wrapper' );
+
+$mech->get_ok( $url . '/_elements/wrapper', 'getting the wrapper directly');
+$mech->content_contains( 'Something went awry', 'and we were not able to');

Modified: jifty/branches/js-refactor/t/clientside/td.t
==============================================================================
--- jifty/branches/js-refactor/t/clientside/td.t	(original)
+++ jifty/branches/js-refactor/t/clientside/td.t	Thu Nov 29 12:25:16 2007
@@ -47,6 +47,8 @@
 eval 'use Jifty::View::Declare::Compile; 1'
     or plan skip_all => "Can't load Jifty::View::Declare::Compile";
 
+plan skip_all => "require new B::Deparse" unless B::Deparse->can('e_method');
+
 my $jsbin = can_run('js')
     or plan skip_all => "Can't find spidermonkey js binary";
 
@@ -66,6 +68,7 @@
 sub is_compatible {
     my $template = shift;
     my $js = js_output( js_code( Foo->can($template) ) );
+    diag js_code( Foo->can($template) );
     my $td = Template::Declare->show($template);
     $js =~ s/\s*//g;
     $td =~ s/\s*//g;

Modified: jifty/branches/js-refactor/t/lib/Jifty/SubTest.pm
==============================================================================
--- jifty/branches/js-refactor/t/lib/Jifty/SubTest.pm	(original)
+++ jifty/branches/js-refactor/t/lib/Jifty/SubTest.pm	Thu Nov 29 12:25:16 2007
@@ -2,9 +2,12 @@
 
 use FindBin;
 use File::Spec;
+use Cwd;
+
 BEGIN {
+    $Jifty::SubTest::OrigCwd = Cwd::cwd;
+
     @INC = grep { defined } map { ref($_) ? $_ : File::Spec->rel2abs($_) } @INC;
-    $0 = File::Spec->rel2abs($0);
     chdir "$FindBin::Bin/..";
 }
 


More information about the Jifty-commit mailing list