=head1 NAME

Catalyst::Manual::Tutorial::AdvancedCRUD - Catalyst Tutorial - Part 8: Advanced CRUD


=head1 OVERVIEW

This is B<Part 8 of 9> for the Catalyst tutorial.

L<Tutorial Overview|Catalyst::Manual::Tutorial>

=over 4

=item 1

L<Introduction|Catalyst::Manual::Tutorial::Intro>

=item 2

L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>

=item 3

L<Basic CRUD|Catalyst::Manual::Tutorial_BasicCRUD>

=item 4

L<Authentication|Catalyst::Manual::Tutorial::Authentication>

=item 5

L<Authorization|Catalyst::Manual::Tutorial::Authorization>

=item 6

L<Debugging|Catalyst::Manual::Tutorial::Debugging>

=item 7

L<Testing|Catalyst::Manual::Tutorial::Testing>

=item 8

B<AdvancedCRUD>

=item 9

L<Appendices|Catalyst::Manual::Tutorial::Appendices>

=back

=head1 DESCRIPTION

This part of the tutorial explores more advanced functionality for
Create, Read, Update, and Delete (CRUD) than we saw in Part 3.  In
particular, it looks at a number of techniques that can be useful for
the Update portion of CRUD, such as automated form generation,
validation of user-entered data, and automated transfer of data between
forms and model objects.

In keeping with the Catalyst (and Perl) spirit of flexibility, there are
many different ways to approach advanced CRUD operations in a Catalyst
environment.  One alternative is to use
L<Catalyst::Helper::Controller::Scaffold|Catalyst::Helper::Controller::Scaffold> 
to instantly construct a set of Controller methods and templates for 
basic CRUD operations.  Although a popular subject in Quicktime 
movies that serve as promotional material for various frameworks, 
real-world applications generally require more control.  Other 
options include L<Data::FormValidator|Data::FormValidator> and
L<HTML::FillInForm|HTML::FillInForm>.

Here, we will make use of the L<HTML::Widget|HTML::Widget> to not only 
ease form creation, but to also provide validation of the submitted 
data.  The approached used by this part of the tutorial is to slowly 
incorporate additional L<HTML::Widget|HTML::Widget> functionality in a 
step-wise fashion (we start with fairly simple form creation and then 
move on to more complex and "magical" features such as validation and 
auto-population/auto-saving).

B<Note:> Part 8 of the tutorial is optional.  Users who do not wish to
use L<HTML::Widget|HTML::Widget> may skip this part.

You can checkout the source code for this example from the catalyst subversion repository as per the instructions in L<Catalyst::Manual::Tutorial::Intro>

=head1 C<HTML::WIDGET> FORM CREATION

This section looks at how L<HTML::Widget|HTML::Widget> can be used to
add additional functionality to the manually created form from Part 3.

=head2 Add the C<HTML::Widget> Plugin

Open C<lib/MyApp.pm> in your editor and add the following to the list of
plugins (be sure to leave the existing plugins enabled):

    HTML::Widget

=head2 Add a Form Creation Helper Method

Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
following method:

    =head2 make_book_widget
    
    Build an HTML::Widget form for book creation and updates
    
    =cut
    
    sub make_book_widget {
        my ($self, $c) = @_;
    
        # Create an HTML::Widget to build the form
        my $w = $c->widget('book_form')->method('post');
    
        # Get authors
        my @authorObjs = $c->model("MyAppDB::Author")->all();
        my @authors = map {$_->id => $_->last_name }
                           sort {$a->last_name cmp $b->last_name} @authorObjs;
    
        # Create the form feilds
        $w->element('Textfield', 'title'  )->label('Title')->size(60);
        $w->element('Textfield', 'rating' )->label('Rating')->size(1);
        $w->element('Select',    'authors')->label('Authors')
            ->options(@authors);
        $w->element('Submit',    'submit' )->value('submit');
    
        # Return the widget    
        return $w;
    }

