diff options
Diffstat (limited to 'perllib/FixMyStreet/App/Form')
-rw-r--r-- | perllib/FixMyStreet/App/Form/Field/JSON.pm | 42 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Form/Field/Postcode.pm | 50 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Form/Page/Simple.pm | 25 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Form/Page/Waste.pm | 11 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Form/Waste.pm | 52 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Form/Waste/AboutYou.pm | 38 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Form/Waste/Enquiry.pm | 48 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Form/Waste/Report.pm | 65 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Form/Waste/Request.pm | 64 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Form/Waste/UPRN.pm | 37 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Form/Wizard.pm | 115 |
11 files changed, 547 insertions, 0 deletions
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/Field/Postcode.pm b/perllib/FixMyStreet/App/Form/Field/Postcode.pm new file mode 100644 index 000000000..093ae66a3 --- /dev/null +++ b/perllib/FixMyStreet/App/Form/Field/Postcode.pm @@ -0,0 +1,50 @@ +package FixMyStreet::App::Form::Field::Postcode; + +use HTML::FormHandler::Moose; +extends 'HTML::FormHandler::Field::Text'; + +use mySociety::PostcodeUtil; + +apply( + [ + { + transform => sub { + my ( $value, $field ) = @_; + $value =~ s/[^A-Z0-9]//i; + return mySociety::PostcodeUtil::canonicalise_postcode($value); + } + }, + { + check => sub { mySociety::PostcodeUtil::is_valid_postcode(shift) }, + message => 'Sorry, we did not recognise that postcode.', + } + ] +); + + +__PACKAGE__->meta->make_immutable; +use namespace::autoclean; + +1; + +__END__ + +=pod + +=encoding UTF-8 + +=head1 NAME + +FixMyStreet::App::Form::Field::Postcode - validates postcode using mySociety::PostcodeUtil + +=head1 DESCRIPTION + +Validates that the input looks like a postcode using L<mySociety::PostcodeUtil>. +Widget type is 'text'. + +=head1 DEPENDENCIES + +L<mySociety::PostcodeUtil> + +=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/Page/Waste.pm b/perllib/FixMyStreet/App/Form/Page/Waste.pm new file mode 100644 index 000000000..5275cae7f --- /dev/null +++ b/perllib/FixMyStreet/App/Form/Page/Waste.pm @@ -0,0 +1,11 @@ +package FixMyStreet::App::Form::Page::Waste; +use Moose; +extends 'FixMyStreet::App::Form::Page::Simple'; + +# Title to use for this page +has title => ( is => 'ro', isa => 'Str' ); + +# Special template to use in preference to the default +has template => ( is => 'ro', isa => 'Str' ); + +1; diff --git a/perllib/FixMyStreet/App/Form/Waste.pm b/perllib/FixMyStreet/App/Form/Waste.pm new file mode 100644 index 000000000..c430506c7 --- /dev/null +++ b/perllib/FixMyStreet/App/Form/Waste.pm @@ -0,0 +1,52 @@ +package FixMyStreet::App::Form::Waste; + +use HTML::FormHandler::Moose; +extends 'FixMyStreet::App::Form::Wizard'; + +has c => ( is => 'ro' ); + +has default_page_type => ( is => 'ro', isa => 'Str', default => 'Waste' ); + +has finished_action => ( is => 'ro' ); + +before _process_page_array => sub { + my ($self, $pages) = @_; + foreach my $page (@$pages) { + $page->{type} = $self->default_page_type + unless $page->{type}; + } +}; + +# Add some functions to the form to pass through to the current page +has '+current_page' => ( + handles => { + title => 'title', + template => 'template', + } +); + +sub wizard_finished { + my ($form, $action) = @_; + my $c = $form->c; + my $success = $c->forward($action, [ $form ]); + if (!$success) { + $form->add_form_error('Something went wrong, please try again'); + foreach (keys %{$c->stash->{field_errors}}) { + $form->add_form_error("$_: " . $c->stash->{field_errors}{$_}); + } + } + return $success; +} + +# Make sure we can have pre-ticked things on the first page +before after_build => sub { + my $self = shift; + + my $saved_data = $self->previous_form ? $self->previous_form->saved_data : $self->saved_data; + + my $c = $self->c; + + map { $saved_data->{$_} = 1 } grep { /^(service|container)-/ && $c->req->params->{$_} } keys %{$c->req->params}; +}; + +1; diff --git a/perllib/FixMyStreet/App/Form/Waste/AboutYou.pm b/perllib/FixMyStreet/App/Form/Waste/AboutYou.pm new file mode 100644 index 000000000..d5bb3df2b --- /dev/null +++ b/perllib/FixMyStreet/App/Form/Waste/AboutYou.pm @@ -0,0 +1,38 @@ +package FixMyStreet::App::Form::Waste::AboutYou; + +use utf8; +use HTML::FormHandler::Moose::Role; +use FixMyStreet::SMS; + +has_field name => ( + type => 'Text', + label => 'Your name', + required => 1, + validate_method => sub { + my $self = shift; + $self->add_error('Please enter your full name.') + if length($self->value) < 5 + || $self->value !~ m/\s/ + || $self->value =~ m/\ba\s*n+on+((y|o)mo?u?s)?(ly)?\b/i; + }, +); + +has_field phone => ( + type => 'Text', + label => 'Telephone number', + validate_method => sub { + my $self = shift; + my $parsed = FixMyStreet::SMS->parse_username($self->value); + $self->add_error('Please provide a valid phone number') + unless $parsed->{phone}; + } +); + +has_field email => ( + type => 'Email', + tags => { + hint => 'If you provide an email address, we can send you order status updates' + }, +); + +1; diff --git a/perllib/FixMyStreet/App/Form/Waste/Enquiry.pm b/perllib/FixMyStreet/App/Form/Waste/Enquiry.pm new file mode 100644 index 000000000..fa85d5d4c --- /dev/null +++ b/perllib/FixMyStreet/App/Form/Waste/Enquiry.pm @@ -0,0 +1,48 @@ +package FixMyStreet::App::Form::Waste::Enquiry; + +use utf8; +use HTML::FormHandler::Moose; +extends 'FixMyStreet::App::Form::Waste'; + +# First page has dynamic fields, so is set in code + +has_field category => ( type => 'Hidden' ); +has_field service_id => ( type => 'Hidden' ); + +has_page about_you => ( + fields => ['name', 'phone', 'email', 'continue'], + title => 'About you', + next => 'summary', +); + +with 'FixMyStreet::App::Form::Waste::AboutYou'; + +has_page summary => ( + fields => ['submit'], + title => 'Submit missed collection', + template => 'waste/summary_enquiry.html', + finished => sub { + return $_[0]->wizard_finished('process_enquiry_data'); + }, + next => 'done', +); + +has_page done => ( + title => 'Enquiry sent', + template => 'waste/confirmation.html', +); + +has_field continue => ( + type => 'Submit', + value => 'Continue', + element_attr => { class => 'govuk-button' }, + order => 999 +); + +has_field submit => ( + type => 'Submit', + value => 'Submit', + element_attr => { class => 'govuk-button' } +); + +1; diff --git a/perllib/FixMyStreet/App/Form/Waste/Report.pm b/perllib/FixMyStreet/App/Form/Waste/Report.pm new file mode 100644 index 000000000..589e75d48 --- /dev/null +++ b/perllib/FixMyStreet/App/Form/Waste/Report.pm @@ -0,0 +1,65 @@ +package FixMyStreet::App::Form::Waste::Report; + +use utf8; +use HTML::FormHandler::Moose; +extends 'FixMyStreet::App::Form::Waste'; + +# First page has dynamic fields, so is set in code + +has_page about_you => ( + fields => ['name', 'email', 'phone', 'continue'], + title => 'About you', + next => 'summary', +); + +with 'FixMyStreet::App::Form::Waste::AboutYou'; + +has_page summary => ( + fields => ['submit'], + title => 'Submit missed collection', + template => 'waste/summary_report.html', + finished => sub { + return $_[0]->wizard_finished('process_report_data'); + }, + next => 'done', +); + +has_page done => ( + title => 'Missed collection sent', + template => 'waste/confirmation.html', +); + +has_field category => ( + type => 'Hidden', + default => 'Report missed collection' +); + +has_field continue => ( + type => 'Submit', + value => 'Continue', + element_attr => { class => 'govuk-button' }, +); + +has_field submit => ( + type => 'Submit', + value => 'Report collection as missed', + element_attr => { class => 'govuk-button' }, + order => 999, +); + +sub validate { + my $self = shift; + my $any = 0; + + foreach ($self->all_fields) { + $any = 1 if $_->name =~ /^service-/ && ($_->value || $self->saved_data->{$_->name}); + } + $self->add_form_error('Please specify what was missed') + unless $any; + + $self->add_form_error('Please specify at least one of phone or email') + unless $self->field('phone')->is_inactive || $self->field('phone')->value || $self->field('email')->value; +} + +1; + diff --git a/perllib/FixMyStreet/App/Form/Waste/Request.pm b/perllib/FixMyStreet/App/Form/Waste/Request.pm new file mode 100644 index 000000000..e7caaa206 --- /dev/null +++ b/perllib/FixMyStreet/App/Form/Waste/Request.pm @@ -0,0 +1,64 @@ +package FixMyStreet::App::Form::Waste::Request; + +use utf8; +use HTML::FormHandler::Moose; +extends 'FixMyStreet::App::Form::Waste'; + +# First page has dynamic fields, so is set in code + +has_page about_you => ( + fields => ['name', 'email', 'phone', 'continue'], + title => 'About you', + next => 'summary', +); + +with 'FixMyStreet::App::Form::Waste::AboutYou'; + +has_page summary => ( + fields => ['submit'], + title => 'Submit container request', + template => 'waste/summary_request.html', + finished => sub { + return $_[0]->wizard_finished('process_request_data'); + }, + next => 'done', +); + +has_page done => ( + title => 'Container request sent', + template => 'waste/confirmation.html', +); + +has_field category => ( + type => 'Hidden', + default => 'Request new container', +); + +has_field continue => ( + type => 'Submit', + value => 'Continue', + element_attr => { class => 'govuk-button' }, +); + +has_field submit => ( + type => 'Submit', + value => 'Request new containers', + element_attr => { class => 'govuk-button' }, + order => 999, +); + +sub validate { + my $self = shift; + my $any = 0; + + foreach ($self->all_fields) { + $any = 1 if $_->name =~ /^container-/ && ($_->value || $self->saved_data->{$_->name}); + } + $self->add_form_error('Please specify what you need') + unless $any; + + $self->add_form_error('Please specify at least one of phone or email') + unless $self->field('phone')->is_inactive || $self->field('phone')->value || $self->field('email')->value; +} + +1; diff --git a/perllib/FixMyStreet/App/Form/Waste/UPRN.pm b/perllib/FixMyStreet/App/Form/Waste/UPRN.pm new file mode 100644 index 000000000..d0ac7b3cb --- /dev/null +++ b/perllib/FixMyStreet/App/Form/Waste/UPRN.pm @@ -0,0 +1,37 @@ +package FixMyStreet::App::Form::Waste::UPRN; + +use utf8; +use HTML::FormHandler::Moose; +extends 'HTML::FormHandler'; + +use mySociety::PostcodeUtil qw(is_valid_postcode); + +has '+field_name_space' => ( default => 'FixMyStreet::App::Form::Field' ); + +has cobrand => ( is => 'ro' ); + +has_field postcode => ( + required => 1, + type => 'Postcode', + validate_method => sub { + my $self = shift; + return if $self->has_errors; # Called even if already failed + my $data = $self->form->cobrand->bin_addresses_for_postcode($self->value); + if (!@$data) { + $self->add_error('Sorry, we did not find any results for that postcode'); + } + push @$data, { value => 'missing', label => 'I can’t find my address' }; + $self->value($data); + }, + tags => { autofocus => 1 }, +); + +has_field go => ( + type => 'Submit', + value => 'Go', + element_attr => { class => 'govuk-button' }, +); + +__PACKAGE__->meta->make_immutable; + +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; |