Now that the URL is mapped to the Controller method as expected, let us try something more challenging and show some HTML content. Instead of writing the HTML inside the response body as in the previous example, in this section, we will use a View. A View is a system that defines how content will be rendered. For this book, we will mostly be using the TT view which is based on a templating package, TT, that is available for any Perl program independent of Catalyst. To learn more about the TT package, visit http://template-toolkit.org/.
In the Controller method (sub index in Hello.pm
), we will remove the line $c->response->body()
. So, the Controller method looks like the following:
sub index :Path :Args(0) { my ( $self, $c ) = @_; }
Please note that Catalyst takes care of forwarding the response to the default view after the Controller's execution. Later in this chapter, we will discover how Catalyst handles that and how it can be manipulated.
Let us create a View (using TT as mentioned) that the application can use as the default view.
The Template Toolkit module is included with the Catalyst::Devel
package, so you should already have it. You'll need to install the Catalyst interface to it though by running the following command line:
$ cpan -i Catalyst::View::TT
If you don't have TT for some reason, cpan
will detect that and install it for you. Once that's complete, create the View by running the following command line:
$ perl script/myapp_create.pl view TT TT
You will see something like this following:
$ perl script/myapp_create.pl view TT TT
exists "/Volumes/Home/Users/solar/Projects/CatalystBook/MyApp/script/../lib/MyApp/View"
exists "/Volumes/Home/Users/solar/Projects/CatalystBook/MyApp/script/../t"
created "/Volumes/Home/Users/solar/Projects/CatalystBook/MyApp/script/../lib/MyApp/View/TT.pm"
created "/Volumes/Home/Users/solar/Projects/CatalystBook/MyApp/script/../t/view_TT.t"
The TT TT
part of the previous command means to create a View called View/TT.pm
(the first TT
) based on the standard Catalyst::View::TT
(the second TT
). This technically means you can have multiple views of Catalyst::View::TT
with different names. You can choose any valid Perl filename as the name of your View (the first TT)
, but a simple TT
makes the most sense here. For more complex applications with multiple views, it is a best practice to name them after the purpose they fulfill such as HTML or JSON.
Next, it's a good idea to add Catalyst::View::TT
to the prerequisites section of Makefile.PL
. Your application will run fine if you omit this step, but if you're diligent about adding your prerequisites to Makefile.PL
, it will be very easy to move your application to another machine. A simple perl Makefile.PL && make
on the new machine will allow Catalyst to automatically install all of your application's dependencies while you have a cup of coffee and relax. A minute of your time here will save much more time later.
Currently, the Makefile.PL
looks something like the following:
#!/usr/bin/env perl # IMPORTANT: if you delete this file your app will not work as # expected. You have been warned. use inc::Module::Install; use Module::Install::Catalyst; # Complain loudly if you don't have # Catalyst::Devel installed or haven't said # 'make dist' to create a standalone tarball. name 'MyApp'; all_from 'lib/MyApp.pm'; requires 'Catalyst::Runtime' => '5.80021'; requires 'Catalyst::Plugin::ConfigLoader'; requires 'Catalyst::Plugin::Static::Simple'; requires 'Catalyst::Action::RenderView'; requires 'Moose'; requires 'namespace::autoclean'; requires 'Config::General'; # This should reflect the config file format you've chosen # See Catalyst::Plugin::ConfigLoader for supported formats test_requires 'Test::More' => '0.88'; catalyst; install_script glob('script/*.pl'), auto_install; WriteAll;
All you need to do to add a prerequisite is add a line of code like:
requires 'Catalyst::View::TT' => 0;
near the other requires
statements in the previous code snippet (order isn't important). The 0
represents the minimum version of the module that your application requires. Leaving it at 0
is fine if you don't know the version, but it's best to specify the current version you're using to develop with. You can determine the version of any module on your system by typing the following line of code:
perl -MSome::Module -e 'print Some::Module->VERSION'
The next step is to create a template in the root/hello
directory. For this example, we're going to create a page that says "Hello, world!", so let's call it root/hello/index.tt
. The root
directory is the default place where Catalyst::View::TT
will look for templates. This can be changed by configuration if you really need to.
Here's the code to use in index.tt:
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> <head> <title>Hello, world!</title> </head> <body> <h1>Hello, world.</h1> <p> Here's a word from our Controller: [% word | html %]. </p> </body> </html>
This is just a regular XHTML file, with some special control sequences for Template Toolkit.
[% word%]
tells TT
to get the value of a variable called "word" and display it at that point in the template. The | html
part after word
in the example is a TT filter that means to escape any HTML in word
so that<
is rendered as an actual<
sign and not an HTML tag. This prevents a type of security hole called cross-site scripting (XSS). While security probably isn't important for a "hello world" page, it's wise to get into good habits now.
Run the server, as explained, by typing perl script/myapp_server.pl
, and open the page http://localhost/hello
.
Right now, this index.tt
will show the following:
Hello, world.
Here's a word from our Controller:
Note that nothing is rendered in the place of [% word | html %]
. This is because we haven't set that variable "word" in the Controller. Such variables that are passed between Controllers and Views are called stash.
We can set the stash in the Controller by modifying the code to the following:
sub index :Path :Args(0) {
my ( $self, $c) = @;
$c->stash->{word} = "Bonjour!";
}
Now open http://localhost:3000/hello
, and you will see the following:
Hello, world.
Here's a word from our Controller: Bonjour!.
Note that the word set in the stash for the variable word
is now rendered instead of [% word | html %]
.
In this section, we will discover the available flexibility on method declaration and URL mapping. We will start by trying to change the last example to accept an argument from the URL that it can render instead of Bonjour!
We can start by modifying the index subroutine. If we have to pass an argument Hello/index/dsads
, then we can change the code to the following:
sub index :Path :Args(1) { my ( $self, $c, @args ) = @_; //Store the argument(s) from the URL in the list @args $c->stash->{word} = $args[0]; }
Notice that we have changed Args(0)
to Args(1)
. This will accept the argument after index
. The URL /hello
is derived from the name of the Controller with colons converted to /
and the Hello
from Hello.pm
converted to all lowercase. The :Path
attribute after index
tells Catalyst that this method will handle URL requests that do not mention the method name such as /hello
. The Args(1)
attribute declares that this action expects one argument.
Now if you run the server and open a URL like http://localhost:3000/hello/Bonjour!
, then you will see the following output:
Hello, world.
Here's a word from our Controller: Bonjour!.
Notice that the [% word | html %]
is replaced with Bonjour!
. Change Bonjour!
to another word in the URL and that word should show up.
Congratulations! You have created your first dynamic Catalyst application.
If we specified :Local
instead of :Path
, then Catalyst would map the index
action to handle a URL that looks like /hello/index
. So, the URL looks like the following http://localhost/hello/index/Bonjour!
, where Bonjour!
is the argument that is being passed to the Controller. If we omitted the attributes (:Local
, :Path
, and so on), Catalyst would ignore the action entirely and it would be a normal Perl subroutine in the package. If we want to handle any URL with this method, then we can use the :Global
attribute.
We also changed the first line of the hello subroutine to receive the parameter within the method. Initially, it looked like my ( $self, $c ) = @_
;. This gets the first two arguments passed to the action by Catalyst, $self
and $c
.
$self
is a MyApp::Controller::Hello
object and is not of much use right now. $c
is the Catalyst context and contains all the information about our application and the current request (and therefore is very useful). Catalyst passes more than just $self
and $c
though, so we want to modify that line to read my ($self, $c, @args)=@_
;. This will allow us to access the rest of the arguments via the @args
array.
Arguments are everything in the URL that Catalyst didn't use when matching an action. As an example, /hello/foo
would invoke the action matching /hello
followed by one argument (if we defined index as :Global
above, it would accept any number of arguments. That is /hello/foo/bar
will match the index
method and consider foo
and bar
as two arguments). These two arguments will be passed to the action and will be available in the @args
variable that you specified.
When you run the Catalyst server, you would see something like the following in the log:
[debug] Loaded Path actions:
.-------------------------------------+---------------------------------.
| Path | Private |
+-------------------------------------+---------------------------------+
| / | /index |
| / | /default |
| /hello | /hello/index |
'-------------------------------------+---------------------------------'
Notice that this table has two columns, Path
and Private. Path
is what you can mention in the URL and Private
is which Controller (hello.pm
) and method (sub index
) it would get mapped to.
Let us revise what just happened:
my ( $self, $c, @args ) = @_; my $word = $args[0] || 'Default word'; $c->stash->{word} = $word;
The first line receives all the arguments from the URL in @args
.
The second line assigns the first argument to a variable called word
(or uses Default Word
if there isn't a first argument). Finally, we put $word
in the stash
as word
, so the [% word %]
statement in our template can access that variable.
The template that will be rendered is determined by the private name of your action. In this case, the action /hello/hello
will be attempted to be rendered with root/hello/hello.tt
. If you haven't set $c->response->body
yet, the default View will be called to render the template. If you have more than one View, you will have to configure a default one by setting the default_view
option in your config file to the name of your View. In this example, it would be "TT".
The View will then proceed to look for a stash variable named template
that you can set to force a particular template to be rendered. If none is found, then it will use the private path of your action (not the public URI that dispatches to your action, but the /hello/everything
path consisting of the Controller and action name) and append .tt
as extension. It will look for this hello/hello.tt
in the default template include path, which is root
.
The stash is a data structure that exists throughout a single Catalyst request. Data you insert into the stash in an action will be available to the View (and other actions). Templates can only access variables that have been explicitly placed here, so it's important to remember to put your useful data in the stash (otherwise it will be gone at the end of the subroutine in the Controller, instead of at the end of the request).
If you're interested in experimenting some more with this setup, here are some things to try. Edit the hello.tt
template to change the look of the page. You shouldn't even need to restart your development server for changes to take effect. After that, add another page to your application by creating another subroutine in Hello.pm
and another template. Editing Hello.pm
will require a restart, but you can automatically restart the server when necessary by running the server like perl script/myapp_server.pl -r -d
. The -r
will cause the server to restart when appropriate, and the -d
will show debugging information, even if you've turned it off inside your application. This is especially useful as you will want to deactivate the hardcoded -Debug
option in your MyApp.pm
, once you are ready to deploy your application.
At this point, you may be wondering why the hello.tt
template showed up without your code ever calling any methods in the Template Toolkit View. This is because the end
action in the default Root.pm
uses an ActionClass called RenderView. The end
action in Root is called at the end of every request, and the RenderView ActionClass
will automatically forward to the default View. If you'd rather be explicit about invoking the View, then you can add a line like:
$c->forward('View::TT'),
at the end of your action (RenderView
will stay out of the way). You can also set a default_view
config option for a general default View, or set a stash variable named current_view
to the name of the View you want to forward to for the current request only. If you have more than one View you should always explicitly forward or set any of these variables, as currently there's no rigorous definition of "default". You can make every action in a single Controller forward to the same View by overriding the end
action in that Controller as follows:
sub end : Private { my ($self, $c) = @_; $c->forward('View::TT'), }
If you override end
like this, the default end
in Root.pm
will not be called. (Only one end
action is executed per request—the one that's "closest" to the action that started the request cycle.)
end
actions (as well as the similar begin
and auto
actions) will be covered in more detail throughout the book.
3.141.38.121