This method provides a central location that builds an 
HTML::Widget-based form with the appropriate fields.  The "Get authors" 
code uses DBIC to retrieve a list of model objects and then uses C<map> 
to create a hash where the hash keys are the database primary keys from 
the authors table and the associated values are the last names of the 
authors.

=head2 Add Actions to Display and Save the Form

Open C<lib/MyApp/Controller/Books.pm> in your editor and add the
following methods:

    =head2 hw_create
    
    Build an HTML::Widget form for book creation and updates
    
    =cut
    
    sub hw_create : Local {
        my ($self, $c) = @_;
    
        # Create the widget and set the action for the form
        my $w = $self->make_book_widget($c);
        $w->action($c->uri_for('hw_create_do'));
    
        # Write form to stash variable for use in template
        $c->stash->{widget_result} = $w->result;
    
        # Set the template
        $c->stash->{template} = 'books/hw_form.tt2';
    }
    
    
    =head2 hw_create_do
    
    Build an HTML::Widget form for book creation and updates
    
    =cut
    
    sub hw_create_do : Local {
        my ($self, $c) = @_;
    
        # Retrieve the data from the form
        my $title   = $c->request->params->{title};
        my $rating  = $c->request->params->{rating};
        my $authors = $c->request->params->{authors};
    
        # Call create() on the book model object. Pass the table 
        # columns/field values we want to set as hash values
        my $book = $c->model('MyAppDB::Book')->create({
                title   => $title,
                rating  => $rating
            });
        
        # Add a record to the join table for this book, mapping to 
        # appropriate author
        $book->add_to_book_authors({author_id => $authors});
        
        # Set a status message for the user
        $c->stash->{status_msg} = 'Book created';
    
        # Use 'hw_create' to redisplay the form.  As discussed in 
        # Part 3, 'detach' is like 'forward', but it does not return
        $c->detach('hw_create');
    }

Note how we use C<make_book_widget> to build the core parts of the form
in one location, but we set the action (the URL the form is sent to when
the user clicks the 'Submit' button) separately in C<hw_create>.  Doing
so allows us to have the same form submit the data to different actions
(e.g., C<hw_create_do> for a create operation but C<hw_update_do> to
update an existing book object).

B<NOTE:> If you receive an error about Catalyst not being able to find
the template C<hw_create_do.tt2>, please verify that you followed the
instructions in the final section of
L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics> where
you returned to a manually-specified template.  You can either use 
C<forward>/C<detach> B<OR> default template names, but the two cannot
be used together.


=head2 Update the CSS

Edit C<root/src/ttsite.css> and add the following lines to the bottom of
the file:

    label {
        display: block;
        width: 10em;
        position: relative;
        margin: .5em 0em;
    }
    label input {
        position: absolute;
        left: 100%;
    }
    label select {
        position: absolute;
        left: 100%;
    }
    .submit {
        margin-top: 2em;;
    }
    .error_messages {
        color: [% site.col.error %];
    }

These changes will display form elements vertically and also show error
messages in red.  Note that we are pulling the color scheme settings
from the C<root/lib/config/col> file that was created by the TTSite
helper.  This allows us to change the color used by various error styles
in the CSS from a single location.

=head2 Create a Template Page To Display The Form

Open C<root/src/books/hw_form.tt2> in your editor and enter the following:

    [% META title = 'Create/Update Book' %]
    
    [% widget_result.as_xml %]
    
    <p><a href="[% Catalyst.uri_for('list') %]">Return to book list</a></p>

=head2 Add Links for Create and Update via C<HTML::Widget>

Open C<root/src/books/list.tt2> in your editor and add the following to
the bottom of the existing file:

    <p>
      HTML::Widget:
      <a href="[% Catalyst.uri_for('hw_create') %]">Create</a>
    </p>


=head2 Test The <HTML::Widget> Create Form

