diff options
author | M Somerville <matthew-github@dracos.co.uk> | 2020-09-02 16:30:57 +0100 |
---|---|---|
committer | M Somerville <matthew-github@dracos.co.uk> | 2020-11-11 09:55:53 +0000 |
commit | 9292866fbc1be364a716ac9efb105a0350a2de72 (patch) | |
tree | 5641b271b46ede3e4caea8b7eee0eb44a0cd31c7 | |
parent | 808f4d7c18f2c3a1b776900820e604d896072499 (diff) |
Form wizard code.
-rw-r--r-- | perllib/FixMyStreet/App.pm | 1 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Form/Field/JSON.pm | 42 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Form/Page/Simple.pm | 25 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Form/Wizard.pm | 115 |
4 files changed, 183 insertions, 0 deletions
diff --git a/perllib/FixMyStreet/App.pm b/perllib/FixMyStreet/App.pm index 638fcc4e4..e367f0332 100644 --- a/perllib/FixMyStreet/App.pm +++ b/perllib/FixMyStreet/App.pm @@ -514,6 +514,7 @@ Sets the query parameter to the passed variable. sub set_param { my ($c, $param, $value) = @_; $c->req->params->{$param} = $value; + $c->req->body_params->{$param} = $value; } =head2 check_2fa diff --git a/perllib/FixMyStreet/App/Form/Field/JSON.pm b/perllib/FixMyStreet/App/Form/Field/JSON.pm new file mode 100644 index 000000000..4da4ef2b0 --- /dev/null +++ b/perllib/FixMyStreet/App/Form/Field/JSON.pm @@ -0,0 +1,42 @@ +package FixMyStreet::App::Form::Field::JSON; + +use HTML::FormHandler::Moose; +extends 'HTML::FormHandler::Field::Hidden'; + +use JSON::MaybeXS; +use MIME::Base64; + +has '+inflate_method' => ( default => sub { \&inflate_json } ); +has '+deflate_method' => ( default => sub { \&deflate_json } ); +has '+fif_from_value' => ( default => 1 ); + +sub inflate_json { + my ($self, $value) = @_; + return $value unless $value; + $value = decode_json(decode_base64($value)); + return $value; +} + +sub deflate_json { + my ($self, $value) = @_; + return $value unless $value; + $value = encode_base64(encode_json($value), ""); + return $value; +} + +__PACKAGE__->meta->make_immutable; +use namespace::autoclean; + +1; + +__END__ + +=pod + +=encoding UTF-8 + +=head1 NAME + +FixMyStreet::App::Form::Field::JSON - used to store some data in a hidden field + +=cut diff --git a/perllib/FixMyStreet/App/Form/Page/Simple.pm b/perllib/FixMyStreet/App/Form/Page/Simple.pm new file mode 100644 index 000000000..89a871e2e --- /dev/null +++ b/perllib/FixMyStreet/App/Form/Page/Simple.pm @@ -0,0 +1,25 @@ +package FixMyStreet::App::Form::Page::Simple; +use Moose; +extends 'HTML::FormHandler::Page'; + +# What page to go to after successful submission of this page +has next => ( is => 'ro', isa => 'Str|CodeRef' ); + +# A function that will be called to generate an update_field_list parameter +has update_field_list => ( + is => 'ro', + isa => 'CodeRef', + predicate => 'has_update_field_list', +); + +# A function called after all form processing, just before template display +# (to e.g. set up the map) +has post_process => ( + is => 'ro', + isa => 'CodeRef', +); + +# Catalyst action to forward to once this page has been reached +has finished => ( is => 'ro', isa => 'CodeRef' ); + +1; diff --git a/perllib/FixMyStreet/App/Form/Wizard.pm b/perllib/FixMyStreet/App/Form/Wizard.pm new file mode 100644 index 000000000..edb7b0c5c --- /dev/null +++ b/perllib/FixMyStreet/App/Form/Wizard.pm @@ -0,0 +1,115 @@ +package FixMyStreet::App::Form::Wizard; +# ABSTRACT: create a multi-page form, based on HTML::FormHandler::Wizard, but not numbered + +use HTML::FormHandler::Moose; +extends 'HTML::FormHandler'; +with ('HTML::FormHandler::BuildPages', 'HTML::FormHandler::Pages' ); + +sub is_wizard { 1 } # So build_active is called + +sub build_page_name_space { 'FixMyStreet::App::Form::Page' } +has '+field_name_space' => ( default => 'FixMyStreet::App::Form::Field' ); + +# Internal attributes and fields to handle multi-page forms +has page_name => ( is => 'ro', isa => 'Str' ); +has current_page => ( is => 'ro', lazy => 1, + default => sub { $_[0]->page($_[0]->page_name) }, + predicate => 'has_current_page', +); + +has saved_data_encoded => ( is => 'ro', isa => 'Maybe[Str]' ); +has saved_data => ( is => 'rw', lazy => 1, isa => 'HashRef', default => sub { + $_[0]->field('saved_data')->inflate_json($_[0]->saved_data_encoded) || {}; +}); +has previous_form => ( is => 'ro', isa => 'Maybe[HTML::FormHandler]' ); +has csrf_token => ( is => 'ro', isa => 'Str' ); + +has_field saved_data => ( type => 'JSON' ); +has_field token => ( type => 'Hidden', required => 1 ); +has_field process => ( type => 'Hidden', required => 1 ); + +sub next { + my $self = shift; + my $next = $self->current_page->next; + if (ref $next eq 'CODE') { + $next = $next->($self->saved_data); + } + return $next; +} + +# Override HFH default and set current page only to active +sub build_active { + my $self = shift; + + my %active; + foreach my $fname ($self->current_page->all_fields) { + $active{$fname} = 1; + } + + foreach my $page ( $self->all_pages ) { + foreach my $fname ( $page->all_fields ) { + my $field = $self->field($fname); + $field->inactive(1) unless $active{$fname}; + } + } +} + +# Stuff to set up as soon as we have a form +sub after_build { + my $self = shift; + my $page = $self->current_page; + + my $saved_data = $self->previous_form ? $self->previous_form->saved_data : $self->saved_data; + + $self->init_object($saved_data); # For filling in existing values + $self->saved_data($saved_data); + + # Fill in internal fields + $self->update_field(saved_data => { default => $saved_data }); + $self->update_field(token => { default => $self->csrf_token }); + $self->update_field(process => { default => $page->name }); + + # Update field list with any dynamic things (eg user-based, address lookup, geocoding) + if ($page->has_update_field_list) { + my $updates = $page->update_field_list->($self) || {}; + foreach my $field_name (keys %$updates) { + $self->update_field($field_name, $updates->{$field_name}); + } + } +} + +# After a form has been processed, run any post process functions +after 'process' => sub { + my $self = shift; + my $page = $self->current_page; + $page->post_process->($self) if $page->post_process; +}; + +after 'validate_form' => sub { + my $self = shift; + + if ($self->validated) { + # Update saved_data for the next page + my $saved_data = { %{$self->saved_data}, %{$self->value} }; + delete $saved_data->{process}; + delete $saved_data->{token}; + delete $saved_data->{saved_data}; + $self->saved_data($saved_data); + $self->field('saved_data')->_set_value($saved_data); + + # And check to see if there is a function to call on the page + my $page = $self->current_page; + if ($page->finished) { + my $success = $page->finished->($self); + if (!$success) { + $self->add_form_error('Something went wrong, please try again') + unless $self->has_form_errors; + $self->validated(0); + } + } + } +}; + +__PACKAGE__->meta->make_immutable; +use namespace::autoclean; +1; |