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