Adding a View

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 %].

More on Controller methods

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.

Some technical details

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.157.142