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> and how it can be
60 used to manage forms, perform validation of form input, as well as save
61 and restore data to/from the database.
63 See L<Catalyst::Manual::Tutorial::09_AdvancedCRUD> for additional form
64 management options other than L<HTML::FormFu>.
66 Source code for the tutorial in included in the F</home/catalyst/Final>
67 directory of the Tutorial Virtual machine (one subdirectory per
68 chapter). There are also instructions for downloading the code in
69 L<Catalyst::Manual::Tutorial::01_Intro>.
72 =head1 HTML::FormFu FORM CREATION
74 This section looks at how L<HTML::FormFu> can be used to add additional
75 functionality to the manually created form from
76 L<Chapter 4|Catalyst::Manual::Tutorial::04_BasicCRUD>.
79 =head2 Inherit From Catalyst::Controller::HTML::FormFu
81 First, change your F<lib/MyApp/Controller/Books.pm> to inherit from
82 L<Catalyst::Controller::HTML::FormFu> by changing the C<extends> line
85 BEGIN {extends 'Catalyst::Controller'; }
87 to use the FormFu base controller class:
89 BEGIN {extends 'Catalyst::Controller::HTML::FormFu'; }
93 requires 'HTML::FormFu';
94 requires 'Catalyst::Controller::HTML::FormFu';
95 requires 'HTML::FormFu::Model::DBIC';
97 to your C<Makefile.PL>.
100 =head2 Add Action to Display and Save the Form
102 Open F<lib/MyApp/Controller/Books.pm> in your editor and add the
107 Use HTML::FormFu to create a new book
111 sub formfu_create :Chained('base') :PathPart('formfu_create') :Args(0) :FormConfig {
114 # Get the form that the :FormConfig attribute saved in the stash
115 my $form = $c->stash->{form};
117 # Check if the form has been submitted (vs. displaying the initial
118 # form) and if the data passed validation. "submitted_and_valid"
119 # is shorthand for "$form->submitted && !$form->has_errors"
120 if ($form->submitted_and_valid) {
122 my $book = $c->model('DB::Book')->new_result({});
123 # Save the form data for the book
124 $form->model->update($book);
125 # Set a status message for the user & return to books list
126 $c->response->redirect($c->uri_for($self->action_for('list'),
127 {mid => $c->set_status_msg("Book created")}));
130 # Get the authors from the DB
131 my @author_objs = $c->model("DB::Author")->all();
132 # Create an array of arrayrefs where each arrayref is an author
134 foreach (sort {$a->last_name cmp $b->last_name} @author_objs) {
135 push(@authors, [$_->id, $_->last_name]);
137 # Get the select added by the config file
138 my $select = $form->get_element({type => 'Select'});
139 # Add the authors to it
140 $select->options(\@authors);
144 $c->stash(template => 'books/formfu_create.tt2');
148 =head2 Create a Form Config File
150 Although L<HTML::FormFu> supports any configuration file handled by
151 L<Config::Any>, most people tend to use YAML. First create a directory
152 to hold your form configuration files:
154 $ mkdir -p root/forms/books
156 Then create the file F<root/forms/books/formfu_create.yml> and enter the
160 # indicator is the field that is used to test for form submission
162 # Start listing the form elements
164 # The first element will be a text field for the title
168 # This is an optional 'mouse over' title pop-up
170 title: Enter a book title here
172 # Another text field for the numeric rating
177 title: Enter a rating between 1 and 5 here
179 # Add a drop-down list for the author selection. Note that we will
180 # dynamically fill in all the authors from the controller but we
181 # could manually set items in the drop-list by adding this YAML code:
183 # - [ '1', 'Bastien' ]
184 # - [ '2', 'Nasseh' ]
194 B<NOTE:> Copying and pasting YAML from Perl documentation is sometimes
195 tricky. See the L</Config::General Config for this tutorial> section of
196 this document for a more foolproof config format.
199 =head2 Update the CSS
201 Edit F<root/static/css/main.css> and add the following lines to the
216 These changes will display form elements vertically.
219 =head2 Create a Template Page To Display The Form
221 Open F<root/src/books/formfu_create.tt2> in your editor and enter the
224 [% META title = 'Create/Update Book' %]
226 [%# Render the HTML::FormFu Form %]
229 <p><a href="[% c.uri_for(c.controller.action_for('list'))
230 %]">Return to book list</a></p>
233 =head2 Add Links for Create and Update via C<HTML::FormFu>
235 Open F<root/src/books/list.tt2> in your editor and add the following to
236 the bottom of the existing file:
241 <a href="[% c.uri_for(c.controller.action_for('formfu_create')) %]">Create</a>
244 This adds a new link to the bottom of the book list page that we can use
245 to easily launch our HTML::FormFu-based form.
248 =head2 Test The HTML::FormFu Create Form
250 Make sure the server is running with the "-r" restart option:
252 $ script/myapp_server.pl -r
254 Login as C<test01> (password: mypass). Once at the Book List page,
255 click the new HTML::FormFu "Create" link at the bottom to display the
256 form. Fill in the following values:
258 Title: Internetworking with TCP/IP Vol. II
262 Click the "Submit" button, and you will be returned to the Book List page
263 with a "Book created" status message displayed.
265 Also note that this implementation allows you to create books with any
266 bogus information. Although we have constrained the authors with the
267 drop-down list (note that this isn't bulletproof because we still have
268 not prevented a user from "hacking" the form to specify other values),
269 there are no restrictions on items such as the length of the title (for
270 example, you can create a one-letter title) and the value of the rating
271 (you can use any number you want, and even non-numeric values with
272 SQLite). The next section will address this concern.
274 B<Note:> Depending on the database you are using and how you established
275 the columns in your tables, the database could obviously provide various
276 levels of "type enforcement" on your data. The key point being made in
277 the previous paragraph is that the I<web application> itself is not
278 performing any validation.
281 =head1 HTML::FormFu VALIDATION AND FILTERING
283 Although the use of L<HTML::FormFu> in the previous section did provide
284 an automated mechanism to build the form, the real power of this module
285 stems from functionality that can automatically validate and filter the
286 user input. Validation uses constraints to be sure that users input
287 appropriate data (for example, that the email field of a form contains a
288 valid email address). Filtering can also be used to remove extraneous
289 whitespace from fields or to escape meta-characters in user input.
292 =head2 Add Constraints
294 Open F<root/forms/books/formfu_create.yml> in your editor and update it
298 # indicator is the field that is used to test for form submission
300 # Start listing the form elements
302 # The first element will be a text field for the title
306 # This is an optional 'mouse over' title pop-up
308 title: Enter a book title here
309 # Add constraints for the field
311 # Force the length to be between 5 and 40 chars
315 # Override the default of 'Invalid input'
316 message: Length must be between 5 and 40 characters
318 # Another text field for the numeric rating
323 title: Enter a rating between 1 and 5 here
324 # Use Filter to clean up the input data
325 # Could use 'NonNumeric' below, but since Filters apply *before*
326 # constraints, it would conflict with the 'Integer' constraint below.
327 # So let's skip this and just use the constraint.
329 # Remove everything except digits
331 # Add constraints to the field
333 # Make sure it's a number
335 message: "Required. Digits only, please."
336 # Check the min & max values
340 message: "Must be between 1 and 5."
342 # Add a select list for the author selection. Note that we will
343 # dynamically fill in all the authors from the controller but we
344 # could manually set items in the select by adding this YAML code:
346 # - [ '1', 'Bastien' ]
347 # - [ '2', 'Nasseh' ]
351 # Convert the drop-down to a multi-select list
353 # Display 3 entries (user can scroll to see others)
355 # One could argue we don't need to do filters or constraints for
356 # a select list, but it's smart to do validation and sanity
357 # checks on this data in case a user "hacks" the input
358 # Add constraints to the field
360 # Make sure it's a number
368 # Global filters and constraints.
370 # The user cannot leave any fields blank
372 # If not all fields are required, move the Required constraint to the
375 # Remove whitespace at both ends
377 # Escape HTML characters for safety
380 B<NOTE:> Copying and pasting YAML from Perl documentation is sometimes
381 tricky. See the L</Config::General Config for this tutorial> section of
382 this document for a more foolproof config format.
384 The main changes are:
390 The C<Select> element for C<authors> is changed from a single-select
391 drop-down to a multi-select list by adding configuration for the
392 C<multiple> and C<size> options in F<formfu_create.yml>.
396 Constraints are added to provide validation of the user input. See
397 L<HTML::FormFu::Constraint> for other constraints that are available.
401 A variety of filters are run on every field to remove and escape
402 unwanted input. See L<HTML::FormFu::Filter> for more filter options.
407 =head2 Try Out the Updated Form
409 Make sure you are still logged in as C<test01> and try adding a book
410 with various errors: title less than 5 characters, non-numeric rating, a
411 rating of 0 or 6, etc. Also try selecting one, two, and zero authors.
412 When you click Submit, the HTML::FormFu C<constraint> items will
413 validate the logic and insert feedback as appropriate. Try adding blank
414 spaces at the front or the back of the title and note that it will be
417 Note that you can update your FormFu YAML forms and the development
418 server does not need to reload -- the form definition is read from
419 the YAML file each time a controller action uses it.
422 =head1 CREATE AND UPDATE/EDIT ACTION
424 Let's expand the work done above to add an edit action. First, open
425 F<lib/MyApp/Controller/Books.pm> and add the following method to the
430 Use HTML::FormFu to update an existing book
434 sub formfu_edit :Chained('object') :PathPart('formfu_edit') :Args(0)
435 :FormConfig('books/formfu_create.yml') {
438 # Get the specified book already saved by the 'object' method
439 my $book = $c->stash->{object};
441 # Make sure we were able to get a book
443 # Set an error message for the user & return to books list
444 $c->response->redirect($c->uri_for($self->action_for('list'),
445 {mid => $c->set_error_msg("Invalid book -- Cannot edit")}));
449 # Get the form that the :FormConfig attribute saved in the stash
450 my $form = $c->stash->{form};
452 # Check if the form has been submitted (vs. displaying the initial
453 # form) and if the data passed validation. "submitted_and_valid"
454 # is shorthand for "$form->submitted && !$form->has_errors"
455 if ($form->submitted_and_valid) {
456 # Save the form data for the book
457 $form->model->update($book);
458 # Set a status message for the user
459 # Set a status message for the user & return to books list
460 $c->response->redirect($c->uri_for($self->action_for('list'),
461 {mid => $c->set_status_msg("Book edited")}));
464 # Get the authors from the DB
465 my @author_objs = $c->model("DB::Author")->all();
466 # Create an array of arrayrefs where each arrayref is an author
468 foreach (sort {$a->last_name cmp $b->last_name} @author_objs) {
469 push(@authors, [$_->id, $_->last_name]);
471 # Get the select added by the config file
472 my $select = $form->get_element({type => 'Select'});
473 # Add the authors to it
474 $select->options(\@authors);
475 # Populate the form with existing values from DB
476 $form->model->default_values($book);
480 $c->stash(template => 'books/formfu_create.tt2');
483 Most of this code should look familiar to what we used in the
484 C<formfu_create> method (in fact, we should probably centralize some of
485 the common code in separate methods). The main differences are:
491 We have to manually specify the name of the FormFu .yml file as an
492 argument to C<:FormConfig> because the name can no longer be
493 automatically deduced from the name of our action/method (by default,
494 FormFu would look for a file named F<books/formfu_edit.yml>).
498 We load the book object from the stash (found using the $id passed to
499 the Chained object method)
503 We use C<$id> to look up the existing book from the database.
507 We make sure the book lookup returned a valid book. If not, we set the
508 error message and return to the book list.
512 If the form has been submitted and passes validation, we skip creating a
513 new book and just use C<< $form->model->update >> to update the
518 If the form is being displayed for the first time (or has failed
519 validation and it being redisplayed), we use
520 C<< $form->model->default_values >> to populate the form with data
525 Then, edit F<root/src/books/list.tt2> and add a new link below the
526 existing "Delete" link that allows us to edit/update each existing book.
527 The last <td> cell in the book list table should look like the
532 [% # Add a link to delete a book %]
534 c.uri_for(c.controller.action_for('delete'), [book.id]) %]">Delete</a>
535 [% # Add a link to edit a book %]
537 c.uri_for(c.controller.action_for('formfu_edit'), [book.id]) %]">Edit</a>
541 B<Note:> Only add three lines (the "Add a link to edit a book" comment and
542 the href for C<formfu_edit>). Make sure you add it below the existing
546 =head2 Try Out the Edit/Update Feature
548 Make sure you are still logged in as C<test01> and go to the
549 L<http://localhost:3000/books/list> URL in your browser. Click the
550 "Edit" link next to "Internetworking with TCP/IP Vol. II", change the
551 rating to a 3, the "II" at end of the title to the number "2", add
552 Stevens as a co-author (control-click), and click Submit. You will then
553 be returned to the book list with a "Book edited" message at the top in
554 green. Experiment with other edits to various books.
557 =head2 More Things to Try
559 You are now armed with enough knowledge to be dangerous. You can keep
560 tweaking the example application; some things you might want to do:
566 Add an appropriate authorization check to the new Edit function.
570 Cleanup the List page so that the Login link only displays when the user
571 isn't logged in and the Logout link only displays when a user is logged
576 Add a more sensible policy for when and how users and admins can do
577 things in the CRUD cycle.
581 Support the CRUD cycle for authors.
585 Or you can proceed to write your own application, which is probably the
586 real reason you worked through this Tutorial in the first place.
589 =head2 Config::General Config for this tutorial
591 If you are having difficulty with YAML config above, please save the
592 below into the file F<formfu_create.conf> and delete the
593 F<formfu_create.yml> file. The below is in L<Config::General> format
594 which follows the syntax of Apache config files.
602 message Length must be between 5 and 40 characters
610 title Enter a book title here
621 title Enter a rating between 1 and 5 here
644 Kennedy Clark, C<hkclark@gmail.com>
646 Feel free to contact the author for any errors or suggestions, but the
647 best way to report issues is via the CPAN RT Bug system at
648 L<https://rt.cpan.org/Public/Dist/Display.html?Name=Catalyst-Manual>.
650 Copyright 2006-2011, Kennedy Clark, under the
651 Creative Commons Attribution Share-Alike License Version 3.0
652 (L<http://creativecommons.org/licenses/by-sa/3.0/us/>).