Updates for VM, switch to C::P::StatusMessage
[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
66
8a472b34 67=head1 HTML::FormFu FORM CREATION
cca5cd98 68
16d0d396 69This section looks at how L<HTML::FormFu> can be used to add additional
c016427f 70functionality to the manually created form from
71L<Chapter 4|Catalyst::Manual::Tutorial::04_BasicCRUD>.
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
c016427f 88 requires 'HTML::FormFu';
9372a4db 89 requires 'Catalyst::Controller::HTML::FormFu';
c016427f 90 requires 'requires 'HTML::FormFu::Model::DBIC';';
9372a4db 91
92to your C<Makefile.PL>.
cca5cd98 93
c016427f 94
cca5cd98 95=head2 Add Action to Display and Save the Form
96
97Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
98following method:
99
d682d02d 100 =head2 formfu_create
cca5cd98 101
59d6a035 102 Use HTML::FormFu to create a new book
cca5cd98 103
104 =cut
105
ccc9b2bc 106 sub formfu_create :Chained('base') :PathPart('formfu_create') :Args(0) :FormConfig {
cca5cd98 107 my ($self, $c) = @_;
108
109 # Get the form that the :FormConfig attribute saved in the stash
110 my $form = $c->stash->{form};
3dba69ab 111
166c90a0 112 # Check if the form has been submitted (vs. displaying the initial
113 # form) and if the data passed validation. "submitted_and_valid"
cca5cd98 114 # is shorthand for "$form->submitted && !$form->has_errors"
115 if ($form->submitted_and_valid) {
116 # Create a new book
3b1fa91b 117 my $book = $c->model('DB::Book')->new_result({});
cca5cd98 118 # Save the form data for the book
aee27483 119 $form->model->update($book);
c016427f 120 # Set a status message for the user & return to books list
121 $c->response->redirect($c->uri_for($self->action_for('list'),
122 {mid => $c->set_status_msg("Book created")}));
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
16d0d396 146L<Config::Any>, most people tend to use YAML. First create a directory
147to hold your form configuration files:
cca5cd98 148
c016427f 149 $ mkdir -p root/forms/books
cca5cd98 150
16d0d396 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
16d0d396 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
16d0d396 196Edit C<root/static/css/main.css> and add the following lines to the
197bottom of the file:
cca5cd98 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
16d0d396 216Open C<root/src/books/formfu_create.tt2> in your editor and enter the
217following:
cca5cd98 218
219 [% META title = 'Create/Update Book' %]
220
221 [%# Render the HTML::FormFu Form %]
222 [% form %]
223
aeaa7db5 224 <p><a href="[% c.uri_for(c.controller.action_for('list'))
225 %]">Return to book list</a></p>
cca5cd98 226
227
228=head2 Add Links for Create and Update via C<HTML::FormFu>
229
230Open C<root/src/books/list.tt2> in your editor and add the following to
231the bottom of the existing file:
232
acbd7bdd 233 ...
cca5cd98 234 <p>
235 HTML::FormFu:
e075db0c 236 <a href="[% c.uri_for(c.controller.action_for('formfu_create')) %]">Create</a>
cca5cd98 237 </p>
238
16d0d396 239This adds a new link to the bottom of the book list page that we can use
240to easily launch our HTML::FormFu-based form.
d682d02d 241
cca5cd98 242
8a472b34 243=head2 Test The HTML::FormFu Create Form
cca5cd98 244
aeaa7db5 245Make sure the server is running with the "-r" restart option:
cca5cd98 246
aeaa7db5 247 $ script/myapp_server.pl -r
cca5cd98 248
0909d46f 249Login as C<test01> (password: mypass). Once at the Book List page,
250click the new HTML::FormFu "Create" link at the bottom to display the
3b1fa91b 251form. Fill in the following values:
d682d02d 252
aeaa7db5 253 Title: Internetworking with TCP/IP Vol. II
254 Rating: 4
255 Author: Comer
256
c016427f 257Click the "Submit" button, and you will be returned to the Book List page
3b1fa91b 258with a "Book created" status message displayed.
259
260Also note that this implementation allows you to create books with any
16d0d396 261bogus information. Although we have constrained the authors with the
262drop-down list (note that this isn't bulletproof because we still have
263not prevented a user from "hacking" the form to specify other values),
264there are no restrictions on items such as the length of the title (for
265example, you can create a one-letter title) and the value of the rating
266(you can use any number you want, and even non-numeric values with
59d6a035 267SQLite). The next section will address this concern.
cca5cd98 268
269B<Note:> Depending on the database you are using and how you established
270the columns in your tables, the database could obviously provide various
271levels of "type enforcement" on your data. The key point being made in
272the previous paragraph is that the I<web application> itself is not
273performing any validation.
274
275
8a472b34 276=head1 HTML::FormFu VALIDATION AND FILTERING
cca5cd98 277
16d0d396 278Although the use of L<HTML::FormFu> in the previous section did provide
279an automated mechanism to build the form, the real power of this module
280stems from functionality that can automatically validate and filter the
281user input. Validation uses constraints to be sure that users input
282appropriate data (for example, that the email field of a form contains a
283valid email address). Filtering can also be used to remove extraneous
284whitespace from fields or to escape meta-characters in user input.
cca5cd98 285
cca5cd98 286
d682d02d 287=head2 Add Constraints
cca5cd98 288
16d0d396 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:
c016427f 365 # The user cannot leave any fields blank
366 - Required
367 # If not all fields are required, move the Required constraint to the
368 # fields that are
f279297a 369 filter:
c016427f 370 # Remove whitespace at both ends
371 - TrimEdges
372 # Escape HTML characters for safety
373 - HTMLEscape
cca5cd98 374
16d0d396 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
16d0d396 386drop-down to a multi-select list by adding configuration for the
d682d02d 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
16d0d396 392L<HTML::FormFu::Constraint> for other constraints that are available.
d682d02d 393
394=item *
395
16d0d396 396A variety of filters are run on every field to remove and escape
397unwanted input. See L<HTML::FormFu::Filter> for more filter options.
d682d02d 398
399=back
400
401
402=head2 Try Out the Updated Form
403
16d0d396 404Make sure you are still logged in as C<test01> and try adding a book
405with various errors: title less than 5 characters, non-numeric rating, a
406rating of 0 or 6, etc. Also try selecting one, two, and zero authors.
407When you click Submit, the HTML::FormFu C<constraint> items will
408validate the logic and insert feedback as appropriate. Try adding blank
409spaces at the front or the back of the title and note that it will be
59d6a035 410removed.
411
c016427f 412Note that you can update your FormFu YAML forms and the development
413server does not need to reload -- the form definition is read from
414the YAML file each time a controller action uses it.
415
59d6a035 416
417=head1 CREATE AND UPDATE/EDIT ACTION
418
16d0d396 419Let's expand the work done above to add an edit action. First, open
420C<lib/MyApp/Controller/Books.pm> and add the following method to the
59d6a035 421bottom:
422
423 =head2 formfu_edit
424
425 Use HTML::FormFu to update an existing book
426
427 =cut
428
acbd7bdd 429 sub formfu_edit :Chained('object') :PathPart('formfu_edit') :Args(0)
430 :FormConfig('books/formfu_create.yml') {
ccc9b2bc 431 my ($self, $c) = @_;
59d6a035 432
faa6f5bc 433 # Get the specified book already saved by the 'object' method
ccc9b2bc 434 my $book = $c->stash->{object};
59d6a035 435
436 # Make sure we were able to get a book
437 unless ($book) {
c016427f 438 # Set an error message for the user & return to books list
439 $c->response->redirect($c->uri_for($self->action_for('list'),
440 {mid => $c->set_error_msg("Invalid book -- Cannot edit")}));
59d6a035 441 $c->detach;
442 }
443
444 # Get the form that the :FormConfig attribute saved in the stash
445 my $form = $c->stash->{form};
446
166c90a0 447 # Check if the form has been submitted (vs. displaying the initial
448 # form) and if the data passed validation. "submitted_and_valid"
59d6a035 449 # is shorthand for "$form->submitted && !$form->has_errors"
450 if ($form->submitted_and_valid) {
451 # Save the form data for the book
aee27483 452 $form->model->update($book);
59d6a035 453 # Set a status message for the user
c016427f 454 # Set a status message for the user & return to books list
455 $c->response->redirect($c->uri_for($self->action_for('list'),
456 {mid => $c->set_status_msg("Book edited")}));
59d6a035 457 $c->detach;
458 } else {
459 # Get the authors from the DB
3b1fa91b 460 my @author_objs = $c->model("DB::Author")->all();
59d6a035 461 # Create an array of arrayrefs where each arrayref is an author
462 my @authors;
ccc9b2bc 463 foreach (sort {$a->last_name cmp $b->last_name} @author_objs) {
59d6a035 464 push(@authors, [$_->id, $_->last_name]);
465 }
466 # Get the select added by the config file
467 my $select = $form->get_element({type => 'Select'});
468 # Add the authors to it
469 $select->options(\@authors);
470 # Populate the form with existing values from DB
aee27483 471 $form->model->default_values($book);
59d6a035 472 }
473
474 # Set the template
766e64ba 475 $c->stash(template => 'books/formfu_create.tt2');
59d6a035 476 }
477
16d0d396 478Most of this code should look familiar to what we used in the
479C<formfu_create> method (in fact, we should probably centralize some of
59d6a035 480the common code in separate methods). The main differences are:
481
482=over 4
483
484=item *
485
16d0d396 486We have to manually specify the name of the FormFu .yml file as an
487argument to C<:FormConfig> because the name can no longer be
ccc9b2bc 488automatically deduced from the name of our action/method (by default,
489FormFu would look for a file named C<books/formfu_edit.yml>).
59d6a035 490
491=item *
492
16d0d396 493We load the book object from the stash (found using the $id passed to
ccc9b2bc 494the Chained object method)
59d6a035 495
496=item *
497
ccc9b2bc 498We use C<$id> to look up the existing book from the database.
499
500=item *
59d6a035 501
16d0d396 502We make sure the book lookup returned a valid book. If not, we set the
503error message and return to the book list.
2314f851 504
59d6a035 505=item *
506
16d0d396 507If the form has been submitted and passes validation, we skip creating a
508new book and just use C<$form-E<gt>model-E<gt>update> to update the
509existing book.
59d6a035 510
511=item *
512
16d0d396 513If the form is being displayed for the first time (or has failed
59d6a035 514validation and it being redisplayed), we use
16d0d396 515C<$form-E<gt>model-E<gt>default_values> to populate the form with data
516from the database.
59d6a035 517
518=back
519
16d0d396 520Then, edit C<root/src/books/list.tt2> and add a new link below the
521existing "Delete" link that allows us to edit/update each existing book.
522The last E<lt>tdE<gt> cell in the book list table should look like the
59d6a035 523following:
524
ab0db558 525 ...
59d6a035 526 <td>
527 [% # Add a link to delete a book %]
c016427f 528 <a href="[%
529 c.uri_for(c.controller.action_for('delete'), [book.id]) %]">Delete</a>
59d6a035 530 [% # Add a link to edit a book %]
c016427f 531 <a href="[%
532 c.uri_for(c.controller.action_for('formfu_edit'), [book.id]) %]">Edit</a>
59d6a035 533 </td>
ab0db558 534 ...
535
c016427f 536B<Note:> Only add three lines (the "Add a link to edit a book" comment and
16d0d396 537the href for C<formfu_edit>). Make sure you add it below the existing
538C<delete> link.
59d6a035 539
540
541=head2 Try Out the Edit/Update Feature
542
16d0d396 543Make sure you are still logged in as C<test01> and go to the
544L<http://localhost:3000/books/list> URL in your browser. Click the
545"Edit" link next to "Internetworking with TCP/IP Vol. II", change the
546rating to a 3, the "II" at end of the title to the number "2", add
547Stevens as a co-author (control-click), and click Submit. You will then
548be returned to the book list with a "Book edited" message at the top in
59d6a035 549green. Experiment with other edits to various books.
d682d02d 550
8a472b34 551
5fe0e6dd 552=head2 More Things to Try
553
554You are now armed with enough knowledge to be dangerous. You can keep
555tweaking the example application; some things you might want to do:
556
557=over 4
558
559=item *
560
acbd7bdd 561Add an appropriate authorization check to the new Edit function.
5fe0e6dd 562
563=item *
564
565Cleanup the List page so that the Login link only displays when the user
566isn't logged in and the Logout link only displays when a user is logged
567in.
568
569=item *
570
571Add a more sensible policy for when and how users and admins can do
572things in the CRUD cycle.
573
574=item *
575
576Support the CRUD cycle for authors.
577
578=back
579
580Or you can proceed to write your own application, which is probably the
581real reason you worked through this Tutorial in the first place.
cca5cd98 582
8a472b34 583
766e64ba 584=head2 Config::General Config for this tutorial
1fba2369 585
586If you are having difficulty with YAML config above, please save the
587below into the file C<formfu_create.conf> and delete the
16d0d396 588C<formfu_create.yml> file. The below is in L<Config::General> format
589which follows the syntax of Apache config files.
1fba2369 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
3dba69ab 635
1fba2369 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/>).