=head1 NAME
-Catalyst::Manual::Tutorial::AdvancedCRUD::FormFu - Catalyst Tutorial - Part 9: Advanced CRUD - FormFu
-NOTE: This part of the tutorial is in progress and will be ready soon.
+Catalyst::Manual::Tutorial::AdvancedCRUD::FormFu - Catalyst Tutorial - Part 9: Advanced CRUD - FormFu
+
=head1 OVERVIEW
=item 9
-B<Advanced CRUD>
+B<Advanced CRUD::FormFu>
=item 10
=head1 DESCRIPTION
+This portion of the tutorial explores L<HTML::FormFu|HTML::FormFu> and
+how it can be used to manage forms, perform validation of form input,
+as well as save and restore data to/from the database. This was written
+using HTML::FormFu version 0.03007.
+
+See
+L<Catalyst::Manual::Tutorial::AdvancedCRUD|Catalyst::Manual::Tutorial::AdvancedCRUD>
+for additional form management options other than
+L<HTML::FormFu|HTML::FormFu>.
+
+
+=head1 Install C<HTML::FormFu>
+
+If you are following along in Ubuntu, it turns out that C<HTML::FormFu>
+is not yet available as a package at the time this was written. To
+install it with a combination of C<apt-get> packages and traditional
+CPAN modules, first use C<apt-get> to install most of the modules
+required by C<HTML::FormFu>:
+
+ sudo apt-get install libtest-nowarnings-perl libdatetime-format-builder-perl \
+ libdatetime-format-strptime-perl libdatetime-locale-perl \
+ libhtml-tokeparser-simple-perl liblist-moreutils-perl \
+ libregexp-copy-perl libregexp-common-perl libyaml-syck-perl libparams-util-perl \
+ libcrypt-des-perl libcaptcha-recaptcha-perl libcrypt-cbc-perl \
+ libreadonly-xs-perl libmoose-perl libregexp-assemble-perl
+
+ ...
+
+ sudo apt-get clean
+
+Then use the following command to install directly from CPAN the modules
+that aren't available as Ubuntu/Debian packages via C<apt-get>:
+
+ sudo cpan File::ShareDir Task::Weaken Config::Any Test::Harness Test::Aggregate \
+ boolean Test::MockTime DateTime::Format::Natural HTML::FormFu \
+ Catalyst::Component::InstancePerContext Catalyst::Controller::HTML::FormFu \
+ HTML::FormFu::Model::DBIC
+
+ ...
+
+ Is it OK to try to connect to the Internet? [yes] yes
+
+ ...
+
+
+B<Note:> If you are following along with the Ubuntu LiveCD, you might
+want to make sure you still have adequate free disk space in the root
+partition with the C<df> command. You can free up some space with
+C<rm -rf /root/.cpan/*>.
+
+
+=head1 C<HTML::FormFu> FORM CREATION
+
+This section looks at how L<HTML::FormFu|HTML::FormFu> can be used to
+add additional functionality to the manually created form from Part 4.
+
+
+=head2 Inherit From C<Catalyst::Controller::HTML::FormFu>
+
+First, change your C<lib/MyApp/Controller/Books.pm> to inherit from
+L<Catalyst::Controller::HTML::FormFu|Catalyst::Controller::HTML::FormFu>
+by changing the C<use base> line from the default of:
+
+ use parent 'Catalyst::Controller';
+
+to use the FormFu base controller class:
+
+ use parent 'Catalyst::Controller::HTML::FormFu';
+
+
+=head2 Add Action to Display and Save the Form
+
+Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
+following method:
+
+ =head2 formfu_create
+
+ Use HTML::FormFu to create a new book
+
+ =cut
+
+ sub formfu_create :Local :FormConfig {
+ my ($self, $c) = @_;
+
+ # Get the form that the :FormConfig attribute saved in the stash
+ my $form = $c->stash->{form};
+
+ # Check if the form has been submitted (vs. displaying the initial
+ # form) and if the data passed validation. "submitted_and_valid"
+ # is shorthand for "$form->submitted && !$form->has_errors"
+ if ($form->submitted_and_valid) {
+ # Create a new book
+ my $book = $c->model('DB::Books')->new_result({});
+ # Save the form data for the book
+ $form->save_to_model($book);
+ # Set a status message for the user
+ $c->flash->{status_msg} = 'Book created';
+ # Return to the books list
+ $c->response->redirect($c->uri_for('list'));
+ $c->detach;
+ } else {
+ # Get the authors from the DB
+ my @authorObjs = $c->model("DB::Authors")->all();
+ # Create an array of arrayrefs where each arrayref is an author
+ my @authors;
+ foreach (sort {$a->last_name cmp $b->last_name} @authorObjs) {
+ push(@authors, [$_->id, $_->last_name]);
+ }
+ # Get the select added by the config file
+ my $select = $form->get_element({type => 'Select'});
+ # Add the authors to it
+ $select->options(\@authors);
+ }
+
+ # Set the template
+ $c->stash->{template} = 'books/formfu_create.tt2';
+ }
+
+
+=head2 Create a Form Config File
+
+Although C<HTML::FormFu> supports any configuration file handled by
+L<Config::Any|Config::Any>, most people tend to use YAML. First
+create a directory to hold your form configuration files:
+
+ mkdir -p root/forms/books
+
+Then create the file C<root/forms/books/formfu_create.yml> and enter the
+following text:
+
+ ---
+ # indicator is the field that is used to test for form submission
+ indicator: submit
+ # Start listing the form elements
+ elements:
+ # The first element will be a text field for the title
+ - type: Text
+ name: title
+ label: Title
+ # This is an optional 'mouse over' title pop-up
+ attributes:
+ title: Enter a book title here
+
+ # Another text field for the numeric rating
+ - type: Text
+ name: rating
+ label: Rating
+ attributes:
+ title: Enter a rating between 1 and 5 here
+
+ # Add a drop-down list for the author selection. Note that we will
+ # dynamically fill in all the authors from the controller but we
+ # could manually set items in the drop-list by adding this YAML code:
+ # options:
+ # - [ '1', 'Bastien' ]
+ # - [ '2', 'Nasseh' ]
+ - type: Select
+ name: authors
+ label: Author
+
+ # The submit button
+ - type: Submit
+ name: submit
+ value: Submit
+
+B<NOTE:> Copying and pasting YAML from perl documentation is sometimes
+tricky. See the L<Config::General Config for this tutorial> section of
+this document for a more foolproof config format.
+
+
+=head2 Update the CSS
+
+Edit C<root/static/css/main.css> and add the following lines to the bottom of
+the file:
+
+ input {
+ display: block;
+ }
+ select {
+ display: block;
+ }
+ .submit {
+ padding-top: .5em;
+ display: block;
+ }
+
+These changes will display form elements vertically. Note that the
+existing definition of the C<.error> class is pulling the color scheme
+settings from the C<root/lib/config/col> file that was created by the
+TTSite helper. This allows control over the CSS color settings from a
+single location.
+
+
+=head2 Create a Template Page To Display The Form
+
+Open C<root/src/books/formfu_create.tt2> in your editor and enter the following:
+
+ [% META title = 'Create/Update Book' %]
+
+ [%# Render the HTML::FormFu Form %]
+ [% form %]
+
+ <p><a href="[% c.uri_for('list') %]">Return to book list</a></p>
+
+
+=head2 Add Links for Create and Update via C<HTML::FormFu>
+
+Open C<root/src/books/list.tt2> in your editor and add the following to
+the bottom of the existing file:
+
+ <p>
+ HTML::FormFu:
+ <a href="[% c.uri_for('formfu_create') %]">Create</a>
+ </p>
+
+This adds a new link to the bottom of the book list page that we can
+use to easily launch our HTML::FormFu-based form.
+
+
+=head2 Test The <HTML::FormFu> Create Form
+
+Press C<Ctrl-C> to kill the previous server instance (if it's still
+running) and restart it:
+
+ $ script/myapp_server.pl
+
+Login as C<test01> (password: mypass). Once at the Book List page,
+click the new HTML::FormFu "Create" link at the bottom to display the
+form. Fill in the following values: Title = "Internetworking with
+TCP/IP Vol. II", Rating = "4", and Author = "Comer". Click Submit,
+and you will be returned to the Book List page with a "Book created"
+status message displayed.
+
+Also note that this implementation allows you to can create books with
+bogus information. Although we have constrained the authors with the
+drop-down list (note that this isn't bulletproof because we still have
+not prevented a user from "hacking" the form to specify other values),
+there are no restrictions on items such as the length of the title (for
+example, you can create a one-letter title) and value for the rating
+(you can use any number you want, and even non-numeric values with
+SQLite). The next section will address this concern.
+
+B<Note:> Depending on the database you are using and how you established
+the columns in your tables, the database could obviously provide various
+levels of "type enforcement" on your data. The key point being made in
+the previous paragraph is that the I<web application> itself is not
+performing any validation.
+
+
+=head1 C<HTML::FormFu> VALIDATION AND FILTERING
+
+Although the use of L<HTML::FormFu|HTML::FormFu> in the previous section
+did provide an automated mechanism to build the form, the real power of
+this module stems from functionality that can automatically validate and
+filter the user input. Validation uses constraints to be sure that
+users input appropriate data (for example, that the email field of a
+form contains a valid email address). Filtering can also be used to
+remove extraneous whitespace from fields or to escape meta-characters in
+user input.
+
+
+=head2 Add Constraints
+
+Open C<root/forms/books/formfu_create.yml> in your editor and update it
+to match:
+
+ ---
+ # indicator is the field that is used to test for form submission
+ indicator: submit
+ # Start listing the form elements
+ elements:
+ # The first element will be a text field for the title
+ - type: Text
+ name: title
+ label: Title
+ # This is an optional 'mouse over' title pop-up
+ attributes:
+ title: Enter a book title here
+ # Add constraints for the field
+ constraints:
+ # Force the length to be between 5 and 40 chars
+ - type: Length
+ min: 5
+ max: 40
+ # Override the default of 'Invalid input'
+ message: Length must be between 5 and 40 characters
+
+ # Another text field for the numeric rating
+ - type: Text
+ name: rating
+ label: Rating
+ attributes:
+ title: Enter a rating between 1 and 5 here
+ # Use Filter to clean up the input data
+ # Could use 'NonNumeric' below, but since Filters apply *before*
+ # constraints, it would conflict with the 'Integer' constraint below.
+ # So let's skip this and just use the constraint.
+ #filter:
+ # Remove everything except digits
+ #- NonNumeric
+ # Add constraints to the field
+ constraints:
+ # Make sure it's a number
+ - type: Integer
+ message: "Required. Digits only, please."
+ # Check the min & max values
+ - type: Range
+ min: 1
+ max: 5
+ message: "Must be between 1 and 5."
+
+ # Add a select list for the author selection. Note that we will
+ # dynamically fill in all the authors from the controller but we
+ # could manually set items in the select by adding this YAML code:
+ # options:
+ # - [ '1', 'Bastien' ]
+ # - [ '2', 'Nasseh' ]
+ - type: Select
+ name: authors
+ label: Author
+ # Convert the drop-down to a multi-select list
+ multiple: 1
+ # Display 3 entries (user can scroll to see others)
+ size: 3
+ # One could argue we don't need to do filters or constraints for
+ # a select list, but it's smart to do validation and sanity
+ # checks on this data in case a user "hacks" the input
+ # Add constraints to the field
+ constraints:
+ # Make sure it's a number
+ - Integer
+
+ # The submit button
+ - type: Submit
+ name: submit
+ value: Submit
+
+ # Global filters and constraints.
+ constraints:
+ # The user cannot leave any fields blank
+ - Required
+ # If not all fields are required, move the Required constraint to the
+ # fields that are
+ filter:
+ # Remove whitespace at both ends
+ - TrimEdges
+ # Escape HTML characters for safety
+ - HTMLEscape
+
+B<NOTE:> Copying and pasting YAML from perl documentation is sometimes
+tricky. See the L<Config::General Config for this tutorial> section of
+this document for a more foolproof config format.
+
+The main changes are:
+
+=over 4
+
+=item *
+
+The C<Select> element for C<authors> is changed from a single-select
+drop-down to a multi-select list by adding configuration for the
+C<multiple> and C<size> options in C<formfu_create.yml>.
+
+=item *
+
+Constraints are added to provide validation of the user input. See
+L<HTML::FormFu::Constraint|HTML::FormFu::Constraint> for other
+constraints that are available.
+
+=item *
+
+A variety of filters are run on every field to remove and escape
+unwanted input. See L<HTML::FormFu::Filter|HTML::FormFu::Filter>
+for more filter options.
+
+=back
+
+
+=head2 Try Out the Updated Form
+
+Press C<Ctrl-C> to kill the previous server instance (if it's still
+running) and restart it:
+
+ $ script/myapp_server.pl
+
+Make sure you are still logged in as C<test01> and try adding a book
+with various errors: title less than 5 characters, non-numeric rating, a
+rating of 0 or 6, etc. Also try selecting one, two, and zero authors.
+When you click Submit, the HTML::FormFu C<constraint> items will
+validate the logic and insert feedback as appropriate. Try adding blank
+spaces at the front or the back of the title and note that it will be
+removed.
+
+
+=head1 CREATE AND UPDATE/EDIT ACTION
+
+Let's expand the work done above to add an edit action. First, open
+C<lib/MyApp/Controller/Books.pm> and add the following method to the
+bottom:
+
+ =head2 formfu_edit
+
+ Use HTML::FormFu to update an existing book
+
+ =cut
+
+ sub formfu_edit :Local :FormConfig('books/formfu_create.yml') {
+ my ($self, $c, $id) = @_;
+
+ # Get the specified book
+ my $book = $c->model('DB::Books')->find($id);
+
+ # Make sure we were able to get a book
+ unless ($book) {
+ $c->flash->{error_msg} = "Invalid book -- Cannot edit";
+ $c->response->redirect($c->uri_for('list'));
+ $c->detach;
+ }
+
+ # Get the form that the :FormConfig attribute saved in the stash
+ my $form = $c->stash->{form};
+
+ # Check if the form has been submitted (vs. displaying the initial
+ # form) and if the data passed validation. "submitted_and_valid"
+ # is shorthand for "$form->submitted && !$form->has_errors"
+ if ($form->submitted_and_valid) {
+ # Save the form data for the book
+ $form->save_to_model($book);
+ # Set a status message for the user
+ $c->flash->{status_msg} = 'Book edited';
+ # Return to the books list
+ $c->response->redirect($c->uri_for('list'));
+ $c->detach;
+ } else {
+ # Get the authors from the DB
+ my @authorObjs = $c->model("DB::Authors")->all();
+ # Create an array of arrayrefs where each arrayref is an author
+ my @authors;
+ foreach (sort {$a->last_name cmp $b->last_name} @authorObjs) {
+ push(@authors, [$_->id, $_->last_name]);
+ }
+ # Get the select added by the config file
+ my $select = $form->get_element({type => 'Select'});
+ # Add the authors to it
+ $select->options(\@authors);
+ # Populate the form with existing values from DB
+ $form->defaults_from_model($book);
+ }
+
+ # Set the template
+ $c->stash->{template} = 'books/formfu_create.tt2';
+ }
+
+Most of this code should look familiar to what we used in the
+C<formfu_create> method (in fact, we should probably centralize some of
+the common code in separate methods). The main differences are:
+
+=over 4
+
+=item *
+
+We accept C<$id> as an argument via the URL.
+
+=item *
+
+We use C<$id> to look up the existing book from the database.
+
+=item *
+
+We make sure the C<$id> and book lookup returned a valid book. If not,
+we set the error message and return to the book list.
+
+=item *
+
+If the form has been submitted and passes validation, we skip creating a
+new book and just use C<$form-E<gt>save_to_model> to update the existing
+book.
+
+=item *
+
+If the form is being displayed for the first time (or has failed
+validation and it being redisplayed), we use
+ C<$form-E<gt>default_from_model> to populate the form with data from the
+database.
+
+=back
+
+Then, edit C<root/src/books/list.tt2> and add a new link below the
+existing "Delete" link that allows us to edit/update each existing book.
+The last E<lt>tdE<gt> cell in the book list table should look like the
+following:
+
+ ...
+ <td>
+ [% # Add a link to delete a book %]
+ <a href="[% c.uri_for('delete', book.id) %]">Delete</a>
+ [% # Add a link to edit a book %]
+ <a href="[% c.uri_for('formfu_edit', book.id) %]">Edit</a>
+ </td>
+ ...
+
+B<Note:> Only add two lines (the "Add a link to edit a book" comment
+and the href for C<formfu_edit>). Make sure you add it below the
+existing C<delete> link.
+
+
+=head2 Try Out the Edit/Update Feature
+
+Press C<Ctrl-C> to kill the previous server instance (if it's still
+running) and restart it:
+
+ $ script/myapp_server.pl
+
+Make sure you are still logged in as C<test01> and go to the
+L<http://localhost:3000/books/list> URL in your browser. Click the
+"Edit" link next to "Internetworking with TCP/IP Vol. II", change the
+rating to a 3, the "II" at end of the title to the number "2", add
+Stevens as a co-author (control-click), and click Submit. You will then
+be returned to the book list with a "Book edited" message at the top in
+green. Experiment with other edits to various books.
+
+
+=head2 Config::General Config for this tutorial
+
+If you are having difficulty with YAML config above, please save the
+below into the file C<formfu_create.conf> and delete the
+C<formfu_create.yml> file. The below is in
+L<Config::General|Config::General> format which follows the syntax of
+Apache config files.
+
+ constraints Required
+ <elements>
+ <constraints>
+ min 5
+ max 40
+ type Length
+ message Length must be between 5 and 40 characters
+ </constraints>
+ filter TrimEdges
+ filter HTMLEscape
+ name title
+ type Text
+ label Title
+ <attributes>
+ title Enter a book title here
+ </attributes>
+ </elements>
+ <elements>
+ constraints Integer
+ filter TrimEdges
+ filter NonNumeric
+ name rating
+ type Text
+ label Rating
+ <attributes>
+ title Enter a rating between 1 and 5 here
+ </attributes>
+ </elements>
+ <elements>
+ constraints Integer
+ filter TrimEdges
+ filter HTMLEscape
+ name authors
+ type Select
+ label Author
+ multiple 1
+ size 3
+ </elements>
+ <elements>
+ value Submit
+ name submit
+ type Submit
+ </elements>
+ indicator submit
+
+
+
+=head1 AUTHOR
+
+Kennedy Clark, C<hkclark@gmail.com>
+
+Please report any errors, issues or suggestions to the author. The
+most recent version of the Catalyst Tutorial can be found at
+L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/>.
+
+Copyright 2006-2008, Kennedy Clark, under Creative Commons License
+(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).