Press C<Ctrl-C> to kill the previous server instance (if it's still
running) and restart it:

    $ script/myapp_server.pl

Login as C<test01>.  Once at the Book List page, click the HTML::Widget
"Create" link to display for form produced by C<make_book_widget>.  Fill
out the form with the following values: Title = "Internetworking with
TCP/IP Vol. II", Rating = "4", and Author = "Comer".  Click Submit, and
you will be returned to the Create/Update Book page with a "Book
created" status message displayed.  Click "Return to book list" to view
the newly created book on the main list.

Also note that this implementation allows you to can create books with
bogus information.  Although we have constrained the authors with the
drop-down list, there are no restrictions on items such as the length of
the title (for example, you can create a one-letter title) and value for
the rating (you can use any number you want, and even non-numeric values
with SQLite).  The next section will address this concern.

B<Note:> Depending on the database you are using and how you established
the columns in your tables, the database could obviously provide various
levels of "type enforcement" on your data.  The key point being made in
the previous paragraph is that the I<web application> itself is not
performing any validation.

=head1 C<HTML::WIDGET> VALIDATION AND FILTERING

Although the use of L<HTML::Widget|HTML::Widget> in the previous section
did provide an automated mechanism to build the form, the real power of
this module stems from functionality that can automatically validate and
filter the user input.  Validation uses constraints to be sure that
users input appropriate data (for example, that the email field of a
form contains a valid email address).  Filtering can be used to remove
extraneous whitespace from fields or to escape meta-characters in user
input.

=head2 Add Constraints and Filters to the Widget Creation Method

Open C<lib/MyApp/Controller/Books.pm> in your editor and update the
C<make_book_widget> method to match the following (new sections have
been marked with a C<*** NEW:> comment):

    sub make_book_widget {
        my ($self, $c) = @_;
    
        # Create an HTML::Widget to build the form
        my $w = $c->widget('book_form')->method('post');
            
        # Get authors
        my @authorObjs = $c->model("MyAppDB::Author")->all();
        my @authors = map {$_->id => $_->last_name }
                           sort {$a->last_name cmp $b->last_name} @authorObjs;
    
        # Create the form feilds
        $w->element('Textfield', 'title'  )->label('Title')->size(60);
        $w->element('Textfield', 'rating' )->label('Rating')->size(1);
        # ***NEW: Convert to multi-select list
        $w->element('Select',    'authors')->label('Authors')
            ->options(@authors)->multiple(1)->size(3);
        $w->element('Submit',    'submit' )->value('submit');
    
        # ***NEW: Set constraints
        $w->constraint(All     => qw/title rating authors/)
            ->message('Required. ');
        $w->constraint(Integer => qw/rating/)
            ->message('Must be an integer. ');
        $w->constraint(Range   => qw/rating/)->min(1)->max(5)
            ->message('Must be a number between 1 and 5. ');
        $w->constraint(Length  => qw/title/)->min(5)->max(50)
            ->message('Must be between 5 and 50 characters. ');
    
        # ***NEW: Set filters
        for my $column (qw/title rating authors/) {
            $w->filter( HTMLEscape => $column );
            $w->filter( TrimEdges  => $column );
        }
    
        # Return the widget    
        return $w;
    }

The main changes are:

=over 4

=item *

The C<Select> element for C<authors> is changed from a single-select
drop-down to a multi-select list by adding calls to C<multiple> (set to
C<true>) and C<size> (set to the number of rows to display).

=item *

Four sets of constraints are added to provide validation of the user input.

=item *

Two filters are run on every field to remove and escape unwanted input.

=back

=head2 Rebuild the Form Submission Method to Include Validation

