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