[Jifty-commit] r3901 - in jifty/branches/virtual-models: . lib lib/Jifty lib/Jifty/Action lib/Jifty/Action/Record lib/Jifty/Plugin lib/Jifty/Plugin/Image lib/Jifty/View/Declare lib/Jifty/Web lib/Jifty/Web/Form/Field share/plugins/Jifty/Plugin/GoogleMap/web/static/js

jifty-commit at lists.jifty.org jifty-commit at lists.jifty.org
Tue Aug 14 17:11:58 EDT 2007


Author: sterling
Date: Tue Aug 14 17:11:55 2007
New Revision: 3901

Added:
   jifty/branches/virtual-models/lib/Jifty/Plugin/Image/
      - copied from r3698, /jifty/branches/virtual-models/lib/Jifty/Plugin/Userpic/
   jifty/branches/virtual-models/lib/Jifty/Plugin/Image.pm
      - copied unchanged from r3698, /jifty/branches/virtual-models/lib/Jifty/Plugin/Userpic.pm
Modified:
   jifty/branches/virtual-models/   (props changed)
   jifty/branches/virtual-models/AUTHORS
   jifty/branches/virtual-models/Makefile.PL
   jifty/branches/virtual-models/lib/Jifty.pm
   jifty/branches/virtual-models/lib/Jifty/API.pm
   jifty/branches/virtual-models/lib/Jifty/Action.pm
   jifty/branches/virtual-models/lib/Jifty/Action/Autocomplete.pm
   jifty/branches/virtual-models/lib/Jifty/Action/Record.pm
   jifty/branches/virtual-models/lib/Jifty/Action/Record/Create.pm
   jifty/branches/virtual-models/lib/Jifty/Action/Record/Delete.pm
   jifty/branches/virtual-models/lib/Jifty/Action/Record/Search.pm
   jifty/branches/virtual-models/lib/Jifty/Action/Record/Update.pm
   jifty/branches/virtual-models/lib/Jifty/Action/Redirect.pm
   jifty/branches/virtual-models/lib/Jifty/Bootstrap.pm
   jifty/branches/virtual-models/lib/Jifty/ClassLoader.pm
   jifty/branches/virtual-models/lib/Jifty/Client.pm
   jifty/branches/virtual-models/lib/Jifty/Collection.pm
   jifty/branches/virtual-models/lib/Jifty/Config.pm
   jifty/branches/virtual-models/lib/Jifty/View/Declare/CRUD.pm
   jifty/branches/virtual-models/lib/Jifty/View/Declare/Compile.pm
   jifty/branches/virtual-models/lib/Jifty/View/Declare/Helpers.pm
   jifty/branches/virtual-models/lib/Jifty/Web/Form/Element.pm
   jifty/branches/virtual-models/lib/Jifty/Web/Form/Field.pm
   jifty/branches/virtual-models/lib/Jifty/Web/Form/Field/Combobox.pm
   jifty/branches/virtual-models/lib/Jifty/Web/Form/Field/Date.pm
   jifty/branches/virtual-models/lib/Jifty/Web/Form/Field/Hidden.pm
   jifty/branches/virtual-models/lib/Jifty/Web/Form/Field/Radio.pm
   jifty/branches/virtual-models/lib/Jifty/Web/Form/Field/ResetButton.pm
   jifty/branches/virtual-models/lib/Jifty/Web/Form/Field/Select.pm
   jifty/branches/virtual-models/lib/Jifty/Web/Form/Field/Text.pm
   jifty/branches/virtual-models/lib/Jifty/Web/Form/Field/Textarea.pm
   jifty/branches/virtual-models/lib/Jifty/Web/Form/Field/Unrendered.pm
   jifty/branches/virtual-models/lib/Jifty/Web/Form/Field/Upload.pm
   jifty/branches/virtual-models/lib/Jifty/Web/PageRegion.pm
   jifty/branches/virtual-models/share/plugins/Jifty/Plugin/GoogleMap/web/static/js/google_map.js

