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