[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