6f8e31a56bf9e06bc8258b4558beac3fad3ef688
[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 C<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 C<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 C<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::Books')->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::Authors")->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: Title = "Internetworking with
274 TCP/IP Vol. II", Rating = "4", and Author = "Comer".  Click Submit,
275 and you will be returned to the Book List page with a "Book created"
276 status message displayed.
277
278 Also note that this implementation allows you to can create books with 
279 bogus information.  Although we have constrained the authors with the 
280 drop-down list (note that this isn't bulletproof because we still have 
281 not prevented a user from "hacking" the form to specify other values), 
282 there are no restrictions on items such as the length of the title (for 
283 example, you can create a one-letter title) and value for the rating 
284 (you can use any number you want, and even non-numeric values with 
285 SQLite).  The next section will address this concern.
286
287 B<Note:> Depending on the database you are using and how you established
288 the columns in your tables, the database could obviously provide various
289 levels of "type enforcement" on your data.  The key point being made in
290 the previous paragraph is that the I<web application> itself is not
291 performing any validation.
292
293
294 =head1 C<HTML::FormFu> VALIDATION AND FILTERING
295
296 Although the use of L<HTML::FormFu|HTML::FormFu> in the previous section 
297 did provide an automated mechanism to build the form, the real power of 
298 this module stems from functionality that can automatically validate and 
299 filter the user input.  Validation uses constraints to be sure that 
300 users input appropriate data (for example, that the email field of a 
301 form contains a valid email address).  Filtering can also be used to 
302 remove extraneous whitespace from fields or to escape meta-characters in 
303 user input.
304
305
306 =head2 Add Constraints
307
308 Open C<root/forms/books/formfu_create.yml> in your editor and update it 
309 to match:
310
311     ---
312     # indicator is the field that is used to test for form submission
313     indicator: submit
314     # Start listing the form elements
315     elements:
316         # The first element will be a text field for the title
317         - type: Text
318           name: title
319           label: Title
320           # This is an optional 'mouse over' title pop-up
321           attributes:
322             title: Enter a book title here
323           # Add constraints for the field
324           constraints:
325             # Force the length to be between 5 and 40 chars
326             - type: Length
327               min: 5
328               max: 40
329               # Override the default of 'Invalid input'
330               message: Length must be between 5 and 40 characters
331     
332         # Another text field for the numeric rating
333         - type: Text
334           name: rating
335           label: Rating
336           attributes:
337             title: Enter a rating between 1 and 5 here
338           # Use Filter to clean up the input data
339           # Could use 'NonNumeric' below, but since Filters apply *before*
340           # constraints, it would conflict with the 'Integer' constraint below.
341           # So let's skip this and just use the constraint.
342           #filter:
343             # Remove everything except digits
344             #- NonNumeric
345           # Add constraints to the field
346           constraints:
347             # Make sure it's a number
348             - type: Integer
349               message: "Required. Digits only, please."
350             # Check the min & max values
351             - type: Range
352               min: 1
353               max: 5
354               message: "Must be between 1 and 5."
355     
356         # Add a select list for the author selection.  Note that we will
357         # dynamically fill in all the authors from the controller but we
358         # could manually set items in the select by adding this YAML code:
359         # options:
360         #   - [ '1', 'Bastien' ]
361         #   - [ '2', 'Nasseh'  ]
362         - type: Select
363           name: authors
364           label: Author
365           # Convert the drop-down to a multi-select list
366           multiple: 1
367           # Display 3 entries (user can scroll to see others)
368           size: 3
369           # One could argue we don't need to do filters or constraints for
370           # a select list, but it's smart to do validation and sanity
371           # checks on this data in case a user "hacks" the input
372           # Add constraints to the field
373           constraints:
374             # Make sure it's a number
375             - Integer
376     
377         # The submit button
378         - type: Submit
379           name: submit
380           value: Submit
381     
382     # Global filters and constraints.
383     constraints:
384       # The user cannot leave any fields blank
385       - Required
386       # If not all fields are required, move the Required constraint to the 
387       # fields that are
388     filter:
389       # Remove whitespace at both ends
390       - TrimEdges
391       # Escape HTML characters for safety
392       - HTMLEscape
393
394 B<NOTE:> Copying and pasting YAML from perl documentation is sometimes
395 tricky.  See the L<Config::General Config for this tutorial> section of
396 this document for a more foolproof config format.
397
398 The main changes are:
399
400 =over 4
401
402 =item *
403
404 The C<Select> element for C<authors> is changed from a single-select
405 drop-down to a multi-select list by adding configuration for the 
406 C<multiple> and C<size> options in C<formfu_create.yml>.
407
408 =item *
409
410 Constraints are added to provide validation of the user input.  See
411 L<HTML::FormFu::Constraint|HTML::FormFu::Constraint> for other
412 constraints that are available.
413
414 =item *
415
416 A variety of filters are run on every field to remove and escape 
417 unwanted input.  See L<HTML::FormFu::Filter|HTML::FormFu::Filter>
418 for more filter options.
419
420 =back
421
422
423 =head2 Try Out the Updated Form
424
425 Press C<Ctrl-C> to kill the previous server instance (if it's still 
426 running) and restart it:
427
428     $ script/myapp_server.pl
429
430 Make sure you are still logged in as C<test01> and try adding a book 
431 with various errors: title less than 5 characters, non-numeric rating, a 
432 rating of 0 or 6, etc.  Also try selecting one, two, and zero authors. 
433 When you click Submit, the HTML::FormFu C<constraint> items will 
434 validate the logic and insert feedback as appropriate.  Try adding blank 
435 spaces at the front or the back of the title and note that it will be 
436 removed.
437
438
439 =head1 CREATE AND UPDATE/EDIT ACTION
440
441 Let's expand the work done above to add an edit action.  First, open 
442 C<lib/MyApp/Controller/Books.pm> and add the following method to the 
443 bottom:
444
445     =head2 formfu_edit
446     
447     Use HTML::FormFu to update an existing book
448     
449     =cut
450     
451     sub formfu_edit :Chained('object') :PathPart('formfu_edit') :Args(0) 
452             :FormConfig('books/formfu_create.yml') {
453         my ($self, $c) = @_;
454     
455         # Get the specified book already saved by the 'base' method
456         my $book = $c->stash->{object};
457     
458         # Make sure we were able to get a book
459         unless ($book) {
460             $c->flash->{error_msg} = "Invalid book -- Cannot edit";
461             $c->response->redirect($c->uri_for($self->action_for('list')));
462             $c->detach;
463         }
464     
465         # Get the form that the :FormConfig attribute saved in the stash
466         my $form = $c->stash->{form};
467     
468         # Check if the form has been submitted (vs. displaying the initial
469         # form) and if the data passed validation.  "submitted_and_valid"
470         # is shorthand for "$form->submitted && !$form->has_errors"
471         if ($form->submitted_and_valid) {
472             # Save the form data for the book
473             $form->model->update($book);
474             # Set a status message for the user
475             $c->flash->{status_msg} = 'Book edited';
476             # Return to the books list
477             $c->response->redirect($c->uri_for($self->action_for('list')));
478             $c->detach;
479         } else {
480             # Get the authors from the DB
481             my @author_objs = $c->model("DB::Authors")->all();
482             # Create an array of arrayrefs where each arrayref is an author
483             my @authors;
484             foreach (sort {$a->last_name cmp $b->last_name} @author_objs) {
485                 push(@authors, [$_->id, $_->last_name]);
486             }
487             # Get the select added by the config file
488             my $select = $form->get_element({type => 'Select'});
489             # Add the authors to it
490             $select->options(\@authors);
491             # Populate the form with existing values from DB
492             $form->model->default_values($book);
493         }
494     
495         # Set the template
496         $c->stash->{template} = 'books/formfu_create.tt2';
497     }
498
499 Most of this code should look familiar to what we used in the 
500 C<formfu_create> method (in fact, we should probably centralize some of 
501 the common code in separate methods).  The main differences are:
502
503 =over 4
504
505 =item *
506
507 We have to manually specify the name of the FormFu .yml file as an 
508 argument to C<:FormConfig> because the name can no longer be 
509 automatically deduced from the name of our action/method (by default,
510 FormFu would look for a file named C<books/formfu_edit.yml>).
511
512 =item *
513
514 We load the book object from the stash (found using the $id passed to 
515 the Chained object method)
516
517 =item *
518
519 We use C<$id> to look up the existing book from the database.
520
521 =item *
522
523 We make sure the book lookup returned a valid book.  If not, we set 
524 the error message and return to the book list.
525  
526 =item *
527
528 If the form has been submitted and passes validation, we skip creating a 
529 new book and just use C<$form-E<gt>model-E<gt>update> to update the existing 
530 book.
531
532 =item *
533
534 If the form is being displayed for the first time (or has failed 
535 validation and it being redisplayed), we use
536  C<$form-E<gt>model-E<gt>default_values> to populate the form with data from the
537 database.
538
539 =back
540
541 Then, edit C<root/src/books/list.tt2> and add a new link below the 
542 existing "Delete" link that allows us to edit/update each existing book. 
543 The last E<lt>tdE<gt> cell in the book list table should look like the 
544 following:
545
546     ...
547     <td>
548       [% # Add a link to delete a book %]
549       <a href="[% c.uri_for(c.controller.action_for('delete'), [book.id]) %]">Delete</a>
550       [% # Add a link to edit a book %]
551       <a href="[% c.uri_for(c.controller.action_for('formfu_edit'), [book.id]) %]">Edit</a>
552     </td>
553     ...
554
555 B<Note:> Only add two lines (the "Add a link to edit a book" comment
556 and the href for C<formfu_edit>).  Make sure you add it below the
557 existing C<delete> link.
558
559
560 =head2 Try Out the Edit/Update Feature
561
562 Press C<Ctrl-C> to kill the previous server instance (if it's still 
563 running) and restart it:
564
565     $ script/myapp_server.pl
566
567 Make sure you are still logged in as C<test01> and go to the 
568 L<http://localhost:3000/books/list> URL in your browser.  Click the 
569 "Edit" link next to "Internetworking with TCP/IP Vol. II", change the 
570 rating to a 3, the "II" at end of the title to the number "2", add 
571 Stevens as a co-author (control-click), and click Submit.  You will then 
572 be returned to the book list with a "Book edited" message at the top in 
573 green.  Experiment with other edits to various books.
574
575 =head2 More Things to Try
576
577 You are now armed with enough knowledge to be dangerous.  You can keep
578 tweaking the example application; some things you might want to do:
579
580 =over 4
581
582 =item *
583
584 Add an appropriate authorization check to the new Edit function.
585
586 =item *
587
588 Cleanup the List page so that the Login link only displays when the user
589 isn't logged in and the Logout link only displays when a user is logged
590 in.
591
592 =item *
593
594 Add a more sensible policy for when and how users and admins can do
595 things in the CRUD cycle.
596
597 =item *
598
599 Support the CRUD cycle for authors.
600
601 =back
602
603 Or you can proceed to write your own application, which is probably the
604 real reason you worked through this Tutorial in the first place.
605
606 =head2  Config::General Config for this tutorial
607
608 If you are having difficulty with YAML config above, please save the
609 below into the file C<formfu_create.conf> and delete the
610 C<formfu_create.yml> file.  The below is in
611 L<Config::General|Config::General> format which follows the syntax of
612 Apache config files.
613
614    constraints   Required
615    <elements>
616        <constraints>
617            min   5
618            max   40
619            type   Length
620            message   Length must be between 5 and 40 characters
621        </constraints>
622        filter   TrimEdges
623        filter   HTMLEscape
624        name   title
625        type   Text
626        label   Title
627        <attributes>
628            title   Enter a book title here
629        </attributes>
630    </elements>
631    <elements>
632        constraints   Integer
633        filter   TrimEdges
634        filter   NonNumeric
635        name   rating
636        type   Text
637        label   Rating
638        <attributes>
639            title   Enter a rating between 1 and 5 here
640        </attributes>
641    </elements>
642    <elements>
643        constraints   Integer
644        filter   TrimEdges
645        filter   HTMLEscape
646        name   authors
647        type   Select
648        label   Author
649        multiple   1
650        size   3
651    </elements>
652    <elements>
653        value   Submit
654        name   submit
655        type   Submit
656    </elements>
657    indicator   submit
658    
659
660
661 =head1 AUTHOR
662
663 Kennedy Clark, C<hkclark@gmail.com>
664
665 Please report any errors, issues or suggestions to the author.  The
666 most recent version of the Catalyst Tutorial can be found at
667 L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/>.
668
669 Copyright 2006-2008, Kennedy Clark, under Creative Commons License
670 (L<http://creativecommons.org/licenses/by-sa/3.0/us/>).