3 Catalyst::Manual::Tutorial::09_AdvancedCRUD::09_FormFu - Catalyst Tutorial - Chapter 9: Advanced CRUD - FormFu
8 This is B<Chapter 9 of 10> for the Catalyst tutorial.
10 L<Tutorial Overview|Catalyst::Manual::Tutorial>
16 L<Introduction|Catalyst::Manual::Tutorial::01_Intro>
20 L<Catalyst Basics|Catalyst::Manual::Tutorial::02_CatalystBasics>
24 L<More Catalyst Basics|Catalyst::Manual::Tutorial::03_MoreCatalystBasics>
28 L<Basic CRUD|Catalyst::Manual::Tutorial::04_BasicCRUD>
32 L<Authentication|Catalyst::Manual::Tutorial::05_Authentication>
36 L<Authorization|Catalyst::Manual::Tutorial::06_Authorization>
40 L<Debugging|Catalyst::Manual::Tutorial::07_Debugging>
44 L<Testing|Catalyst::Manual::Tutorial::08_Testing>
48 B<09_Advanced CRUD::09_FormFu>
52 L<Appendices|Catalyst::Manual::Tutorial::10_Appendices>
59 This portion of the tutorial explores L<HTML::FormFu|HTML::FormFu> and
60 how it can be used to manage forms, perform validation of form input,
61 as well as save and restore data to/from the database. This was written
62 using HTML::FormFu version 0.03007.
65 L<Catalyst::Manual::Tutorial::09_AdvancedCRUD|Catalyst::Manual::Tutorial::09_AdvancedCRUD>
66 for additional form management options other than
67 L<HTML::FormFu|HTML::FormFu>.
70 =head1 Install HTML::FormFu
72 If you are following along in Debian 5, it turns out that some of the
73 modules we need are not yet available as Debian packages at the time
74 this was written. To install it with a combination of Debian packages
75 and traditional CPAN modules, first use C<aptitude> to install most of
78 we need to install the
79 L<HTML::FormFu|HTML::FormFu> package:
81 sudo aptitude -y install libhtml-formfu-perl libmoose-perl \
82 libregexp-assemble-perl libhtml-formfu-model-dbic-perl
88 Then use the following command to install directly from CPAN the modules
89 that aren't available as Debian packages:
91 sudo cpan Catalyst::Component::InstancePerContext Catalyst::Controller::HTML::FormFu
94 =head1 HTML::FormFu FORM CREATION
96 This section looks at how L<HTML::FormFu|HTML::FormFu> can be used to
97 add additional functionality to the manually created form from Chapter 4.
100 =head2 Inherit From Catalyst::Controller::HTML::FormFu
102 First, change your C<lib/MyApp/Controller/Books.pm> to inherit from
103 L<Catalyst::Controller::HTML::FormFu|Catalyst::Controller::HTML::FormFu>
104 by changing the C<use parent> line from the default of:
106 use parent 'Catalyst::Controller';
108 to use the FormFu base controller class:
110 use parent 'Catalyst::Controller::HTML::FormFu';
113 =head2 Add Action to Display and Save the Form
115 Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
120 Use HTML::FormFu to create a new book
124 sub formfu_create :Chained('base') :PathPart('formfu_create') :Args(0) :FormConfig {
127 # Get the form that the :FormConfig attribute saved in the stash
128 my $form = $c->stash->{form};
130 # Check if the form has been submitted (vs. displaying the initial
131 # form) and if the data passed validation. "submitted_and_valid"
132 # is shorthand for "$form->submitted && !$form->has_errors"
133 if ($form->submitted_and_valid) {
135 my $book = $c->model('DB::Book')->new_result({});
136 # Save the form data for the book
137 $form->model->update($book);
138 # Set a status message for the user
139 $c->flash->{status_msg} = 'Book created';
140 # Return to the books list
141 $c->response->redirect($c->uri_for($self->action_for('list')));
144 # Get the authors from the DB
145 my @author_objs = $c->model("DB::Author")->all();
146 # Create an array of arrayrefs where each arrayref is an author
148 foreach (sort {$a->last_name cmp $b->last_name} @author_objs) {
149 push(@authors, [$_->id, $_->last_name]);
151 # Get the select added by the config file
152 my $select = $form->get_element({type => 'Select'});
153 # Add the authors to it
154 $select->options(\@authors);
158 $c->stash->{template} = 'books/formfu_create.tt2';
162 =head2 Create a Form Config File
164 Although C<HTML::FormFu> supports any configuration file handled by
165 L<Config::Any|Config::Any>, most people tend to use YAML. First
166 create a directory to hold your form configuration files:
168 mkdir -p root/forms/books
170 Then create the file C<root/forms/books/formfu_create.yml> and enter the
174 # indicator is the field that is used to test for form submission
176 # Start listing the form elements
178 # The first element will be a text field for the title
182 # This is an optional 'mouse over' title pop-up
184 title: Enter a book title here
186 # Another text field for the numeric rating
191 title: Enter a rating between 1 and 5 here
193 # Add a drop-down list for the author selection. Note that we will
194 # dynamically fill in all the authors from the controller but we
195 # could manually set items in the drop-list by adding this YAML code:
197 # - [ '1', 'Bastien' ]
198 # - [ '2', 'Nasseh' ]
208 B<NOTE:> Copying and pasting YAML from perl documentation is sometimes
209 tricky. See the L<Config::General Config for this tutorial> section of
210 this document for a more foolproof config format.
213 =head2 Update the CSS
215 Edit C<root/static/css/main.css> and add the following lines to the bottom of
230 These changes will display form elements vertically. Note that the
231 existing definition of the C<.error> class is pulling the color scheme
232 settings from the C<root/lib/config/col> file that was created by the
233 TTSite helper. This allows control over the CSS color settings from a
237 =head2 Create a Template Page To Display The Form
239 Open C<root/src/books/formfu_create.tt2> in your editor and enter the following:
241 [% META title = 'Create/Update Book' %]
243 [%# Render the HTML::FormFu Form %]
246 <p><a href="[% c.uri_for(c.controller.action_for('list')) %]">Return to book list</a></p>
249 =head2 Add Links for Create and Update via C<HTML::FormFu>
251 Open C<root/src/books/list.tt2> in your editor and add the following to
252 the bottom of the existing file:
257 <a href="[% c.uri_for(c.controller.action_for('formfu_create')) %]">Create</a>
260 This adds a new link to the bottom of the book list page that we can
261 use to easily launch our HTML::FormFu-based form.
264 =head2 Test The HTML::FormFu Create Form
266 Press C<Ctrl-C> to kill the previous server instance (if it's still
267 running) and restart it:
269 $ script/myapp_server.pl
271 Login as C<test01> (password: mypass). Once at the Book List page,
272 click the new HTML::FormFu "Create" link at the bottom to display the
273 form. Fill in the following values:
275 Title = "Internetworking with TCP/IP Vol. II"
279 Click the Submit button, and you will be returned to the Book List page
280 with a "Book created" status message displayed.
282 Also note that this implementation allows you to create books with any
283 bogus information. Although we have constrained the authors with the
284 drop-down list (note that this isn't bulletproof because we still have
285 not prevented a user from "hacking" the form to specify other values),
286 there are no restrictions on items such as the length of the title (for
287 example, you can create a one-letter title) and the value of the rating
288 (you can use any number you want, and even non-numeric values with
289 SQLite). The next section will address this concern.
291 B<Note:> Depending on the database you are using and how you established
292 the columns in your tables, the database could obviously provide various
293 levels of "type enforcement" on your data. The key point being made in
294 the previous paragraph is that the I<web application> itself is not
295 performing any validation.
298 =head1 HTML::FormFu VALIDATION AND FILTERING
300 Although the use of L<HTML::FormFu|HTML::FormFu> in the previous section
301 did provide an automated mechanism to build the form, the real power of
302 this module stems from functionality that can automatically validate and
303 filter the user input. Validation uses constraints to be sure that
304 users input appropriate data (for example, that the email field of a
305 form contains a valid email address). Filtering can also be used to
306 remove extraneous whitespace from fields or to escape meta-characters in
310 =head2 Add Constraints
312 Open C<root/forms/books/formfu_create.yml> in your editor and update it
316 # indicator is the field that is used to test for form submission
318 # Start listing the form elements
320 # The first element will be a text field for the title
324 # This is an optional 'mouse over' title pop-up
326 title: Enter a book title here
327 # Add constraints for the field
329 # Force the length to be between 5 and 40 chars
333 # Override the default of 'Invalid input'
334 message: Length must be between 5 and 40 characters
336 # Another text field for the numeric rating
341 title: Enter a rating between 1 and 5 here
342 # Use Filter to clean up the input data
343 # Could use 'NonNumeric' below, but since Filters apply *before*
344 # constraints, it would conflict with the 'Integer' constraint below.
345 # So let's skip this and just use the constraint.
347 # Remove everything except digits
349 # Add constraints to the field
351 # Make sure it's a number
353 message: "Required. Digits only, please."
354 # Check the min & max values
358 message: "Must be between 1 and 5."
360 # Add a select list for the author selection. Note that we will
361 # dynamically fill in all the authors from the controller but we
362 # could manually set items in the select by adding this YAML code:
364 # - [ '1', 'Bastien' ]
365 # - [ '2', 'Nasseh' ]
369 # Convert the drop-down to a multi-select list
371 # Display 3 entries (user can scroll to see others)
373 # One could argue we don't need to do filters or constraints for
374 # a select list, but it's smart to do validation and sanity
375 # checks on this data in case a user "hacks" the input
376 # Add constraints to the field
378 # Make sure it's a number
386 # Global filters and constraints.
388 # The user cannot leave any fields blank
390 # If not all fields are required, move the Required constraint to the
393 # Remove whitespace at both ends
395 # Escape HTML characters for safety
398 B<NOTE:> Copying and pasting YAML from perl documentation is sometimes
399 tricky. See the L<Config::General Config for this tutorial> section of
400 this document for a more foolproof config format.
402 The main changes are:
408 The C<Select> element for C<authors> is changed from a single-select
409 drop-down to a multi-select list by adding configuration for the
410 C<multiple> and C<size> options in C<formfu_create.yml>.
414 Constraints are added to provide validation of the user input. See
415 L<HTML::FormFu::Constraint|HTML::FormFu::Constraint> for other
416 constraints that are available.
420 A variety of filters are run on every field to remove and escape
421 unwanted input. See L<HTML::FormFu::Filter|HTML::FormFu::Filter>
422 for more filter options.
427 =head2 Try Out the Updated Form
429 Press C<Ctrl-C> to kill the previous server instance (if it's still
430 running) and restart it:
432 $ script/myapp_server.pl
434 Make sure you are still logged in as C<test01> and try adding a book
435 with various errors: title less than 5 characters, non-numeric rating, a
436 rating of 0 or 6, etc. Also try selecting one, two, and zero authors.
437 When you click Submit, the HTML::FormFu C<constraint> items will
438 validate the logic and insert feedback as appropriate. Try adding blank
439 spaces at the front or the back of the title and note that it will be
443 =head1 CREATE AND UPDATE/EDIT ACTION
445 Let's expand the work done above to add an edit action. First, open
446 C<lib/MyApp/Controller/Books.pm> and add the following method to the
451 Use HTML::FormFu to update an existing book
455 sub formfu_edit :Chained('object') :PathPart('formfu_edit') :Args(0)
456 :FormConfig('books/formfu_create.yml') {
459 # Get the specified book already saved by the 'object' method
460 my $book = $c->stash->{object};
462 # Make sure we were able to get a book
464 $c->flash->{error_msg} = "Invalid book -- Cannot edit";
465 $c->response->redirect($c->uri_for($self->action_for('list')));
469 # Get the form that the :FormConfig attribute saved in the stash
470 my $form = $c->stash->{form};
472 # Check if the form has been submitted (vs. displaying the initial
473 # form) and if the data passed validation. "submitted_and_valid"
474 # is shorthand for "$form->submitted && !$form->has_errors"
475 if ($form->submitted_and_valid) {
476 # Save the form data for the book
477 $form->model->update($book);
478 # Set a status message for the user
479 $c->flash->{status_msg} = 'Book edited';
480 # Return to the books list
481 $c->response->redirect($c->uri_for($self->action_for('list')));
484 # Get the authors from the DB
485 my @author_objs = $c->model("DB::Author")->all();
486 # Create an array of arrayrefs where each arrayref is an author
488 foreach (sort {$a->last_name cmp $b->last_name} @author_objs) {
489 push(@authors, [$_->id, $_->last_name]);
491 # Get the select added by the config file
492 my $select = $form->get_element({type => 'Select'});
493 # Add the authors to it
494 $select->options(\@authors);
495 # Populate the form with existing values from DB
496 $form->model->default_values($book);
500 $c->stash->{template} = 'books/formfu_create.tt2';
503 Most of this code should look familiar to what we used in the
504 C<formfu_create> method (in fact, we should probably centralize some of
505 the common code in separate methods). The main differences are:
511 We have to manually specify the name of the FormFu .yml file as an
512 argument to C<:FormConfig> because the name can no longer be
513 automatically deduced from the name of our action/method (by default,
514 FormFu would look for a file named C<books/formfu_edit.yml>).
518 We load the book object from the stash (found using the $id passed to
519 the Chained object method)
523 We use C<$id> to look up the existing book from the database.
527 We make sure the book lookup returned a valid book. If not, we set
528 the error message and return to the book list.
532 If the form has been submitted and passes validation, we skip creating a
533 new book and just use C<$form-E<gt>model-E<gt>update> to update the existing
538 If the form is being displayed for the first time (or has failed
539 validation and it being redisplayed), we use
540 C<$form-E<gt>model-E<gt>default_values> to populate the form with data from the
545 Then, edit C<root/src/books/list.tt2> and add a new link below the
546 existing "Delete" link that allows us to edit/update each existing book.
547 The last E<lt>tdE<gt> cell in the book list table should look like the
552 [% # Add a link to delete a book %]
553 <a href="[% c.uri_for(c.controller.action_for('delete'), [book.id]) %]">Delete</a>
554 [% # Add a link to edit a book %]
555 <a href="[% c.uri_for(c.controller.action_for('formfu_edit'), [book.id]) %]">Edit</a>
559 B<Note:> Only add two lines (the "Add a link to edit a book" comment
560 and the href for C<formfu_edit>). Make sure you add it below the
561 existing C<delete> link.
564 =head2 Try Out the Edit/Update Feature
566 Press C<Ctrl-C> to kill the previous server instance (if it's still
567 running) and restart it:
569 $ script/myapp_server.pl
571 Make sure you are still logged in as C<test01> and go to the
572 L<http://localhost:3000/books/list> URL in your browser. Click the
573 "Edit" link next to "Internetworking with TCP/IP Vol. II", change the
574 rating to a 3, the "II" at end of the title to the number "2", add
575 Stevens as a co-author (control-click), and click Submit. You will then
576 be returned to the book list with a "Book edited" message at the top in
577 green. Experiment with other edits to various books.
580 =head2 More Things to Try
582 You are now armed with enough knowledge to be dangerous. You can keep
583 tweaking the example application; some things you might want to do:
589 Add an appropriate authorization check to the new Edit function.
593 Cleanup the List page so that the Login link only displays when the user
594 isn't logged in and the Logout link only displays when a user is logged
599 Add a more sensible policy for when and how users and admins can do
600 things in the CRUD cycle.
604 Support the CRUD cycle for authors.
608 Or you can proceed to write your own application, which is probably the
609 real reason you worked through this Tutorial in the first place.
612 =head2 Config::General Config for this tutorial
614 If you are having difficulty with YAML config above, please save the
615 below into the file C<formfu_create.conf> and delete the
616 C<formfu_create.yml> file. The below is in
617 L<Config::General|Config::General> format which follows the syntax of
626 message Length must be between 5 and 40 characters
634 title Enter a book title here
645 title Enter a rating between 1 and 5 here
668 Kennedy Clark, C<hkclark@gmail.com>
670 Please report any errors, issues or suggestions to the author. The
671 most recent version of the Catalyst Tutorial can be found at
672 L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/>.
674 Copyright 2006-2008, Kennedy Clark, under Creative Commons License
675 (L<http://creativecommons.org/licenses/by-sa/3.0/us/>).