Monday, April 27, 2009

Compound fields in forms

Many Perl form packages require nested forms of some sort for compound fields, but there are problems with that model. A form class--whether constructed from config settings in a file or declared in a Perl class--has lots of other attributes besides the fields. It made more sense to me to have a form with nested fields, where the fields can form a tree. That way attributes of the form do not have to be repeated in sub-forms. It also allows declaring subfields in the containing form, if that's what makes sense for this particular case. Here's an example of how to do a form with a nested field in HTML::FormHandler:
   package MyApp::Form;
   use HTML::FormHandler::Moose;
   extends 'HTML::FormHandler';

   has_field 'address' => ( type => 'Compound' );
   has_field 'address.street';
   has_field 'address.city';
   has_field 'address.zip' => ( type => '+Zip' );
   1;
The subfields are indicated by prefacing the field name with the name of the compound field and a dot. This form class will create a form with an 'address' field that contains an array of fields.
You can achieve the same thing by creating an 'Address' field:
   package MyApp::Field::Address;
   use HTML::FormHandler::Moose;
   extends 'HTML::FormHandler::Field::Compound';

   has_field => 'street';
   has_field => 'city';
   has_field => 'zip' => ( type => '+Zip' );
   1;
(Where '+Zip' is a field class that you have written...) Then this field can be used in a form by simply doing:
   has_field 'addresss' => ( type => '+Address' );
This feels simple and straightforward and, for me at least, matches my conceptual model of a form better than nested forms.

Thursday, April 23, 2009

HTML::FormHandler

When I started on a Catalyst project a year and a half ago I picked Form::Processor for form handling. I liked the architecture and I liked having the form in a Perl class so that I could do more-or-less anything that I wanted to with it. But then there was Moose, and I didn't want to have to switch back and forth between object systems. Plus you can do lots of shiny things with Moose. It's like a big Christmas present with lots of little parts that you can play with for ages...

So I converted Form::Processor to Moose. But it was tough to maintain back compatibility and still use all that Moose-y goodness. In the end it seemed rational to start fresh with a new project: HTML::FormHandler. It has many excellent features, such as being able to define the fields declaratively, and there are plans for many more. Moose continues to be a pleasure to work with. It makes features easy that would have been horrendously difficult without it. Of course, Reaction does wonderful things with Moose too, and is in many ways a more complete solution. But I think that HTML::FormHandler can still fill a niche. This is Perl after all--there has to be more than one way to do things.

Tuesday, July 15, 2008

Moose is fun

I've been converting an old, badly designed set of Perl classes to Moose, and it's actually been fun. I split the old classes up into a number of sub-objects, but was able to maintain the silly old interface with "handles", forwarding the calls to the appropriate class.

If you work with Perl and haven't tried Moose yet, you're missing something. The current three classes are 67 pages of printed Perl. I estimate that I'll end up with maybe 6 or 7 classes and probably no more than 20 pages.

Friday, July 11, 2008

Color schemes with vifm

This has been a "tools" day. I use vim for my IDE, but after 13 years of using still don't consider myself an expert. I've been using vimmate (in Ruby) for accessing the files and directories in my Catalyst project, but decided to find some option to recover the screen real estate. I ran across vifm, an ncurses vi-like file manager, which does pretty much what I want. However it comes with a dark background and I only do light backgrounds. The code has the ability to use a file of "colorschemes", but there is no documentation or example. I read the C code and figured out how to create a light background colorschemes file. vifm uses a ~/.vifm directory for configuration. In that directory, create a "colorschemes" file (no extension). The goal with the colorschemes was apparently to have different colors for different types of directories, as you can see in the screen shot at http://vifm.sourceforge.net/picture.html, but I just wanted a different default setup. In the "colorschemes" file I have the following:
COLORSCHEME=Default
COLOR=MENU=4=7
COLOR=BORDER=7=0
COLOR=WIN=0=7
COLOR=STATUS_BAR=0=7
COLOR=CURR_LINE=7=4
COLOR=DIRECTORY=4=7
COLOR=LINK=4=7
COLOR=SOCKET=4=7
COLOR=DEVICE=1=7
COLOR=EXECUTABLE=2=7
COLOR=SELECTED=5=0
COLOR=CURRENT=7=0
This is not completely tested but seems to be working so far. If you want a different colorscheme for a different directory, you would add a line such as: DIRECTORY=/Full/Path/To/Base/Directory after the COLORSCHEME line. Comments start with # G.Shank

