1c7176d1b0c8236933c715f9e38ad374ac7213f2
[catagits/Catalyst-Manual.git] / lib / Catalyst / Manual / Tutorial / AdvancedCRUD / FormFu.pod
1 =head1 NAME
2
3 Catalyst::Manual::Tutorial::AdvancedCRUD::FormFu - Catalyst Tutorial - Chapter 9: Advanced CRUD - FormFu
4
5
6 =head1 OVERVIEW
7
8 This is B<Chapter 9 of 10> for the Catalyst tutorial.
9
10 L<Tutorial Overview|Catalyst::Manual::Tutorial>
11
12 =over 4
13
14 =item 1
15
16 L<Introduction|Catalyst::Manual::Tutorial::Intro>
17
18 =item 2
19
20 L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
21
22 =item 3
23
24 L<More Catalyst Basics|Catalyst::Manual::Tutorial::MoreCatalystBasics>
25
26 =item 4
27
28 L<Basic CRUD|Catalyst::Manual::Tutorial::BasicCRUD>
29
30 =item 5
31
32 L<Authentication|Catalyst::Manual::Tutorial::Authentication>
33
34 =item 6
35
36 L<Authorization|Catalyst::Manual::Tutorial::Authorization>
37
38 =item 7
39
40 L<Debugging|Catalyst::Manual::Tutorial::Debugging>
41
42 =item 8
43
44 L<Testing|Catalyst::Manual::Tutorial::Testing>
45
46 =item 9
47
48 B<Advanced CRUD::FormFu>
49
50 =item 10
51
52 L<Appendices|Catalyst::Manual::Tutorial::Appendices>
53
54 =back
55
56
57 =head1 DESCRIPTION
58
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.
63
64 See 
65 L<Catalyst::Manual::Tutorial::AdvancedCRUD|Catalyst::Manual::Tutorial::AdvancedCRUD>
66 for additional form management options other than 
67 L<HTML::FormFu|HTML::FormFu>.
68
69
70 =head1 Install HTML::FormFu
71
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 
76 the modules:
77
78 we need to install the
79 L<HTML::FormFu|HTML::FormFu> package: 
80
81     sudo aptitude -y install libhtml-formfu-perl libmoose-perl \
82         libregexp-assemble-perl libhtml-formfu-model-dbic-perl
83         
84     ...
85     
86     sudo aptitude clean
87
88 Then use the following command to install directly from CPAN the modules 
89 that aren't available as Debian packages:
90
91     sudo cpan Catalyst::Component::InstancePerContext Catalyst::Controller::HTML::FormFu
92
93
94 =head1 HTML::FormFu FORM CREATION
95
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.
98
99
100 =head2 Inherit From Catalyst::Controller::HTML::FormFu
101
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:
105
106     use parent 'Catalyst::Controller';
107
108 to use the FormFu base controller class:
109
110     use parent 'Catalyst::Controller::HTML::FormFu';
111
112
113 =head2 Add Action to Display and Save the Form
114
115 Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
116 following method:
117
118     =head2 formfu_create
119     
120     Use HTML::FormFu to create a new book
121     
122     =cut
123     
124     sub formfu_create :Chained('base') :PathPart('formfu_create') :Args(0) :FormConfig {
125         my ($self, $c) = @_;
126     
127         # Get the form that the :FormConfig attribute saved in the stash
128         my $form = $c->stash->{form};
129   
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) {
134             # Create a new book
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'))); 
142             $c->detach;
143         } else {
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
147             my @authors;
148             foreach (sort {$a->last_name cmp $b->last_name} @author_objs) {
149                 push(@authors, [$_->id, $_->last_name]);
150             }
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);
155         }
156         
157         # Set the template
158         $c->stash->{template} = 'books/formfu_create.tt2';
159     }
160
161
162 =head2 Create a Form Config File
163
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:
167
168     mkdir -p root/forms/books
169
170 Then create the file C<root/forms/books/formfu_create.yml> and enter the 
171 following text:
172
173     ---
174     # indicator is the field that is used to test for form submission
175     indicator: submit
176     # Start listing the form elements
177     elements:
178         # The first element will be a text field for the title
179         - type: Text
180           name: title
181           label: Title
182           # This is an optional 'mouse over' title pop-up
183           attributes:
184             title: Enter a book title here
185     
186         # Another text field for the numeric rating
187         - type: Text
188           name: rating
189           label: Rating
190           attributes:
191             title: Enter a rating between 1 and 5 here
192     
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:
196         # options:
197         #   - [ '1', 'Bastien' ]
198         #   - [ '2', 'Nasseh'  ]
199         - type: Select
200           name: authors
201           label: Author
202     
203         # The submit button
204         - type: Submit
205           name: submit
206           value: Submit
207
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.
211
212
213 =head2 Update the CSS
214
215 Edit C<root/static/css/main.css> and add the following lines to the bottom of
216 the file:
217
218     ...
219     input {
220         display: block;
221     }
222     select {
223         display: block;
224     }
225     .submit {
226         padding-top: .5em;
227         display: block;
228     }
229
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 
234 single location.
235
236
237 =head2 Create a Template Page To Display The Form
238
239 Open C<root/src/books/formfu_create.tt2> in your editor and enter the following:
240
241     [% META title = 'Create/Update Book' %]
242     
243     [%# Render the HTML::FormFu Form %]
244     [% form %]
245     
246     <p><a href="[% c.uri_for(c.controller.action_for('list')) %]">Return to book list</a></p>
247
248
249 =head2 Add Links for Create and Update via C<HTML::FormFu>
250
251 Open C<root/src/books/list.tt2> in your editor and add the following to
252 the bottom of the existing file:
253
254     ...
255     <p>
256       HTML::FormFu:
257       <a href="[% c.uri_for(c.controller.action_for('formfu_create')) %]">Create</a>
258     </p>
259
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.
262
263
264 =head2 Test The HTML::FormFu Create Form
265
266 Press C<Ctrl-C> to kill the previous server instance (if it's still
267 running) and restart it:
268
269     $ script/myapp_server.pl
270
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:
274
275     Title  = "Internetworking with TCP/IP Vol. II"
276     Rating = "4"
277     Author = "Comer"
278     
279 Click the Submit button, and you will be returned to the Book List page
280 with a "Book created" status message displayed.
281
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.
290
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.
296
297
298 =head1 HTML::FormFu VALIDATION AND FILTERING
299
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 
307 user input.
308
309
310 =head2 Add Constraints
311
312 Open C<root/forms/books/formfu_create.yml> in your editor and update it 
313 to match:
314
315     ---
316     # indicator is the field that is used to test for form submission
317     indicator: submit
318     # Start listing the form elements
319     elements:
320         # The first element will be a text field for the title
321         - type: Text
322           name: title
323           label: Title
324           # This is an optional 'mouse over' title pop-up
325           attributes:
326             title: Enter a book title here
327           # Add constraints for the field
328           constraints:
329             # Force the length to be between 5 and 40 chars
330             - type: Length
331               min: 5
332               max: 40
333               # Override the default of 'Invalid input'
334               message: Length must be between 5 and 40 characters
335     
336         # Another text field for the numeric rating
337         - type: Text
338           name: rating
339           label: Rating
340           attributes:
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.
346           #filter:
347             # Remove everything except digits
348             #- NonNumeric
349           # Add constraints to the field
350           constraints:
351             # Make sure it's a number
352             - type: Integer
353               message: "Required. Digits only, please."
354             # Check the min & max values
355             - type: Range
356               min: 1
357               max: 5
358               message: "Must be between 1 and 5."
359     
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:
363         # options:
364         #   - [ '1', 'Bastien' ]
365         #   - [ '2', 'Nasseh'  ]
366         - type: Select
367           name: authors
368           label: Author
369           # Convert the drop-down to a multi-select list
370           multiple: 1
371           # Display 3 entries (user can scroll to see others)
372           size: 3
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
377           constraints:
378             # Make sure it's a number
379             - Integer
380     
381         # The submit button
382         - type: Submit
383           name: submit
384           value: Submit
385     
386     # Global filters and constraints.
387     constraints:
388       # The user cannot leave any fields blank
389       - Required
390       # If not all fields are required, move the Required constraint to the 
391       # fields that are
392     filter:
393       # Remove whitespace at both ends
394       - TrimEdges
395       # Escape HTML characters for safety
396       - HTMLEscape
397
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.
401
402 The main changes are:
403
404 =over 4
405
406 =item *
407
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>.
411
412 =item *
413
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.
417
418 =item *
419
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.
423
424 =back
425
426
427 =head2 Try Out the Updated Form
428
429 Press C<Ctrl-C> to kill the previous server instance (if it's still 
430 running) and restart it:
431
432     $ script/myapp_server.pl
433
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 
440 removed.
441
442
443 =head1 CREATE AND UPDATE/EDIT ACTION
444
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 
447 bottom:
448
449     =head2 formfu_edit
450     
451     Use HTML::FormFu to update an existing book
452     
453     =cut
454     
455     sub formfu_edit :Chained('object') :PathPart('formfu_edit') :Args(0) 
456             :FormConfig('books/formfu_create.yml') {
457         my ($self, $c) = @_;
458     
459         # Get the specified book already saved by the 'object' method
460         my $book = $c->stash->{object};
461     
462         # Make sure we were able to get a book
463         unless ($book) {
464             $c->flash->{error_msg} = "Invalid book -- Cannot edit";
465             $c->response->redirect($c->uri_for($self->action_for('list')));
466             $c->detach;
467         }
468     
469         # Get the form that the :FormConfig attribute saved in the stash
470         my $form = $c->stash->{form};
471     
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')));
482             $c->detach;
483         } else {
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
487             my @authors;
488             foreach (sort {$a->last_name cmp $b->last_name} @author_objs) {
489                 push(@authors, [$_->id, $_->last_name]);
490             }
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);
497         }
498     
499         # Set the template
500         $c->stash->{template} = 'books/formfu_create.tt2';
501     }
502
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:
506
507 =over 4
508
509 =item *
510
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>).
515
516 =item *
517
518 We load the book object from the stash (found using the $id passed to 
519 the Chained object method)
520
521 =item *
522
523 We use C<$id> to look up the existing book from the database.
524
525 =item *
526
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.
529  
530 =item *
531
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 
534 book.
535
536 =item *
537
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
541 database.
542
543 =back
544
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 
548 following:
549
550     ...
551     <td>
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>
556     </td>
557     ...
558
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.
562
563
564 =head2 Try Out the Edit/Update Feature
565
566 Press C<Ctrl-C> to kill the previous server instance (if it's still 
567 running) and restart it:
568
569     $ script/myapp_server.pl
570
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.
578
579
580 =head2 More Things to Try
581
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:
584
585 =over 4
586
587 =item *
588
589 Add an appropriate authorization check to the new Edit function.
590
591 =item *
592
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
595 in.
596
597 =item *
598
599 Add a more sensible policy for when and how users and admins can do
600 things in the CRUD cycle.
601
602 =item *
603
604 Support the CRUD cycle for authors.
605
606 =back
607
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.
610
611
612 =head2  Config::General Config for this tutorial
613
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
618 Apache config files.
619
620    constraints   Required
621    <elements>
622        <constraints>
623            min   5
624            max   40
625            type   Length
626            message   Length must be between 5 and 40 characters
627        </constraints>
628        filter   TrimEdges
629        filter   HTMLEscape
630        name   title
631        type   Text
632        label   Title
633        <attributes>
634            title   Enter a book title here
635        </attributes>
636    </elements>
637    <elements>
638        constraints   Integer
639        filter   TrimEdges
640        filter   NonNumeric
641        name   rating
642        type   Text
643        label   Rating
644        <attributes>
645            title   Enter a rating between 1 and 5 here
646        </attributes>
647    </elements>
648    <elements>
649        constraints   Integer
650        filter   TrimEdges
651        filter   HTMLEscape
652        name   authors
653        type   Select
654        label   Author
655        multiple   1
656        size   3
657    </elements>
658    <elements>
659        value   Submit
660        name   submit
661        type   Submit
662    </elements>
663    indicator   submit
664    
665
666 =head1 AUTHOR
667
668 Kennedy Clark, C<hkclark@gmail.com>
669
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/>.
673
674 Copyright 2006-2008, Kennedy Clark, under Creative Commons License
675 (L<http://creativecommons.org/licenses/by-sa/3.0/us/>).