diff options
Diffstat (limited to 'perllib/FixMyStreet/App/Form/Wizard.pm')
-rw-r--r-- | perllib/FixMyStreet/App/Form/Wizard.pm | 115 |
1 files changed, 115 insertions, 0 deletions
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; |