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