package Contentment::Form;
use strict;
use warnings;
our $VERSION = '0.10';
use Contentment::Exception;
use Contentment::Form::Definition;
use Contentment::Form::Submission;
use File::Spec;
use List::Util qw( reduce );
use Params::Validate qw( validate_with :types );
use Test::Deep::NoTest;
=head1 NAME
Contentment::Form - forms API for Contentment
=head1 SYNOPSIS
# Typically, you want a two part Perl-script. The first part sets up the form
# definition and initial data. The second is a template for rendering.
my $template = <<'END_OF_TEMPLATE';
[% form.begin %]
[% form.widgets.username.label.render %] [% Form.widgets.username.render %]
[% form.widgets.password.label.render %] [% Form.widgets.password.render %]
[% form.widgets.submit.render %]
[% form.end %]
END_OF_TEMPLATE
my $form = $context->form->define({
name => 'Contentment::Security::Manager::login_form',
method => 'POST',
action => 'Contentment::Security::Manager::process_login_form',
activate => 1,
template => [ Template => {
source => $template,
properties => {
kind => 'text/html',
},
],
widgets => [
username => {
name => 'username',
class => 'Text',
},
password => {
name => 'password',
class => 'Text',
type => 'password',
},
submit => {
value => 'Login',
class => 'Submit',
},
],
});
if ($form->submission->is_finished) {
$context->response->redirect('index.html')->generate;
}
else {
print $form->render;
}
=head1 DESCRIPTION
One of the biggest hassles of writing a web application is handling the forms. It's such a domain-specific hassle that there aren't many general solutions out there and the ones I looked at couldn't handle the needs of Contentment. So, I wrote my own---though, if I can eventually extrapolate this into a more general offering, I hope to do so.
Using this forms system in Contentment involves the following steps:
=over
=item 1.
B First you must define the form. This is done using the C method. This specifies widgets used and the general structure of the data to be entered into the form.
=item 2.
B Once defined, the form is rendered. The definition should include a template that can be used to render the form fields. Rendering occurs via the C method of the object returned by the C method.
=item 3.
B As of this writing, client-side validation is pretty sparse. However, as the API matures, this will be fleshed out more. Client-side validation performs a sanity check on the data prior to submission to help save the user some time.
It's important to note that client-side validation is of secondary importance. Server-side validation is the most important because we can't ultimately trust client-side validation. Some clients may not support it. Malicious clients will purposely ignore it. Thus, we must provide server-side validation. Client-side validation is just icing on the cake.
=item 4.
B Once the client hits the submit button, we need to make sure the data given is sane. Validation performs the task of making sure each piece of data is well-formed and performs any data conversion necessary to make the data useful to our code.
It is very important that this process is done very carefully. If this step isn't taken seriously our code will contain security vulnerabilities.
Server-side validation is performed by the "Contentment::Form::process" hook handler, which then calls the C method for each widget associated with the form.
=item 5.
B If the submitted form has the activation flag set, we need to take action. Action will only be taken if the activation flag is set and the form has passed validation with no errors. Once activated, the subroutine associated with the form will be executed with the validated data.
Activation is performed by the "Contentment::Form::process", which calls the action subroutine associated with the form.
=item 6.
B If the action executes without error, the form submission is marked as finished.
The form is finished within the "Contentment::Form::process" hook handler when the action subroutine executes without throwing an exception.
=back
=head2 METHODS
The C class defines the following methods:
=over
=cut
sub new {
my $class = shift;
return bless {}, $class;
}
=item $form = $context-Eform-Edefine(\%args)
This method is used to construct a form's definition. The form definition is stored the the L class.
This method returns an instance of L, which has methods for rendering and such.
A form definition accepts the following arguments:
=over
=item name (required)
This is the name of the form. This name should be unique throughout your application. It is recommended that you use a Perl package or subroutine name for this string to make sure it is unique.
For example, consider these names:
Contentment::Security::Manager::login_form
Contentment::Security::Profile::Persistent::edit_user_form
Contentment::Setting::edit_setting_form
=item action (optional)
This is the name of the subroutine responsible for taking action when the form is submitted. If not given, the action defaults to "Contentment::Form::process_noop". This form handler is pretty much what it says, a no-op. It does nothing, but allows you to perform actions late in the process if you need lightweight form handling.
The action subroutine should expect a single argument, the data constructed by the validation step. The subroutine will not be called unless the form has passed validation without any errors.
sub form_action {
my $results = shift;
Contentment->context->security_manager->login(
$results->{username},
$results->{password},
);
}
The action subroutine should throw an exception on failure so that the form can be kept unfinished and be reactivated by the user. On success, the subroutine should exit normally (the return value is ignored).
=item widgets (required)
This option must be set to a reference to an array containing the definition of each widget to be used in the form. Each widget is defined as a key/value pair as if it were a reference to a hash (i.e., the order the widgets are defined is significant). The keys are mnemonic names that are used to look the widget up via the C method of L. The values are passed to the widgets' constructors.
Each value is a hash of options. One of the options should be named "class" and should either be the full name of the widget class or the last element of the class name if it is defined under the "Contentment::Form::Widget::" namespace.
For example:
widgets => [
username => {
name => 'username',
class => 'Text',
},
password => {
name => 'password',
class => 'Text',
type => 'password',
},
],
=item template (optional)
This is the generator factory method arguments used to construct a generator object responsible for rendering the template. This comes in the form of an array reference where the first argument is the name of the generator class and the second argument is the hash containing the arguments for the generator constructor. The arguments must be serializable with L.
The C method of the object will be passed the C<%vars> hash, which is the second argument to the C method of the form definition object.
When you need to access the form definition within the template, use the C