Log:
 r8658 at dynpc145:  andrew | 2007-08-14 14:53:19 -0500
 Merge down from trunk.
  r8534 at dynpc145:  andrew | 2007-08-12 15:13:21 -0500
  Improving some of the Pod, code comments, and minor perl tidying.
  r8538 at dynpc145:  andrew | 2007-08-12 15:21:41 -0500
   r8514 at riddle (orig r3848):  clkao | 2007-08-11 02:48:56 -0500
   Follow up to r3839, do not change the logic if refers_to is not jifty::record
   r8515 at riddle (orig r3849):  agentz | 2007-08-11 06:54:48 -0500
    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
   
   r8516 at riddle (orig r3850):  agentz | 2007-08-11 06:57:41 -0500
    r1268 at agentz-office:  agentz | 2007-08-11 19:54:25 +0800
    made jifty trunk work with earlier versions of TD
   
   r8517 at riddle (orig r3851):  agentz | 2007-08-11 07:01:07 -0500
    r1272 at agentz-office:  agentz | 2007-08-11 20:00:22 +0800
    'svk push' sometimes gives me trouble...and i dunno why...
   
  
  r8541 at dynpc145:  andrew | 2007-08-12 15:31:19 -0500
  Fixing error that occurred during previous push.
  r8577 at dynpc145:  andrew | 2007-08-12 17:12:34 -0500
  Fixing POD issues in CRUD, adding some code comments, and minor perl tidying.
  r8579 at dynpc145:  andrew | 2007-08-12 17:31:33 -0500
  Additional fix to POD coverage for the CRUDView attribute.
  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.
  r8583 at dynpc145:  andrew | 2007-08-12 20:17:16 -0500
  Improved code comments and minor perl tidy.
  r8618 at dynpc145:  andrew | 2007-08-13 19:47:39 -0500
   r8614 at dynpc145 (orig r3873):  agentz | 2007-08-13 01:34:11 -0500
    r1297 at agentz-office:  agentz | 2007-08-13 14:33:35 +0800
    Makefile.PL - Jifty now depends on TD 0.26
   
  
  r8619 at dynpc145:  andrew | 2007-08-13 19:51:58 -0500
  Cleaning up the Pod for Jifty::Bootstrap
  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.
  r8630 at dynpc145:  andrew | 2007-08-13 21:16:20 -0500
  Updating Pod and adding code comments to Jifty::Client.
  r8634 at dynpc145:  andrew | 2007-08-13 21:45:32 -0500
   r8632 at dynpc145 (orig r3887):  efunneko | 2007-08-13 21:38:51 -0500
   Added myself to AUTHORS
  
  r8635 at dynpc145:  andrew | 2007-08-13 21:46:18 -0500
  Cleaning up the Pod and adding a few code comments to Jifty::Collection.
  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).
  r8648 at dynpc145:  andrew | 2007-08-14 07:48:11 -0500
  Fixing POD test failure.
  r8657 at dynpc145:  andrew | 2007-08-14 14:48:14 -0500
   r8656 at dynpc145 (orig r3900):  efunneko | 2007-08-14 12:16:43 -0500
   Added support for more javascript triggers for most form elements:  onclick onchange ondblclick onmousedown onmouseup onmouseover onmousemove onmouseout onfocus onblur onkeypress onkeydown onkeyup onselect
  
 


Modified: jifty/branches/virtual-models/AUTHORS
==============================================================================
--- jifty/branches/virtual-models/AUTHORS	(original)
+++ jifty/branches/virtual-models/AUTHORS	Tue Aug 14 17:11:55 2007
@@ -31,3 +31,4 @@
 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>

Modified: jifty/branches/virtual-models/Makefile.PL
==============================================================================
--- jifty/branches/virtual-models/Makefile.PL	(original)
+++ jifty/branches/virtual-models/Makefile.PL	Tue Aug 14 17:11:55 2007
@@ -64,11 +64,10 @@
 requires('Shell::Command');
 requires('String::Koremutake');
 requires('SQL::ReservedWords');
