[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>
+  &lt;h1&gt;
+    [% IF item.article_id %]Editing "[% item.title %]"
+    [% ELSE %]Adding a new article[% END %]
+  &lt;/h1&gt;
+  
+  [% form.render_start %]
+  
+  [% form.render_field('title') %]
+  [% form.render_field('ts') %]
+  [% form.render_field('content') %]
+  [% form.render_field('tags') %]
+  
+  &lt;div class="submit"&gt;&lt;input type="submit" value="Save" /&gt;&lt;/div&gt;
+  [% form.render_end %]
+
+  &lt;style type="text/css"&gt;
+    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;
+    }
+  &lt;/style&gt;
+  </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