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