-requires('SVN::Fs');
 requires('SVN::Repos');
 requires('SVN::Simple::Edit');
 requires('Template::Declare' => '0.07');                # Template::Declare::Tags
-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');

Modified: jifty/branches/virtual-models/lib/Jifty.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty.pm	Tue Aug 14 17:11:55 2007
@@ -143,13 +143,13 @@
 sub new {
     my $ignored_class = shift;
 
+    # Setup the defaults
     my %args = (
         no_handle        => 0,
         logger_component => undef,
         @_
     );
 
-
     # Add the appliation's library path
     push @INC, File::Spec->catdir(Jifty::Util->app_root, "lib");
 
@@ -161,10 +161,12 @@
     # 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
@@ -172,19 +174,30 @@
     my @plugins_to_load = @{Jifty->config->framework('Plugins')};
     my $app_plugin = Jifty->app_class('Plugin');
     for (my $i = 0; my $plugin = $plugins_to_load[$i]; $i++) {
+
+        # Prepare to learn the plugin class name
         my $plugin_name = (keys %{$plugin})[0];
         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;
         }
+
+        # Load the plugin options
         my %options = %{ $plugin->{ $plugin_name } };
+
+        # 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) {
@@ -193,6 +206,7 @@
         }
     }
 
+    # All plugins loaded, save them for later reference
     Jifty->plugins(@plugins);
 
     # Now that we have the config set up and loaded plugins,
@@ -204,8 +218,10 @@
         base => Jifty->app_class,
     );
 
+    # Save the class loader for later reference
     Jifty->class_loader($class_loader);
     Jifty->class_loader->require;
+    # Configure the request handler and action API handler
     Jifty->handler(Jifty::Handler->new());
     Jifty->api(Jifty::API->new());
 
@@ -220,7 +236,9 @@
     # application specific for startup
     my $app = Jifty->app_class;
     
-    $app->start() if $app->can('start');
+    # 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
@@ -432,15 +450,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/virtual-models/lib/Jifty/API.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/API.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/API.pm	Tue Aug 14 17:11:55 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/virtual-models/lib/Jifty/Action.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Action.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Action.pm	Tue Aug 14 17:11:55 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/virtual-models/lib/Jifty/Action/Autocomplete.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Action/Autocomplete.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Action/Autocomplete.pm	Tue Aug 14 17:11:55 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/virtual-models/lib/Jifty/Action/Record.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Action/Record.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Action/Record.pm	Tue Aug 14 17:11:55 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,195 +138,270 @@
 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;
-            }
+            # The record's current value becomes the widget's default value
+            $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' );
-                    }
-                };
+        # 
+        #  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' );
+                }
+            };
 
-                $field_info->{ $field . "_confirm" } = {
-                    render_as => 'Password',
-                    virtual => '1',
-                    validator => $same,
-                    sort_order => ($column->sort_order +.01),
-                    mandatory => 0
-                };
-            }
+            # Add the extra confirmation field
+            $field_info->{ $field . "_confirm" } = {
+                render_as => 'Password',
+                virtual => '1',
+                validator => $same,
+                sort_order => ($column->sort_order +.01),
+                mandatory => 0
+            };
+        }
 
-            elsif ( defined (my $refers_to = $column->refers_to) ) {
-                if ( UNIVERSAL::isa( $refers_to, 'Jifty::Record' ) ) {
-                    $info->{render_as} = $render_as || 'Select';
-                }
+        # Handle the X-to-one references
+        elsif ( defined (my $refers_to = $column->refers_to) ) {
 
-                if ( $info->{render_as} eq 'Select' ) {
-                    my $collection = Jifty::Collection->new(
-                        record_class => $refers_to,
-                        current_user => $self->record->current_user
-                    );
-                    $collection->unlimit;
+            # Render as a select box unless they override
+            if ( UNIVERSAL::isa( $refers_to, 'Jifty::Record' ) ) {
+                $info->{render_as} = $render_as || 'Select';
+            }
 
-                    my $method = $refers_to->_brief_description();
+            # If it's a select box, setup the available values
+            if ( UNIVERSAL::isa( $refers_to, 'Jifty::Record' ) && $info->{render_as} eq 'Select' ) {
 
-                    # FIXME: we should get value_from with the actualy refered by key
-                    $info->{valid_values} = [
-                        {   display_from => $refers_to->can($method) ? $method : "id",
-                            value_from => 'id',
-                            collection => $collection
-                        }
-                    ];
-                } 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
+                    }
+                ];
+            } 
+            
+            # 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;
 
