Sign In/My Account | View Cart  
advertisement


Listen Print Discuss

Catalyst
by Jesse Sheidlower | Pages: 1, 2

Sample Application: MiniMojo, an Ajax-Based Wiki in 30 Lines of Written Code

Now that you have a sense of what Catalyst is, it's time to look at what it can do. The example application is MiniMojo, a wiki based on Ajax, which is a JavaScript framework that uses the XMLHttpRequest object to create highly dynamic web pages without needing to send full pages back and forth between the server and client.

Remember that from the Catalyst perspective, Ajax is just a case of sending more text to the browser, except that this text is in the form of client-side JavaScript that talks to the server, rather than a boilerplate copyright notice or a navigation sidebar. It makes no difference to Catalyst.

Installation

Catalyst has a relatively large number of requirements; most, however, are easy to install, along with their dependencies, from CPAN. The following list should take care of everything you need for this project:

Generate the Application Skeleton

Run this command:

$ catalyst.pl MiniMojo
$ cd MiniMojo

You've just created the skeleton for your entire application, complete with a helper script keyed to MiniMojo to generate individual classes, basic test scripts, and more.

Run the built-in server:

$ script/minimojo_server.pl

MiniMojo is already running, though it isn't doing much just yet. (You should have received a web page consisting solely of the text "Congratulations, MiniMojo is on Catalyst!") Press Ctrl-C to stop the server.

Add Basic Methods to Your Application Class

Add a private end action to your application class, lib/MiniMojo.pm, by editing the new file:

sub end : Private {
    my ( $self, $c ) = @_;
    $c->forward('MiniMojo::V::TT') unless $c->res->output;
}

Catalyst automatically calls the end action at the end of a request cycle. It's one of four built-in Private actions. It's a typical pattern in Catalyst to use end to forward the application to the View component for rendering, though if necessary you could do it yourself (for example, if you want to use different Views in the same application--perhaps one to generate web pages with Template Toolkit and another to generate PDFs with PDF::Template).

Replace the existing, helper-generated default action in the same class with:

sub default : Private {
    my ( $self, $c ) = @_;
    $c->forward('/page/show');
}

In case the client has specified no other appropriate action, this will forward on to the page controller's show method. As Private actions, nothing can call these from outside the application. Any method from within the application can call them. The default action is another built-in Private action, along with begin, auto, and end. Again, Catalyst calls them automatically at relevant points in the request cycle.

Set Up the Model (SQLite Database) and Use the Helper to Create Model Classes

Next, create a file, minimojo.sql, that contains the SQL for setting up your page table in SQLite.

-- minimojo.sql
CREATE TABLE page (
    id INTEGER PRIMARY KEY,
    title TEXT,
    body TEXT
);

Create a database from it, using the sqlite command-line program:

$ sqlite minimojo.db < minimojo.sql

Depending on your setup, it might be necessary to call this as sqlite3.

Use the helper to create model classes and basic unit tests (Figure 3 shows the results):

$ script/minimojo_create.pl model CDBI CDBI dbi:SQLite:/path/to/minimojo.db

Model-creation screenshot
Figure 3. Creating the model

The minimojo_create.pl script is a helper that uses Template Toolkit to automate the creation of particular modules. The previous command creates a model (in contrast to a controller or a view) called CDBI.pm, using the CDBI helper, setting the connection string to dbi:SQLite:/path/to/minimojo.db, the database you just created. (Use the appropriate path for your system.) The helper will write the models into lib/MiniMojo/M/. There are various options for the helper scripts; the only requirement is the type and the name. (You can create your own modules from scratch, without using the helper.)

Set Up the View (Template::Toolkit) and Use the Helper to Create View Classes

Use the helper to create a view class:

$ script/minimojo_create.pl view TT TT

View classes go into lib/MiniMojo/V/.

Set Up a Controller Class Using the Helper

Create a controller class called Page with the helper:

$ script/minimojo_create.pl controller Page

Controller classes live in lib/MiniMojo/C/.

Add a show action to lib/MiniMojo/C/Page.pm:

sub show : Regex('^(\w+)\.html$') {
    my ( $self, $c ) = @_;
    $c->stash->{template} = 'view.tt';
    # $c->forward('page');
}

The Regex dispatch matches a page in foo.html, where foo is any sequence of word characters. This sequence is available in the $context->request->snippets array, where the page action uses it to display an existing page or to create a new one. The rest of this action sets the appropriate template and sends the application to the page action. (Leave the forward command commented out until you have written the page action.)

