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