@@ -335,9 +416,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);
     }
@@ -374,6 +459,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;
@@ -387,10 +473,13 @@
 sub _setup_event_after_action {
     my $self = shift;
     my $event_info = shift;
+
+    # 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/virtual-models/lib/Jifty/Action/Record/Create.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Action/Record/Create.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Action/Record/Create.pm	Tue Aug 14 17:11:55 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/virtual-models/lib/Jifty/Action/Record/Delete.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Action/Record/Delete.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Action/Record/Delete.pm	Tue Aug 14 17:11:55 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/virtual-models/lib/Jifty/Action/Record/Search.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Action/Record/Search.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Action/Record/Search.pm	Tue Aug 14 17:11:55 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/virtual-models/lib/Jifty/Action/Record/Update.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Action/Record/Update.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Action/Record/Update.pm	Tue Aug 14 17:11:55 2007
@@ -33,12 +33,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 +66,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 +87,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 +115,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;
@@ -127,17 +135,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 +168,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/virtual-models/lib/Jifty/Action/Redirect.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Action/Redirect.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Action/Redirect.pm	Tue Aug 14 17:11:55 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/virtual-models/lib/Jifty/Bootstrap.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Bootstrap.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Bootstrap.pm	Tue Aug 14 17:11:55 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/virtual-models/lib/Jifty/ClassLoader.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/ClassLoader.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/ClassLoader.pm	Tue Aug 14 17:11:55 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
 
@@ -192,28 +226,50 @@
 
     }
 
-    # 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 +285,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,16 +304,22 @@
 
 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')],
@@ -264,14 +327,20 @@
         except  => qr/\.#/,
         inner   => 0
     );
+    
+    # Construct the list of models for the application for later reference
+    my %models;
     $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);
     }
-
 }
 
+# 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;
@@ -281,7 +350,6 @@
     push( @$models, $full ) unless grep { $_ eq $full } @$models;
 
     my $base = $self->{base};
-
     my ($short) = $full =~ /::Model::(.*)/;
     Jifty::Util->require( $full . "Collection" );
     Jifty::Util->require( $base . "::Action::" . $_ . $short )
@@ -305,6 +373,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;
@@ -327,8 +399,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");
@@ -345,9 +417,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};
 }
 
@@ -370,17 +446,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:
@@ -395,6 +471,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/virtual-models/lib/Jifty/Client.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Client.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Client.pm	Tue Aug 14 17:11:55 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,9 +93,11 @@
     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) {
@@ -98,6 +106,7 @@
         $input->value($args{$arg});
     } 
 
+    # Return the form in case they want to do soemthing with it
     return $action_form;
 }
 
@@ -116,14 +125,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 +157,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 +183,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 +203,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 +223,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 +243,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} } || '';
@@ -263,10 +285,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 +318,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 +337,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 +358,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 +372,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/virtual-models/lib/Jifty/Collection.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Collection.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Collection.pm	Tue Aug 14 17:11:55 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,11 +33,6 @@
 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
@@ -57,7 +55,9 @@
 
 =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
 
@@ -65,16 +65,25 @@
     my $self = shift;
     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,
@@ -82,12 +91,17 @@
         @_
     );
 
+    # 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});
+
+    # Bad stuff, we really need one of these
     unless ($self->current_user) {
         Carp::confess("Collection created without a current user");
     }
+
+    # Setup the table and call the super-implementation
     $self->table($self->new_item->table());
     $self->SUPER::_init(%args);
 }
