[Jifty-commit] r5603 - in jifty/trunk: . t/Multipage t/Multipage/bin t/Multipage/etc t/Multipage/lib t/Multipage/lib/Multipage t/Multipage/lib/Multipage/Action t/Multipage/share t/Multipage/share/web t/Multipage/share/web/templates t/Multipage/t
Jifty commits
jifty-commit at lists.jifty.org
Mon Jul 28 12:50:05 EDT 2008
Author: alexmv
Date: Mon Jul 28 12:50:05 2008
New Revision: 5603
Added:
jifty/trunk/lib/Jifty/Action/Multipage.pm
jifty/trunk/t/Multipage/
jifty/trunk/t/Multipage/bin/
jifty/trunk/t/Multipage/bin/jifty (contents, props changed)
jifty/trunk/t/Multipage/etc/
jifty/trunk/t/Multipage/etc/config.yml
jifty/trunk/t/Multipage/lib/
jifty/trunk/t/Multipage/lib/Multipage/
jifty/trunk/t/Multipage/lib/Multipage/Action/
jifty/trunk/t/Multipage/lib/Multipage/Action/BigTest.pm
jifty/trunk/t/Multipage/share/
jifty/trunk/t/Multipage/share/web/
jifty/trunk/t/Multipage/share/web/templates/
jifty/trunk/t/Multipage/share/web/templates/done
jifty/trunk/t/Multipage/share/web/templates/index.html
jifty/trunk/t/Multipage/share/web/templates/page2
jifty/trunk/t/Multipage/share/web/templates/page3
jifty/trunk/t/Multipage/t/
jifty/trunk/t/Multipage/t/01-multipage.t
Modified:
jifty/trunk/ (props changed)
Log:
r34696 at kohr-ah: chmrr | 2008-07-28 12:49:25 -0400
* Basic support for multipage actions or "wizards"
Added: jifty/trunk/lib/Jifty/Action/Multipage.pm
==============================================================================
--- (empty file)
+++ jifty/trunk/lib/Jifty/Action/Multipage.pm Mon Jul 28 12:50:05 2008
@@ -0,0 +1,167 @@
+use warnings;
+use strict;
+
+package Jifty::Action::Multipage;
+
+=head1 NAME
+
+Jifty::Action::Multipage - Actions stretched across multiple pages
+
+=head1 DESCRIPTION
+
+C<Jifty::Action::Multipage> is the action base class for actions which
+span across multiple pages, in a "wizard" workflow. Each page but the
+last gets a chance to validate all of the inputs yet entered, and
+transparently remembers inputs from previous pages. Since the
+information is stored in continuations, not directly on sessions, it
+is "back"-button compatible, and the same user can be in the middle of
+multiple instances of the same multipage action at once.
+
+The action must have the same moniker on all pages which it appears.
+Field validators on the action must be prepared to be called with no
+value, if the argument field has not been seen by the user yet. That
+is, if a multipage action has three fields, labelled C<A>, C<B>, and
+C<C>, one on each page, then L</validate> will first be called with
+only a value for C<A>, for the first page; then with C<A> and C<B>,
+for the second page; then with C<A>, C<B>, and C<C> for the third
+page; then it will be run.
+
+=cut
+
+use base qw/Jifty::Action/;
+
+=head1 METHODS
+
+=head2 new
+
+Sets up the multipage action, by examining previous continuations for
+this action. If it finds any, it populates the argument values using
+them, most recent ones taking precedence.
+
+=cut
+
+sub new {
+ my $class = shift;
+ my %args = @_;
+ my $self = $class->SUPER::new(%args);
+
+ # Fetch any arguments from a passed in request
+ my @actions;
+ push @actions, Jifty->web->request->action( $self->moniker )
+ if Jifty->web->request->action( $self->moniker );
+
+ # Also, all earlier versions of this action in the continuation tree
+ my $cont = Jifty->web->request->continuation;
+ while ( $cont and $cont->request->action( $self->moniker ) ) {
+ push @actions, $cont->request->action( $self->moniker );
+ $self->{top_continuation} = $cont;
+ $cont = $cont->parent;
+ $cont = Jifty->web->session->get_continuation($cont) if defined $cont;
+ }
+
+ # Extract their arguments, earliest to latest
+ my %earlier;
+ for (reverse grep {$_->arguments} @actions) {
+ %earlier = (%earlier, %{$_->arguments});
+ }
+
+ # Setup the argument values with the new_action arguments taking precedent
+ $self->argument_values( { %earlier, %{ $args{'arguments'} } } );
+
+ # Track how an argument was set, again new_action args taking precedent
+ $self->values_from_request({});
+ $self->values_from_request->{$_} = 1 for keys %earlier;
+ $self->values_from_request->{$_} = 0 for keys %{ $args{'arguments' } };
+
+ return $self;
+}
+
+=head2 validate
+
+If the action doesn't validate, modify the request to not be a
+continuation push. Otherwise, mark the action as inactive, so it
+doesn't get run if the continuation somehow gets called.
+
+=cut
+
+sub validate {
+ my $self = shift;
+ $self->SUPER::validate(@_);
+ if ($self->result->failure) {
+ Jifty->web->request->continuation_path(undef);
+ } elsif (Jifty->web->request->continuation_path) {
+ Jifty->web->request->action( $self->moniker )->active(0);
+ }
+ return $self->result->success;
+}
+
+=head2 top_continuation
+
+Returns the topmost continuation which contains this multipage action.
+
+=cut
+
+sub top_continuation {
+ my $self = shift;
+ return $self->{top_continuation};
+}
+
+=head2 next_page_button url => PATH, [ARGS]
+
+Acts like L<Jifty::Action/button>, except with appropriate defaults to
+move to the next page in the action. Failure to validate means the
+user is kept on the same page.
+
+=cut
+
+sub next_page_button {
+ my $self = shift;
+ my %args = @_;
+ die "No 'url' passed to next_page_button for @{[ref $self]}\n" unless $args{url};
+ return $self->button( returns => {}, label => "Next", @_);
+}
+
+=head2 finish_button [ARGS]
+
+Acts like L<Jifty::Action/button>, except with appropriate defaults to
+cause the action to run if it validates. Failure to validate means
+the user is kept on the same page.
+
+Note: Unlike most uses of continuations in the Jifty core, simply
+I<rendering> this button creates a continuation.
+
+=cut
+
+sub finish_button {
+ my $self = shift;
+ my %args = @_;
+ my $top = $self->top_continuation;
+
+ my $req;
+ if ( $args{url} ) {
+ $req = Jifty::Request->new( path => $args{url} );
+ } else {
+ $req = $top->request;
+ $req->remove_action( $self->moniker );
+ }
+ my $return = Jifty::Continuation->new(
+ request => $req,
+ response => Jifty::Response->new,
+ parent => $top->parent
+ );
+ return $self->button( call => $return, label => "Finish", @_ );
+}
+
+=head2 cancel_button [ARGS]
+
+Acts like L<Jifty::Action/button>, except with appropriate defaults to
+return the user to the page where the multipage action started.
+
+=cut
+
+sub cancel_button {
+ my $self = shift;
+ return Jifty->web->link( call => $self->top_continuation, label => "Cancel", as_button => 1, @_ );
+}
+
+1;
Added: jifty/trunk/t/Multipage/bin/jifty
==============================================================================
--- (empty file)
+++ jifty/trunk/t/Multipage/bin/jifty Mon Jul 28 12:50:05 2008
@@ -0,0 +1,16 @@
+#!/usr/bin/env perl
+use warnings;
+use strict;
+use UNIVERSAL::require;
+
+BEGIN {
+ Jifty::Util->require or die $UNIVERSAL::require::ERROR;
+ my $root = Jifty::Util->app_root;
+ unshift @INC, "$root/lib" if ($root);
+}
+
+use Jifty;
+use Jifty::Script;
+
+local $SIG{INT} = sub { warn "Stopped\n"; exit; };
+Jifty::Script->dispatch();
Added: jifty/trunk/t/Multipage/etc/config.yml
==============================================================================
--- (empty file)
+++ jifty/trunk/t/Multipage/etc/config.yml Mon Jul 28 12:50:05 2008
@@ -0,0 +1,51 @@
+---
+framework:
+ AdminMode: 0
+ ApplicationClass: Multipage
+ ApplicationName: Multipage
+ ApplicationUUID: D83B7E32-5CBE-11DD-AF1B-D54175617E32
+ ConfigFileVersion: 4
+ Database:
+ AutoUpgrade: 1
+ CheckSchema: 1
+ Database: multipage
+ Driver: SQLite
+ Host: localhost
+ Password: ''
+ RecordBaseClass: Jifty::DBI::Record::Cachable
+ User: ''
+ Version: 0.0.1
+ DevelMode: 0
+ L10N:
+ PoDir: share/po
+ LogLevel: INFO
+ Mailer: Sendmail
+ MailerArgs: []
+ Plugins:
+ - SkeletonApp: {}
+ - ErrorTemplates: {}
+ - CompressedCSSandJS: {}
+ PubSub:
+ Backend: Memcached
+ Enable: ~
+ SkipAccessControl: 0
+ TemplateClass: Multipage::View
+ View:
+ FallbackHandler: Jifty::View::Mason::Handler
+ Handlers:
+ - Jifty::View::Static::Handler
+ - Jifty::View::Declare::Handler
+ - Jifty::View::Mason::Handler
+ Web:
+ BaseURL: http://localhost
+ DataDir: var/mason
+ Globals: []
+ MasonConfig:
+ autoflush: 0
+ default_escape_flags: h
+ error_format: text
+ error_mode: fatal
+ Port: 8888
+ ServeStaticFiles: 1
+ StaticRoot: share/web/static
+ TemplateRoot: share/web/templates
Added: jifty/trunk/t/Multipage/lib/Multipage/Action/BigTest.pm
==============================================================================
--- (empty file)
+++ jifty/trunk/t/Multipage/lib/Multipage/Action/BigTest.pm Mon Jul 28 12:50:05 2008
@@ -0,0 +1,57 @@
+package Multipage::Action::BigTest;
+
+use base 'Jifty::Action::Multipage';
+
+use Jifty::Param::Schema;
+use Jifty::Action schema {
+
+param age =>
+ label is "Age", is mandatory;
+
+param email =>
+ label is 'Email', is mandatory;
+
+param name =>
+ label is "Name", is mandatory;
+
+};
+
+sub validate_email {
+ my $self = shift;
+ my $address = shift;
+
+ return unless $self->has_argument( "email" );
+ return $self->validation_error( email => "Not an email address")
+ unless $address =~ /@/;
+ return $self->validation_ok( "email" );
+}
+
+sub validate_name {
+ my $self = shift;
+ my $name = shift;
+
+ return unless $self->has_argument( "name" );
+ return $self->validation_error( name => "Not alex" )
+ unless $name eq "alex";
+ return $self->validation_ok( "name" );
+}
+
+sub validate_age {
+ my $self = shift;
+ my $age = shift;
+
+ return unless $self->has_argument( "age" );
+ return $self->validation_error( age => "Too young" )
+ unless $age > 18;
+ return $self->validation_ok( "age" );
+}
+
+sub take_action {
+ my $self = shift;
+ my $name = $self->argument_value("name");
+ my $email = $self->argument_value("email");
+ my $age = $self->argument_value("age");
+ $self->result->message("All done, '$name', '$email', '$age'!");
+}
+
+1;
Added: jifty/trunk/t/Multipage/share/web/templates/done
==============================================================================
--- (empty file)
+++ jifty/trunk/t/Multipage/share/web/templates/done Mon Jul 28 12:50:05 2008
@@ -0,0 +1,5 @@
+<&|/_elements/wrapper, title => 'Jifty Test Application' &>
+
+All done!
+
+</&>
\ No newline at end of file
Added: jifty/trunk/t/Multipage/share/web/templates/index.html
==============================================================================
--- (empty file)
+++ jifty/trunk/t/Multipage/share/web/templates/index.html Mon Jul 28 12:50:05 2008
@@ -0,0 +1,15 @@
+<&|/_elements/wrapper, title => 'Jifty Test Application' &>
+
+From here, you can start on your magical journey!
+
+<% Jifty->web->form->start %>
+
+<% $action->form_field("name") %>
+<% $action->next_page_button( url => "/page2" ) %>
+
+<% Jifty->web->form->end %>
+
+</&>
+<%init>
+my $action = Jifty->web->new_action( moniker => "multi", class => "BigTest" );
+</%init>
Added: jifty/trunk/t/Multipage/share/web/templates/page2
==============================================================================
--- (empty file)
+++ jifty/trunk/t/Multipage/share/web/templates/page2 Mon Jul 28 12:50:05 2008
@@ -0,0 +1,18 @@
+<&|/_elements/wrapper, title => 'Jifty Test Application' &>
+
+Step two
+
+<% Jifty->web->form->start %>
+
+Your name is <% $action->argument_value("name") %>
+
+<% $action->form_field("email") %>
+<% $action->cancel_button %>
+<% $action->next_page_button( url => "/page3") %>
+
+<% Jifty->web->form->end %>
+
+</&>
+<%init>
+my $action = Jifty->web->new_action( moniker => "multi", class => "BigTest" );
+</%init>
Added: jifty/trunk/t/Multipage/share/web/templates/page3
==============================================================================
--- (empty file)
+++ jifty/trunk/t/Multipage/share/web/templates/page3 Mon Jul 28 12:50:05 2008
@@ -0,0 +1,18 @@
+<&|/_elements/wrapper, title => 'Jifty Test Application' &>
+
+Step three
+
+<% Jifty->web->form->start %>
+
+Your name is <% $action->argument_value("name") %>, email is <% $action->argument_value("email") %>
+
+<% $action->form_field("age") %>
+<% $action->cancel_button %>
+<% $action->finish_button( url => "/done") %>
+
+<% Jifty->web->form->end %>
+
+</&>
+<%init>
+my $action = Jifty->web->new_action( moniker => "multi", class => "BigTest" );
+</%init>
Added: jifty/trunk/t/Multipage/t/01-multipage.t
==============================================================================
--- (empty file)
+++ jifty/trunk/t/Multipage/t/01-multipage.t Mon Jul 28 12:50:05 2008
@@ -0,0 +1,85 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+=head1 DESCRIPTION
+
+Multipage action tests
+
+=cut
+
+use lib 't/lib';
+use Jifty::SubTest;
+use lib '../lib';
+use Jifty::Test tests => 41;
+
+use_ok('Jifty::Test::WWW::Mechanize');
+
+# Set up server
+my $server = Jifty::Test->make_server;
+my $URL = $server->started_ok;
+my $mech = Jifty::Test::WWW::Mechanize->new;
+
+# Check that the first page is as we expect it
+$mech->get("$URL/");
+$mech->content_contains("start on your magical journey", "First page");
+
+# Wrong name
+$mech->fill_in_action_ok("multi", name => "bob");
+ok($mech->click_button(value => "Next"));
+$mech->content_contains("Not alex");
+$mech->content_contains("start on your magical journey", "Still first page");
+
+# Right content, gets to page two, contains first page content
+$mech->fill_in_action_ok("multi", name => "alex");
+ok($mech->click_button(value => "Next"));
+$mech->content_contains("Step two");
+$mech->content_contains("alex");
+
+# Cancel gets us back
+ok($mech->click_button(value => "Cancel"));
+$mech->content_contains("start on your magical journey");
+$mech->content_lacks("alex");
+$mech->back;
+
+# Invalid email address leaves us on page two, remembers state
+$mech->fill_in_action_ok("multi", email => "foo");
+ok($mech->click_button(value => "Next"));
+$mech->content_contains("Not an email address");
+$mech->content_contains("Step two", "Still second page");
+$mech->content_contains("alex", "Still remembers name");
+
+# Cancel still gets us back
+ok($mech->click_button(value => "Cancel"));
+$mech->content_contains("start on your magical journey");
+$mech->content_lacks("alex");
+$mech->back;
+
+# On to page three
+$mech->fill_in_action_ok("multi", email => 'foo at bar');
+ok($mech->click_button(value => "Next"));
+$mech->content_contains("Step three", "Third page");
+$mech->content_contains("alex", "Remembers name");
+$mech->content_contains('foo at bar', "Remembers email");
+
+# Wrong content on page three leaves us there
+$mech->fill_in_action_ok("multi", age => "1");
+ok($mech->click_button(value => "Finish"));
+$mech->content_contains("Too young");
+$mech->content_contains("Step three", "Still third page");
+$mech->content_contains("alex", "Still remembers name");
+$mech->content_contains('foo at bar', "Still remembers email");
+
+# Cancel still gets us back
+ok($mech->click_button(value => "Cancel"));
+$mech->content_contains("start on your magical journey");
+$mech->content_lacks("alex");
+$mech->content_lacks('foo at bar');
+$mech->back;
+
+# Right content finishes it off, returning to startpoint
+$mech->fill_in_action_ok("multi", age => "25");
+ok($mech->click_button(value => "Finish"));
+$mech->content_contains(q|All done, 'alex', 'foo at bar', '25'!|, "Completed");
+$mech->content_contains('All done!', "Went to final page");
More information about the Jifty-commit
mailing list