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