Fix erroneous "Part #" cross references between parts of the tutorial.
[catagits/Catalyst-Manual.git] / lib / Catalyst / Manual / Tutorial / AdvancedCRUD / FormFu.pod
CommitLineData
b80f58e5 1=head1 NAME
2
3Catalyst::Manual::Tutorial::AdvancedCRUD::FormFu - Catalyst Tutorial - Part 9: Advanced CRUD - FormFu
4
5
c010ae0d 6NOTE: This part of the tutorial is in progress and will be ready soon.
7
b80f58e5 8=head1 OVERVIEW
9
10This is B<Part 9 of 10> for the Catalyst tutorial.
11
12L<Tutorial Overview|Catalyst::Manual::Tutorial>
13
14=over 4
15
16=item 1
17
18L<Introduction|Catalyst::Manual::Tutorial::Intro>
19
20=item 2
21
22L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
23
24=item 3
25
26L<More Catalyst Basics|Catalyst::Manual::Tutorial::MoreCatalystBasics>
27
28=item 4
29
30L<Basic CRUD|Catalyst::Manual::Tutorial::BasicCRUD>
31
32=item 5
33
34L<Authentication|Catalyst::Manual::Tutorial::Authentication>
35
36=item 6
37
38L<Authorization|Catalyst::Manual::Tutorial::Authorization>
39
40=item 7
41
42L<Debugging|Catalyst::Manual::Tutorial::Debugging>
43
44=item 8
45
46L<Testing|Catalyst::Manual::Tutorial::Testing>
47
48=item 9
49
d682d02d 50B<Advanced CRUD::FormFu>
b80f58e5 51
52=item 10
53
54L<Appendices|Catalyst::Manual::Tutorial::Appendices>
55
56=back
57
58
59=head1 DESCRIPTION
60
cca5cd98 61This portion of the tutorial explores L<HTML::FormFu|HTML::FormFu> and
62how it can be used to manage forms, perform validation of form input,
63as well as save and restore data to/from the database.
64
65See
66L<Catalyst::Manual::Tutorial::AdvancedCRUD|Catalyst::Manual::Tutorial::AdvancedCRUD>
67for additional form management options other than
68L<HTML::FormFu|HTML::FormFu>.
69
70
71=head1 Install C<HTML::FormFu>
72
73If you are following along in Ubuntu, it turns out that C<HTML::FormFu>
74is not yet available as a package at the time this was written. To
75install it with a combination of C<apt-get> packages and traditional
76CPAN modules, first use C<apt-get> to install most of the modules
77required by C<HTML::FormFu>:
78
79 sudo apt-get install libtest-nowarnings-perl libdatetime-format-builder-perl \
80 libdatetime-format-strptime-perl libdatetime-locale-perl \
81 libhtml-tokeparser-simple-perl liblist-moreutils-perl \
82 libregexp-copy-perl libregexp-common-perl libyaml-syck-perl libparams-util-perl
83
84Then use the following command to install directly from CPAN the modules
85that aren't available as Ubuntu/Debian packages via C<apt-get>:
86
87 sudo cpan File::ShareDir Task::Weaken Config::Any HTML::FormFu \
88 Catalyst::Controller::HTML::FormFu
89
90
91=head1 C<HTML::FormFu> FORM CREATION
92
93This section looks at how L<HTML::FormFu|HTML::FormFu> can be used to
94add additional functionality to the manually created form from Part 4.
95
96
97=head2 Inherit From C<Catalyst::Controller::HTML::FormFu>
98
99First, change your C<lib/MyApp/Controller/Books.pm> to inherit from
100L<Catalyst::Controller::HTML::FormFu|Catalyst::Controller::HTML::FormFu>
101by changing the C<use base> line from the default of:
102
103 use base 'Catalyst::Controller';
104
105to use the FormFu base controller class:
106
107 use base 'Catalyst::Controller::HTML::FormFu';
108
109
110=head2 Add Action to Display and Save the Form
111
112Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
113following method:
114
d682d02d 115 =head2 formfu_create
cca5cd98 116
59d6a035 117 Use HTML::FormFu to create a new book
cca5cd98 118
119 =cut
120
d682d02d 121 sub formfu_create :Local :FormConfig {
cca5cd98 122 my ($self, $c) = @_;
123
124 # Get the form that the :FormConfig attribute saved in the stash
125 my $form = $c->stash->{form};
126
127 # Check if the form as been submitted (vs. displaying the initial
128 # form) and if the data based validation. "submitted_and_valid"
129 # is shorthand for "$form->submitted && !$form->has_errors"
130 if ($form->submitted_and_valid) {
131 # Create a new book
132 my $book = $c->model('DB::Books')->new_result({});
133 # Save the form data for the book
134 $form->save_to_model($book);
135 # Set a status message for the user
136 $c->flash->{status_msg} = 'Book created';
137 # Return to the books list
138 $c->response->redirect($c->uri_for('list'));
139 $c->detach;
9cb64a37 140 } else {
141 # Get the authors from the DB
142 my @authorObjs = $c->model("DB::Authors")->all();
143 # Create an array of arrayrefs where each arrayref is an author
144 my @authors;
145 foreach (sort {$a->last_name cmp $b->last_name} @authorObjs) {
146 push(@authors, [$_->id, $_->last_name]);
147 }
148 # Get the select added by the config file
149 my $select = $form->get_element({type => 'Select'});
150 # Add the authors to it
151 $select->options(\@authors);
152 }
153
cca5cd98 154 # Set the template
d682d02d 155 $c->stash->{template} = 'books/formfu_create.tt2';
cca5cd98 156 }
157
158
159=head2 Create a Form Config File
160
161Although C<HTML::FormFu> supports any configuration file handled by
162L<Config::Any|Config::Any>, most people tend to use YAML. First
163create a directory to hold your form configuration files:
164
165 mkdir -p root/forms/books
166
d682d02d 167Then create the file C<root/forms/books/formfu_create.yml> and enter the
cca5cd98 168following text:
169
170 ---
9cb64a37 171 # indicator is the field that is used to test for form submission
cca5cd98 172 indicator: submit
9cb64a37 173 # Start listing the form elements
cca5cd98 174 elements:
9cb64a37 175 # The first element will be a text field for the title
cca5cd98 176 - type: Text
177 name: title
178 label: Title
9cb64a37 179 # This is an optional 'mouse over' title pop-up
cca5cd98 180 attributes:
181 title: Enter a book title here
d682d02d 182
183 # Another text field for the numeric rating
cca5cd98 184 - type: Text
185 name: rating
186 label: Rating
187 attributes:
188 title: Enter a rating between 1 and 5 here
d682d02d 189
190 # Add a drop-down list for the author selection. Note that we will
191 # dynamically fill in all the authors from the controller but we
192 # could manually set items in the drop-list by adding this YAML code:
193 # options:
194 # - [ '1', 'Bastien' ]
195 # - [ '2', 'Nasseh' ]
196 - type: Select
197 name: authors
198 label: Author
199
9cb64a37 200 # The submit button
cca5cd98 201 - type: Submit
202 name: submit
203 value: Submit
204
205
206=head2 Update the CSS
207
208Edit C<root/src/ttsite.css> and add the following lines to the bottom of
209the file:
210
d682d02d 211 input {
cca5cd98 212 display: block;
213 }
d682d02d 214 select {
cca5cd98 215 display: block;
216 }
d682d02d 217 .submit {
218 padding-top: .5em;
219 display: block;
cca5cd98 220 }
221
d682d02d 222These changes will display form elements vertically. Note that the
223existing definition of the C<.error> class is pulling the color scheme
224settings from the C<root/lib/config/col> file that was created by the
225TTSite helper. This allows control over the CSS color settings from a
226single location.
cca5cd98 227
228
229=head2 Create a Template Page To Display The Form
230
d682d02d 231Open C<root/src/books/formfu_create.tt2> in your editor and enter the following:
cca5cd98 232
233 [% META title = 'Create/Update Book' %]
234
235 [%# Render the HTML::FormFu Form %]
236 [% form %]
237
238 <p><a href="[% Catalyst.uri_for('list') %]">Return to book list</a></p>
239
240
241=head2 Add Links for Create and Update via C<HTML::FormFu>
242
243Open C<root/src/books/list.tt2> in your editor and add the following to
244the bottom of the existing file:
245
246 <p>
247 HTML::FormFu:
d682d02d 248 <a href="[% Catalyst.uri_for('formfu_create') %]">Create</a>
cca5cd98 249 </p>
250
d682d02d 251This adds a new link to the bottom of the book list page that we can
252use to easily launch our HTML::FormFu-based form.
253
cca5cd98 254
d682d02d 255=head2 Test The <HTML::FormFu> Create Form
cca5cd98 256
257Press C<Ctrl-C> to kill the previous server instance (if it's still
258running) and restart it:
259
260 $ script/myapp_server.pl
261
d682d02d 262Login as C<test01>. Once at the Book List page, click the new
263HTML::FormFu "Create" link at the bottom to display the form. Fill in
264the following values: Title = "Internetworking with TCP/IP Vol. II",
265Rating = "4", and Author = "Comer". Click Submit, and you will be
266returned to the Book List page with a "Book created" status message
267displayed.
268
269Also note that this implementation allows you to can create books with
270bogus information. Although we have constrained the authors with the
59d6a035 271drop-down list (note that this isn't bulletproof because we still have
272not prevented a user from "hacking" the form to specify other values),
273there are no restrictions on items such as the length of the title (for
274example, you can create a one-letter title) and value for the rating
275(you can use any number you want, and even non-numeric values with
276SQLite). The next section will address this concern.
cca5cd98 277
278B<Note:> Depending on the database you are using and how you established
279the columns in your tables, the database could obviously provide various
280levels of "type enforcement" on your data. The key point being made in
281the previous paragraph is that the I<web application> itself is not
282performing any validation.
283
284
285=head1 C<HTML::FormFu> VALIDATION AND FILTERING
286
d682d02d 287Although the use of L<HTML::FormFu|HTML::FormFu> in the previous section
288did provide an automated mechanism to build the form, the real power of
289this module stems from functionality that can automatically validate and
290filter the user input. Validation uses constraints to be sure that
291users input appropriate data (for example, that the email field of a
292form contains a valid email address). Filtering can also be used to
293remove extraneous whitespace from fields or to escape meta-characters in
294user input.
cca5cd98 295
cca5cd98 296
d682d02d 297=head2 Add Constraints
cca5cd98 298
d682d02d 299Open C<root/forms/books/formfu_create.yml> in your editor and update it
cca5cd98 300to match:
301
302 ---
9cb64a37 303 # indicator is the field that is used to test for form submission
cca5cd98 304 indicator: submit
9cb64a37 305 # Start listing the form elements
cca5cd98 306 elements:
9cb64a37 307 # The first element will be a text field for the title
cca5cd98 308 - type: Text
309 name: title
310 label: Title
9cb64a37 311 # This is an optional 'mouse over' title pop-up
cca5cd98 312 attributes:
313 title: Enter a book title here
d682d02d 314 # Use Filter to clean up the input data
315 filter:
316 # Remove whitespace at both ends
317 - TrimEdges
318 # Escape HTML characters for safety
319 - HTMLEscape
9cb64a37 320 # Add constraints for the field
cca5cd98 321 constraints:
9cb64a37 322 # The user cannot leave this field blank
59d6a035 323 - SingleValue
324 # Force the length to be between 5 and 40 chars
cca5cd98 325 - type: Length
d682d02d 326 min: 5
59d6a035 327 max: 40
9cb64a37 328 # Override the default of 'Invalid input'
59d6a035 329 message: Length must be between 5 and 40 characters
d682d02d 330
331 # Another text field for the numeric rating
cca5cd98 332 - type: Text
333 name: rating
334 label: Rating
335 attributes:
336 title: Enter a rating between 1 and 5 here
d682d02d 337 # Use Filter to clean up the input data
338 filter:
339 # Remove whitespace at both ends
340 - TrimEdges
341 # Remove everything except digits
342 - NonNumeric
343 # Add constraints to the field
cca5cd98 344 constraints:
59d6a035 345 - SingleValue
9cb64a37 346 # Make sure it's a number
cca5cd98 347 - Integer
d682d02d 348
349 # Add a select list for the author selection. Note that we will
350 # dynamically fill in all the authors from the controller but we
351 # could manually set items in the select by adding this YAML code:
352 # options:
353 # - [ '1', 'Bastien' ]
354 # - [ '2', 'Nasseh' ]
355 - type: Select
356 name: authors
357 label: Author
358 # Convert the drop-down to a multi-select list
359 multiple: 1
360 # Display 3 entries (user can scroll to see others)
361 size: 3
362 # One could argue we don't need to do filters or constraints for
363 # a select list, but it's smart to do validation and sanity
364 # checks on this data in case a user "hacks" the input
365 # Use Filter to clean up the input data
366 filter:
367 # Remove whitespace at both ends
368 - TrimEdges
369 # Escape HTML characters for safety
370 - HTMLEscape
371 # Add constraints to the field
372 constraints:
d682d02d 373 # Make sure it's a number
374 - Integer
375
9cb64a37 376 # The submit button
cca5cd98 377 - type: Submit
378 name: submit
379 value: Submit
d682d02d 380
9cb64a37 381 # Globally ensure that each field only specified one value
cca5cd98 382 constraints:
59d6a035 383 # The user cannot leave any fields blank
384 - Required
cca5cd98 385
d682d02d 386The main changes are:
387
388=over 4
389
390=item *
391
392The C<Select> element for C<authors> is changed from a single-select
393drop-down to a multi-select list by adding configuration for the
394C<multiple> and C<size> options in C<formfu_create.yml>.
395
396=item *
397
398Constraints are added to provide validation of the user input. See
399L<HTML::FormFu::Constraint|HTML::FormFu::Constraint> for other
400constraints that are available.
401
402=item *
403
404A variety of filters are run on every field to remove and escape
405unwanted input. See L<HTML::FormFu::Filter|HTML::FormFu::Filter>
406for more filter options.
407
408=back
409
410
411=head2 Try Out the Updated Form
412
413Press C<Ctrl-C> to kill the previous server instance (if it's still
414running) and restart it:
415
416 $ script/myapp_server.pl
417
59d6a035 418Make sure you are still logged in as C<test01> and try adding a book
419with various errors: title less than 5 characters, non-numeric rating, a
420rating of 0 or 6, etc. Also try selecting one, two, and zero authors.
421When you click Submit, the HTML::FormFu C<constraint> items will
422validate the logic and insert feedback as appropriate. Try adding blank
423spaces at the front or the back of the title and note that it will be
424removed.
425
426
427=head1 CREATE AND UPDATE/EDIT ACTION
428
429Let's expand the work done above to add an edit action. First, open
430C<lib/MyApp/Controller/Books.pm> and add the following method to the
431bottom:
432
433 =head2 formfu_edit
434
435 Use HTML::FormFu to update an existing book
436
437 =cut
438
439 sub formfu_edit :Local :FormConfig('books/formfu_create.yml') {
440 my ($self, $c, $id) = @_;
441
442 # Get the specified book
443 my $book = $c->model('DB::Books')->find($id);
444
445 # Make sure we were able to get a book
446 unless ($book) {
447 $c->flash->{error_msg} = "Invalid book -- Cannot edit";
448 $c->response->redirect($c->uri_for('list'));
449 $c->detach;
450 }
451
452 # Get the form that the :FormConfig attribute saved in the stash
453 my $form = $c->stash->{form};
454
455 # Check if the form as been submitted (vs. displaying the initial
456 # form) and if the data based validation. "submitted_and_valid"
457 # is shorthand for "$form->submitted && !$form->has_errors"
458 if ($form->submitted_and_valid) {
459 # Save the form data for the book
460 $form->save_to_model($book);
461 # Set a status message for the user
462 $c->flash->{status_msg} = 'Book edited';
463 # Return to the books list
464 $c->response->redirect($c->uri_for('list'));
465 $c->detach;
466 } else {
467 # Get the authors from the DB
468 my @authorObjs = $c->model("DB::Authors")->all();
469 # Create an array of arrayrefs where each arrayref is an author
470 my @authors;
471 foreach (sort {$a->last_name cmp $b->last_name} @authorObjs) {
472 push(@authors, [$_->id, $_->last_name]);
473 }
474 # Get the select added by the config file
475 my $select = $form->get_element({type => 'Select'});
476 # Add the authors to it
477 $select->options(\@authors);
478 # Populate the form with existing values from DB
479 $form->defaults_from_model($book);
480 }
481
482 # Set the template
483 $c->stash->{template} = 'books/formfu_create.tt2';
484 }
485
486Most of this code should look familiar to what we used in the
487C<formfu_create> method (in fact, we should probably centralize some of
488the common code in separate methods). The main differences are:
489
490=over 4
491
492=item *
493
494We accept C<$id> as an argument via the URL.
495
496=item *
497
498We use C<$id> to look up the existing book from the database.
499
500=item *
501
502We make sure the C<$id> and book lookup returned a valid book. If not,
503we set the error message and return to the book list.
504
505=item *
506
507If the form has been submitted and passes validation, we skip creating a
508new book and just use C<$form-E<gt>save_to_model> to update the existing
509book.
510
511=item *
512
513If the form is being displayed for the first time (or has failed
514validation and it being redisplayed), we use
515 C<$form-E<gt>default_from_model> to populate the form with data from the
516database.
517
518=back
519
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
523following:
524
525 <td>
526 [% # Add a link to delete a book %]
527 <a href="[% Catalyst.uri_for('delete', book.id) %]">Delete</a>
528 [% # Add a link to edit a book %]
529 <a href="[% Catalyst.uri_for('formfu_edit', book.id) %]">Edit</a>
530 </td>
531
532
533=head2 Try Out the Edit/Update Feature
534
535Press C<Ctrl-C> to kill the previous server instance (if it's still
536running) and restart it:
537
538 $ script/myapp_server.pl
539
540Make sure you are still logged in as C<test01> and go to the
541L<http://localhost:3000/books/list> URL in your browser. Click the
542"Edit" link next to "Internetworking with TCP/IP Vol. II", change the
543rating to a 3, the "II" at end of the title to the number "2", add
544Stevens as a co-author (control-click), and click Submit. You will then
545be returned to the book list with a "Book edited" message at the top in
546green. Experiment with other edits to various books.
d682d02d 547
cca5cd98 548
549=head1 AUTHOR
550
551Kennedy Clark, C<hkclark@gmail.com>
552
553Please report any errors, issues or suggestions to the author. The
554most recent version of the Catalyst Tutorial can be found at
555L<http://dev.catalyst.perl.org/repos/Catalyst/trunk/Catalyst-Manual/lib/Catalyst/Manual/Tutorial/>.
556
d682d02d 557Copyright 20066-2008, Kennedy Clark, under Creative Commons License
cca5cd98 558(L<http://creativecommons.org/licenses/by-nc-sa/2.5/>).
559