=head1 NAME Catalyst::Manual::Tutorial::AdvancedCRUD - Catalyst Tutorial - Part 8: Advanced CRUD =head1 OVERVIEW This is B for the Catalyst tutorial. L =over 4 =item 1 L =item 2 L =item 3 L =item 4 L =item 5 L =item 6 L =item 7 L =item 8 B =item 9 L =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 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 and L. Here, we will make use of the L 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 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 Part 8 of the tutorial is optional. Users who do not wish to use L may skip this part. You can checkout the source code for this example from the catalyst subversion repository as per the instructions in L =head1 C FORM CREATION This section looks at how L can be used to add additional functionality to the manually created form from Part 3. =head2 Add the C Plugin Open C 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 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 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 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 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. Doing so allows us to have the same form submit the data to different actions (e.g., C for a create operation but C to update an existing book object). B If you receive an error about Catalyst not being able to find the template C, please verify that you followed the instructions in the final section of L where you returned to a manually-specified template. You can either use C/C B default template names, but the two cannot be used together. =head2 Update the CSS Edit C 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 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 in your editor and enter the following: [% META title = 'Create/Update Book' %] [% widget_result.as_xml %]

Return to book list

=head2 Add Links for Create and Update via C Open C in your editor and add the following to the bottom of the existing file:

HTML::Widget: Create

=head2 Test The Create Form Press C to kill the previous server instance (if it's still running) and restart it: $ script/myapp_server.pl Login as C. Once at the Book List page, click the HTML::Widget "Create" link to display for form produced by C. 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 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 itself is not performing any validation. =head1 C VALIDATION AND FILTERING Although the use of L 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 in your editor and update the C 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