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