[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