@@ -119,4 +133,15 @@
     return $class->new(current_user => $self->current_user);
 }
 
+=head1 SEE ALSO
+
+L<Jifty::DBI::Collection>, L<Jifty::Object>, 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/virtual-models/lib/Jifty/Config.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Config.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Config.pm	Tue Aug 14 17:11:55 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 top 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
@@ -85,32 +99,31 @@
 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 +131,37 @@
         # 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(
             $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 +173,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 +208,7 @@
     $self->_get( 'application', $var );
 }
 
+# A teeny helper for framework and app
 sub _get {
     my $self    = shift;
     my $section = shift;
@@ -198,7 +217,7 @@
     return  $self->stash->{$section}->{$var} 
 }
 
-
+# Sets up the initial location of the site configuration file
 sub _default_config_files {
     my $self = shift;
     my $config  = {
@@ -209,7 +228,6 @@
     return $self->_expand_relative_paths($config);
 }
 
-
 =head2 guess
 
 Attempts to guess (and return) a configuration hash based solely
@@ -222,27 +240,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,7 +279,7 @@
             PubSub           => {
                 Enable => undef,
                 Backend => 'Memcached',
-                    },
+            },
             Database         => {
                 Database =>  $db_name,
                 Driver   => "SQLite",
@@ -286,14 +314,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
 
@@ -303,21 +333,20 @@
     $guess->{'framework'}->{'ConfigFileVersion'} = 2;
 
     # These are the plugins which new apps will get by default
-            $guess->{'framework'}->{'Plugins'} = [
-              { LetMe               => {}, },
-                { SkeletonApp            => {}, },
-                { REST               => {}, },
-                { Halo               => {}, },
-                { ErrorTemplates     => {}, },
-                { OnlineDocs         => {}, },
-                { CompressedCSSandJS => {}, },
-                { AdminUI            => {}, }
-                ];
+    $guess->{'framework'}->{'Plugins'} = [
+        { LetMe              => {}, },
+        { SkeletonApp        => {}, },
+        { REST               => {}, },
+        { Halo               => {}, },
+        { ErrorTemplates     => {}, },
+        { OnlineDocs         => {}, },
+        { CompressedCSSandJS => {}, },
+        { AdminUI            => {}, }
+    ];
+
     return $guess;
 }
 
-
-
 =head2 update_config  $CONFIG
 
 Takes an application's configuration as a hashref.  Right now, it just sets up
@@ -328,25 +357,26 @@
 sub update_config {
     my $self = shift;
     my $config = shift;
-    if ( $config->{'framework'}->{'ConfigFileVersion'} <2) {
-            # These are the plugins which old apps expect because their
-            # features used to be in the core.
-            unshift (@{$config->{'framework'}->{'Plugins'}}, 
-                { SkeletonApp            => {}, },
-                { REST               => {}, },
-                { Halo               => {}, },
-                { ErrorTemplates     => {}, },
-                { OnlineDocs         => {}, },
-                { CompressedCSSandJS => {}, },
-                { AdminUI            => {}, }
-            );
+
+    # This app configuration predates the plugin refactor
+    if ( $config->{'framework'}->{'ConfigFileVersion'} < 2) {
+
+        # These are the plugins which old apps expect because their
+        # features used to be in the core.
+        unshift (@{$config->{'framework'}->{'Plugins'}}, 
+            { SkeletonApp            => {}, },
+            { REST               => {}, },
+            { Halo               => {}, },
+            { ErrorTemplates     => {}, },
+            { OnlineDocs         => {}, },
+            { CompressedCSSandJS => {}, },
+            { AdminUI            => {}, }
+        );
     }
 
     return $config;
 }
 
-
-
 =head2 defaults
 
 We have a couple default values that shouldn't be included in the
@@ -381,7 +411,6 @@
 =cut
 
 sub load_file {
-
     my $self = shift;
     my $file = shift;
 
@@ -390,6 +419,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;
 }
@@ -399,17 +429,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);
         }
