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