Docendo discimus

$self->explain

  • Calendar

    May 2017
    M T W T F S S
    « Sep    
    1234567
    891011121314
    15161718192021
    22232425262728
    293031  
  • Archives

  • Recent Posts

  • Bling

Posts Tagged ‘user interaction’

Catalyst is easy, second movement

Posted by brunorc on June 21, 2009

After some digressions I get back to the main course – the Catalyst itself. I promised to show something more dynamic, so here we go.

It is obvious that at some point application needs to get some input from the user. It is usually done by showing the form to the user, and then using the data user puts into the form. Showing the form should be rather easy – let’s start from copying “header” and “footer” of the index action, renaming the action to add:

sub add :Path :Args(0) {
    my ( $self, $c ) = @_;

    $c->response->body( $template );
}

Of course the Catalyst server was ran with the -r flag, this code will die immediately, since the variable $template was not defined. This can be easily solved by putting some HTML code in the mentioned variable:

sub add :Path :Args(0) {
    my ( $self, $c ) = @_;

    my $template = qq[
<html>
    <head>
        <title>Add new item</title>
    </head>
    <body>
        <div align="center">
            <h3>Add something!</h3>
            <form method="get">
                <input type="text" size="24" id="newitem" name="newitem">
                <input type="submit" id="addit" name="addit" value="Add it!">
            </form>
        </div>
    </body>
</html>
];

    $c->response->body( $template );
}

Now we are ready to visit the URL http://localhost:3000/add… huh?

Yeah, page not found.

Let us take a look at the output of our server:

[info] *** Request 4 (0.028/s) [3055] [Sun Jun 21 14:41:35 2009] ***
[debug] "GET" request for "add" from "127.0.0.1"
[debug] Arguments are "add"
[info] Request took 0.004999s (200.040/s)
.------------------------------------------------------------+-----------.
| Action                                                     | Time      |
+------------------------------------------------------------+-----------+
| /default                                                   | 0.000192s |
| /end                                                       | 0.000275s |
'------------------------------------------------------------+-----------'

Looks like Catalyst server launched the default action. When it gets hit with the request, server tries to dispatch the URL into action, and if everything fails, the default action of the Root controller is used. It is done by checking actions’ attributes, like Path or Args. Both index and add have the empty Path attribute, and that’s why the add was not launched.

Now there are two basic options:

The attribute Local will make the action (yes, you guessed it!) local to the current controller, thus making the action reachable. If we make the action local, it will be bound to the containing controller. If the action dump of the controller Core is local, after renaming the controller to Kernel the URL /core/dump will become unreachable, and the action will be available under /kernel/dump. After making the add action local, the output of starting Catalyst server will look like this:

[debug] Loaded Private actions:
.----------------------+--------------------------------------+--------------.
| Private              | Class                                | Method       |
+----------------------+--------------------------------------+--------------+
| /add                 | helloworld::Controller::Root         | add          |
| /default             | helloworld::Controller::Root         | default      |
| /end                 | helloworld::Controller::Root         | end          |
| /index               | helloworld::Controller::Root         | index        |
'----------------------+--------------------------------------+--------------'

[debug] Loaded Path actions:
.-------------------------------------+--------------------------------------.
| Path                                | Private                              |
+-------------------------------------+--------------------------------------+
| /                                   | /default                             |
| /                                   | /index                               |
| /add                                | /add                                 |
'-------------------------------------+--------------------------------------'

The attribute Path has to contain (yes, you guessed it again!) the path under which the action will be available. Now nobody can stop us from making the add action reachable under some really fancy URL:

sub add :Path('/now/you/can/add') :Args(0) {

and then enjoying the following output:

[debug] Loaded Private actions:
.----------------------+--------------------------------------+--------------.
| Private              | Class                                | Method       |
+----------------------+--------------------------------------+--------------+
| /add                 | helloworld::Controller::Root         | add          |
| /default             | helloworld::Controller::Root         | default      |
| /end                 | helloworld::Controller::Root         | end          |
| /index               | helloworld::Controller::Root         | index        |
'----------------------+--------------------------------------+--------------'

[debug] Loaded Path actions:
.-------------------------------------+--------------------------------------.
| Path                                | Private                              |
+-------------------------------------+--------------------------------------+
| /                                   | /default                             |
| /                                   | /index                               |
| /now/you/can/add                    | /add                                 |
'-------------------------------------+--------------------------------------'

There is really no distinction in the Private actions section, but when it comes to Path actions, the difference is quite easy to spot. In this example I decided to use the Local attribute.

Now the action should at last work. After filling the textbox we can hit the button, seeing the server coping with the data we provided:

[info] *** Request 2 (0.667/s) [3107] [Sun Jun 21 15:16:38 2009] ***
[debug] Query Parameters are:
.-------------------------------------+--------------------------------------.
| Parameter                           | Value                                |
+-------------------------------------+--------------------------------------+
| addit                               | Add it!                              |
| newitem                             | magic                                |
'-------------------------------------+--------------------------------------'
[debug] "GET" request for "add" from "127.0.0.1"
[debug] Path is "add"
[info] Request took 0.004661s (214.546/s)
.------------------------------------------------------------+-----------.
| Action                                                     | Time      |
+------------------------------------------------------------+-----------+
| /add                                                       | 0.000187s |
| /end                                                       | 0.000324s |
'------------------------------------------------------------+-----------'

which is, of course, impressive. But it doesn’t add anything at all! If we go to the “main page”, we will see the same list. That should be changed, so the add action can modify the list of items. To achieve this, first we have to make the list available for the action, making it global:

my @items = ( 'beer', 'bacon', 'pierogi' );

sub index :Path :Args(0) {
    my ( $self, $c ) = @_;

    my $list = '';

    foreach my $item ( @items ) {
        $list .= "<li>$item</li>\n";
    }

    my $template = qq[
<html>
    <head>
        <title>My items</title>
    </head>
    <body>
        <ul>
        $list
        </ul>
        <p>Only \$5!</p>
    </body>
</html>
];

    $c->response->body( $template );
}

Now the second action can modify the list:

sub add :Local :Args(0) {
    my ( $self, $c ) = @_;

    if ( $c->req->params->{newitem} ) {
        push @items, $c->req->params->{newitem};
    }

    my $template = qq[
<html>
    <head>
        <title>Add new item</title>
    </head>
    <body>
        <div align="center">
            <h3>Add something!</h3>
            <form method="get">
                <input type="text" size="24" id="newitem" name="newitem">
                <input type="submit" id="addit" name="addit" value="Add it!">
            </form>
        </div>
    </body>
</html>
];

    $c->response->body( $template );
}

The $c variable is called “context”. Usually if you want to get something from Catalyst, first try to ask the context object if it has seen it. Inside of the context one can find the request and response object (with their req and res aliases for those of us who lost the war with laziness). Request may contain some parameters, called params; and those parameters are accessed by their respective names. This may be used to write the code like this:

sub add :Local :Args(0) {
    my ( $self, $c ) = @_;

    my $optional;

    if ( $c->req->params->{newitem} ) {
        push @items, $c->req->params->{newitem};

        if ( $c->req->params->{addit} eq 'Add it!' ) {
            $optional = '<h4>Thank you for filling the form!</h4>';
        }
    }

    my $template = qq[
<html>
    <head>
        <title>Add new item</title>
    </head>
    <body>
        <div align="center">
            <h3>Add something!</h3>
            <form method="get">
                <input type="text" size="24" id="newitem" name="newitem">
                <input type="submit" id="addit" name="addit" value="Add it!">
            </form>
            $optional
        </div>
    </body>
</html>
];

    $c->response->body( $template );
}

Next time I will dwell a little bit on the second attribute – Args; that in turn will be useful for creating the API, so you would be able to build Facebook, in case it wasn’t built already.

Posted in Catalyst for intimidated | Tagged: , , , , , , , | 1 Comment »