@@ -417,10 +458,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/virtual-models/lib/Jifty/View/Declare/CRUD.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/View/Declare/CRUD.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/View/Declare/CRUD.pm	Tue Aug 14 17:11:55 2007
@@ -12,33 +12,69 @@
     $VIEW{$_[2]}++;
 }
 
-
 =head1 NAME
 
 Jifty::View::Declare::CRUD - Provides typical CRUD views to a model
 
+=head1 SYNOPSIS
+
+  package App::View;
+  use Jifty::View::Declare -base;
+
+  use Jifty::View::Declare::CRUD;
+  Jifty::View::Declare::CRUD->mount_view('User');
+  Jifty::View::Declare::CRUD->mount_view('Category', 'App::View::Tag', '/tag');
+
 =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 $@;
@@ -46,6 +82,10 @@
     *{$vclass."::object_type"} = sub { $model };
 }
 
+# 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 +99,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 +110,53 @@
     return $self->package_variable('object_type') || get('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,6 +169,7 @@
 sub _get_record {
     my ( $self, $id ) = @_;
 
+    # Load the model, create an empty object, load the object by ID
     my $record_class = Jifty->app_class( "Model", $self->object_type );
     my $record = $record_class->new();
     $record->load($id);
@@ -126,36 +189,30 @@
      return   grep { !( m/_confirm/ || lc $action->arguments->{$_}{render_as} eq 'password' ) } $action->argument_names;
 }
 
-
 =head1 TEMPLATES
 
-
-=cut
-
 =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
 
@@ -181,12 +238,12 @@
             }
         );
 
-        }
+    }
 };
 
 =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
 
@@ -212,6 +269,12 @@
 
 };
 
+=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;
@@ -228,11 +291,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
 
@@ -256,11 +319,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
@@ -346,8 +407,11 @@
 
 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 $search = $search_collection || ( Jifty->web->response->result('search') ? Jifty->web->response->result('search')->content('search') : undef );
@@ -400,7 +464,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');
@@ -415,26 +478,31 @@
     }
 };
 
+=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 { 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');
     }
 
+    my $i = 0;
     div {
         { class is 'list' };
         while ( my $item = $collection->next ) {
@@ -443,19 +511,18 @@
                 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;
@@ -507,16 +574,12 @@
     };
 };
 
-
-
 =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;
@@ -558,7 +621,7 @@
                 ]
             )
         );
-        }
+    }
 };
 
 =head1 SEE ALSO

Modified: jifty/branches/virtual-models/lib/Jifty/View/Declare/Compile.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/View/Declare/Compile.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/View/Declare/Compile.pm	Tue Aug 14 17:11:55 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/virtual-models/lib/Jifty/View/Declare/Helpers.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/View/Declare/Helpers.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/View/Declare/Helpers.pm	Tue Aug 14 17:11:55 2007
@@ -5,7 +5,14 @@
 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,
+);
 
 =head1 NAME
 
@@ -260,7 +267,7 @@
     return '';
 }
 
-=head2 page 
+=head2 page
 
  template 'foo' => page {{ title is 'Foo' } ... };
 

Modified: jifty/branches/virtual-models/lib/Jifty/Web/Form/Element.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Web/Form/Element.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Web/Form/Element.pm	Tue Aug 14 17:11:55 2007
@@ -13,8 +13,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
@@ -132,14 +132,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
 
@@ -150,7 +161,10 @@
 =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));
+__PACKAGE__->mk_accessors(qw(_onclick _onchange _ondblclick _onmousedown _onmouseup _onmouseover 
+                             _onmousemove _onmouseout _onfocus _onblur _onkeypress _onkeydown 
+                             _onkeyup _onselect
+                             class key_binding key_binding_label id label tooltip continuation));
 
 =head2 new PARAMHASH OVERRIDE
 
@@ -162,8 +176,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 +194,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) = @_;
 
@@ -218,7 +416,8 @@
 
     }
 
-    $self->_onclick($arg);
+    return $arg;
+
 }
 
 =head2 javascript
@@ -226,11 +425,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;
@@ -245,6 +445,13 @@
 
         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