Friday, November 30, 2007

Catalyst for Clueless Beginners

The Catalyst Tutorial is an excellent introduction to Catalyst, but can be hard for total beginners to get into. When I went through the "learning Catalyst" process I tried to make some notes and put together a verrrry simple step-by-step introduction for those of equal cluelessness. I've avoided using a database here because it adds a lot of complexity, and isn't necessary for "Hello, World".

Note: the “$” at the beginning of shell commands is just to indicate a shell prompt. Do not type it.

This tutorial assumes that you are running on a form of Linux. If you are running on Windows, you will have to translate the commands appropriately. The tutorial also assumes basic Perl knowledge. If you don't have that, you should get “Learning Perl” or some other tutorial and learn some Perl first.

Catalyst is a Model/View/Controller framework. You should at least know what that means before you start...

Install and Start Catalyst

Create a Catalyst application:

$ catalyst.pl MyApp

A long list of created directories and files should scroll up your screen.

Change to the MyApp directory that was just created:

$ cd MyApp

Start up the server:

$ script/myapp_server.pl

Go to a browser and look at the url http://localhost:3000

It should display the Catalyst welcome page. If it doesn't, you probably have installation problems. You'll have to get things working before you can do anything else.

Stop the server with a Ctrl-C.

Hello World!

Edit the lib/MyApp/Controller/Root.pm file. You will see the "default" subroutine, which is responsible for displaying the "welcome" screen that you just saw. Later on you'll want to change that to something more reasonable, such as a "404" message, but for now just leave it alone. Add the following subroutine:

sub hello : Global {
  my ( $self, $c) = @_;
  $c->response->body('Hello World!');
}

Notice the "Global term. This is a Catalyst "action" which will make this method one which executes directly after the site URL, or "localhost:3000/hello" at this point. “$c” is the Catalyst “context”, which is used to access Catalyst methods and variables. “response” refers to the Catalyst::Response class, which provides methods for responding to browser requests. “body” is the method which sets the output text to be sent to the browser. This command will send the string “Hello World!” to your browser.

Save the file.

Start the server, open a browser, and go to http://localhost:3000/hello to see "Hello World!"

Hello World using a View and a template

In the Catalyst world, a "View" is not a page of XHTML or a template designed to present page to the browser. It is a Perl module which is more-or-less the equivalent of a config file for the template processor that you have chosen to use. Normally nothing much is in the lib/MyApp/View directory but a couple of Perl modules that set configuration options for Template Toolkit, or Mason, or whatever Perl module(s) you've chosen to handle your presentation (views).

The actual templates are put someplace else, usually underneath the "root" directory. The default, if you do not actually set the directory in a config file, is in the root directory itself: MyApp/root.

The template files are not in the “View” directory, because the lib hierarchy is just for Perl modules. The perl module names are related to their place in the hierarchy. For exammple, MyApp/View/TT.pm is the module MyApp::View::TT.

In order to create the TT config file, run:

$ script/myapp_create.pl view TT TT

This creates the lib/MyApp/View/TT.pm module, which is a subclass of Catalyst::View::TT

Now that the TT.pm "View" exists, Catalyst will automatically use it for displaying the view templates, using the "process" method that it inherits from the Catalyst::View::TT.pm class.

Create a "hello.tt" template file, and put it in the "root" directory (MyApp/root):

[% META title = 'Hello World!' %]
<p>
  This is a TT view template, located in the root directory
<p>

Change the hello method to the following:

sub hello : Global 
{
  my ( $self, $c ) = @_;
  $c->stash->{template} = 'hello.tt';
}

The “stash” is a Perl hash where Catalyst stores variables that will be needed by other components, including templates. Here you are setting a hash element named “template” to “hello.tt”. The Catalyst “renderview” method will automatically take the file you set here, process it with your View module (Template Toolkit), and send it to the browser.

Start up the server and look at http://localhost:3000/hello again. You should see a page saying "Hello World".

Create a simple controller and an action

Create a controller, "Site", for example:

$ script/myapp_create.pl controller Site

In the Site.pm file (in lib/MyApp/Controller) add the following method:

sub test : Local 
{
  my ( $self, $c ) = @_;
  $c->stash->{template} = 'site/test.tt';
}

