[Catalyst-commits] r14416 - in trunk/examples/CatalystAdvent/root:
2012 2012/pen static/2012
jnapiorkowski at dev.catalyst.perl.org
jnapiorkowski at dev.catalyst.perl.org
Wed Dec 5 18:14:46 GMT 2012
Author: jnapiorkowski
Date: 2012-12-05 18:14:46 +0000 (Wed, 05 Dec 2012)
New Revision: 14416
Added:
trunk/examples/CatalystAdvent/root/2012/16.pod
trunk/examples/CatalystAdvent/root/2012/17.pod
trunk/examples/CatalystAdvent/root/static/2012/admin_screen.png
trunk/examples/CatalystAdvent/root/static/2012/user_screen.png
Removed:
trunk/examples/CatalystAdvent/root/2012/pen/action_role_JSON.pod
Log:
added more articles and moved one to publish
Copied: trunk/examples/CatalystAdvent/root/2012/16.pod (from rev 14412, trunk/examples/CatalystAdvent/root/2012/pen/action_role_JSON.pod)
===================================================================
--- trunk/examples/CatalystAdvent/root/2012/16.pod (rev 0)
+++ trunk/examples/CatalystAdvent/root/2012/16.pod 2012-12-05 18:14:46 UTC (rev 14416)
@@ -0,0 +1,124 @@
+
+=head1 Action Roles for cleaner, less stashy Catalyst Actions
+
+=head2 Overview
+
+How to Serialize return values from Catalyst Actions
+
+=head3 About this Article
+
+Please note that this article leverages behavior that used to only exist in L<Catalyst::Controller::ActionRole>
+but has been merged into Catalyst proper in Catalyst v5.90013 .
+
+To take advantage of ActionRoles with an older Catalyst see the docs for L<Catalyst::Controller::ActionRole>.
+
+=head2 { hello => 'world' }
+
+
+Write a function that given a name, returns a user readable greeting along with a hashref of the data used.
+Simple.
+
+ sub hello {
+ my($name) = @_;
+ return {
+ message => "howdy $name",
+ metadata => {
+ name => $name,
+ },
+ };
+ }
+
+It's readable and extensible.
+
+Now re-written as a Catalyst Ajax endpoint:
+
+
+ sub hello {
+ my ( $self, $ctx ) = @_;
+ my $name = $ctx->req->params->{name};
+ $ctx->res->headers->{ContentType} = "text/javascript";
+ $ctx->res->body(
+ $self->json->encode(
+ {
+ message => "hello $name!",
+ metadata => { name => $name },
+ }
+ )
+ );
+ }
+
+Pretty gross. Most of the method is no longer concerned with the actual data transformation and is instead doing a bunch of
+crap that you're probably repeating elsewhere.
+
+When that goes from being ugly to being a pain is when, for example, you decide you want migrate to using L<Catalyst::Controller::REST>.
+
+The method becomes:
+
+ sub hello {
+ my ( $self, $ctx ) = @_;
+ my $name = $ctx->req->params->{name};
+ return $ctx->stash->{rest} =
+ {
+ message => "hello $name!",
+ metadata => { name => $name },
+ };
+ }
+
+which is cleaner, but now you have to modify every action that used to use the first method.
+
+Let's clean this up a bit.
+
+In your controller:
+
+ __PACKAGE__->config(
+ action => {
+ hello => {
+ Does => "SerializeReturnValue"
+ }
+ }
+ );
+
+....meanwhile, in lib/WebApp/ActionRole/SerializeReturnValue.pm
+
+ package WebApp::ActionRole::SerializeReturnValue;
+ use strictures 1;
+ use Moose::Role;
+
+ around execute => sub {
+ # $self is the $c->action being 'executed'
+ my ( $orig, $self ) = ( shift, shift );
+ # execute is called on an action in a controller context
+ my ( $controller, $ctx ) = @_;
+ # wrap the original method
+ return $ctx->stash->{rest} = $self->$orig(@_);
+ };
+
+ no Moose::Role;
+ 1;
+
+The method now becomes:
+
+ sub hello {
+ my ( $self, $ctx ) = @_;
+ my $name = $ctx->req->params->{name};
+ return {
+ message => "hello $name!",
+ metadata => { name => $name },
+ };
+ )
+ }
+
+So while the 'name' param is still being grabbed from a global (we'll leave fixing that for another time and place),
+the method is returning the intended hash without worrying about the encoding.
+
+Now you're sick of L<Catalyst::Controller::Rest> and want to handle serialization yourself?
+No problem, change the ActionRole to JSON encode the return and drop it into the response body,
+or create a JSON (or xml or yml...) view and stash it in another key, or subclass L<Catalyst::Response> and give
+it a 'data' attribute, etc etc TIMDOWDI.
+
+Point is, you won't have to edit your 'hello' method or your 30 others that return serializable data because you've properly
+encapsulated your methods.
+
+=head1 AUTHOR
+
+Samuel Kaufman <skaufman at cpan.org>
Added: trunk/examples/CatalystAdvent/root/2012/17.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2012/17.pod (rev 0)
+++ trunk/examples/CatalystAdvent/root/2012/17.pod 2012-12-05 18:14:46 UTC (rev 14416)
@@ -0,0 +1,302 @@
+=head1 Dynamic forms with HTML::FormFu
+
+=head1 OVERVIEW
+
+Working with forms can quickly become nasty. Two very popular form handling
+libraries for Catalyst are L<HTML::FormHandler|https://metacpan.org/module/HTML::FormHandler>
+and L<HTML::FormFu|https://metacpan.org/module/HTML::FormFu>. Both have their
+pros and cons. This article shows how to use HTML::FormFu in an
+easy way for creating dynamic forms.
+
+=head1 HTML::FormFu and Catalyst
+
+=head2 What FormFu can do
+
+HTML::FormFu calls itself a form creation, rendering and validation framework.
+Usually a form is constructed from a data structure describing all of the
+form's requirements and listing all of its fields in their displayed order.
+Typically the description for a form is loaded from a config file which may
+get stored in various formats like YAML, XML or in literal Perl syntax.
+
+Static forms may be sufficient in most cases. If two users should be allowed
+to do different things or certain fields are only required under certain
+conditions, multiple forms may be required. Using a form containing the
+summary of all fields and a logic for removing or changing fields sounds like
+a solution at first.
+
+HTML::FormFu provides an API for introspecting and manipulating a form.
+As long as the required manipulations remain simple, using this API is
+easy and straight forward. If, however, the number of manipulations will
+increase or the number of forms grow, chances are high to generate
+unreadable do-always-the-same-manipulation code. Within very short time
+you end up in 2000+ line Controller-Classes mostly doing form manipulation.
+But there is an easier and more readable way.
+
+For demonstration how to use and modify forms, we will construct a
+hypothetical workflow of some entity. The entity will have a name, a comment
+and a simple workflow with the steps "create", "edit", "check" and "finish".
+Name and workflow may only get edited by administrators, regular
+users may only see these fields without editing capability.
+
+=head2 Preparing our app
+
+For the following examples we need a simple Catalyst application. We will
+not use any templates and just a single controller.
+
+These lines will create all needed things and launch your app.
+
+ # ensure you have FormFu installed
+ $ cpanm HTML::FormFu
+ $ cpanm Catalyst::Controller::HTML::FormFu
+
+ # create and launch app
+ $ catalyst.pl FormDemo
+ $ cd FormDemo
+ $ ./script/formdemo_create.pl controller Entity
+ $ ./script/formdemo_server -drf
+
+=head2 A simple form
+
+By convention, a form is located in a directory inside F<root/forms> matching
+the controller's namespace having the action's name plus an extension matching
+the chosen file format.
+
+In Perl syntax the form for the fields required above could look like this.
+Please create a file with this content and save it into F<root/forms/entity/edit.pl>.
+
+ {
+ indicator => 'save',
+ auto_fieldset => { legend => 'Edit Entity' },
+ elements => [
+ {
+ # in case we want to save the record back to a database
+ type => 'Hidden',
+ name => 'entity_id',
+ },
+ {
+ type => 'Text',
+ name => 'name',
+ label => 'Name:',
+ # for later expansion, see below
+ stash => { allow_admin_only => 1 },
+ },
+ {
+ type => 'Textarea',
+ name => 'comment',
+ label => 'Comment:',
+ rows => 5,
+ },
+ {
+ type => 'Select',
+ name => 'step_id',
+ label => 'Status:',
+ options => [
+ [ 0 => 'create' ],
+ [ 1 => 'edit' ],
+ [ 2 => 'check' ],
+ [ 3 => 'finish' ],
+ ],
+ # for later expansion, see below
+ stash => { allow_admin_only => 1 },
+ },
+ {
+ type => 'Submit',
+ name => 'save',
+ value => 'save',
+ },
+ ],
+ }
+
+To get the form running in our application we need a controller that extends
+C<Catalyst::Controller::HTML::FormFu> and an action that loads the form. The
+C<FormConfig> attribute tells our action to load the form from the matching
+path. If you plan to organize your forms in a different directory layout, you
+may add the relative path as an argument to the attribute like
+C<FormConfig('directory/file.pl')>. Please read
+L<Catalyst::Controller::HTML::FormFu|https://metacpan.org/module/Catalyst::Controller::HTML::FormFu>
+for the glory details.
+
+ package FormDemo::Controller::Entity;
+ use Moose;
+ BEGIN { extends 'Catalyst::Controller::HTML::FormFu' }
+
+ sub begin :Private {
+ my ($self, $c) = @_;
+
+ # simulate admin switch using a GET parameter
+ if ($c->req->params->{user_is_admin}) {
+ $c->stash->{user_is_admin} = 1;
+ }
+ }
+
+ sub edit :Local :FormConfig {
+ my ($self, $c) = @_;
+
+ my $form = $c->stash->{form};
+
+ # simulate a database lookup
+ $form->default_values({
+ entity_id => 42,
+ name => 'My Entity',
+ comment => 'The quick brown fox jumps over the lazy dog',
+ step_id => 1,
+ });
+
+ $c->res->body($form->render);
+ }
+
+ 1;
+
+Now, point your browser to your app and you will see a form (well, this
+screenshot has some CSS applied, but this is not the point).
+
+=begin xhtml
+
+<img src="/calendar/static/2012/admin_screen.png" />
+
+=end xhtml
+
+=head2 Manipulating forms the hard way
+
+HTML::FormFu offers methods for traversing the form's fields,
+searching form elements by various criteria as well as inserting, modifying
+and removing form elements. Doing lots of manipulations can lead to
+hard-to-maintain code. You will find a complete explanation of all available
+methods in L<HTML::FormFu|https://metacpan.org/module/Catalyst::Controller::HTML::FormFu>.
+If your code exceeds a few lines or will contain repeated sequences please
+think about using a plugin for the manipulations.
+
+=head2 Using a Plugin
+
+To get a plugin running, you could either add a C<plugins> key
+to every form you like to get enriched by one or more plugins. However, this
+may lead to many repetitions. If a plugin is required for every form you
+plan to use, it may get added inside your config. Here is an example configuration
+in Perl syntax:
+
+ 'Controller::HTML::FormFu' => {
+ constructor => {
+ plugins => [ 'ManipulateFields' ],
+ },
+ },
+
+For the purpose we intend to do, we need to find a space for storing additional
+information into every single form field. HTML::FormFu offers two places
+for this: C<attributes> and C<stash>. Each of both places can be used in
+a form definition and queried or modified with the traversal API. If you plan
+to use attributes, prepending an attribute-key with "data-" is nice in order
+to maintain your HTML valid instead of inventing phantasy-HTML-attributes or
+accidentally overwriting existing attributes. For this demonstration we are
+using the stash.
+
+Let us add a simple plugin which reads thru every form element, isolates
+stash keys and tries to call a method with the stash key's name if
+available.
+
+ package HTML::FormFu::Plugin::ManipulateFields;
+ use Moose;
+ extends 'HTML::FormFu::Plugin';
+
+ sub pre_process {
+ my $self = shift;
+
+ my $form = $self->form;
+ my $c = $form->stash->{context};
+
+ foreach my $element ( @{ $form->get_all_elements } ) {
+ $self->$_($c, $form, $element, delete $element->stash->{$_})
+ for grep { $self->can($_) }
+ keys %{$element->stash};
+ }
+ }
+
+ sub allow_admin_only {
+ my ($self, $c, $form, $element) = @_;
+
+ return if $c->stash->{user_is_admin};
+
+ $element->attributes->{readonly} = 'readonly';
+ $element->attributes->{disabled} = 'disabled';
+
+ # if you plan to use HTML::FormFu::Model::DBIC, also add:
+ # $element->model_config->{read_only} = 1;
+ }
+
+ 1;
+
+=begin xhtml
+
+<img src="/calendar/static/2012/user_screen.png" />
+
+=end xhtml
+
+=head2 Alternatives
+
+another place to "hide" the method names to call inside your plugin is
+the C<attributes> attribute every field provides. A field might look like
+
+ {
+ type => 'Text',
+ name => 'name',
+ label => 'Name:',
+ attributes => { 'data-allow-admin-only => 1 },
+ },
+
+The prefix "data-" was chosen to generate a valid HTML and to prevent collisions
+with other HTML attributes of the generated HTML markup.
+
+Possible expansions could be to namespace the methods by using certain parts
+of the name as part of a class, others as the methods. Or you may allow
+extending your plugin by using tools like
+L<Module::Pluggable|https://metacpan.org/module/Module::Pluggable>.
+
+=head2 What to do next?
+
+If you think about different permissions, states, situations or flags sitting
+at your records you will easily find situations that will quickly become
+candidates for form manipulations. Here are some examples
+
+=over
+
+=item display accounting fields for your staff
+
+=item hide prices from non-privileged people
+
+=item show additional fields for your admins
+
+=item add hint messages for certain users
+
+=item construct context-dependant selectbox options
+
+=item add a captcha for anonymous users
+
+=back
+
+Just a small list of raw ideas. Would be great to read a blog entry containing
+more.
+
+=head2 Caveats
+
+A developer is free to call C<<< $form->process() >>> as often as he likes.
+Sometimes these calls are necessary after certain kinds of form manipulation.
+Every call to C<process> will trigger the plugin logic above again. If the
+logic you apply to your form is expensive in terms of CPU or processing time
+or destructive in any kind, you might consider to prevent multiple executions.
+This is the reason why the name of method getting called is removed from the
+form element's stash in the code examples above.
+
+=head1 For More Information
+
+L<Catalyst::Controller::HTML::FormFu|https://metacpan.org/module/Catalyst::Controller::HTML::FormFu>
+
+=head1 Summary
+
+HTML::FormFu provides a clean way to operate with forms. If you use plugins
+to bend your forms to the shape you need, you will keep the form handling
+as simple as it is and can focus on the important parts of your app.
+
+=head1 Author
+
+Wolfgang Kinkeldei E<lt>wolfgang [at] kinkeldei [dot] deE<gt>
+
+=cut
Deleted: trunk/examples/CatalystAdvent/root/2012/pen/action_role_JSON.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2012/pen/action_role_JSON.pod 2012-12-05 17:26:06 UTC (rev 14415)
+++ trunk/examples/CatalystAdvent/root/2012/pen/action_role_JSON.pod 2012-12-05 18:14:46 UTC (rev 14416)
@@ -1,124 +0,0 @@
-
-=head1 Action Roles for cleaner, less stashy Catalyst Actions
-
-=head2 Overview
-
-How to Serialize return values from Catalyst Actions
-
-=head3 About this Article
-
-Please note that this article leverages behavior that used to only exist in L<Catalyst::Controller::ActionRole>
-but has been merged into Catalyst proper in Catalyst v5.90013 .
-
-To take advantage of ActionRoles with an older Catalyst see the docs for L<Catalyst::Controller::ActionRole>.
-
-=head2 { hello => 'world' }
-
-
-Write a function that given a name, returns a user readable greeting along with a hashref of the data used.
-Simple.
-
- sub hello {
- my($name) = @_;
- return {
- message => "howdy $name",
- metadata => {
- name => $name,
- },
- };
- }
-
-It's readable and extensible.
-
-Now re-written as a Catalyst Ajax endpoint:
-
-
- sub hello {
- my ( $self, $ctx ) = @_;
- my $name = $ctx->req->params->{name};
- $ctx->res->headers->{ContentType} = "text/javascript";
- $ctx->res->body(
- $self->json->encode(
- {
- message => "hello $name!",
- metadata => { name => $name },
- }
- )
- );
- }
-
-Pretty gross. Most of the method is no longer concerned with the actual data transformation and is instead doing a bunch of
-crap that you're probably repeating elsewhere.
-
-When that goes from being ugly to being a pain is when, for example, you decide you want migrate to using L<Catalyst::Controller::REST>.
-
-The method becomes:
-
- sub hello {
- my ( $self, $ctx ) = @_;
- my $name = $ctx->req->params->{name};
- return $ctx->stash->{rest} =
- {
- message => "hello $name!",
- metadata => { name => $name },
- };
- }
-
-which is cleaner, but now you have to modify every action that used to use the first method.
-
-Let's clean this up a bit.
-
-In your controller:
-
- __PACKAGE__->config(
- action => {
- hello => {
- Does => "SerializeReturnValue"
- }
- }
- );
-
-....meanwhile, in lib/WebApp/ActionRole/SerializeReturnValue.pm
-
- package WebApp::ActionRole::SerializeReturnValue;
- use strictures 1;
- use Moose::Role;
-
- around execute => sub {
- # $self is the $c->action being 'executed'
- my ( $orig, $self ) = ( shift, shift );
- # execute is called on an action in a controller context
- my ( $controller, $ctx ) = @_;
- # wrap the original method
- return $ctx->stash->{rest} = $self->$orig(@_);
- };
-
- no Moose::Role;
- 1;
-
-The method now becomes:
-
- sub hello {
- my ( $self, $ctx ) = @_;
- my $name = $ctx->req->params->{name};
- return {
- message => "hello $name!",
- metadata => { name => $name },
- };
- )
- }
-
-So while the 'name' param is still being grabbed from a global (we'll leave fixing that for another time and place),
-the method is returning the intended hash without worrying about the encoding.
-
-Now you're sick of L<Catalyst::Controller::Rest> and want to handle serialization yourself?
-No problem, change the ActionRole to JSON encode the return and drop it into the response body,
-or create a JSON (or xml or yml...) view and stash it in another key, or subclass L<Catalyst::Response> and give
-it a 'data' attribute, etc etc TIMDOWDI.
-
-Point is, you won't have to edit your 'hello' method or your 30 others that return serializable data because you've properly
-encapsulated your methods.
-
-=head1 AUTHOR
-
-Samuel Kaufman <skaufman at cpan.org>
Added: trunk/examples/CatalystAdvent/root/static/2012/admin_screen.png
===================================================================
(Binary files differ)
Property changes on: trunk/examples/CatalystAdvent/root/static/2012/admin_screen.png
___________________________________________________________________
Added: svn:mime-type
+ application/octet-stream
Added: trunk/examples/CatalystAdvent/root/static/2012/user_screen.png
===================================================================
(Binary files differ)
Property changes on: trunk/examples/CatalystAdvent/root/static/2012/user_screen.png
___________________________________________________________________
Added: svn:mime-type
+ application/octet-stream
More information about the Catalyst-commits
mailing list