Fix minor typo
[catagits/Catalyst-Manual.git] / lib / Catalyst / Manual / Tutorial / AdvancedCRUD / FormFu.pod
index af75d42..0341461 100644 (file)
@@ -1,13 +1,11 @@
 =head1 NAME
 
-Catalyst::Manual::Tutorial::AdvancedCRUD::FormFu - Catalyst Tutorial - Part 9: Advanced CRUD - FormFu
+Catalyst::Manual::Tutorial::AdvancedCRUD::FormFu - Catalyst Tutorial - Chapter 9: Advanced CRUD - FormFu
 
 
-NOTE:  This part of the tutorial is in progress and will be ready soon.
-
 =head1 OVERVIEW
 
-This is B<Part 9 of 10> for the Catalyst tutorial.
+This is B<Chapter 9 of 10> for the Catalyst tutorial.
 
 L<Tutorial Overview|Catalyst::Manual::Tutorial>
 
@@ -60,7 +58,8 @@ L<Appendices|Catalyst::Manual::Tutorial::Appendices>
 
 This portion of the tutorial explores L<HTML::FormFu|HTML::FormFu> and 
 how it can be used to manage forms, perform validation of form input, 
-as well as save and restore data to/from the database.
+as well as save and restore data to/from the database.  This was written
+using HTML::FormFu version 0.03007.
 
 See 
 L<Catalyst::Manual::Tutorial::AdvancedCRUD|Catalyst::Manual::Tutorial::AdvancedCRUD>
@@ -70,41 +69,45 @@ L<HTML::FormFu|HTML::FormFu>.
 
 =head1 Install C<HTML::FormFu>
 
-If you are following along in Ubuntu, it turns out that C<HTML::FormFu> 
-is not yet available as a package at the time this was written.  To 
-install it with a combination of C<apt-get> packages and traditional 
-CPAN modules, first use C<apt-get> to install most of the modules 
-required by C<HTML::FormFu>:
+If you are following along in Debian 5, it turns out that some of the 
+modules we need are not yet available as Debian packages at the time 
+this was written.  To install it with a combination of Debian packages 
+and traditional CPAN modules, first use C<aptitude> to install most of 
+the modules:
+
+we need to install the
+L<HTML::FormFu|HTML::FormFu> package: 
 
-    sudo apt-get install libtest-nowarnings-perl libdatetime-format-builder-perl \
-    libdatetime-format-strptime-perl libdatetime-locale-perl \
-    libhtml-tokeparser-simple-perl liblist-moreutils-perl \
-    libregexp-copy-perl libregexp-common-perl libyaml-syck-perl libparams-util-perl
+    sudo aptitude -y install libhtml-formfu-perl libmoose-perl \
+        libregexp-assemble-perl libhtml-formfu-model-dbic-perl
+        
+    ...
+    
+    sudo aptitude clean
 
 Then use the following command to install directly from CPAN the modules 
-that aren't available as Ubuntu/Debian packages via C<apt-get>:
+that aren't available as Debian packages:
 
-    sudo cpan File::ShareDir Task::Weaken Config::Any HTML::FormFu \
-    Catalyst::Controller::HTML::FormFu
+    sudo cpan Catalyst::Component::InstancePerContext Catalyst::Controller::HTML::FormFu
 
 
 =head1 C<HTML::FormFu> FORM CREATION
 
 This section looks at how L<HTML::FormFu|HTML::FormFu> can be used to 
-add additional functionality to the manually created form from Part 4.
+add additional functionality to the manually created form from Chapter 4.
 
 
 =head2 Inherit From C<Catalyst::Controller::HTML::FormFu>
 
 First, change your C<lib/MyApp/Controller/Books.pm> to inherit from
 L<Catalyst::Controller::HTML::FormFu|Catalyst::Controller::HTML::FormFu>
-by changing the C<use base> line from the default of:
+by changing the C<use parent> line from the default of:
 
-    use base 'Catalyst::Controller';
+    use parent 'Catalyst::Controller';
 
 to use the FormFu base controller class:
 
-    use base 'Catalyst::Controller::HTML::FormFu';
+    use parent 'Catalyst::Controller::HTML::FormFu';
 
 
 =head2 Add Action to Display and Save the Form
@@ -118,31 +121,31 @@ following method:
     
     =cut
     
