aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorM Somerville <matthew-github@dracos.co.uk>2020-09-02 16:30:57 +0100
committerM Somerville <matthew-github@dracos.co.uk>2020-11-11 09:55:53 +0000
commit9292866fbc1be364a716ac9efb105a0350a2de72 (patch)
tree5641b271b46ede3e4caea8b7eee0eb44a0cd31c7
parent808f4d7c18f2c3a1b776900820e604d896072499 (diff)
Form wizard code.
-rw-r--r--perllib/FixMyStreet/App.pm1
-rw-r--r--perllib/FixMyStreet/App/Form/Field/JSON.pm42
-rw-r--r--perllib/FixMyStreet/App/Form/Page/Simple.pm25
-rw-r--r--perllib/FixMyStreet/App/Form/Wizard.pm115
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;