Notice the "Local" on the method definition. This is necessary for the method to be executed using the "site/test" url. "Global" would put it in the root URL.

Make a subdirectory, "site", in the directory for your template files ("root" by default, "root/src" if you use the TTSite layout), copy the hello.tt file into the site directory as test.tt:

$ cp hello.tt site/test.tt

Edit the test.tt file so you can recognize it...

Bring up the server (or Ctrl-C to bring down and bring it up again), go to http://localhost:3000/site/test

You should see your test.tt file displayed.

Using the TTSite Helper

The TTSite helper will create some useful Template Toolkit templates, including header, footer, wrapper, and other files.

If you want to try it, execute:

$ script/myapp_create.pl view TT TTSite

In addition to the TT.pm file created by "myapp_create.pl view TT TT", TTSite will also create a number of TT templates in the root/src and root/lib directories. It uses the ".tt2" extension, so if you prefer to use the more standard ".tt" extension, you will have to go through and rename the files, and modify the TT.pm file to use that extension.

In addition, it creates a number of template files without extensions: header, footer, wrapper, etc. If you prefer to not have files without file type extensions, you'll have to rename them to header.tt, footer.tt, etc. This also requires changing the references to the files in the templates.

You might want to try TTSite on a sample Catalyst app, examine the template files that it creates, and copy those that you find useful to your own app—unless you're doing a tutorial which counts on having the default TTSite installation. Template Toolkit is a powerful template system with lots of options and a template mini-language. It is well documented at http://template-toolkit.org/

Where does Catalyst look for view templates?

The default directory, if you haven't changed it is "root". If you want to use some other directory name, such as "public", you set the name in MyApp.pm.

For example:

package MyApp::Controller::Test;
sub hello : Local { .. } 

Would by default look for a template in <root>/test/hello. If you set TEMPLATE_EXTENSION in your TT.pm view module to '.tt', it will look for <root>/test/hello.tt

If you are using TTSite defaults, then “included” templates will by in “MyApp/root/lib” and other templates will be in “MyApp/root/src”. It is possible to set both "src" and "include" directories to the same directory. When specifying templates in the stash or including templates in other templates (with the PROCESS, INCLUDE, and INSERT directives) you need to use subdirectories.

For the example above that displays the site/test.tt template, you didn't have to specifically set the template file since it is the default location. You should be able to delete the line setting the template file and Catalyst will still find it, but there will be many times when you will want to specifically set the template to be displayed.

Catalyst Actions

A Catalyst controller contains "actions", subroutines with special attributes, defined using Nicholas Clark's “Attributes” module. These action attributes provide information to the Catalyst dispatcher.

Most URLs look something like:

http://somename.com/site/hello

In this URL, "site" is the controller, and "hello" is an action in the "site" controller.

Actions which are called at the "root" level of the application go in the Root.pm module.

You have seen three different action types: "Private" (on the default subroutine), "Global", and "Local".

A "Global" action is mapped to the application base, with no controller name. A Global "hello" method would be executed with http://somename.com/hello

A "Local" action is executed by prefixing the controller name:

http://somename.com/test/hello

A "Private" action can't be executed directly by URL. It is usually executed by being called by some other action, except for the actions "default", "index", and "auto".

There are a number of additional action types:

Literal Path:  sub bar : Path ('/foo/bar') { }
Regex:         sub bar : Regex ('^item(\d+)/order(\d+)$') { }
               This matches globally, without regard to the controller or namespace.
LocalRegex:    sub bar : LocalRegex('^widget(\d+)$') { }
               This matches only locally.
Chained:       sub bar : Chained : CaptureArgs(1) {} 
     or:       sub bar : Chained('catalog') : Args(1) {}
               See the documentation on DispatchType::Chained
Args:      action type Modifier.
   limits the number of path parts

To start with, most of your Controller actions will probably be "Local". Catalyst is very flexible in how you set up your actions and your URL structure, so you will have lots of choices about how to structure your application and your URLs.

Using the chained type of action can be particularly useful, but isn't advised for your first few Catalyst methods since it's a bit trickier.


--------------------------------------------------------------------------


I hope this has been of some help to someone.

G.Shank

Tuesday, November 13, 2007

Learning Catalyst, a Perl framework

I'm learning Catalyst to use in a project, and find the documentation heavy going for a beginner. I started this blog to collect some of the information that I'm putting together.