fixed typo in AdvancedCRUD::FormFu
[catagits/Catalyst-Manual.git] / lib / Catalyst / Manual / Tutorial / 09_AdvancedCRUD / 09_FormFu.pod
1 =head1 NAME
2
3 Catalyst::Manual::Tutorial::09_AdvancedCRUD::09_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::01_Intro>
17
18 =item 2
19
20 L<Catalyst Basics|Catalyst::Manual::Tutorial::02_CatalystBasics>
21
22 =item 3
23
24 L<More Catalyst Basics|Catalyst::Manual::Tutorial::03_MoreCatalystBasics>
25
26 =item 4
27
28 L<Basic CRUD|Catalyst::Manual::Tutorial::04_BasicCRUD>
29
30 =item 5
31
32 L<Authentication|Catalyst::Manual::Tutorial::05_Authentication>
33
34 =item 6
35
36 L<Authorization|Catalyst::Manual::Tutorial::06_Authorization>
37
38 =item 7
39
40 L<Debugging|Catalyst::Manual::Tutorial::07_Debugging>
41
42 =item 8
43
44 L<Testing|Catalyst::Manual::Tutorial::08_Testing>
45
46 =item 9
47
48 B<09_Advanced CRUD::09_FormFu>
49
50 =item 10
51
52 L<Appendices|Catalyst::Manual::Tutorial::10_Appendices>
53
54 =back
55
56
57 =head1 DESCRIPTION
58
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.
62
63 See L<Catalyst::Manual::Tutorial::09_AdvancedCRUD> for additional form
64 management options other than L<HTML::FormFu>.
65
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>.
70
71
72 =head1 HTML::FormFu FORM CREATION
73
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>.
77
78
79 =head2 Inherit From Catalyst::Controller::HTML::FormFu
80
81 First, change your C<lib/MyApp/Controller/Books.pm> to inherit from
82 L<Catalyst::Controller::HTML::FormFu> by changing the C<extends> line
83 from the default of:
84
85     BEGIN {extends 'Catalyst::Controller'; }
86
87 to use the FormFu base controller class:
88
89     BEGIN {extends 'Catalyst::Controller::HTML::FormFu'; }
90
91 Don't forget to add:
92
93     requires 'HTML::FormFu';
94     requires 'Catalyst::Controller::HTML::FormFu';
95     requires 'HTML::FormFu::Model::DBIC';
96
97 to your C<Makefile.PL>.
98
99
100 =head2 Add Action to Display and Save the Form
101
102 Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
103 following method:
104
105     =head2 formfu_create
106     
107     Use HTML::FormFu to create a new book
108     
109     =cut
110     
111     sub formfu_create :Chained('base') :PathPart('formfu_create') :Args(0) :FormConfig {
112         my ($self, $c) = @_;
113     
114         # Get the form that the :FormConfig attribute saved in the stash
115         my $form = $c->stash->{form};
116     
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) {
121             # Create a new book
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")}));
128             $c->detach;
129         } else {
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
133             my @authors;
134             foreach (sort {$a->last_name cmp $b->last_name} @author_objs) {
135                 push(@authors, [$_->id, $_->last_name]);
136             }
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);
141         }
142     
143         # Set the template
144         $c->stash(template => 'books/formfu_create.tt2');
145     }
146
147
148 =head2 Create a Form Config File
149
150 Although C<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:
153
154     $ mkdir -p root/forms/books
155
156 Then create the file C<root/forms/books/formfu_create.yml> and enter the
157 following text:
158
159     ---
160     # indicator is the field that is used to test for form submission
161     indicator: submit
162     # Start listing the form elements
163     elements:
164         # The first element will be a text field for the title
165         - type: Text
166           name: title
167           label: Title
168           # This is an optional 'mouse over' title pop-up
169           attributes:
170             title: Enter a book title here
171     
172         # Another text field for the numeric rating
173         - type: Text
174           name: rating
175           label: Rating
176           attributes:
177             title: Enter a rating between 1 and 5 here
178     
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:
182         # options:
183         #   - [ '1', 'Bastien' ]
184         #   - [ '2', 'Nasseh'  ]
185         - type: Select
186           name: authors
187           label: Author
188     
189         # The submit button
190         - type: Submit
191           name: submit
192           value: Submit
193
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.
197
198
199 =head2 Update the CSS
200
201 Edit C<root/static/css/main.css> and add the following lines to the
202 bottom of the file:
203
204     ...
205     input {
206         display: block;
207     }
208     select {
209         display: block;
210     }
211     .submit {
212         padding-top: .5em;
213         display: block;
214     }
215
216 These changes will display form elements vertically.
217
218
219 =head2 Create a Template Page To Display The Form
220
221 Open C<root/src/books/formfu_create.tt2> in your editor and enter the
222 following:
223
224     [% META title = 'Create/Update Book' %]
225     
226     [%# Render the HTML::FormFu Form %]
227     [% form %]
228     
229     <p><a href="[% c.uri_for(c.controller.action_for('list')) 
230         %]">Return to book list</a></p>
231
232
233 =head2 Add Links for Create and Update via C<HTML::FormFu>
234
235 Open C<root/src/books/list.tt2> in your editor and add the following to
236 the bottom of the existing file:
237
238     ...
239     <p>
240       HTML::FormFu:
241       <a href="[% c.uri_for(c.controller.action_for('formfu_create')) %]">Create</a>
242     </p>
243
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.
246
247
248 =head2 Test The HTML::FormFu Create Form
249
250 Make sure the server is running with the "-r" restart option:
251
252     $ script/myapp_server.pl -r
253
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:
257
258     Title:  Internetworking with TCP/IP Vol. II
259     Rating: 4
260     Author: Comer
261
262 Click the "Submit" button, and you will be returned to the Book List page
263 with a "Book created" status message displayed.
264
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.
273
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.
279
280
281 =head1 HTML::FormFu VALIDATION AND FILTERING
282
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.
290
291
292 =head2 Add Constraints
293
294 Open C<root/forms/books/formfu_create.yml> in your editor and update it
295 to match:
296
297     ---
298     # indicator is the field that is used to test for form submission
299     indicator: submit
300     # Start listing the form elements
301     elements:
302         # The first element will be a text field for the title
303         - type: Text
304           name: title
305           label: Title
306           # This is an optional 'mouse over' title pop-up
307           attributes:
308             title: Enter a book title here
309           # Add constraints for the field
310           constraints:
311             # Force the length to be between 5 and 40 chars
312             - type: Length
313               min: 5
314               max: 40
315               # Override the default of 'Invalid input'
316               message: Length must be between 5 and 40 characters
317     
318         # Another text field for the numeric rating
319         - type: Text
320           name: rating
321           label: Rating
322           attributes:
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.
328           #filter:
329             # Remove everything except digits
330             #- NonNumeric
331           # Add constraints to the field
332           constraints:
333             # Make sure it's a number
334             - type: Integer
335               message: "Required. Digits only, please."
336             # Check the min & max values
337             - type: Range
338               min: 1
339               max: 5
340               message: "Must be between 1 and 5."
341     
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:
345         # options:
346         #   - [ '1', 'Bastien' ]
347         #   - [ '2', 'Nasseh'  ]
348         - type: Select
349           name: authors
350           label: Author
351           # Convert the drop-down to a multi-select list
352           multiple: 1
353           # Display 3 entries (user can scroll to see others)
354           size: 3
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
359           constraints:
360             # Make sure it's a number
361             - Integer
362     
363         # The submit button
364         - type: Submit
365           name: submit
366           value: Submit
367     
368     # Global filters and constraints.
369     constraints:
370         # The user cannot leave any fields blank
371         - Required
372         # If not all fields are required, move the Required constraint to the
373         # fields that are
374     filter:
375         # Remove whitespace at both ends
376         - TrimEdges
377         # Escape HTML characters for safety
378         - HTMLEscape
379
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.
383
384 The main changes are:
385
386 =over 4
387
388 =item *
389
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 C<formfu_create.yml>.
393
394 =item *
395
396 Constraints are added to provide validation of the user input.  See
397 L<HTML::FormFu::Constraint> for other constraints that are available.
398
399 =item *
400
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.
403
404 =back
405
406
407 =head2 Try Out the Updated Form
408
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
415 removed.
416
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.
420
421
422 =head1 CREATE AND UPDATE/EDIT ACTION
423
424 Let's expand the work done above to add an edit action.  First, open
425 C<lib/MyApp/Controller/Books.pm> and add the following method to the
426 bottom:
427
428     =head2 formfu_edit
429     
430     Use HTML::FormFu to update an existing book
431     
432     =cut
433     
434     sub formfu_edit :Chained('object') :PathPart('formfu_edit') :Args(0) 
435             :FormConfig('books/formfu_create.yml') {
436         my ($self, $c) = @_;
437     
438         # Get the specified book already saved by the 'object' method
439         my $book = $c->stash->{object};
440     
441         # Make sure we were able to get a book
442         unless ($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")}));
446             $c->detach;
447         }
448     
449         # Get the form that the :FormConfig attribute saved in the stash
450         my $form = $c->stash->{form};
451     
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")}));
462             $c->detach;
463         } else {
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
467             my @authors;
468             foreach (sort {$a->last_name cmp $b->last_name} @author_objs) {
469                 push(@authors, [$_->id, $_->last_name]);
470             }
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);
477         }
478     
479         # Set the template
480         $c->stash(template => 'books/formfu_create.tt2');
481     }
482
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:
486
487 =over 4
488
489 =item *
490
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 C<books/formfu_edit.yml>).
495
496 =item *
497
498 We load the book object from the stash (found using the $id passed to
499 the Chained object method)
500
501 =item *
502
503 We use C<$id> to look up the existing book from the database.
504
505 =item *
506
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.
509
510 =item *
511
512 If the form has been submitted and passes validation, we skip creating a
513 new book and just use C<$form-E<gt>model-E<gt>update> to update the
514 existing book.
515
516 =item *
517
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-E<gt>model-E<gt>default_values> to populate the form with data
521 from the database.
522
523 =back
524
525 Then, edit C<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 E<lt>tdE<gt> cell in the book list table should look like the
528 following:
529
530     ...
531     <td>
532       [% # Add a link to delete a book %]
533       <a href="[%
534         c.uri_for(c.controller.action_for('delete'), [book.id]) %]">Delete</a>
535       [% # Add a link to edit a book %]
536       <a href="[%
537         c.uri_for(c.controller.action_for('formfu_edit'), [book.id]) %]">Edit</a>
538     </td>
539     ...
540
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
543 C<delete> link.
544
545
546 =head2 Try Out the Edit/Update Feature
547
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.
555
556
557 =head2 More Things to Try
558
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:
561
562 =over 4
563
564 =item *
565
566 Add an appropriate authorization check to the new Edit function.
567
568 =item *
569
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
572 in.
573
574 =item *
575
576 Add a more sensible policy for when and how users and admins can do
577 things in the CRUD cycle.
578
579 =item *
580
581 Support the CRUD cycle for authors.
582
583 =back
584
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.
587
588
589 =head2 Config::General Config for this tutorial
590
591 If you are having difficulty with YAML config above, please save the
592 below into the file C<formfu_create.conf> and delete the
593 C<formfu_create.yml> file.  The below is in L<Config::General> format
594 which follows the syntax of Apache config files.
595
596    constraints   Required
597    <elements>
598        <constraints>
599            min   5
600            max   40
601            type   Length
602            message   Length must be between 5 and 40 characters
603        </constraints>
604        filter   TrimEdges
605        filter   HTMLEscape
606        name   title
607        type   Text
608        label   Title
609        <attributes>
610            title   Enter a book title here
611        </attributes>
612    </elements>
613    <elements>
614        constraints   Integer
615        filter   TrimEdges
616        filter   NonNumeric
617        name   rating
618        type   Text
619        label   Rating
620        <attributes>
621            title   Enter a rating between 1 and 5 here
622        </attributes>
623    </elements>
624    <elements>
625        constraints   Integer
626        filter   TrimEdges
627        filter   HTMLEscape
628        name   authors
629        type   Select
630        label   Author
631        multiple   1
632        size   3
633    </elements>
634    <elements>
635        value   Submit
636        name   submit
637        type   Submit
638    </elements>
639    indicator   submit
640
641
642 =head1 AUTHOR
643
644 Kennedy Clark, C<hkclark@gmail.com>
645
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>.
649
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/>).