Edit C<lib/MyApp/Controller/Books.pm> and change C<hw_create_do> to
match the following code (enough of the code is different that you
probably want to cut and paste this over code the existing method):

    sub hw_create_do : Local {
        my ($self, $c) = @_;
    
        # Retrieve the data from the form
        my $title   = $c->request->params->{title};
        my $rating  = $c->request->params->{rating};
        my $authors = $c->request->params->{authors};
        
        # Create the widget and set the action for the form
        my $w = $self->make_book_widget($c);
        $w->action($c->uri_for('hw_create_do'));
    
        # Validate the form parameters
        my $result = $w->process($c->req);
    
        # Write form (including validation error messages) to
        # stash variable for use in template
        $c->stash->{widget_result} = $result;
    
        # Were their validation errors?
        if ($result->has_errors) {
            # Warn the user at the top of the form that there were errors.
            # Note that there will also be per-field feedback on
            # validation errors because of '$w->process($c->req)' above.
            $c->stash->{error_msg} = 'Validation errors!';
        } else {
            # Everything validated OK, so do the create
            # Call create() on the book model object. Pass the table
            # columns/field values we want to set as hash values
            my $book = $c->model('MyAppDB::Book')->create({
                    title   => $title,
                    rating  => $rating
                });
    
            # Add a record to the join table for this book, mapping to
            # appropriate author.  Note that $authors will be 1 author as
            # a scalar or ref to list of authors depending on how many the
            # user selected; the 'ref $authors ?...' handles both cases
            foreach my $author (ref $authors ? @$authors : $authors) {
                $book->add_to_book_authors({author_id => $author});
            }    
            # Set a status message for the user
            $c->stash->{status_msg} = 'Book created';
        }
    
        # Set the template
        $c->stash->{template} = 'books/hw_form.tt2';
    }

The key changes to C<hw_create_do> are:

=over 4

=item *

C<hw_create_do> no longer does a C<detach> to C<hw_create> to redisplay
the form.  Now that C<hw_create_do> has to process the form in order to
perform the validation, we go ahead and build a complete set of form
presentation logic into C<hw_create_do> (for example, C<hw_create_do>
now has a C<$c-E<gt>stash-E<gt>{template}> line).  Note that if we
process the form in C<hw_create_do> I<and> forward/detach back to
<hw_create>, we would end up with C<make_book_widget> being called
twice, resulting in a duplicate set of elements being added to the form.
(There are other ways to address the "duplicate form rendering" issue --
just be aware that it exists.)

=item *

C<$w-E<gt>process($c-E<gt>req)> is called to run the validation logic.
Not only does this set the C<has_errors> flag if validation errors are
encountered, it returns a string containing any field-specific warning
messages.

=item *

An C<if> statement checks if any validation errors were encountered.  If
so, C<$c-E<gt>stash-E<gt>{error_msg}> is set and the input form is
redisplayed.  If no errors were found, the object is created in a manner
similar to the prior version of the C<hw_create_do> method.

=back

=head2 Try Out the Form

Press C<Ctrl-C> to kill the previous server instance (if it's still 
running) and restart it:

    $ script/myapp_server.pl

Now try adding a book with various errors: title less than 5 characters,
non-numeric rating, a rating of 0 or 6, etc.  Also try selecting one,
two, and zero authors.  When you click Submit, the HTML::Widget
C<constraint> items will validate the logic and insert feedback as
appropriate.


=head1 Enable C<DBIx::Class::HTMLWidget> Support

In this section we will take advantage of some of the "auto-population"
features of C<DBIx::Class::HTMLWidget>.  Enabling
C<DBIx::Class::HTMLWidget> provides two additional methods to your DBIC
model classes:

=over 4

=item *

fill_widget()

Takes data from the database and transfers it to your form widget.

=item *

populate_from_widget()

Takes data from a form widget and uses it to update the corresponding
records in the database.

=back

In other words, the two methods are a mirror image of each other: one
reads from the database while the other writes to the database.

=head2 Add C<DBIx::Class::HTMLWidget> to DBIC Model

