aboutsummaryrefslogtreecommitdiffstats
path: root/perllib/FixMyStreet/App/Form/Wizard.pm
blob: edb7b0c5c1086d894c2fce6e0f0353135a75d6f0 (plain)
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;