be90d176a428e04a52e04a64c113e17003419de7
[catagits/Catalyst-Manual.git] / lib / Catalyst / Manual / Tutorial / AdvancedCRUD / FormFu.pod
1 =head1 NAME
2
3
4
5 Catalyst::Manual::Tutorial::AdvancedCRUD::FormFu - Catalyst Tutorial - Part 9: Advanced CRUD - FormFu
6
7
8 =head1 OVERVIEW
9
10 This is B<Part 9 of 10> for the Catalyst tutorial.
11
12 L<Tutorial Overview|Catalyst::Manual::Tutorial>
13
14 =over 4
15
16 =item 1
17
18 L<Introduction|Catalyst::Manual::Tutorial::Intro>
19
20 =item 2
21
22 L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
23
24 =item 3
25
26 L<More Catalyst Basics|Catalyst::Manual::Tutorial::MoreCatalystBasics>
27
28 =item 4
29
30 L<Basic CRUD|Catalyst::Manual::Tutorial::BasicCRUD>
31
32 =item 5
33
34 L<Authentication|Catalyst::Manual::Tutorial::Authentication>
35
36 =item 6
37
38 L<Authorization|Catalyst::Manual::Tutorial::Authorization>
39
40 =item 7
41
42 L<Debugging|Catalyst::Manual::Tutorial::Debugging>
43
44 =item 8
45
46 L<Testing|Catalyst::Manual::Tutorial::Testing>
47
48 =item 9
49
50 B<Advanced CRUD::FormFu>
51
52 =item 10
53
54 L<Appendices|Catalyst::Manual::Tutorial::Appendices>
55
56 =back
57
58
59 =head1 DESCRIPTION
60
61 This portion of the tutorial explores L<HTML::FormFu|HTML::FormFu> and 
62 how it can be used to manage forms, perform validation of form input, 
63 as well as save and restore data to/from the database.  This was written
64 using HTML::FormFu version 0.03007.
65
66 See 
67 L<Catalyst::Manual::Tutorial::AdvancedCRUD|Catalyst::Manual::Tutorial::AdvancedCRUD>
68 for additional form management options other than 
69 L<HTML::FormFu|HTML::FormFu>.
70
71
72 =head1 Install C<HTML::FormFu>
73
74 If you are following along in Debian 5, it turns out that some of the 
75 modules we need are not yet available as Debian packages at the time 
76 this was written.  To install it with a combination of Debian packages 
77 and traditional CPAN modules, first use C<aptitude> to install most of 
78 the modules:
79
80 we need to install the
81 L<HTML::FormFu|HTML::FormFu> package: 
82
83     sudo aptitude -y install libhtml-formfu-perl libmoose-perl \
84         libregexp-assemble-perl libhtml-formfu-model-dbic-perl
85         
86     ...
87     
88     sudo aptitude clean
89
90 Then use the following command to install directly from CPAN the modules 
91 that aren't available as Debian packages:
92
93     sudo cpan Catalyst::Component::InstancePerContext Catalyst::Controller::HTML::FormFu
94
95
96 =head1 C<HTML::FormFu> FORM CREATION
97
98 This section looks at how L<HTML::FormFu|HTML::FormFu> can be used to 
99 add additional functionality to the manually created form from Part 4.
100
101
102 =head2 Inherit From C<Catalyst::Controller::HTML::FormFu>
103
104 First, change your C<lib/MyApp/Controller/Books.pm> to inherit from
105 L<Catalyst::Controller::HTML::FormFu|Catalyst::Controller::HTML::FormFu>
106 by changing the C<use parent> line from the default of:
107
108     use parent 'Catalyst::Controller';
109
110 to use the FormFu base controller class:
111
112     use parent 'Catalyst::Controller::HTML::FormFu';
113
114
115 =head2 Add Action to Display and Save the Form
116
117 Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
118 following method:
119
120     =head2 formfu_create
121     
122     Use HTML::FormFu to create a new book
123     
124     =cut
125     
126     sub formfu_create :Chained('base) :PathPart('formfu_create') :Args(0) :FormConfig {
127         my ($self, $c) = @_;
128     
129         # Get the form that the :FormConfig attribute saved in the stash
130         my $form = $c->stash->{form};
131   
132         # Check if the form has been submitted (vs. displaying the initial
133         # form) and if the data passed validation.  "submitted_and_valid"
134         # is shorthand for "$form->submitted && !$form->has_errors"
135         if ($form->submitted_and_valid) {
136             # Create a new book
137             my $book = $c->model('DB::Books')->new_result({});
138             # Save the form data for the book
139             $form->model->update($book);
140             # Set a status message for the user
141             $c->flash->{status_msg} = 'Book created';
142             # Return to the books list
143             $c->response->redirect($c->uri_for($self->action_for('list'))); 
144             $c->detach;
145         } else {
146             # Get the authors from the DB
147             my @author_objs = $c->model("DB::Authors")->all();
148             # Create an array of arrayrefs where each arrayref is an author
149             my @authors;
150             foreach (sort {$a->last_name cmp $b->last_name} @author_objs) {
151                 push(@authors, [$_->id, $_->last_name]);
152             }
153             # Get the select added by the config file
154             my $select = $form->get_element({type => 'Select'});
155             # Add the authors to it
156             $select->options(\@authors);
157         }
158         
159         # Set the template
160         $c->stash->{template} = 'books/formfu_create.tt2';
161     }
162
163
164 =head2 Create a Form Config File
165
166 Although C<HTML::FormFu> supports any configuration file handled by
167 L<Config::Any|Config::Any>, most people tend to use YAML.  First
168 create a directory to hold your form configuration files:
169
170     mkdir -p root/forms/books
171
172 Then create the file C<root/forms/books/formfu_create.yml> and enter the 
173 following text:
174
175     ---
176     # indicator is the field that is used to test for form submission
177     indicator: submit
178     # Start listing the form elements
179     elements:
180         # The first element will be a text field for the title
181         - type: Text
182           name: title
183           label: Title
184           # This is an optional 'mouse over' title pop-up
185           attributes:
186             title: Enter a book title here
187     
188         # Another text field for the numeric rating
189         - type: Text
190           name: rating
191           label: Rating
192           attributes:
193             title: Enter a rating between 1 and 5 here
194     
195         # Add a drop-down list for the author selection.  Note that we will
196         # dynamically fill in all the authors from the controller but we
197         # could manually set items in the drop-list by adding this YAML code:
198         # options:
199         #   - [ '1', 'Bastien' ]
200         #   - [ '2', 'Nasseh'  ]
201         - type: Select
202           name: authors
203           label: Author
204     
205         # The submit button
206         - type: Submit
207           name: submit
208           value: Submit
209
210 B<NOTE:> Copying and pasting YAML from perl documentation is sometimes
211 tricky.  See the L<Config::General Config for this tutorial> section of
212 this document for a more foolproof config format.
213
214
215 =head2 Update the CSS
216
217 Edit C<root/static/css/main.css> and add the following lines to the bottom of
218 the file:
219
220     ...
221     input {
222         display: block;
223     }
224     select {
225         display: block;
226     }
227     .submit {
228         padding-top: .5em;
229         display: block;
230     }
231
232 These changes will display form elements vertically.  Note that the 
233 existing definition of the C<.error> class is pulling the color scheme 
234 settings from the C<root/lib/config/col> file that was created by the 
235 TTSite helper.  This allows control over the CSS color settings from a 
236 single location.
237
238
239 =head2 Create a Template Page To Display The Form
240
241 Open C<root/src/books/formfu_create.tt2> in your editor and enter the following:
242
243     [% META title = 'Create/Update Book' %]
244     
245     [%# Render the HTML::FormFu Form %]
246     [% form %]
247     
248     <p><a href="[% c.uri_for(c.controller.action_for('list')) %]">Return to book list</a></p>
249
250
251 =head2 Add Links for Create and Update via C<HTML::FormFu>
252
253 Open C<root/src/books/list.tt2> in your editor and add the following to
254 the bottom of the existing file:
255
256     ...
257     <p>
258       HTML::FormFu:
259       <a href="[% c.uri_for(c.controller.action_for('formfu_create')) %]">Create</a>
260     </p>
261
262 This adds a new link to the bottom of the book list page that we can
263 use to easily launch our HTML::FormFu-based form.
264
265
266 =head2 Test The <HTML::FormFu> Create Form
267
268 Press C<Ctrl-C> to kill the previous server instance (if it's still
269 running) and restart it:
270
271     $ script/myapp_server.pl
272
273 Login as C<test01> (password: mypass).  Once at the Book List page,
274 click the new HTML::FormFu "Create" link at the bottom to display the
275 form.  Fill in the following values: Title = "Internetworking with
276 TCP/IP Vol. II", Rating = "4", and Author = "Comer".  Click Submit,
277 and you will be returned to the Book List page with a "Book created"
278 status message displayed.
279
280 Also note that this implementation allows you to can create books with 
281 bogus information.  Although we have constrained the authors with the 
282 drop-down list (note that this isn't bulletproof because we still have 
283 not prevented a user from "hacking" the form to specify other values), 
284 there are no restrictions on items such as the length of the title (for 
285 example, you can create a one-letter title) and value for the rating 
286 (you can use any number you want, and even non-numeric values with 
287 SQLite).  The next section will address this concern.
288
289 B<Note:> Depending on the database you are using and how you established
290 the columns in your tables, the database could obviously provide various
291 levels of "type enforcement" on your data.  The key point being made in
292 the previous paragraph is that the I<web application> itself is not
293 performing any validation.
294
295
296 =head1 C<HTML::FormFu> VALIDATION AND FILTERING
297
298 Although the use of L<HTML::FormFu|HTML::FormFu> in the previous section 
299 did provide an automated mechanism to build the form, the real power of 
300 this module stems from functionality that can automatically validate and 
301 filter the user input.  Validation uses constraints to be sure that 
302 users input appropriate data (for example, that the email field of a 
303 form contains a valid email address).  Filtering can also be used to 
304 remove extraneous whitespace from fields or to escape meta-characters in 
305 user input.
306
307
308 =head2 Add Constraints
309
310 Open C<root/forms/books/formfu_create.yml> in your editor and update it 
311 to match:
312
313     ---
314     # indicator is the field that is used to test for form submission
315     indicator: submit
316     # Start listing the form elements
317     elements:
318         # The first element will be a text field for the title
319         - type: Text
320           name: title
321           label: Title
322           # This is an optional 'mouse over' title pop-up
323           attributes:
324             title: Enter a book title here
325           # Add constraints for the field
326           constraints:
327             # Force the length to be between 5 and 40 chars
328             - type: Length
329               min: 5
330               max: 40
331               # Override the default of 'Invalid input'
332               message: Length must be between 5 and 40 characters
333     
334         # Another text field for the numeric rating
335         - type: Text
336           name: rating
337           label: Rating
338           attributes:
339             title: Enter a rating between 1 and 5 here
340           # Use Filter to clean up the input data
341           # Could use 'NonNumeric' below, but since Filters apply *before*
342           # constraints, it would conflict with the 'Integer' constraint below.
343           # So let's skip this and just use the constraint.
344           #filter:
345             # Remove everything except digits
346             #- NonNumeric
347           # Add constraints to the field
348           constraints:
349             # Make sure it's a number
350             - type: Integer
351               message: "Required. Digits only, please."
352             # Check the min & max values
353             - type: Range
354               min: 1
355               max: 5
356               message: "Must be between 1 and 5."
357     
358         # Add a select list for the author selection.  Note that we will
359         # dynamically fill in all the authors from the controller but we
360         # could manually set items in the select by adding this YAML code:
361         # options:
362         #   - [ '1', 'Bastien' ]
363         #   - [ '2', 'Nasseh'  ]
364         - type: Select
365           name: authors
366           label: Author
367           # Convert the drop-down to a multi-select list
368           multiple: 1
369           # Display 3 entries (user can scroll to see others)
370           size: 3
371           # One could argue we don't need to do filters or constraints for
372           # a select list, but it's smart to do validation and sanity
373           # checks on this data in case a user "hacks" the input
374           # Add constraints to the field
375           constraints:
376             # Make sure it's a number
377             - Integer
378     
379         # The submit button
380         - type: Submit
381           name: submit
382           value: Submit
383     
384     # Global filters and constraints.
385     constraints:
386       # The user cannot leave any fields blank
387       - Required
388       # If not all fields are required, move the Required constraint to the 
389       # fields that are
390     filter:
391       # Remove whitespace at both ends
392       - TrimEdges
393       # Escape HTML characters for safety
394       - HTMLEscape
395
396 B<NOTE:> Copying and pasting YAML from perl documentation is sometimes
397 tricky.  See the L<Config::General Config for this tutorial> section of
398 this document for a more foolproof config format.
399
400 The main changes are:
401
402 =over 4
403
404 =item *
405
406 The C<Select> element for C<authors> is changed from a single-select
407 drop-down to a multi-select list by adding configuration for the 
408 C<multiple> and C<size> options in C<formfu_create.yml>.
409
410 =item *
411
412 Constraints are added to provide validation of the user input.  See
413 L<HTML::FormFu::Constraint|HTML::FormFu::Constraint> for other
414 constraints that are available.
415
416 =item *
417
418 A variety of filters are run on every field to remove and escape 
419 unwanted input.  See L<HTML::FormFu::Filter|HTML::FormFu::Filter>
420 for more filter options.
421
422 =back
423
424
425 =head2 Try Out the Updated Form
426
427 Press C<Ctrl-C> to kill the previous server instance (if it's still 
428 running) and restart it:
429
430     $ script/myapp_server.pl
431
432 Make sure you are still logged in as C<test01> and try adding a book 
433 with various errors: title less than 5 characters, non-numeric rating, a 
434 rating of 0 or 6, etc.  Also try selecting one, two, and zero authors. 
435 When you click Submit, the HTML::FormFu C<constraint> items will 
436 validate the logic and insert feedback as appropriate.  Try adding blank 
437 spaces at the front or the back of the title and note that it will be 
438 removed.
439
440
441 =head1 CREATE AND UPDATE/EDIT ACTION
442
443 Let's expand the work done above to add an edit action.  First, open 
444 C<lib/MyApp/Controller/Books.pm> and add the following method to the 
445 bottom:
446
447     =head2 formfu_edit
448     
449     Use HTML::FormFu to update an existing book
450     
451     =cut
452     
453     sub formfu_edit :Chained('object') :PathPart('formfu_edit') :Args(0) 
454             :FormConfig('books/formfu_create.yml') {
455         my ($self, $c, $id) = @_;
456     
457         # Get the specified book
458         my $book = $c->model('DB::Books')->find($id);
459     
460         # Make sure we were able to get a book
461         unless ($book) {
462             $c->flash->{error_msg} = "Invalid book -- Cannot edit";
463             $c->response->redirect($c->uri_for($self->action_for('list')));
464             $c->detach;
465         }
466     
467         # Get the form that the :FormConfig attribute saved in the stash
468         my $form = $c->stash->{form};
469     
470         # Check if the form has been submitted (vs. displaying the initial
471         # form) and if the data passed validation.  "submitted_and_valid"
472         # is shorthand for "$form->submitted && !$form->has_errors"
473         if ($form->submitted_and_valid) {
474             # Save the form data for the book
475             $form->model->update($book);
476             # Set a status message for the user
477             $c->flash->{status_msg} = 'Book edited';
478             # Return to the books list
479             $c->response->redirect($c->uri_for($self->action_for('list')));
480             $c->detach;
481         } else {
482             # Get the authors from the DB
483             my @author_objs = $c->model("DB::Authors")->all();
484             # Create an array of arrayrefs where each arrayref is an author
485             my @authors;
486             foreach (sort {$a->last_name cmp $b->last_name} @author_bjs) {
487                 push(@authors, [$_->id, $_->last_name]);
488             }
489             # Get the select added by the config file
490             my $select = $form->get_element({type => 'Select'});
491             # Add the authors to it
492             $select->options(\@authors);
493             # Populate the form with existing values from DB
494             $form->model->default_values($book);
495         }
496     
497         # Set the template
498         $c->stash->{template} = 'books/formfu_create.tt2';
499     }
500
501 Most of this code should look familiar to what we used in the 
502 C<formfu_create> method (in fact, we should probably centralize some of 
503 the common code in separate methods).  The main differences are:
504
505 =over 4
506
507 =item *
508
509 We accept C<$id> as an argument via the URL.
510
511 =item *
512
513 We use C<$id> to look up the existing book from the database.
514
515 =item *
516
517 We make sure the C<$id> and book lookup returned a valid book.  If not, 
518 we set the error message and return to the book list.
519
520 =item *
521
522 If the form has been submitted and passes validation, we skip creating a 
523 new book and just use C<$form-E<gt>model-E<gt>update> to update the existing 
524 book.
525
526 =item *
527
528 If the form is being displayed for the first time (or has failed 
529 validation and it being redisplayed), we use
530  C<$form-E<gt>model-E<gt>default_values> to populate the form with data from the
531 database.
532
533 =back
534
535 Then, edit C<root/src/books/list.tt2> and add a new link below the 
536 existing "Delete" link that allows us to edit/update each existing book. 
537 The last E<lt>tdE<gt> cell in the book list table should look like the 
538 following:
539
540     ...
541     <td>
542       [% # Add a link to delete a book %]
543       <a href="[% c.uri_for(c.controller.action_for('delete'), [book.id]) %]">Delete</a>
544       [% # Add a link to edit a book %]
545       <a href="[% c.uri_for(c.controller.action_for('formfu_edit'), [book.id]) %]">Edit</a>
546     </td>
547     ...
548
549 B<Note:> Only add two lines (the "Add a link to edit a book" comment
550 and the href for C<formfu_edit>).  Make sure you add it below the
551 existing C<delete> link.
552
553
554 =head2 Try Out the Edit/Update Feature
555
556 Press C<Ctrl-C> to kill the previous server instance (if it's still 
557 running) and restart it:
558
559     $ script/myapp_server.pl
560
561 Make sure you are still logged in as C<test01> and go to the 
562 L<http://localhost:3000/books/list> URL in your browser.  Click the 
563 "Edit" link next to "Internetworking with TCP/IP Vol. II", change the 
564 rating to a 3, the "II" at end of the title to the number "2", add 
565 Stevens as a co-author (control-click), and click Submit.  You will then 
566 be returned to the book list with a "Book edited" message at the top in 
567 green.  Experiment with other edits to various books.
568
569 =head2 More Things to Try
570
571 You are now armed with enough knowledge to be dangerous.  You can keep
572 tweaking the example application; some things you might want to do:
573
574 =over 4
575
576 =item *
577
578 Add an appropriate authorization check to the new Edit function.
579
580 =item *
581
582 Cleanup the List page so that the Login link only displays when the user
583 isn't logged in and the Logout link only displays when a user is logged
584 in.
585
586 =item *
587
588 Add a more sensible policy for when and how users and admins can do
589 things in the CRUD cycle.
590
591 =item *
592
593 Support the CRUD cycle for authors.
594
595 =back
596
597 Or you can proceed to write your own application, which is probably the
598 real reason you worked through this Tutorial in the first place.
599
600 =head2  Config::General Config for this tutorial
601
602 If you are having difficulty with YAML config above, please save the
603 below into the file C<formfu_create.conf> and delete the
604 C<formfu_create.yml> file.  The below is in
605 L<Config::General|Config::General> format which follows the syntax of
606 Apache config files.
607
608    constraints   Required
609    <elements>
610        <constraints>
611            min   5
612            max   40
613            type   Length
614            message   Length must be between 5 and 40 characters
615        </constraints>
616        filter   TrimEdges
617        filter   HTMLEscape
618        name   title
619        type   Text
620        label   Title
621        <attributes>
622            title   Enter a book title here
623        </attributes>
624    </elements>
625    <elements>
626        constraints   Integer
627        filter   TrimEdges
628        filter   NonNumeric
629        name   rating
630        type   Text
631        label   Rating
632        <attributes>
633            title   Enter a rating between 1 and 5 here
634        </attributes>
635    </elements>
636    <elements>
637        constraints   Integer
638        filter   TrimEdges
639        filter   HTMLEscape
640        name   authors
641        type   Select
642        label   Author
643        multiple   1
644        size   3
645    </elements>
646    <elements>
647        value   Submit
648        name   submit
649        type   Submit
650    </elements>
651    indicator   submit
652    
653
654
655 =head1 AUTHOR
656
657 Kennedy Clark, C<hkclark@gmail.com>
658
659 Please report any errors, issues or suggestions to the author.  The
660 most recent version of the Catalyst Tutorial can be found at
661 L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/>.
662
663 Copyright 2006-2008, Kennedy Clark, under Creative Commons License
664 (L<http://creativecommons.org/licenses/by-sa/3.0/us/>).