In order to use L<DBIx::Class::HTMLWidget|DBIx::Class::HTMLWidget>, we
need to add C<HTMLWidget> to the C<load_components> line of DBIC result
source files that need to use the C<fill_widget> and
C<populate_from_widget> methods.  In this case, open
C<lib/MyAppDB/Book.pm> and update the C<load_components> line to match:

	__PACKAGE__->load_components(qw/PK::Auto Core HTMLWidget/);

=head2 Use C<populate_from_widget> in C<hw_create_do>

Edit C<lib/MyApp/Controller/Books.pm> and update C<hw_create_do> to
match the following code:

    =head2 hw_create_do
    
    Build an HTML::Widget form for book creation and updates
    
    =cut
    
    sub hw_create_do : Local {
        my ($self, $c) = @_;
    
        # Create the widget and set the action for the form
        my $w = $self->make_book_widget($c);
        $w->action($c->uri_for('hw_create_do'));
    
        # Validate the form parameters
        my $result = $w->process($c->req);
    
        # Write form (including validation error messages) to
        # stash variable for use in template
        $c->stash->{widget_result} = $result;
    
        # Were their validation errors?
        if ($result->has_errors) {
            # Warn the user at the top of the form that there were errors.
            # Note that there will also be per-field feedback on
            # validation errors because of '$w->process($c->req)' above.
            $c->stash->{error_msg} = 'Validation errors!';
        } else {
            my $book = $c->model('MyAppDB::Book')->new({});
            $book->populate_from_widget($result);
    
            # Add a record to the join table for this book, mapping to
            # appropriate author.  Note that $authors will be 1 author as
            # a scalar or ref to list of authors depending on how many the
            # user selected; the 'ref $authors ?...' handles both cases
            my $authors = $c->request->params->{authors};
            foreach my $author (ref $authors ? @$authors : $authors) {
                $book->add_to_book_authors({author_id => $author});
            }
    
            # Set a status message for the user
            $c->flash->{status_msg} = 'Book created';
            
            # Redisplay an empty form for another
            $c->stash->{widget_result} = $w->result;
        }
    
        # Set the template
        $c->stash->{template} = 'books/hw_form.tt2';
    }

In this version of C<hw_create_do> we removed the logic that manually
pulled the form variables and used them to call
C<$c-E<gt>model('MyAppDB::Book')-E<gt>create> and replaced it with a
single call to C<$book-E<gt>populate_from_widget>.  Note that we still
have to call C<$book-E<gt>add_to_book_authors> once per author because
C<populate_from_widget> does not currently handle the relationships
between tables.  Also, we reset the form to an empty fields by adding
another call to C<$w-E<gt>result> and storing the output in the stash 
(if we don't override the output from C<$w-E<gt>process($c-E<gt>req)>,
the form values already entered will be retained on redisplay --
although this could be desirable for some applications, we avoid it
here to help avoid the creation of duplicate records).


=head2 Try Out the Form

Press C<Ctrl-C> to kill the previous server instance (if it's still 
running) and restart it:

    $ script/myapp_server.pl

Try adding a book that validates.  Return to the book list and the book 
you added should be visible.



=head1 Rendering C<HTMLWidget> Forms in a Table

Some developers my wish to use the "old-fashioned" table style of 
rendering a form in lieu of the default C<HTML::Widget> rendering that 
assumes you will use CSS for formatting.  This section demonstrates
some techniques that can override the default rendering with a 
custom class.


=head2 Add a New "Element Container"

Open C<lib/FormElementContainer.pm> in your editor and enter:

    package FormElementContainer;
    
    use base 'HTML::Widget::Container';
    
    sub _build_element {
        my ($self, $element) = @_;
    
        return () unless $element;
        if (ref $element eq 'ARRAY') {
            return map { $self->_build_element($_) } @{$element};
        }
        my $e = $element->clone;
        $e = new HTML::Element('span', class => 'fields_with_errors')->push_content($e)
            if $self->error && $e->tag eq 'input';
    
        return $e ? ($e) : ();
    }
    
    1;