@@ -326,7 +533,7 @@
         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 .= '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) {
@@ -415,4 +622,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/virtual-models/lib/Jifty/Web/Form/Field.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Web/Form/Field.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Web/Form/Field.pm	Tue Aug 14 17:11:55 2007
@@ -493,11 +493,14 @@
     $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/virtual-models/lib/Jifty/Web/Form/Field/Combobox.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Web/Form/Field/Combobox.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Web/Form/Field/Combobox.pm	Tue Aug 14 17:11:55 2007
@@ -29,6 +29,7 @@
        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 +68,7 @@
     '';
 }
 
+
 =head2 render_autocomplete
 
 Never render anything for autocomplete.

Modified: jifty/branches/virtual-models/lib/Jifty/Web/Form/Field/Date.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Web/Form/Field/Date.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Web/Form/Field/Date.pm	Tue Aug 14 17:11:55 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/virtual-models/lib/Jifty/Web/Form/Field/Hidden.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Web/Form/Field/Hidden.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Web/Form/Field/Hidden.pm	Tue Aug 14 17:11:55 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/virtual-models/lib/Jifty/Web/Form/Field/Radio.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Web/Form/Field/Radio.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Web/Form/Field/Radio.pm	Tue Aug 14 17:11:55 2007
@@ -64,6 +64,7 @@
 
     $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 +74,5 @@
     '';
 }
 
+
 1;

Modified: jifty/branches/virtual-models/lib/Jifty/Web/Form/Field/ResetButton.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Web/Form/Field/ResetButton.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Web/Form/Field/ResetButton.pm	Tue Aug 14 17:11:55 2007
@@ -35,4 +35,5 @@
 }
 
 
+
 1;

Modified: jifty/branches/virtual-models/lib/Jifty/Web/Form/Field/Select.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Web/Form/Field/Select.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Web/Form/Field/Select.pm	Tue Aug 14 17:11:55 2007
@@ -23,6 +23,7 @@
     $field .= qq! name="@{[ $self->input_name ]}"!;
     $field .= qq! id="@{[ $self->element_id ]}"!;
     $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/virtual-models/lib/Jifty/Web/Form/Field/Text.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Web/Form/Field/Text.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Web/Form/Field/Text.pm	Tue Aug 14 17:11:55 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/virtual-models/lib/Jifty/Web/Form/Field/Textarea.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Web/Form/Field/Textarea.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Web/Form/Field/Textarea.pm	Tue Aug 14 17:11:55 2007
@@ -37,6 +37,7 @@
     $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 +45,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/virtual-models/lib/Jifty/Web/Form/Field/Unrendered.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Web/Form/Field/Unrendered.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Web/Form/Field/Unrendered.pm	Tue Aug 14 17:11:55 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/virtual-models/lib/Jifty/Web/Form/Field/Upload.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Web/Form/Field/Upload.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Web/Form/Field/Upload.pm	Tue Aug 14 17:11:55 2007
@@ -28,11 +28,41 @@
     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 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 {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};
+
+}
+
+
 =head2 render_value
 
 The 'value', rendered, is empty so that BLOBs and the like don't get

Modified: jifty/branches/virtual-models/lib/Jifty/Web/PageRegion.pm
==============================================================================
--- jifty/branches/virtual-models/lib/Jifty/Web/PageRegion.pm	(original)
+++ jifty/branches/virtual-models/lib/Jifty/Web/PageRegion.pm	Tue Aug 14 17:11:55 2007
@@ -388,6 +388,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 +399,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/virtual-models/share/plugins/Jifty/Plugin/GoogleMap/web/static/js/google_map.js
==============================================================================
--- jifty/branches/virtual-models/share/plugins/Jifty/Plugin/GoogleMap/web/static/js/google_map.js	(original)
+++ jifty/branches/virtual-models/share/plugins/Jifty/Plugin/GoogleMap/web/static/js/google_map.js	Tue Aug 14 17:11:55 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) };


More information about the Jifty-commit mailing list