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