1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
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;
|