Point users to RT
[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
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
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
aeaa7db5 227 <p><a href="[% c.uri_for(c.controller.action_for('list'))
228 %]">Return to book list</a></p>
cca5cd98 229
230
231=head2 Add Links for Create and Update via C<HTML::FormFu>
232
233Open C<root/src/books/list.tt2> in your editor and add the following to
234the bottom of the existing file:
235
acbd7bdd 236 ...
cca5cd98 237 <p>
238 HTML::FormFu:
e075db0c 239 <a href="[% c.uri_for(c.controller.action_for('formfu_create')) %]">Create</a>
cca5cd98 240 </p>
241
d682d02d 242This adds a new link to the bottom of the book list page that we can
243use to easily launch our HTML::FormFu-based form.
244
cca5cd98 245
8a472b34 246=head2 Test The HTML::FormFu Create Form
cca5cd98 247
aeaa7db5 248Make sure the server is running with the "-r" restart option:
cca5cd98 249
aeaa7db5 250 $ script/myapp_server.pl -r
cca5cd98 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
aeaa7db5 256 Title: Internetworking with TCP/IP Vol. II
257 Rating: 4
258 Author: Comer
259
3b1fa91b 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
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
766e64ba 476 $c->stash(template => 'books/formfu_create.tt2');
59d6a035 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
59d6a035 542Make sure you are still logged in as C<test01> and go to the
543L<http://localhost:3000/books/list> URL in your browser. Click the
544"Edit" link next to "Internetworking with TCP/IP Vol. II", change the
545rating to a 3, the "II" at end of the title to the number "2", add
546Stevens as a co-author (control-click), and click Submit. You will then
547be returned to the book list with a "Book edited" message at the top in
548green. Experiment with other edits to various books.
d682d02d 549
8a472b34 550
5fe0e6dd 551=head2 More Things to Try
552
553You are now armed with enough knowledge to be dangerous. You can keep
554tweaking the example application; some things you might want to do:
555
556=over 4
557
558=item *
559
acbd7bdd 560Add an appropriate authorization check to the new Edit function.
5fe0e6dd 561
562=item *
563
564Cleanup the List page so that the Login link only displays when the user
565isn't logged in and the Logout link only displays when a user is logged
566in.
567
568=item *
569
570Add a more sensible policy for when and how users and admins can do
571things in the CRUD cycle.
572
573=item *
574
575Support the CRUD cycle for authors.
576
577=back
578
579Or you can proceed to write your own application, which is probably the
580real reason you worked through this Tutorial in the first place.
cca5cd98 581
8a472b34 582
766e64ba 583=head2 Config::General Config for this tutorial
1fba2369 584
585If you are having difficulty with YAML config above, please save the
586below into the file C<formfu_create.conf> and delete the
587C<formfu_create.yml> file. The below is in
588L<Config::General|Config::General> format which follows the syntax of
589Apache config files.
590
591 constraints Required
592 <elements>
1fba2369 593 <constraints>
594 min 5
595 max 40
596 type Length
597 message Length must be between 5 and 40 characters
598 </constraints>
599 filter TrimEdges
600 filter HTMLEscape
601 name title
602 type Text
603 label Title
604 <attributes>
605 title Enter a book title here
606 </attributes>
607 </elements>
608 <elements>
1fba2369 609 constraints Integer
610 filter TrimEdges
611 filter NonNumeric
612 name rating
613 type Text
614 label Rating
615 <attributes>
616 title Enter a rating between 1 and 5 here
617 </attributes>
618 </elements>
619 <elements>
620 constraints Integer
621 filter TrimEdges
622 filter HTMLEscape
623 name authors
624 type Select
625 label Author
626 multiple 1
627 size 3
628 </elements>
629 <elements>
630 value Submit
631 name submit
632 type Submit
633 </elements>
634 indicator submit
635
636
cca5cd98 637=head1 AUTHOR
638
639Kennedy Clark, C<hkclark@gmail.com>
640
53243324 641Feel free to contact the author for any errors or suggestions, but the
642best way to report issues is via the CPAN RT Bug system at
643<https://rt.cpan.org/Public/Dist/Display.html?Name=Catalyst-Manual>.
644
645The most recent version of the Catalyst Tutorial can be found at
59884771 646L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.80/trunk/lib/Catalyst/Manual/Tutorial/>.
cca5cd98 647
ec3ef4ad 648Copyright 2006-2010, Kennedy Clark, under the
649Creative Commons Attribution Share-Alike License Version 3.0
4768184b 650(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).