Add edit/update section & misc updates
[catagits/Catalyst-Manual.git] / lib / Catalyst / Manual / Tutorial / AdvancedCRUD / FormFu.pod
1 =head1 NAME
2
3 Catalyst::Manual::Tutorial::AdvancedCRUD::FormFu - Catalyst Tutorial - Part 9: Advanced CRUD - FormFu
4
5
6 NOTE:  This part of the tutorial is in progress and will be ready soon.
7
8 =head1 OVERVIEW
9
10 This is B<Part 9 of 10> for the Catalyst tutorial.
11
12 L<Tutorial Overview|Catalyst::Manual::Tutorial>
13
14 =over 4
15
16 =item 1
17
18 L<Introduction|Catalyst::Manual::Tutorial::Intro>
19
20 =item 2
21
22 L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
23
24 =item 3
25
26 L<More Catalyst Basics|Catalyst::Manual::Tutorial::MoreCatalystBasics>
27
28 =item 4
29
30 L<Basic CRUD|Catalyst::Manual::Tutorial::BasicCRUD>
31
32 =item 5
33
34 L<Authentication|Catalyst::Manual::Tutorial::Authentication>
35
36 =item 6
37
38 L<Authorization|Catalyst::Manual::Tutorial::Authorization>
39
40 =item 7
41
42 L<Debugging|Catalyst::Manual::Tutorial::Debugging>
43
44 =item 8
45
46 L<Testing|Catalyst::Manual::Tutorial::Testing>
47
48 =item 9
49
50 B<Advanced CRUD::FormFu>
51
52 =item 10
53
54 L<Appendices|Catalyst::Manual::Tutorial::Appendices>
55
56 =back
57
58
59 =head1 DESCRIPTION
60
61 This portion of the tutorial explores L<HTML::FormFu|HTML::FormFu> and 
62 how it can be used to manage forms, perform validation of form input, 
63 as well as save and restore data to/from the database.
64
65 See 
66 L<Catalyst::Manual::Tutorial::AdvancedCRUD|Catalyst::Manual::Tutorial::AdvancedCRUD>
67 for additional form management options other than 
68 L<HTML::FormFu|HTML::FormFu>.
69
70
71 =head1 Install C<HTML::FormFu>
72
73 If you are following along in Ubuntu, it turns out that C<HTML::FormFu> 
74 is not yet available as a package at the time this was written.  To 
75 install it with a combination of C<apt-get> packages and traditional 
76 CPAN modules, first use C<apt-get> to install most of the modules 
77 required 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
84 Then use the following command to install directly from CPAN the modules 
85 that 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
93 This section looks at how L<HTML::FormFu|HTML::FormFu> can be used to 
94 add additional functionality to the manually created form from Part 4.
95
96
97 =head2 Inherit From C<Catalyst::Controller::HTML::FormFu>
98
99 First, change your C<lib/MyApp/Controller/Books.pm> to inherit from
100 L<Catalyst::Controller::HTML::FormFu|Catalyst::Controller::HTML::FormFu>
101 by changing the C<use base> line from the default of:
102
103     use base 'Catalyst::Controller';
104
105 to 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
112 Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
113 following method:
114
115     =head2 formfu_create
116     
117     Use HTML::FormFu to create a new book
118     
119     =cut
120     
121     sub formfu_create :Local :FormConfig {
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;
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         
154         # Set the template
155         $c->stash->{template} = 'books/formfu_create.tt2';
156     }
157
158
159 =head2 Create a Form Config File
160
161 Although C<HTML::FormFu> supports any configuration file handled by
162 L<Config::Any|Config::Any>, most people tend to use YAML.  First
163 create a directory to hold your form configuration files:
164
165     mkdir -p root/forms/books
166
167 Then create the file C<root/forms/books/formfu_create.yml> and enter the 
168 following text:
169
170     ---
171     # indicator is the field that is used to test for form submission
172     indicator: submit
173     # Start listing the form elements
174     elements:
175         # The first element will be a text field for the title
176         - type: Text
177           name: title
178           label: Title
179           # This is an optional 'mouse over' title pop-up
180           attributes:
181             title: Enter a book title here
182     
183         # Another text field for the numeric rating
184         - type: Text
185           name: rating
186           label: Rating
187           attributes:
188             title: Enter a rating between 1 and 5 here
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     
200         # The submit button
201         - type: Submit
202           name: submit
203           value: Submit
204
205
206 =head2 Update the CSS
207
208 Edit C<root/src/ttsite.css> and add the following lines to the bottom of
209 the file:
210
211     input {
212         display: block;
213     }
214     select {
215         display: block;
216     }
217     .submit {
218         padding-top: .5em;
219         display: block;
220     }
221
222 These changes will display form elements vertically.  Note that the 
223 existing definition of the C<.error> class is pulling the color scheme 
224 settings from the C<root/lib/config/col> file that was created by the 
225 TTSite helper.  This allows control over the CSS color settings from a 
226 single location.
227
228
229 =head2 Create a Template Page To Display The Form
230
231 Open C<root/src/books/formfu_create.tt2> in your editor and enter the following:
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
243 Open C<root/src/books/list.tt2> in your editor and add the following to
244 the bottom of the existing file:
245
246     <p>
247       HTML::FormFu:
248       <a href="[% Catalyst.uri_for('formfu_create') %]">Create</a>
249     </p>
250
251 This adds a new link to the bottom of the book list page that we can
252 use to easily launch our HTML::FormFu-based form.
253
254
255 =head2 Test The <HTML::FormFu> Create Form
256
257 Press C<Ctrl-C> to kill the previous server instance (if it's still
258 running) and restart it:
259
260     $ script/myapp_server.pl
261
262 Login as C<test01>.  Once at the Book List page, click the new 
263 HTML::FormFu "Create" link at the bottom to display the form.  Fill in 
264 the following values: Title = "Internetworking with TCP/IP Vol. II", 
265 Rating = "4", and Author = "Comer".  Click Submit, and you will be 
266 returned to the Book List page with a "Book created" status message 
267 displayed.
268
269 Also note that this implementation allows you to can create books with 
270 bogus information.  Although we have constrained the authors with the 
271 drop-down list (note that this isn't bulletproof because we still have 
272 not prevented a user from "hacking" the form to specify other values), 
273 there are no restrictions on items such as the length of the title (for 
274 example, 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 
276 SQLite).  The next section will address this concern.
277
278 B<Note:> Depending on the database you are using and how you established
279 the columns in your tables, the database could obviously provide various
280 levels of "type enforcement" on your data.  The key point being made in
281 the previous paragraph is that the I<web application> itself is not
282 performing any validation.
283
284
285 =head1 C<HTML::FormFu> VALIDATION AND FILTERING
286
287 Although the use of L<HTML::FormFu|HTML::FormFu> in the previous section 
288 did provide an automated mechanism to build the form, the real power of 
289 this module stems from functionality that can automatically validate and 
290 filter the user input.  Validation uses constraints to be sure that 
291 users input appropriate data (for example, that the email field of a 
292 form contains a valid email address).  Filtering can also be used to 
293 remove extraneous whitespace from fields or to escape meta-characters in 
294 user input.
295
296
297 =head2 Add Constraints
298
299 Open C<root/forms/books/formfu_create.yml> in your editor and update it 
300 to match:
301
302     ---
303     # indicator is the field that is used to test for form submission
304     indicator: submit
305     # Start listing the form elements
306     elements:
307         # The first element will be a text field for the title
308         - type: Text
309           name: title
310           label: Title
311           # This is an optional 'mouse over' title pop-up
312           attributes:
313             title: Enter a book title here
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
320           # Add constraints for the field
321           constraints:
322             # The user cannot leave this field blank
323             - SingleValue
324             # Force the length to be between 5 and 40 chars
325             - type: Length
326               min: 5
327               max: 40
328               # Override the default of 'Invalid input'
329               message: Length must be between 5 and 40 characters
330     
331         # Another text field for the numeric rating
332         - type: Text
333           name: rating
334           label: Rating
335           attributes:
336             title: Enter a rating between 1 and 5 here
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
344           constraints:
345             - SingleValue
346             # Make sure it's a number
347             - Integer
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:
373             # Make sure it's a number
374             - Integer
375     
376         # The submit button
377         - type: Submit
378           name: submit
379           value: Submit
380     
381     # Globally ensure that each field only specified one value
382     constraints:
383         # The user cannot leave any fields blank
384         - Required
385
386 The main changes are:
387
388 =over 4
389
390 =item *
391
392 The C<Select> element for C<authors> is changed from a single-select
393 drop-down to a multi-select list by adding configuration for the 
394 C<multiple> and C<size> options in C<formfu_create.yml>.
395
396 =item *
397
398 Constraints are added to provide validation of the user input.  See
399 L<HTML::FormFu::Constraint|HTML::FormFu::Constraint> for other
400 constraints that are available.
401
402 =item *
403
404 A variety of filters are run on every field to remove and escape 
405 unwanted input.  See L<HTML::FormFu::Filter|HTML::FormFu::Filter>
406 for more filter options.
407
408 =back
409
410
411 =head2 Try Out the Updated Form
412
413 Press C<Ctrl-C> to kill the previous server instance (if it's still 
414 running) and restart it:
415
416     $ script/myapp_server.pl
417
418 Make sure you are still logged in as C<test01> and try adding a book 
419 with various errors: title less than 5 characters, non-numeric rating, a 
420 rating of 0 or 6, etc.  Also try selecting one, two, and zero authors. 
421 When you click Submit, the HTML::FormFu C<constraint> items will 
422 validate the logic and insert feedback as appropriate.  Try adding blank 
423 spaces at the front or the back of the title and note that it will be 
424 removed.
425
426
427 =head1 CREATE AND UPDATE/EDIT ACTION
428
429 Let's expand the work done above to add an edit action.  First, open 
430 C<lib/MyApp/Controller/Books.pm> and add the following method to the 
431 bottom:
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
486 Most of this code should look familiar to what we used in the 
487 C<formfu_create> method (in fact, we should probably centralize some of 
488 the common code in separate methods).  The main differences are:
489
490 =over 4
491
492 =item *
493
494 We accept C<$id> as an argument via the URL.
495
496 =item *
497
498 We use C<$id> to look up the existing book from the database.
499
500 =item *
501
502 We make sure the C<$id> and book lookup returned a valid book.  If not, 
503 we set the error message and return to the book list.
504
505 =item *
506
507 If the form has been submitted and passes validation, we skip creating a 
508 new book and just use C<$form-E<gt>save_to_model> to update the existing 
509 book.
510
511 =item *
512
513 If the form is being displayed for the first time (or has failed 
514 validation and it being redisplayed), we use
515  C<$form-E<gt>default_from_model> to populate the form with data from the 
516 database.
517
518 =back
519
520 Then, edit C<root/src/books/list.tt2> and add a new link below the 
521 existing "Delete" link that allows us to edit/update each existing book. 
522 The last E<lt>tdE<gt> cell in the book list table should look like the 
523 following:
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
535 Press C<Ctrl-C> to kill the previous server instance (if it's still 
536 running) and restart it:
537
538     $ script/myapp_server.pl
539
540 Make sure you are still logged in as C<test01> and go to the 
541 L<http://localhost:3000/books/list> URL in your browser.  Click the 
542 "Edit" link next to "Internetworking with TCP/IP Vol. II", change the 
543 rating to a 3, the "II" at end of the title to the number "2", add 
544 Stevens as a co-author (control-click), and click Submit.  You will then 
545 be returned to the book list with a "Book edited" message at the top in 
546 green.  Experiment with other edits to various books.
547
548
549 =head1 AUTHOR
550
551 Kennedy Clark, C<hkclark@gmail.com>
552
553 Please report any errors, issues or suggestions to the author.  The
554 most recent version of the Catalyst Tutorial can be found at
555 L<http://dev.catalyst.perl.org/repos/Catalyst/trunk/Catalyst-Manual/lib/Catalyst/Manual/Tutorial/>.
556
557 Copyright 20066-2008, Kennedy Clark, under Creative Commons License
558 (L<http://creativecommons.org/licenses/by-nc-sa/2.5/>).
559