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