Restart the server with $ script/minimojo_server.pl and point a web browser to http://localhost:3000/show/ to see the debug screen (you don't yet have the template that show is trying to send people to).

Create root/view.tt:

<html>
    <head><title>MiniMojo</title></head>
    <body>
        <h1>MiniMojo is set up!</h1>
    </body>
</html>

Test again by killing the server with Ctrl-C and restarting it, and go to http://localhost:3000/show/. You should see the page you just defined.

Add the Display and Edit Code

Modify the application class lib/MiniMojo.pm to include the Prototype and Textile plugins:

use Catalyst qw/-Debug Prototype Textile/;

Note that you can use the plugins by specifying their base names; Catalyst figures out what you mean without making you use Catalyst::Plugin::Prototype.

Modify the page controller, lib/MiniMojo/C/Page.pm, to add page-view and editing code:

sub page : Private {
    my ( $self, $c, $title ) = @_;
    $title ||= $c->req->snippets->[0] || 'Frontpage';
    my $query = { title => $title };
    $c->stash->{page} = MiniMojo::M::CDBI::Page->find_or_create($query);
}

The private page method sets a title--whether passed in to it, taken from the snippets array (that matches the regex in show), or defaulting to "Frontpage." The $query variable holds a hashref used for Class::DBI's find_or_create method, seeding the stash for the page variable with the result of this CDBI query. At the end of the method, control flow returns to the calling method.

Now uncomment the $c->forward('page'); line in the show action.

sub edit : Local {
    my ( $self, $c, $title ) = @_;
    $c->forward('page');
    $c->stash->{page}->body( $c->req->params->{body} )
      if $c->req->params->{body};
    my $body = $c->stash->{page}->body || 'Just type something...';
    my $html = $c->textile->process($body);

    my $base = $c->req->base;
    $html    =~ s{(?<![\?\\\/\[])(\b[A-Z][a-z]+[A-Z]\w*)}
                 {<a href="$base$1.html">$1</a>}g;

    $c->res->output($html);
}

The edit method first forwards the action off to page, so that the stash's page object contains the result of the CDBI query. If there is a value for body, it will use this; otherwise "Just type something..." is the default. The code then processes the body with Textile, which converts plain text to HTML, and then runs the body through a regex to convert camel-case text into links, with the URL base taken from the Catalyst request object. Finally, it outputs the HTML.

Set Up the Wiki with Ajax

Modify root/view.tt to include Ajax code:

<html>
     <head><title>MiniMojo</title></head>
     [% c.prototype.define_javascript_functions %]
     [% url = base _ 'page/edit/' _ page.title %]
     <body Onload="new Ajax.Updater( 'view',  '[% url %]' )">
         <h1>[% page.title %]</h1>
         <div id="view"></div>
         <textarea id="editor" rows="24" cols="80">[% page.body %]</textarea>
         [% c.prototype.observe_field( 'editor', {
             url => url,
             with => "'body='+value",
             update => 'view' }
         ) %]
     </body>
</html>

The line:

[% c.prototype.define_javascript_functions %]

includes the whole prototype.js library in a script block. Note that the prototype plugin is available in the context object.

The section

[% url = base _ 'page/edit/' _ page.title %] 
<body Onload="new Ajax.Updater( 'view',  '[% url %]' )">
<h1>[% page.title %]</h1>
<div id="view"></div>

constructs the Ajax URL and updates the view div when loading the page.

Finally:

<textarea id="editor" rows="24" cols="80">[% page.body %]</textarea>
    [% c.prototype.observe_field( 'editor', {
        url => url,
        with => "'body='+value",
        update => 'view' }
    ) %]

periodically checks the textarea for changes and makes an Ajax request on demand.

That's it! Now you can re-run the server and your wiki is up and running (Figure 4). To use the wiki, simply start typing in the textarea. As you type, the wiki will regularly echo your entry above, passing it through the formatter. When you type something in camel case, it will automatically create a link you can click to go to the new page.

screenshot of the running wiki
Figure 4. The running wiki

Enjoy your new Catalyst-powered Ajax wiki!

Resources

For more information, see the Catalyst documentation, in particular the Catalyst::Manual::Intro module, which gives a thorough introduction to the framework. There are two Catalyst mailing lists, a general list and a developer list. The best place to discuss Catalyst, though, is the #catalyst IRC channel at irc.perl.org. The Catalyst home page is currently just a collection of a few links, but we will extend it in the near future.

Thanks to Catalyst lead developer Sebastian Riedel for help with this article and, of course, for Catalyst itself.