This simply dumps the HTML code for a given form element, followed by a 
C<span> that can contain validation error message.


=head2 Enable the New Element Container When Building the Form

Open C<lib/MyApp/Controller/Books.pm> in your editor.  First add a
C<use> for your element container class:

    use FormElementContainer;

B<Note:> If you forget to C<use> your container class in your 
controller, then your form will not be displayed and no error messages 
will be generated. Don't forget this important step!

Then tell C<HTML::Widget> to use that class during rendering by updating
C<make_book_widget> to match the following:

    sub make_book_widget {
        my ($self, $c) = @_;
    
        # Create an HTML::Widget to build the form
        my $w = $c->widget('book_form')->method('post');
    
        # ***New: Use custom class to render each element in the form    
        $w->element_container_class('FormElementContainer');
        
        # Get authors
        my @authorObjs = $c->model("MyAppDB::Author")->all();
        my @authors = map {$_->id => $_->last_name }
                           sort {$a->last_name cmp $b->last_name} @authorObjs;
    
        # Create the form feilds
        $w->element('Textfield', 'title'  )->label('Title')->size(60);
        $w->element('Textfield', 'rating' )->label('Rating')->size(1);
        # Convert to multi-select list
        $w->element('Select',    'authors')->label('Authors')
            ->options(@authors)->multiple(1)->size(3);
        $w->element('Submit',    'submit' )->value('submit');
    
        # Set constraints
        $w->constraint(All     => qw/title rating authors/)
            ->message('Required. ');
        $w->constraint(Integer => qw/rating/)
            ->message('Must be an integer. ');
        $w->constraint(Range   => qw/rating/)->min(1)->max(5)
            ->message('Must be a number between 1 and 5. ');
        $w->constraint(Length  => qw/title/)->min(5)->max(50)
            ->message('Must be between 5 and 50 characters. ');
    
        # Set filters
        for my $column (qw/title rating authors/) {
            $w->filter( HTMLEscape => $column );
            $w->filter( TrimEdges  => $column );
        }
    
        # Return the widget    
        return $w;
    }

The two new lines are marked with C<***New:>.


=head2 Update the TT Template

Open C<root/src/books/hw_form.tt2> and edit it to match:

    [% META title = 'Create/Update Book' %]
    
    [%# Comment out the auto-rendered form %]
    [%# widget_result.as_xml %]
    
    
    [%# Iterate over the form elements and display each -%]
    <form name="book_form" action="[% widget_result.action %]" method="post">
    <table border="0">
    [% FOREACH element = widget_result.elements %]
      <tr>
        <td class="form-label">
          [% element.label.as_text %]
        </td>
        <td class="form-element">
          [% element.element_xml %]
          <span class="form-error">
            [% element.error_xml %]
          </span>
        </td>
      </tr>
    [% END %]
    </table>
    </form>
    
    
    <p><a href="[% Catalyst.uri_for('list') %]">Return to book list</a></p>
    
    
    [%# A little JavaScript to move the cursor to the first field %]
    <script LANGUAGE="JavaScript">
    document.book_form.book_form_title.focus();
    </script>

This represents three changes:

=over 4

=item *

The existing C<widget_result.as_xml> has been commented out.

=item *

It loops through each form element, displaying the field name in the 
first table cell along with the form element and validation errors in 
the second field.

=item *

JavaScript to position the user's cursor in the first field of the form.

=back


=head2 Try Out the Form

Press C<Ctrl-C> to kill the previous server instance (if it's still 
running) and restart it:

    $ script/myapp_server.pl

Try adding a book that validates.  Return to the book list and the book 
you added should be visible.


=head1 AUTHOR

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/>.

Copyright 2006, Kennedy Clark, under Creative Commons License
(L<http://creativecommons.org/licenses/by-nc-sa/2.5/>).