-    sub formfu_create :Local :FormConfig {
+    sub formfu_create :Chained('base') :PathPart('formfu_create') :Args(0) :FormConfig {
         my ($self, $c) = @_;
     
         # Get the form that the :FormConfig attribute saved in the stash
         my $form = $c->stash->{form};
   
-        # Check if the form as been submitted (vs. displaying the initial
-        # form) and if the data based validation.  "submitted_and_valid"
+        # Check if the form has been submitted (vs. displaying the initial
+        # form) and if the data passed validation.  "submitted_and_valid"
         # is shorthand for "$form->submitted && !$form->has_errors"
         if ($form->submitted_and_valid) {
             # Create a new book
             my $book = $c->model('DB::Books')->new_result({});
             # Save the form data for the book
-            $form->save_to_model($book);
+            $form->model->update($book);
             # Set a status message for the user
             $c->flash->{status_msg} = 'Book created';
             # Return to the books list
-            $c->response->redirect($c->uri_for('list')); 
+            $c->response->redirect($c->uri_for($self->action_for('list'))); 
             $c->detach;
         } else {
             # Get the authors from the DB
-            my @authorObjs = $c->model("DB::Authors")->all();
+            my @author_objs = $c->model("DB::Authors")->all();
             # Create an array of arrayrefs where each arrayref is an author
             my @authors;
-            foreach (sort {$a->last_name cmp $b->last_name} @authorObjs) {
+            foreach (sort {$a->last_name cmp $b->last_name} @author_objs) {
                 push(@authors, [$_->id, $_->last_name]);
             }
             # Get the select added by the config file
@@ -203,15 +206,16 @@ following text:
           value: Submit
 
 B<NOTE:> Copying and pasting YAML from perl documentation is sometimes
-tricky.  See the L<Copy-Paste versions of the YAML Config> section of
-this document for a foolproof procedure.
+tricky.  See the L<Config::General Config for this tutorial> section of
+this document for a more foolproof config format.
 
 
 =head2 Update the CSS
 
-Edit C<root/src/ttsite.css> and add the following lines to the bottom of
+Edit C<root/static/css/main.css> and add the following lines to the bottom of
 the file:
 
+    ...
     input {
         display: block;
     }
@@ -239,7 +243,7 @@ Open C<root/src/books/formfu_create.tt2> in your editor and enter the following:
     [%# Render the HTML::FormFu Form %]
     [% form %]
     
-    <p><a href="[% Catalyst.uri_for('list') %]">Return to book list</a></p>
+    <p><a href="[% c.uri_for(c.controller.action_for('list')) %]">Return to book list</a></p>
 
 
 =head2 Add Links for Create and Update via C<HTML::FormFu>
@@ -247,9 +251,10 @@ Open C<root/src/books/formfu_create.tt2> in your editor and enter the following:
 Open C<root/src/books/list.tt2> in your editor and add the following to
 the bottom of the existing file:
 
+    ...
     <p>
       HTML::FormFu:
-      <a href="[% Catalyst.uri_for('formfu_create') %]">Create</a>
+      <a href="[% c.uri_for(c.controller.action_for('formfu_create')) %]">Create</a>
     </p>
 
 This adds a new link to the bottom of the book list page that we can
@@ -315,16 +320,8 @@ to match:
           # This is an optional 'mouse over' title pop-up
           attributes:
             title: Enter a book title here
-          # Use Filter to clean up the input data
-          filter:
-            # Remove whitespace at both ends
-            - TrimEdges
-            # Escape HTML characters for safety
-            - HTMLEscape
           # Add constraints for the field
           constraints:
-            # The user cannot leave this field blank
-            - SingleValue
             # Force the length to be between 5 and 40 chars
             - type: Length
               min: 5
@@ -339,16 +336,22 @@ to match:
           attributes:
             title: Enter a rating between 1 and 5 here
           # Use Filter to clean up the input data
-          filter:
-            # Remove whitespace at both ends
-            - TrimEdges
+          # Could use 'NonNumeric' below, but since Filters apply *before*
+          # constraints, it would conflict with the 'Integer' constraint below.
+          # So let's skip this and just use the constraint.
+          #filter:
             # Remove everything except digits
-            - NonNumeric
+            #- NonNumeric
           # Add constraints to the field
           constraints:
-            - SingleValue
             # Make sure it's a number
-            - Integer
+            - type: Integer
+              message: "Required. Digits only, please."
+            # Check the min & max values
+            - type: Range
+              min: 1
+              max: 5
+              message: "Must be between 1 and 5."
     
         # Add a select list for the author selection.  Note that we will
         # dynamically fill in all the authors from the controller but we
@@ -366,12 +369,6 @@ to match:
           # One could argue we don't need to do filters or constraints for
           # a select list, but it's smart to do validation and sanity
           # checks on this data in case a user "hacks" the input
-          # Use Filter to clean up the input data
-          filter:
-            # Remove whitespace at both ends
-            - TrimEdges
-            # Escape HTML characters for safety
-            - HTMLEscape
           # Add constraints to the field
           constraints:
             # Make sure it's a number
@@ -382,14 +379,21 @@ to match:
           name: submit
           value: Submit
     
-    # Globally ensure that each field only specified one value
+    # Global filters and constraints.
     constraints:
-        # The user cannot leave any fields blank
-        - Required
+      # The user cannot leave any fields blank
+      - Required
+      # If not all fields are required, move the Required constraint to the 
+      # fields that are
+    filter:
+      # Remove whitespace at both ends
+      - TrimEdges
+      # Escape HTML characters for safety
+      - HTMLEscape
 
 B<NOTE:> Copying and pasting YAML from perl documentation is sometimes
-tricky.  See the L<Copy-Paste versions of the YAML Config> section of
-this document for a foolproof procedure.
+tricky.  See the L<Config::General Config for this tutorial> section of
+this document for a more foolproof config format.
 
 The main changes are:
 
@@ -444,39 +448,40 @@ bottom:
     
     =cut
     
-    sub formfu_edit :Local :FormConfig('books/formfu_create.yml') {
-        my ($self, $c, $id) = @_;
+    sub formfu_edit :Chained('object') :PathPart('formfu_edit') :Args(0) 
+            :FormConfig('books/formfu_create.yml') {
+        my ($self, $c) = @_;
     
-        # Get the specified book
-        my $book = $c->model('DB::Books')->find($id);
+        # Get the specified book already saved by the 'object' method
+        my $book = $c->stash->{object};
     
         # Make sure we were able to get a book
         unless ($book) {
             $c->flash->{error_msg} = "Invalid book -- Cannot edit";
-            $c->response->redirect($c->uri_for('list'));
+            $c->response->redirect($c->uri_for($self->action_for('list')));
             $c->detach;
         }
     
         # Get the form that the :FormConfig attribute saved in the stash
         my $form = $c->stash->{form};
     
-        # Check if the form as been submitted (vs. displaying the initial
-        # form) and if the data based validation.  "submitted_and_valid"
+        # Check if the form has been submitted (vs. displaying the initial
+        # form) and if the data passed validation.  "submitted_and_valid"
         # is shorthand for "$form->submitted && !$form->has_errors"
         if ($form->submitted_and_valid) {
             # Save the form data for the book
-            $form->save_to_model($book);
+            $form->model->update($book);
             # Set a status message for the user
             $c->flash->{status_msg} = 'Book edited';
             # Return to the books list
-            $c->response->redirect($c->uri_for('list'));
+            $c->response->redirect($c->uri_for($self->action_for('list')));
             $c->detach;
         } else {
             # Get the authors from the DB
-            my @authorObjs = $c->model("DB::Authors")->all();
+            my @author_objs = $c->model("DB::Authors")->all();
             # Create an array of arrayrefs where each arrayref is an author
             my @authors;
-            foreach (sort {$a->last_name cmp $b->last_name} @authorObjs) {
+            foreach (sort {$a->last_name cmp $b->last_name} @author_objs) {
                 push(@authors, [$_->id, $_->last_name]);
             }
             # Get the select added by the config file
@@ -484,7 +489,7 @@ bottom:
             # Add the authors to it
             $select->options(\@authors);
             # Populate the form with existing values from DB
-            $form->defaults_from_model($book);
+            $form->model->default_values($book);
         }
     
         # Set the template
@@ -499,28 +504,36 @@ the common code in separate methods).  The main differences are:
 
 =item *
 
-We accept C<$id> as an argument via the URL.
+We have to manually specify the name of the FormFu .yml file as an 
+argument to C<:FormConfig> because the name can no longer be 
+automatically deduced from the name of our action/method (by default,
+FormFu would look for a file named C<books/formfu_edit.yml>).
 
 =item *
 
-We use C<$id> to look up the existing book from the database.
+We load the book object from the stash (found using the $id passed to 
+the Chained object method)
 
 =item *
 
-We make sure the C<$id> and book lookup returned a valid book.  If not, 
-we set the error message and return to the book list.
+We use C<$id> to look up the existing book from the database.
+
+=item *
 
+We make sure the book lookup returned a valid book.  If not, we set 
+the error message and return to the book list.
 =item *
 
 If the form has been submitted and passes validation, we skip creating a 
-new book and just use C<$form-E<gt>save_to_model> to update the existing 
+new book and just use C<$form-E<gt>model-E<gt>update> to update the existing 
 book.
 
 =item *
 
 If the form is being displayed for the first time (or has failed 
 validation and it being redisplayed), we use
- C<$form-E<gt>default_from_model> to populate the form with data from the 
+ C<$form-E<gt>model-E<gt>default_values> to populate the form with data from the
 database.
 
 =back
@@ -530,12 +543,18 @@ existing "Delete" link that allows us to edit/update each existing book.
 The last E<lt>tdE<gt> cell in the book list table should look like the 
 following:
 
+    ...
     <td>
       [% # Add a link to delete a book %]
-      <a href="[% Catalyst.uri_for('delete', book.id) %]">Delete</a>
+      <a href="[% c.uri_for(c.controller.action_for('delete'), [book.id]) %]">Delete</a>
       [% # Add a link to edit a book %]
-      <a href="[% Catalyst.uri_for('formfu_edit', book.id) %]">Edit</a>
+      <a href="[% c.uri_for(c.controller.action_for('formfu_edit'), [book.id]) %]">Edit</a>
     </td>
+    ...
+
+B<Note:> Only add two lines (the "Add a link to edit a book" comment
+and the href for C<formfu_edit>).  Make sure you add it below the
+existing C<delete> link.
 
 
 =head2 Try Out the Edit/Update Feature
@@ -553,140 +572,91 @@ Stevens as a co-author (control-click), and click Submit.  You will then
 be returned to the book list with a "Book edited" message at the top in 
 green.  Experiment with other edits to various books.
 
+=head2 More Things to Try
+
+You are now armed with enough knowledge to be dangerous.  You can keep
+tweaking the example application; some things you might want to do:
+
+=over 4
+
+=item *
+
+Add an appropriate authorization check to the new Edit function.
+
+=item *
+
+Cleanup the List page so that the Login link only displays when the user
+isn't logged in and the Logout link only displays when a user is logged
+in.
+
+=item *
+
+Add a more sensible policy for when and how users and admins can do
+things in the CRUD cycle.
+
+=item *
+
+Support the CRUD cycle for authors.
+
+=back
+
+Or you can proceed to write your own application, which is probably the
+real reason you worked through this Tutorial in the first place.
+
+=head2  Config::General Config for this tutorial
+
+If you are having difficulty with YAML config above, please save the
+below into the file C<formfu_create.conf> and delete the
+C<formfu_create.yml> file.  The below is in
+L<Config::General|Config::General> format which follows the syntax of
+Apache config files.
+
+   constraints   Required
+   <elements>
+       <constraints>
+           min   5
+           max   40
+           type   Length
+           message   Length must be between 5 and 40 characters
+       </constraints>
+       filter   TrimEdges
+       filter   HTMLEscape
+       name   title
+       type   Text
+       label   Title
+       <attributes>
+           title   Enter a book title here
+       </attributes>
+   </elements>
+   <elements>
+       constraints   Integer
+       filter   TrimEdges
+       filter   NonNumeric
+       name   rating
+       type   Text
+       label   Rating
+       <attributes>
+           title   Enter a rating between 1 and 5 here
+       </attributes>
+   </elements>
+   <elements>
+       constraints   Integer
+       filter   TrimEdges
+       filter   HTMLEscape
+       name   authors
+       type   Select
+       label   Author
+       multiple   1
+       size   3
+   </elements>
+   <elements>
+       value   Submit
+       name   submit
+       type   Submit
+   </elements>
+   indicator   submit
+   
 
-=head2 Copy-Paste versions of the YAML Config.
-
-YAML, the config format used in formfu depends on whitespace and can
-behave strangely in perl documentation because POD is also whitespace
-dependent.  If you copy and paste the YAML below into the file
-C<root/forms/books/formfu_create.yml> and then run the following perl
-oneliner, you're pretty much guaranteed to get valid YAML:
-
- $ perl -p -i -e 's/\s+\|//g' root/forms/books/formfu_create.yml
-
-=head2 YAML for the first half of the tutorial:
-
-    |---
-    |# indicator is the field that is used to test for form submission
-    |indicator: submit
-    |# Start listing the form elements
-    |elements:
-    |    # The first element will be a text field for the title
-    |    - type: Text
-    |      name: title
-    |      label: Title
-    |      # This is an optional 'mouse over' title pop-up
-    |      attributes:
-    |        title: Enter a book title here
-    |
-    |    # Another text field for the numeric rating
-    |    - type: Text
-    |      name: rating
-    |      label: Rating
-    |      attributes:
-    |        title: Enter a rating between 1 and 5 here
-    |
-    |    # Add a drop-down list for the author selection.  Note that we will
-    |    # dynamically fill in all the authors from the controller but we
-    |    # could manually set items in the drop-list by adding this YAML code:
-    |    # options:
-    |    #   - [ '1', 'Bastien' ]
-    |    #   - [ '2', 'Nasseh'  ]
-    |    - type: Select
-    |      name: authors
-    |      label: Author
-    |
-    |    # The submit button
-    |    - type: Submit
-    |      name: submit
-    |      value: Submit
-
-
-=head2  YAML for the second part of the tutorial
-
-    |---
-    |# indicator is the field that is used to test for form submission
-    |indicator: submit
-    |# Start listing the form elements
-    |elements:
-    |    # The first element will be a text field for the title
-    |    - type: Text
-    |      name: title
-    |      label: Title
-    |      # This is an optional 'mouse over' title pop-up
-    |      attributes:
-    |        title: Enter a book title here
-    |      # Use Filter to clean up the input data
-    |      filter:
-    |        # Remove whitespace at both ends
-    |        - TrimEdges
-    |        # Escape HTML characters for safety
-    |        - HTMLEscape
-    |      # Add constraints for the field
-    |      constraints:
-    |        # The user cannot leave this field blank
-    |        - SingleValue
-    |        # Force the length to be between 5 and 40 chars
-    |        - type: Length
-    |          min: 5
-    |          max: 40
-    |          # Override the default of 'Invalid input'
-    |          message: Length must be between 5 and 40 characters
-    |
-    |    # Another text field for the numeric rating
-    |    - type: Text
-    |      name: rating
-    |      label: Rating
-    |      attributes:
-    |        title: Enter a rating between 1 and 5 here
-    |      # Use Filter to clean up the input data
-    |      filter:
-    |        # Remove whitespace at both ends
-    |        - TrimEdges
-    |        # Remove everything except digits
-    |        - NonNumeric
-    |      # Add constraints to the field
-    |      constraints:
-    |        - SingleValue
-    |        # Make sure it's a number
-    |        - Integer
-    |
-    |    # Add a select list for the author selection.  Note that we will
-    |    # dynamically fill in all the authors from the controller but we
-    |    # could manually set items in the select by adding this YAML code:
-    |    # options:
-    |    #   - [ '1', 'Bastien' ]
-    |    #   - [ '2', 'Nasseh'  ]
-    |    - type: Select
-    |      name: authors
-    |      label: Author
-    |      # Convert the drop-down to a multi-select list
-    |      multiple: 1
-    |      # Display 3 entries (user can scroll to see others)
-    |      size: 3
-    |      # One could argue we don't need to do filters or constraints for
-    |      # a select list, but it's smart to do validation and sanity
-    |      # checks on this data in case a user "hacks" the input
-    |      # Use Filter to clean up the input data
-    |      filter:
-    |        # Remove whitespace at both ends
-    |        - TrimEdges
-    |        # Escape HTML characters for safety
-    |        - HTMLEscape
-    |      # Add constraints to the field
-    |      constraints:
-    |        # Make sure it's a number
-    |        - Integer
-    |
-    |    # The submit button
-    |    - type: Submit
-    |      name: submit
-    |      value: Submit
-    |
-    |# Globally ensure that each field only specified one value
-    |constraints:
-    |    # The user cannot leave any fields blank
-    |    - Required
 
 =head1 AUTHOR
 
@@ -694,8 +664,7 @@ Kennedy Clark, C<hkclark@gmail.com>
 
 Please report any errors, issues or suggestions to the author.  The
 most recent version of the Catalyst Tutorial can be found at
-L<http://dev.catalyst.perl.org/repos/Catalyst/trunk/Catalyst-Manual/lib/Catalyst/Manual/Tutorial/>.
+L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.70/trunk/lib/Catalyst/Manual/Tutorial/>.
 
-Copyright 20066-2008, Kennedy Clark, under Creative Commons License
-(L<http://creativecommons.org/licenses/by-nc-sa/2.5/>).
-    
+Copyright 2006-2008, Kennedy Clark, under Creative Commons License
+(L<http://creativecommons.org/licenses/by-sa/3.0/us/>).