Fix some typos and clarifications.
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Manual / Tutorial / AdvancedCRUD.pod
1 =head1 NAME
2
3 Catalyst::Manual::Tutorial::AdvancedCRUD - Catalyst Tutorial - Part 8: Advanced CRUD
4
5 =head1 OVERVIEW
6
7 This is B<Part 8 of 9> for the Catalyst tutorial.
8
9 L<Tutorial Overview|Catalyst::Manual::Tutorial>
10
11 =over 4
12
13 =item 1
14
15 L<Introduction|Catalyst::Manual::Tutorial::Intro>
16
17 =item 2
18
19 L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
20
21 =item 3
22
23 L<Basic CRUD|Catalyst::Manual::Tutorial_BasicCRUD>
24
25 =item 4
26
27 L<Authentication|Catalyst::Manual::Tutorial::Authentication>
28
29 =item 5
30
31 L<Authorization|Catalyst::Manual::Tutorial::Authorization>
32
33 =item 6
34
35 L<Debugging|Catalyst::Manual::Tutorial::Debugging>
36
37 =item 7
38
39 L<Testing|Catalyst::Manual::Tutorial::Testing>
40
41 =item 8
42
43 B<AdvancedCRUD>
44
45 =item 9
46
47 L<Appendices|Catalyst::Manual::Tutorial::Appendices>
48
49 =back
50
51 =head1 DESCRIPTION
52
53 This part of the tutorial explores more advanced functionality for
54 Create, Read, Update, and Delete (CRUD) than we saw in Part 3.  In
55 particular, it looks at a number of techniques that can be useful for
56 the Update portion of CRUD, such as automated form generation,
57 validation of user-entered data, and automated transfer of data between
58 forms and model objects.
59
60 In keeping with the Catalyst (and Perl) spirit of flexibility, there are
61 many different ways to approach advanced CRUD operations in a Catalyst
62 environment.  One alternative is to use
63 L<Catalyst::Helper::Controller::Scaffold|Catalyst::Helper::Controller::Scaffold> 
64 to instantly construct a set of Controller methods and templates for 
65 basic CRUD operations.  Although a popular subject in Quicktime 
66 movies that serve as promotional material for various frameworks, 
67 real-world applications generally require more control.  Other 
68 options include L<Data::FormValidator|Data::FormValidator> and
69 L<HTML::FillInForm|HTML::FillInForm>.
70
71 Here, we will make use of the L<HTML::Widget|HTML::Widget> to not only 
72 ease form creation, but to also provide validation of the submitted 
73 data.  The approached used by this part of the tutorial is to slowly 
74 incorporate additional L<HTML::Widget|HTML::Widget> functionality in a 
75 step-wise fashion (we start with fairly simple form creation and then 
76 move on to more complex and "magical" features such as validation and 
77 auto-population/auto-saving).
78
79 B<Note:> Part 8 of the tutorial is optional.  Users who do not wish to
80 use L<HTML::Widget|HTML::Widget> may skip this part.
81
82 B<TIP>: Note that all of the code for this part of the tutorial can be
83 pulled from the Catalyst Subversion repository in one step with the
84 following command:
85
86     svn co http://dev.catalyst.perl.org/repos/Catalyst/tags/examples/Tutorial/MyApp/5.7/AdvancedCRUD MyApp
87
88 =head1 C<HTML::WIDGET> FORM CREATION
89
90 This section looks at how L<HTML::Widget|HTML::Widget> can be used to
91 add additional functionality to the manually created form from Part 3.
92
93 =head2 Add the C<HTML::Widget> Plugin
94
95 Open C<lib/MyApp.pm> in your editor and add the following to the list of
96 plugins (be sure to leave the existing plugins enabled):
97
98     HTML::Widget
99
100 =head2 Add a Form Creation Helper Method
101
102 Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
103 following method:
104
105     =head2 make_book_widget
106     
107     Build an HTML::Widget form for book creation and updates
108     
109     =cut
110     
111     sub make_book_widget {
112         my ($self, $c) = @_;
113     
114         # Create an HTML::Widget to build the form
115         my $w = $c->widget('book_form')->method('post');
116     
117         # Get authors
118         my @authorObjs = $c->model("MyAppDB::Author")->all();
119         my @authors = map {$_->id => $_->last_name }
120                            sort {$a->last_name cmp $b->last_name} @authorObjs;
121     
122         # Create the form feilds
123         $w->element('Textfield', 'title'  )->label('Title')->size(60);
124         $w->element('Textfield', 'rating' )->label('Rating')->size(1);
125         $w->element('Select',    'authors')->label('Authors')
126             ->options(@authors);
127         $w->element('Submit',    'submit' )->value('submit');
128     
129         # Return the widget    
130         return $w;
131     }
132
133 This method provides a central location that builds an 
134 HTML::Widget-based form with the appropriate fields.  The "Get authors" 
135 code uses DBIC to retrieve a list of model objects and then uses C<map> 
136 to create a hash where the hash keys are the database primary keys from 
137 the authors table and the associated values are the last names of the 
138 authors.
139
140 =head2 Add Actions to Display and Save the Form
141
142 Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
143 following methods:
144
145     =head2 hw_create
146     
147     Build an HTML::Widget form for book creation and updates
148     
149     =cut
150     
151     sub hw_create : Local {
152         my ($self, $c) = @_;
153     
154         # Create the widget and set the action for the form
155         my $w = $self->make_book_widget($c);
156         $w->action($c->uri_for('hw_create_do'));
157     
158         # Write form to stash variable for use in template
159         $c->stash->{widget_result} = $w->result;
160     
161         # Set the template
162         $c->stash->{template} = 'books/hw_form.tt2';
163     }
164     
165     
166     =head2 hw_create_do
167     
168     Build an HTML::Widget form for book creation and updates
169     
170     =cut
171     
172     sub hw_create_do : Local {
173         my ($self, $c) = @_;
174     
175         # Retrieve the data from the form
176         my $title   = $c->request->params->{title};
177         my $rating  = $c->request->params->{rating};
178         my $authors = $c->request->params->{authors};
179     
180         # Call create() on the book model object. Pass the table 
181         # columns/field values we want to set as hash values
182         my $book = $c->model('MyAppDB::Book')->create({
183                 title   => $title,
184                 rating  => $rating
185             });
186         
187         # Add a record to the join table for this book, mapping to 
188         # appropriate author
189         $book->add_to_book_authors({author_id => $authors});
190         
191         # Set a status message for the user
192         $c->stash->{status_msg} = 'Book created';
193     
194         # Use 'hw_create' to redisplay the form.  As discussed in 
195         # Part 3, 'detach' is like 'forward', but it does not return
196         $c->detach('hw_create');
197     }
198
199 Note how we use C<make_book_widget> to build the core parts of the form
200 in one location, but we set the action (the URL the form is sent to when
201 the user clicks the 'Submit' button) separately in C<hw_create>.  Doing
202 so allows us to have the same form submit the data to different actions
203 (e.g., C<hw_create_do> for a create operation but C<hw_update_do> to
204 update an existing book object).
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     label {
212         display: block;
213         width: 10em;
214         position: relative;
215         margin: .5em 0em;
216     }
217     label input {
218         position: absolute;
219         left: 100%;
220     }
221     label select {
222         position: absolute;
223         left: 100%;
224     }
225     .submit {
226         margin-top: 2em;;
227     }
228     .error_messages {
229         color: [% site.col.error %];
230     }
231
232 These changes will display form elements vertically and also show error
233 messages in red.  Note that we are pulling the color scheme settings
234 from the C<root/lib/config/col> file that was created by the TTSite
235 helper.  This allows us to change the color used by various error styles
236 in the CSS from a single location.
237
238 =head2 Create a Template Page To Display The Form
239
240 Open C<root/src/books/hw_form.tt2> in your editor and enter the following:
241
242     [% META title = 'Create/Update Book' %]
243     
244     [% widget_result.as_xml %]
245     
246     <p><a href="[% Catalyst.uri_for('list') %]">Return to book list</a></p>
247
248 =head2 Add Links for Create and Update via C<HTML::Widget>
249
250 Open C<root/src/books/list.tt2> in your editor and add the following to
251 the bottom of the existing file:
252
253     <p>
254       HTML::Widget:
255       <a href="[% Catalyst.uri_for('hw_create') %]">Create</a>
256       <a href="[% Catalyst.uri_for('hw_update') %]">Update</a>
257     </p>
258
259 =head2 Test The <HTML::Widget> Create Form
260
261 Press C<Ctrl-C> to kill the previous server instance (if it's still
262 running) and restart it:
263
264     $ script/myapp_server.pl
265
266 Login as C<test01>.  Once at the Book List page, click the HTML::Widget
267 "Create" link to display for form produced by C<make_book_widget>.  Fill
268 out the form with the following values: Title = "Internetworking with
269 TCP/IP Vol. II", Rating = "4", and Author = "Comer".  Click Submit, and
270 you will be returned to the Create/Update Book page with a "Book
271 created" status message displayed.  Click "Return to book list" to view
272 the newly created book on the main list.
273
274 Also note that this implementation allows you to can create books with
275 bogus information.  Although we have constrained the authors with the
276 drop-down list, there are no restrictions on items such as the length of
277 the title (for example, you can create a one-letter title) and value for
278 the rating (you can use any number you want, and even non-numeric values
279 with SQLite).  The next section will address this concern.
280
281 B<Note:> Depending on the database you are using and how you established
282 the columns in your tables, the database could obviously provide various
283 levels of "type enforcement" on your data.  The key point being made in
284 the previous paragraph is that the I<web application> itself is not
285 performing any validation.
286
287 =head1 C<HTML::WIDGET> VALIDATION AND FILTERING
288
289 Although the use of L<HTML::Widget|HTML::Widget> in the previous section
290 did provide an automated mechanism to build the form, the real power of
291 this module stems from functionality that can automatically validate and
292 filter the user input.  Validation uses constraints to be sure that
293 users input appropriate data (for example, that the email field of a
294 form contains a valid email address).  Filtering can be used to remove
295 extraneous whitespace from fields or to escape meta-characters in user
296 input.
297
298 =head2 Add Constraints and Filters to the Widget Creation Method
299
300 Open C<lib/MyApp/Controller/Books.pm> in your editor and update the
301 C<make_book_widget> method to match the following (new sections have
302 been marked with a C<*** NEW:> comment):
303
304     sub make_book_widget {
305         my ($self, $c) = @_;
306     
307         # Create an HTML::Widget to build the form
308         my $w = $c->widget('book_form')->method('post');
309             
310         # Get authors
311         my @authorObjs = $c->model("MyAppDB::Author")->all();
312         my @authors = map {$_->id => $_->last_name }
313                            sort {$a->last_name cmp $b->last_name} @authorObjs;
314     
315         # Create the form feilds
316         $w->element('Textfield', 'title'  )->label('Title')->size(60);
317         $w->element('Textfield', 'rating' )->label('Rating')->size(1);
318         # ***NEW: Convert to multi-select list
319         $w->element('Select',    'authors')->label('Authors')
320             ->options(@authors)->multiple(1)->size(3);
321         $w->element('Submit',    'submit' )->value('submit');
322     
323         # ***NEW: Set constraints
324         $w->constraint(All     => qw/title rating authors/)
325             ->message('Required. ');
326         $w->constraint(Integer => qw/rating/)
327             ->message('Must be an integer. ');
328         $w->constraint(Range   => qw/rating/)->min(1)->max(5)
329             ->message('Must be a number between 1 and 5. ');
330         $w->constraint(Length  => qw/title/)->min(5)->max(50)
331             ->message('Must be between 5 and 50 characters. ');
332     
333         # ***NEW: Set filters
334         for my $column (qw/title rating authors/) {
335             $w->filter( HTMLEscape => $column );
336             $w->filter( TrimEdges  => $column );
337         }
338     
339         # Return the widget    
340         return $w;
341     }
342
343 The main changes are:
344
345 =over 4
346
347 =item *
348
349 The C<Select> element for C<authors> is changed from a single-select
350 drop-down to a multi-select list by adding calls to C<multiple> (set to
351 C<true>) and C<size> (set to the number of rows to display).
352
353 =item *
354
355 Four sets of constraints are added to provide validation of the user input.
356
357 =item *
358
359 Two filters are run on every field to remove and escape unwanted input.
360
361 =back
362
363 =head2 Rebuild the Form Submission Method to Include Validation
364
365 Edit C<lib/MyApp/Controller/Books.pm> and change C<hw_create_do> to
366 match the following code (enough of the code is different that you
367 probably want to cut and paste this over code the existing method):
368
369     sub hw_create_do : Local {
370         my ($self, $c) = @_;
371     
372         # Retrieve the data from the form
373         my $title   = $c->request->params->{title};
374         my $rating  = $c->request->params->{rating};
375         my $authors = $c->request->params->{authors};
376         
377         # Create the widget and set the action for the form
378         my $w = $self->make_book_widget($c);
379         $w->action($c->uri_for('hw_create_do'));
380     
381         # Validate the form parameters
382         my $result = $w->process($c->req);
383     
384         # Write form (including validation error messages) to
385         # stash variable for use in template
386         $c->stash->{widget_result} = $result;
387     
388         # Were their validation errors?
389         if ($result->has_errors) {
390             # Warn the user at the top of the form that there were errors.
391             # Note that there will also be per-field feedback on
392             # validation errors because of '$w->process($c->req)' above.
393             $c->stash->{error_msg} = 'Validation errors!';
394         } else {
395             # Everything validated OK, so do the create
396             # Call create() on the book model object. Pass the table
397             # columns/field values we want to set as hash values
398             my $book = $c->model('MyAppDB::Book')->create({
399                     title   => $title,
400                     rating  => $rating
401                 });
402     
403             # Add a record to the join table for this book, mapping to
404             # appropriate author.  Note that $authors will be 1 author as
405             # a scalar or ref to list of authors depending on how many the
406             # user selected; the 'ref $authors ?...' handles both cases
407             foreach my $author (ref $authors ? @$authors : $authors) {
408                 $book->add_to_book_authors({author_id => $author});
409             }    
410             # Set a status message for the user
411             $c->stash->{status_msg} = 'Book created';
412         }
413     
414         # Set the template
415         $c->stash->{template} = 'books/hw_form.tt2';
416     }
417
418 The key changes to C<hw_create_do> are:
419
420 =over 4
421
422 =item *
423
424 C<hw_create_do> no longer does a C<detach> to C<hw_create> to redisplay
425 the form.  Now that C<hw_create_do> has to process the form in order to
426 perform the validation, we go ahead and build a complete set of form
427 presentation logic into C<hw_create_do> (for example, C<hw_create_do>
428 now has a C<$c-E<gt>stash-E<gt>{template}> line).  Note that if we
429 process the form in C<hw_create_do> I<and> forward/detach back to
430 <hw_create>, we would end up with C<make_book_widget> being called
431 twice, resulting in a duplicate set of elements being added to the form.
432 (There are other ways to address the "duplicate form rendering" issue --
433 just be aware that it exists.)
434
435 =item *
436
437 C<$w-E<gt>process($c-E<gt>req)> is called to run the validation logic.
438 Not only does this set the C<has_errors> flag if validation errors are
439 encountered, it returns a string containing any field-specific warning
440 messages.
441
442 =item *
443
444 An C<if> statement checks if any validation errors were encountered.  If
445 so, C<$c-E<gt>stash-E<gt>{error_msg}> is set and the input form is
446 redisplayed.  If no errors were found, the object is created in a manner
447 similar to the prior version of the C<hw_create_do> method.
448
449 =back
450
451 =head2 Try Out the Form
452
453 Press C<Ctrl-C> to kill the previous server instance (if it's still 
454 running) and restart it:
455
456     $ script/myapp_server.pl
457
458 Now try adding a book with various errors: title less than 5 characters,
459 non-numeric rating, a rating of 0 or 6, etc.  Also try selecting one,
460 two, and zero authors.  When you click Submit, the HTML::Widget
461 C<constraint> items will validate the logic and insert feedback as
462 appropriate.
463
464
465 =head1 Enable C<DBIx::Class::HTMLWidget> Support
466
467 In this section we will take advantage of some of the "auto-population"
468 features of C<DBIx::Class::HTMLWidget>.  Enabling
469 C<DBIx::Class::HTMLWidget> provides two additional methods to your DBIC
470 model classes:
471
472 =over 4
473
474 =item *
475
476 fill_widget()
477
478 Takes data from the database and transfers it to your form widget.
479
480 =item *
481
482 populate_from_widget()
483
484 Takes data from a form widget and uses it to update the corresponding
485 records in the database.
486
487 =back
488
489 In other words, the two methods are a mirror image of each other: one
490 reads from the database while the other writes to the database.
491
492 =head2 Add C<DBIx::Class::HTMLWidget> to DBIC Model
493
494 In order to use L<DBIx::Class::HTMLWidget|DBIx::Class::HTMLWidget>, we
495 need to add C<HTMLWidget> to the C<load_components> line of DBIC result
496 source files that need to use the C<fill_widget> and
497 C<populate_from_widget> methods.  In this case, open
498 C<lib/MyAppDB/Book.pm> and update the C<load_components> line to match:
499
500         __PACKAGE__->load_components(qw/PK::Auto Core HTMLWidget/);
501
502 =head2 Use C<populate_from_widget> in C<hw_create_do>
503
504 Edit C<lib/MyApp/Controller/Books.pm> and update C<hw_create_do> to
505 match the following code:
506
507     =head2 hw_create_do
508     
509     Build an HTML::Widget form for book creation and updates
510     
511     =cut
512     
513     sub hw_create_do : Local {
514         my ($self, $c) = @_;
515     
516         # Create the widget and set the action for the form
517         my $w = $self->make_book_widget($c);
518         $w->action($c->uri_for('hw_create_do'));
519     
520         # Validate the form parameters
521         my $result = $w->process($c->req);
522     
523         # Write form (including validation error messages) to
524         # stash variable for use in template
525         $c->stash->{widget_result} = $result;
526     
527         # Were their validation errors?
528         if ($result->has_errors) {
529             # Warn the user at the top of the form that there were errors.
530             # Note that there will also be per-field feedback on
531             # validation errors because of '$w->process($c->req)' above.
532             $c->stash->{error_msg} = 'Validation errors!';
533         } else {
534             my $book = $c->model('MyAppDB::Book')->new({});
535             $book->populate_from_widget($result);
536     
537             # Add a record to the join table for this book, mapping to
538             # appropriate author.  Note that $authors will be 1 author as
539             # a scalar or ref to list of authors depending on how many the
540             # user selected; the 'ref $authors ?...' handles both cases
541             my $authors = $c->request->params->{authors};
542             foreach my $author (ref $authors ? @$authors : $authors) {
543                 $book->add_to_book_authors({author_id => $author});
544             }
545     
546             # Set a status message for the user
547             $c->stash->{status_msg} = 'Book created';
548             
549             # Redisplay an empty form for another
550             $c->stash->{widget_result} = $w->result;
551         }
552     
553         # Set the template
554         $c->stash->{template} = 'books/hw_form.tt2';
555     }
556
557 In this version of C<hw_create_do> we removed the logic that manually
558 pulled the form variables and used them to call
559 C<$c-E<gt>model('MyAppDB::Book')-E<gt>create> and replaced it with a
560 single call to C<$book-E<gt>populate_from_widget>.  Note that we still
561 have to call C<$book-E<gt>add_to_book_authors> once per author because
562 C<populate_from_widget> does not currently handle the relationships
563 between tables.  Also, we reset the form to an empty fields by adding
564 another call to C<$w-E<gt>result> and storing the output in the stash 
565 (if we don't override the output from C<$w-E<gt>process($c-E<gt>req)>,
566 the form values already entered will be retained on redisplay --
567 although this could be desirable for some applications, we avoid it
568 here to help avoid the creation of duplicate records).
569
570
571 =head2 Try Out the Form
572
573 Press C<Ctrl-C> to kill the previous server instance (if it's still 
574 running) and restart it:
575
576     $ script/myapp_server.pl
577
578 Try adding a book that validate.  Return to the book list and the book 
579 you added should be visible.
580
581
582
583 =head1 Rendering C<HTMLWidget> Forms in a Table
584
585 Some developers my wish to use the "old-fashioned" table style of 
586 rendering a form in lieu of the default C<HTML::Widget> rendering that 
587 assumes you will use CSS for formatting.  This section demonstrates
588 some techniques that can override the default rendering with a 
589 custom class.
590
591
592 =head2 Add a New "Element Container"
593
594 Open C<lib/FormElementContainer.pm> in your editor and enter:
595
596     package FormElementContainer;
597     
598     use base 'HTML::Widget::Container';
599     
600     sub _build_element {
601         my ($self, $element) = @_;
602     
603         return () unless $element;
604         if (ref $element eq 'ARRAY') {
605             return map { $self->_build_element($_) } @{$element};
606         }
607         my $e = $element->clone;
608         my $class = $e->attr('class') || '';
609         $e = new HTML::Element('span', class => 'fields_with_errors')->push_content($e)
610             if $self->error && $e->tag eq 'input';
611     
612         return $e ? ($e) : ();
613     }
614     
615     1;
616
617 This simply dumps the HTML code for a given form element, followed by a 
618 C<span> that can contain validation error message.
619
620
621 =head2 Enable the New Element Container When Building the Form
622
623 Open C<lib/MyApp/Controller/Books.pm> in your editor.  First add a
624 C<use> for your element container class:
625
626     use FormElementContainer;
627
628 B<Note:> If you forget to C<use> your container class in your 
629 controller, then your form will not be displayed and no error messages 
630 will be generated. Don't forget this important step!
631
632 Then tell C<HTML::Widget> to use that class during rendering by updating
633 C<make_book_widget> to match the following:
634
635     sub make_book_widget {
636         my ($self, $c) = @_;
637     
638         # Create an HTML::Widget to build the form
639         my $w = $c->widget('book_form')->method('post');
640     
641         # ***New: Use custom class to render each element in the form    
642         $w->element_container_class('FormElementContainer');
643         
644         # Get authors
645         my @authorObjs = $c->model("MyAppDB::Author")->all();
646         my @authors = map {$_->id => $_->last_name }
647                            sort {$a->last_name cmp $b->last_name} @authorObjs;
648     
649         # Create the form feilds
650         $w->element('Textfield', 'title'  )->label('Title')->size(60);
651         $w->element('Textfield', 'rating' )->label('Rating')->size(1);
652         # Convert to multi-select list
653         $w->element('Select',    'authors')->label('Authors')
654             ->options(@authors)->multiple(1)->size(3);
655         $w->element('Submit',    'submit' )->value('submit');
656     
657         # Set constraints
658         $w->constraint(All     => qw/title rating authors/)
659             ->message('Required. ');
660         $w->constraint(Integer => qw/rating/)
661             ->message('Must be an integer. ');
662         $w->constraint(Range   => qw/rating/)->min(1)->max(5)
663             ->message('Must be a number between 1 and 5. ');
664         $w->constraint(Length  => qw/title/)->min(5)->max(50)
665             ->message('Must be between 5 and 50 characters. ');
666     
667         # Set filters
668         for my $column (qw/title rating authors/) {
669             $w->filter( HTMLEscape => $column );
670             $w->filter( TrimEdges  => $column );
671         }
672     
673         # Return the widget    
674         return $w;
675     }
676
677 The two new lines are marked with C<***New:>.
678
679
680 =head2 Update the TT Template
681
682 Open C<root/src/books/hw_form.tt2> and edit it to match:
683
684     [% META title = 'Create/Update Book' %]
685     
686     [%# Comment out the auto-rendered form %]
687     [%# widget_result.as_xml %]
688     
689     
690     [%# Iterate over the form elements and display each -%]
691     <form name="book_form" action="[% widget_result.action %]" method="post">
692     <table border="0">
693     [% FOREACH element = widget_result.elements %]
694       <tr>
695         <td class="form-label">
696           [% element.label.as_text %]
697         </td>
698         <td class="form-element">
699           [% element.element_xml %]
700           <span class="form-error">
701             [% element.error_xml %]
702           </span>
703         </td>
704       </tr>
705     [% END %]
706     </table>
707     </form>
708     
709     
710     <p><a href="[% Catalyst.uri_for('list') %]">Return to book list</a></p>
711     
712     
713     [%# A little JavaScript to move the cursor to the first field %]
714     <script LANGUAGE="JavaScript">
715     document.book_form.book_form_title.focus();
716     </script>
717
718 This represents three changes:
719
720 =over 4
721
722 =item *
723
724 The existing C<widget_result.as_xml> has been commented out.
725
726 =item *
727
728 It loops through each form element, displaying the field name in the 
729 first table cell along with the form element and validation errors in 
730 the second field.
731
732 =item *
733
734 JavaScript to position the user's cursor in the first field of the form.
735
736 =back
737
738
739 =head2 Try Out the Form
740
741 Press C<Ctrl-C> to kill the previous server instance (if it's still 
742 running) and restart it:
743
744     $ script/myapp_server.pl
745
746 Try adding a book that validate.  Return to the book list and the book 
747 you added should be visible.
748
749
750 =head1 AUTHOR
751
752 Kennedy Clark, C<hkclark@gmail.com>
753
754 Please report any errors, issues or suggestions to the author.  The
755 most recent version of the Catalyst Tutorial can be found at
756 L<http://dev.catalyst.perl.org/repos/Catalyst/trunk/Catalyst-Runtime/lib/Catalyst/Manual/Tutorial/>.
757
758 Copyright 2006, Kennedy Clark, under Creative Commons License
759 (L<http://creativecommons.org/licenses/by-nc-sa/2.5/>).