9e2e6c2a69f1c6e1d9b61872e63a2a1f251a1435
[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
67 =head1 HTML::FormFu FORM CREATION
68
69 This section looks at how L<HTML::FormFu> can be used to add additional
70 functionality to the manually created form from
71 L<Chapter 4|Catalyst::Manual::Tutorial::04_BasicCRUD>.
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 'HTML::FormFu';
89     requires 'Catalyst::Controller::HTML::FormFu';
90     requires 'requires 'HTML::FormFu::Model::DBIC';';
91
92 to your C<Makefile.PL>.
93
94
95 =head2 Add Action to Display and Save the Form
96
97 Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
98 following method:
99
100     =head2 formfu_create
101     
102     Use HTML::FormFu to create a new book
103     
104     =cut
105     
106     sub formfu_create :Chained('base') :PathPart('formfu_create') :Args(0) :FormConfig {
107         my ($self, $c) = @_;
108     
109         # Get the form that the :FormConfig attribute saved in the stash
110         my $form = $c->stash->{form};
111     
112         # Check if the form has been submitted (vs. displaying the initial
113         # form) and if the data passed validation.  "submitted_and_valid"
114         # is shorthand for "$form->submitted && !$form->has_errors"
115         if ($form->submitted_and_valid) {
116             # Create a new book
117             my $book = $c->model('DB::Book')->new_result({});
118             # Save the form data for the book
119             $form->model->update($book);
120             # Set a status message for the user & return to books list
121             $c->response->redirect($c->uri_for($self->action_for('list'),
122                 {mid => $c->set_status_msg("Book created")}));
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 create a directory
147 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
197 bottom of 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
217 following:
218
219     [% META title = 'Create/Update Book' %]
220     
221     [%# Render the HTML::FormFu Form %]
222     [% form %]
223     
224     <p><a href="[% c.uri_for(c.controller.action_for('list')) 
225         %]">Return to book list</a></p>
226
227
228 =head2 Add Links for Create and Update via C<HTML::FormFu>
229
230 Open C<root/src/books/list.tt2> in your editor and add the following to
231 the bottom of the existing file:
232
233     ...
234     <p>
235       HTML::FormFu:
236       <a href="[% c.uri_for(c.controller.action_for('formfu_create')) %]">Create</a>
237     </p>
238
239 This adds a new link to the bottom of the book list page that we can use
240 to easily launch our HTML::FormFu-based form.
241
242
243 =head2 Test The HTML::FormFu Create Form
244
245 Make sure the server is running with the "-r" restart option:
246
247     $ script/myapp_server.pl -r
248
249 Login as C<test01> (password: mypass).  Once at the Book List page,
250 click the new HTML::FormFu "Create" link at the bottom to display the
251 form.  Fill in the following values:
252
253     Title:  Internetworking with TCP/IP Vol. II
254     Rating: 4
255     Author: Comer
256
257 Click the "Submit" button, and you will be returned to the Book List page
258 with a "Book created" status message displayed.
259
260 Also note that this implementation allows you to create books with any
261 bogus information.  Although we have constrained the authors with the
262 drop-down list (note that this isn't bulletproof because we still have
263 not prevented a user from "hacking" the form to specify other values),
264 there are no restrictions on items such as the length of the title (for
265 example, you can create a one-letter title) and the value of the rating
266 (you can use any number you want, and even non-numeric values with
267 SQLite).  The next section will address this concern.
268
269 B<Note:> Depending on the database you are using and how you established
270 the columns in your tables, the database could obviously provide various
271 levels of "type enforcement" on your data.  The key point being made in
272 the previous paragraph is that the I<web application> itself is not
273 performing any validation.
274
275
276 =head1 HTML::FormFu VALIDATION AND FILTERING
277
278 Although the use of L<HTML::FormFu> in the previous section did provide
279 an automated mechanism to build the form, the real power of this module
280 stems from functionality that can automatically validate and filter the
281 user input.  Validation uses constraints to be sure that users input
282 appropriate data (for example, that the email field of a form contains a
283 valid email address).  Filtering can also be used to remove extraneous
284 whitespace from fields or to escape meta-characters in 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 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> for more filter options.
398
399 =back
400
401
402 =head2 Try Out the Updated Form
403
404 Make sure you are still logged in as C<test01> and try adding a book
405 with various errors: title less than 5 characters, non-numeric rating, a
406 rating of 0 or 6, etc.  Also try selecting one, two, and zero authors.
407 When you click Submit, the HTML::FormFu C<constraint> items will
408 validate the logic and insert feedback as appropriate.  Try adding blank
409 spaces at the front or the back of the title and note that it will be
410 removed.
411
412 Note that you can update your FormFu YAML forms and the development
413 server does not need to reload -- the form definition is read from
414 the YAML file each time a controller action uses it.
415
416
417 =head1 CREATE AND UPDATE/EDIT ACTION
418
419 Let's expand the work done above to add an edit action.  First, open
420 C<lib/MyApp/Controller/Books.pm> and add the following method to the
421 bottom:
422
423     =head2 formfu_edit
424     
425     Use HTML::FormFu to update an existing book
426     
427     =cut
428     
429     sub formfu_edit :Chained('object') :PathPart('formfu_edit') :Args(0) 
430             :FormConfig('books/formfu_create.yml') {
431         my ($self, $c) = @_;
432     
433         # Get the specified book already saved by the 'object' method
434         my $book = $c->stash->{object};
435     
436         # Make sure we were able to get a book
437         unless ($book) {
438             # Set an error message for the user & return to books list
439             $c->response->redirect($c->uri_for($self->action_for('list'),
440                 {mid => $c->set_error_msg("Invalid book -- Cannot edit")}));
441             $c->detach;
442         }
443     
444         # Get the form that the :FormConfig attribute saved in the stash
445         my $form = $c->stash->{form};
446     
447         # Check if the form has been submitted (vs. displaying the initial
448         # form) and if the data passed validation.  "submitted_and_valid"
449         # is shorthand for "$form->submitted && !$form->has_errors"
450         if ($form->submitted_and_valid) {
451             # Save the form data for the book
452             $form->model->update($book);
453             # Set a status message for the user
454             # Set a status message for the user & return to books list
455             $c->response->redirect($c->uri_for($self->action_for('list'),
456                 {mid => $c->set_status_msg("Book edited")}));
457             $c->detach;
458         } else {
459             # Get the authors from the DB
460             my @author_objs = $c->model("DB::Author")->all();
461             # Create an array of arrayrefs where each arrayref is an author
462             my @authors;
463             foreach (sort {$a->last_name cmp $b->last_name} @author_objs) {
464                 push(@authors, [$_->id, $_->last_name]);
465             }
466             # Get the select added by the config file
467             my $select = $form->get_element({type => 'Select'});
468             # Add the authors to it
469             $select->options(\@authors);
470             # Populate the form with existing values from DB
471             $form->model->default_values($book);
472         }
473     
474         # Set the template
475         $c->stash(template => 'books/formfu_create.tt2');
476     }
477
478 Most of this code should look familiar to what we used in the
479 C<formfu_create> method (in fact, we should probably centralize some of
480 the common code in separate methods).  The main differences are:
481
482 =over 4
483
484 =item *
485
486 We have to manually specify the name of the FormFu .yml file as an
487 argument to C<:FormConfig> because the name can no longer be
488 automatically deduced from the name of our action/method (by default,
489 FormFu would look for a file named C<books/formfu_edit.yml>).
490
491 =item *
492
493 We load the book object from the stash (found using the $id passed to
494 the Chained object method)
495
496 =item *
497
498 We use C<$id> to look up the existing book from the database.
499
500 =item *
501
502 We make sure the book lookup returned a valid book.  If not, we set the
503 error message and return to the book list.
504
505 =item *
506
507 If the form has been submitted and passes validation, we skip creating a
508 new book and just use C<$form-E<gt>model-E<gt>update> to update the
509 existing book.
510
511 =item *
512
513 If the form is being displayed for the first time (or has failed
514 validation and it being redisplayed), we use
515 C<$form-E<gt>model-E<gt>default_values> to populate the form with data
516 from the database.
517
518 =back
519
520 Then, edit C<root/src/books/list.tt2> and add a new link below the
521 existing "Delete" link that allows us to edit/update each existing book.
522 The last E<lt>tdE<gt> cell in the book list table should look like the
523 following:
524
525     ...
526     <td>
527       [% # Add a link to delete a book %]
528       <a href="[%
529         c.uri_for(c.controller.action_for('delete'), [book.id]) %]">Delete</a>
530       [% # Add a link to edit a book %]
531       <a href="[%
532         c.uri_for(c.controller.action_for('formfu_edit'), [book.id]) %]">Edit</a>
533     </td>
534     ...
535
536 B<Note:> Only add three lines (the "Add a link to edit a book" comment and
537 the href for C<formfu_edit>).  Make sure you add it below the existing
538 C<delete> link.
539
540
541 =head2 Try Out the Edit/Update Feature
542
543 Make sure you are still logged in as C<test01> and go to the
544 L<http://localhost:3000/books/list> URL in your browser.  Click the
545 "Edit" link next to "Internetworking with TCP/IP Vol. II", change the
546 rating to a 3, the "II" at end of the title to the number "2", add
547 Stevens as a co-author (control-click), and click Submit.  You will then
548 be returned to the book list with a "Book edited" message at the top in
549 green.  Experiment with other edits to various books.
550
551
552 =head2 More Things to Try
553
554 You are now armed with enough knowledge to be dangerous.  You can keep
555 tweaking the example application; some things you might want to do:
556
557 =over 4
558
559 =item *
560
561 Add an appropriate authorization check to the new Edit function.
562
563 =item *
564
565 Cleanup the List page so that the Login link only displays when the user
566 isn't logged in and the Logout link only displays when a user is logged
567 in.
568
569 =item *
570
571 Add a more sensible policy for when and how users and admins can do
572 things in the CRUD cycle.
573
574 =item *
575
576 Support the CRUD cycle for authors.
577
578 =back
579
580 Or you can proceed to write your own application, which is probably the
581 real reason you worked through this Tutorial in the first place.
582
583
584 =head2 Config::General Config for this tutorial
585
586 If you are having difficulty with YAML config above, please save the
587 below into the file C<formfu_create.conf> and delete the
588 C<formfu_create.yml> file.  The below is in L<Config::General> format
589 which follows the syntax of Apache config files.
590
591    constraints   Required
592    <elements>
593        <constraints>
594            min   5
595            max   40
596            type   Length
597            message   Length must be between 5 and 40 characters
598        </constraints>
599        filter   TrimEdges
600        filter   HTMLEscape
601        name   title
602        type   Text
603        label   Title
604        <attributes>
605            title   Enter a book title here
606        </attributes>
607    </elements>
608    <elements>
609        constraints   Integer
610        filter   TrimEdges
611        filter   NonNumeric
612        name   rating
613        type   Text
614        label   Rating
615        <attributes>
616            title   Enter a rating between 1 and 5 here
617        </attributes>
618    </elements>
619    <elements>
620        constraints   Integer
621        filter   TrimEdges
622        filter   HTMLEscape
623        name   authors
624        type   Select
625        label   Author
626        multiple   1
627        size   3
628    </elements>
629    <elements>
630        value   Submit
631        name   submit
632        type   Submit
633    </elements>
634    indicator   submit
635
636
637 =head1 AUTHOR
638
639 Kennedy Clark, C<hkclark@gmail.com>
640
641 Feel free to contact the author for any errors or suggestions, but the
642 best way to report issues is via the CPAN RT Bug system at
643 <https://rt.cpan.org/Public/Dist/Display.html?Name=Catalyst-Manual>.
644
645 The most recent version of the Catalyst Tutorial can be found at
646 L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.80/trunk/lib/Catalyst/Manual/Tutorial/>.
647
648 Copyright 2006-2010, Kennedy Clark, under the
649 Creative Commons Attribution Share-Alike License Version 3.0
650 (L<http://creativecommons.org/licenses/by-sa/3.0/us/>).