[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