[Catalyst-commits] r12121 -
trunk/examples/CatalystAdvent/root/2009/pen
alexn_org at dev.catalyst.perl.org
alexn_org at dev.catalyst.perl.org
Tue Dec 1 16:58:18 GMT 2009
Author: alexn_org
Date: 2009-12-01 16:58:16 +0000 (Tue, 01 Dec 2009)
New Revision: 12121
Modified:
trunk/examples/CatalystAdvent/root/2009/pen/formhandler.pod
Log:
first draft - in html - sorry
Modified: trunk/examples/CatalystAdvent/root/2009/pen/formhandler.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2009/pen/formhandler.pod 2009-12-01 16:13:56 UTC (rev 12120)
+++ trunk/examples/CatalystAdvent/root/2009/pen/formhandler.pod 2009-12-01 16:58:16 UTC (rev 12121)
@@ -1 +1,356 @@
-soon
+<h1>Creating a simple blog with Catalyst, HTML::FormHandler and DBIx::Class</h1>
+
+<h2>Introduction</h2>
+
+<p>
+ <a href="http://search.cpan.org/~gshank/HTML-FormHandler-0.28001/">
+ HTML::FormHandler</a> is a module for handling forms / HTTP requests
+ that includes validation rules and, in case of DBIC models, it also
+ includes the logic to save the model in your database.
+</p>
+
+<p>
+ I like HTML::FormHandler because of its simplicity, extendability
+ and Moose integration provided.
+</p>
+
+<p>
+ This tutorial includes a fairly simple example ...
+ <ul>
+ <li>an interface for viewing / editing articles in a blog</li>
+ <li>use HTML::FormHandler for the editing functionality</li>
+ </ul>
+</p>
+
+<h2>Setting up the project</h2>
+
+<p>
+ To start a new project ...
+ <pre>
+ # catalyst.pl Blog
+ </pre>
+</p>
+
+<p>
+ Let's generate the model ...
+ <pre>
+ perl script/blog_create.pl model DB DBIC::Schema Blog::Schema \
+ create=static components=TimeStamp \
+ 'dbi:Pg:dbname=blog' 'blog' 'blog' '{ AutoCommit => 1 }'
+ </pre>
+</p>
+
+<p>
+ Our articles will be tagged (to make the form processing more
+ interesting), so add a model representing tags
+ in <i>lib/Blog/Schema/Result/Tag.pm</i> ...
+ <pre>
+ package Blog::Schema::Result::Tag;
+
+ use strict;
+ use warnings;
+ use base qw/DBIx::Class/;
+
+ __PACKAGE__->load_components(qw/Core/);
+ __PACKAGE__->table('tags');
+
+ __PACKAGE__->add_columns(
+ tag_id => {
+ data_type => 'integer' ,
+ is_nullable => 0 ,
+ is_auto_increment => 1
+ },
+ name => {
+ data_type => 'varchar',
+ size => 100,
+ is_nullable => 0,
+ },
+ );
+ __PACKAGE__->set_primary_key('tag_id');
+ 1;
+ </pre>
+</p>
+
+<p>
+ Let's also add an Article model
+ in <i>lib/Blog/Schema/Result/Article.pm</i> ...
+
+ <pre>
+ package Blog::Schema::Result::Article;
+
+ use strict;
+ use warnings;
+ use base qw/DBIx::Class/;
+
+ __PACKAGE__->load_components(qw/TimeStamp InflateColumn::DateTime Core/);
+ __PACKAGE__->table('articles');
+
+ __PACKAGE__->add_columns(
+ article_id => {
+ data_type => 'integer' ,
+ is_nullable => 0 ,
+ is_auto_increment => 1
+ },
+ ts => {
+ data_type => 'datetime' ,
+ is_nullable => 1,
+ set_on_create => 1,
+ },
+
+ title => {
+ data_type => 'varchar',
+ size => 250,
+ is_nullable => 0,
+ },
+ content => {
+ data_type => 'text',
+ is_nullable => 1,
+ },
+
+ );
+ __PACKAGE__->set_primary_key('article_id');
+ __PACKAGE__->has_many(article_tags => 'Blog::Schema::Result::ArticleTag', 'article_fk');
+ __PACKAGE__->many_to_many(tags => 'article_tags', 'tag');
+ </pre>
+
+ ... with a coresponding link table ...
+
+ <pre>
+ package Blog::Schema::Result::ArticleTag;
+
+ use strict;
+ use warnings;
+ use base qw/DBIx::Class/;
+
+ __PACKAGE__->load_components(qw/Core/);
+ __PACKAGE__->table('article_tag');
+
+ __PACKAGE__->add_columns(
+ article_fk => {
+ data_type => 'integer',
+ is_nullable => 0,
+ },
+ tag_fk => {
+ data_type => 'integer',
+ is_nullable => 0,
+ },
+ );
+ __PACKAGE__->add_unique_constraint([ qw/article_fk tag_fk/ ]);
+ __PACKAGE__->belongs_to(tag => 'Blog::Schema::Result::Tag', 'tag_fk');
+ __PACKAGE__->belongs_to(article => 'Blog::Schema::Result::Article', 'article_fk');
+ </pre>
+</p>
+
+<p>
+ We're almost done setting up our project. Now just deploy your
+ schema with a command like this ...
+ <pre>
+ # perl -I./lib -MBlog::Model::DB \
+ -e " Blog::Model::DB->new->schema->deploy "
+ </pre>
+</p>
+
+<p>
+ Also add these utilities to lib/Blog.pm ...
+ <pre>
+ sub redirect_to_action {
+ my ($c, $controller, $action, @params) =@_;
+ $c->response->redirect($c->uri_for($c->controller($controller)->action_for($action), @params));
+ $c->detach;
+ }
+ sub forward_to_action {
+ my ($c, $controller, $action, @params) = @_;
+ $c->forward($controller, $action, @params);
+ $c->detach;
+ }
+ </pre>
+</p>
+
+<h2>Creating the CRUD controller</h2>
+
+<p>
+ Edit a new file in <i>lib/Blog/Controller/Article.pm</i> ...
+
+ <pre>
+ package Blog::Controller::Article
+
+ use strict;
+ use warnings;
+ use parent 'Catalyst::Controller';
+
+ # this class is a HTML::FormHandler class that we'll define below
+ use Blog::Form::Article;
+
+ sub articles : Chained('/') PathPart('article') CaptureArgs(0) {
+ my ($self, $c) = @_;
+ $c->stash->{articles} = $c->model('DB::Article');
+ }
+
+ sub list : Chained('articles') Args(0) {}
+
+ sub item : Chained('articles') PathPart('') CaptureArgs(1) {
+ my ($self, $c, $id) = @_;
+
+ $c->error("ID isn't valid!") unless $id =~ /^\d+$/;
+ $c->stash->{item} = $c->stash->{articles}->find($id)
+ or $c->error("ID $id is nonexistent");
+ }
+
+ sub edit : Chained('item') {
+ my ($self, $c) = @_;
+ $c->forward_to_action('Article', 'add');
+ }
+
+ sub add : Chained('articles') Args(0) FormConfig {
+ my ($self, $c) = @_;
+
+ # if the item doesn't exist, we'll just create a new result
+ my $item = $c->stash->{item} || $c->model('DB::Article')->new_result({});
+ my $form = Blog::Form::Article->new( item => $item );
+
+ # the "process" call has all the saving logic,
+ # if it returns False, then a validation error happened
+ return unless $form->process( params => $c->req->params );
+
+ $c->flash->{info_msg} = "New Article added!";
+ $c->redirect_to_action('Article', 'edit', [$item->article_id]);
+ }
+ </pre>
+
+ This CRUD is pretty standard, and it would be possible to abstract
+ it away in a
+ <a href="http://search.cpan.org/~hkclark/Catalyst-Manual-5.8002/lib/Catalyst/Manual/CatalystAndMoose.pod#Controller_Roles">role</a>.
+</p>
+
+<h2>The Editing Form</h2>
+
+<p>
+ Let's start with the HTML::FormHandler derived class
+ in <i>lib/Blog/Form/Article.pm</i> looking like this ...
+
+ <pre>
+ package Blog::Form::Article;
+
+ use strict;
+ use warnings;
+ use HTML::FormHandler::Moose;
+
+ extends 'HTML::FormHandler::Model::DBIC';
+ with 'HTML::FormHandler::Render::Simple';
+
+ has_field 'title' => ( type => 'Text', required => 1 );
+ has_field 'ts' => ( type => 'Date', label => 'Published Date' );
+ has_field 'content' => ( type => 'TextArea', required => 0 );
+ has_field 'tags' => ( type => 'TextArea', required => 0 );
+
+ 1;
+ </pre>
+
+ ... and to make this work, also add the template ...
+
+ <pre>
+ <h1>
+ [% IF item.article_id %]Editing "[% item.title %]"
+ [% ELSE %]Adding a new article[% END %]
+ </h1>
+
+ [% form.render_start %]
+
+ [% form.render_field('title') %]
+ [% form.render_field('ts') %]
+ [% form.render_field('content') %]
+ [% form.render_field('tags') %]
+
+ <div class="submit"><input type="submit" value="Save" /></div>
+ [% form.render_end %]
+
+ <style type="text/css">
+ form fieldset {
+ width: 450px;
+ }
+ form label {
+ float: left;
+ display: block;
+ width: 150px;
+ }
+ form input, form textarea {
+ width: 300px;
+ }
+ form #tags {
+ height: 50px;
+ }
+ form .error_message {
+ margin-left: 100px;
+ color: red;
+ font-style: italic;
+ }
+ form .submit {
+ margin-top: 10px;
+ text-align: right;
+ }
+ form .submit input {
+ width: 100px;
+ }
+ </style>
+ </pre>
+</p>
+
+<p>
+ OK, so we now have a form with automatic validation ...
+ <ul>
+ <li>The "title" field is required</li>
+ <li>The "published date" field expects the format YYYY-MM-DD</li>
+ </ul>
+
+ There's only one problem ... the "tags" field is a textarea. We want
+ Blog::Form::Article to automatically get the value, split it by ",",
+ create the missing tags, and create the necessary links between the
+ article and those tags. We'll just append the following to
+ Blog::Form::Article ...
+
+ <pre>
+ before 'update_model' => sub {
+ my $self = shift;
+ my $item = $self->item;
+
+ my @tags = split /\s*,\s*/, $self->field('tags_str')->value;
+ $item->article_tags->delete;
+
+ my $tags_rs = $item->result_source->schema->resultset('Tag');
+ foreach my $tag (@tags) {
+ my $tag_obj = $tags_rs->search({ name => $tag })->first
+ || $tags_rs->create({ name => $tag });
+ $item->article_tags->create({ tag => $tag_obj });
+ }
+ };
+ </pre>
+
+ But we also want it to load the current tags in the textarea when
+ the form renders. So we'll just append this ...
+
+ <pre>
+ after 'setup_form' => sub {
+ my $self = shift;
+ my $item = $self->item;
+
+ my $value = join ', ', map { $_->name }
+ $item->tags->search({}, { order_by => 'name' })->all;
+ $self->field('tags_str')->value($value);
+ };
+ </pre>
+</p>
+
+<h2>Testing</h2>
+
+<p>
+ And we are done. Start your development server with ...
+ <pre>
+ perl script/blog_server.pl -r -d
+ </pre>
+
+ and go to the following URL ...
+ <pre>
+ http://localhost:3000/article/add
+ </pre>
+</p>
+
More information about the Catalyst-commits
mailing list