NAME
Catalyst::View::EmbeddedPerl::PerRequest - Per-request embedded Perl view for Catalyst
SYNOPSIS
Declare a view in your Catalyst application:
package Example::View::HelloName;
use Moose;
extends 'Catalyst::View::EmbeddedPerl::PerRequest';
has 'name' => (is => 'ro', isa => 'Str', export=>1);
sub title :Helper { 'Hello Title' }
__PACKAGE__->meta->make_immutable;
__DATA__
<title><%= title() %></title>
<p>Hello <%= $name %></p>
You can also use a standalone text file as a template. This text file will be located in the same directory as the view module and will have a 'snake case' version of the view module name with a '.epl' extension.
# In hello_name.epl
<title><%= title() %></title>
<p>Hello <%= $name %></p>
In your Catalyst controller:
sub some_action :Path('/some/path') ($self, $c) {
# Create the view and render it as a 200 OK response
$c->view('HelloName', name => 'Perl Hacker')->http_ok;
}
Produces the following output:
<title>Hello Title</title>
<p>Hello Perl Hacker!</p>
DESCRIPTION
Catalyst::View::EmbeddedPerl::PerRequest
is a per-request view for the Catalyst framework that uses Template::EmbeddedPerl to process templates containing embedded Perl code. It allows for dynamic content generation with embedded Perl expressions within your templates.
Since it uses 'just Perl' for the template language, it is very flexible and powerful (maybe too much so, if you lack self control...) and has the upside that any Perl programmer will be able to understand and work with it without learning a new language. Anyone that's worked with similar systems like Mason or Mojo::Template will with a bit of reorientation be quickly productive, but I suspect most Perl programmers will pick up the syntax quickly even if they've never seen it before. These templates are not my favorite type of approach but they have the massive upside of not something you need to learn to use, so a type of least common denominator. This is great for Perl only shops, for small projects where you don't want a complex stack and of course for demo applications and presentations to other Perl programmers. That's not to say you can't use it for larger projects, but you should be aware of the tradeoffs here. By exposing Perl in the template it's very easy to write code that is hard to maintain and a bit messy and also its easy to stick too much business logic in the template. You should exercise self control and have strong code review conventions.
You should read the (short) documentation for Template::EmbeddedPerl to understand the syntax and features of the template language. These docs will focus on the how to use this view in a Catalyst application.
This view is a subclass of Catalyst::View::BasePerRequest and is designed to be used in a per-request context. You may wish to read the documentation for that class to get a better understanding of what a 'per-request' view is and how it differs from views like Catalyst::View::TT or Catalyst::View::Mason which you may already be familiar with. The topic will be covered here in brief.
PER-REQUEST VIEWS
A per-request view is a view that is created and destroyed for each request. This is in contrast to a 'per-application' view which is created once when the application is started and shared across all requests. This means you can have a view that contains state that is specific to a single request, has access to the $c
context for that request and you can write display logic methods that are specific to that request. You can also gave object attributes that define the view interface. Unlike other commonly used view like Catalyst::View::TT you can't use the stash to pass data to the view, you must pass it as arguments when you create the view. This creates a strongly typed view, which has an explicit interface leading to fewer bug and easier to understand code. It also means you can't use the stash to pass data to the view, which is a common pattern in Catalyst applications but is one I have found to be a source of confusion and bugs in many applications. When using per request views you will write a view model for each template, which you might find strange at first, and of course its extra work, but over time I think you will have a much more sustainable, maintainable application. Review the documentation, test cases and example applications and decide for yourself.
HTML ESCAPING
By default this view does not automatically escape HTML output. If you are using this view to generate HTML output you should be aware of the security implications of this. You either need to explicitly escape all output or use the auto_escape
option to enable automatic escaping. The latter is the recommended approach. Example:
package Example::View::Escape;
use Moose;
extends 'Catalyst::View::EmbeddedPerl::PerRequest';
__PACKAGE__->config(auto_escape => 1);
If auto_escape
is enabled and you want to output raw HTML you can use the raw
helper. For example:
<%= raw $self->html %>
See Template::EmbeddedPerl::SafeString for more information.
If for some reason you don't want to use the auto_escape
feature you can use the html_escape
helper to escape HTML output. For example:
<%= html_escape($self->html) %>
Or use the safe
helper to mark a string as safe. For example:
<%= safe($self->html) %>
The safe
helper is preferred because it won't double escape content that is already escaped whereas the html_escape
helper will.
Unless you are writing text output or javascript that has special encoding needs you really should use the auto_escape
feature.
METHODS
This class provides the following methods for public use:
render
$output = $view->render($c, @args);
Renders the currently created template to a string, passing addional arguments if needed
view
$output = $self->view($view_name, @args);
Renders another view and returns its content. Useful for including the output of one template within another or using another template as a layout or wrapper.
INHERITED METHODS
This class inherits all methods from Catalyst::View::BasePerRequest but the following are public and considered useful:
Content Block Helpers
Used to capture blocks of template. SEE:
"content" in Catalyst::View::BasePerRequest, "content_for" in Catalyst::View::BasePerRequest, "content_append" in Catalyst::View::BasePerRequest, "content_prepend" in Catalyst::View::BasePerRequest, "content_replace" in Catalyst::View::BasePerRequest, "content_around" in Catalyst::View::BasePerRequest.
Response Helpers
Used to set up the response object. SEE: "RESPONSE-HELPERS" in Catalyst::View::BasePerRequest Example:
$c->view('HelloName', name => 'Perl Hacker')->http_ok;
process
$view->process($c, @args);
Renders a view and sets up the response object. Generally this is called from a controller via the forward method and not directly:
$c->forward($view, @args);
See "process" in Catalyst::View::BasePerRequest for more information.
respond
$view->respond($status, $headers, @args);
See "respond" in Catalyst::View::BasePerRequest for more information.
detach
See "detach" in Catalyst::View::BasePerRequest for more information.
METHODS PROXIED FROM Template::EmbeddedPerl
This class proxies the following methods from Template::EmbeddedPerl:
raw safe safe_concat html_escape url_encode
escape_javascript uri_escape trim mtrim
See "HELPER-FUNCTIONS" in Template::EmbeddedPerl (these are available as template helpers and as methods on the view object).
CONFIGURATION
You can configure the view in your Catalyst application by passing options either in your application configuration or when setting up the view.
Example configuration in your Catalyst application.
# In MyApp.pm or myapp.conf
__PACKAGE__->config(
'View::EmbeddedPerl' => {
template_extension => 'epl',
open_tag => '<%',
close_tag => '%>',
expr_marker => '=',
line_start => '%',
auto_flatten_expr => 1,
use_cache => 1,
helpers => {
helper_name => sub { ... },
},
},
);
The following configuration options are passed thru to Template::EmbeddedPerl:
open_tag close_tag expr_marker line_start
template_extension auto_flatten_expr prepend
use_cache auto_escape
HELPERS
You can define custom helper functions that are available within your templates. Helpers can be defined in the configuration under the helpers
key.
Example:
__PACKAGE__->config(
'View::EmbeddedPerl' => {
helpers => {
format_date => sub {
my ($self, $c, $date) = @_;
return $date->strftime('%Y-%m-%d');
},
},
},
);
In your template:
<%== format_date($data->{date}) %>
You can also define helpers in your view module by defining a helpers
method that returns a list of helper functions. You may prefer this option if you are creating a single base class for all your views, with shared features.
Example:
sub helpers {
my ($class) = @_;
return (
format_date => sub {
my ($self, $c, $date) = @_;
return $date->strftime('%Y-%m-%d');
},
);
}
Lastly, experimentally you can use the Helper
attribute to define helpers in your view module. This requires MooseX::MethodAttributes.
Example:
package MyApp::View::MyView;
use Moose;
use MooseX::MethodAttributes;
extends 'Catalyst::View::EmbeddedPerl::PerRequest';
sub my_helper :Helper {
my ($self, $c, $arg) = @_;
return "Hello $arg";
}
__PACKAGE__->meta->make_immutable;
Please note that if you override a helper method in a subclass you currently need to also add the Helper attribute to the method in the subclass.
TEMPLATE INHERITANCE
This is an experimental feature that allows you to inherit from other views. When you inherit from a view, the parent's template automatically becomes the base template Example:
package Example::View::Base;
use Moose;
use MooseX::MethodAttributes;
extends 'Catalyst::View::EmbeddedPerl::PerRequest';
sub title :Helper { 'Missing title' }
sub styles {
my ($self, $cb) = @_;
my $styles = $self->content('css') || return '';
return $cb->($styles);
}
__PACKAGE__->meta->make_immutable;
__PACKAGE__->config(
auto_escape => 1,
content_type => 'text/html',
);
__DATA__
<html>
<head>
<title><%= title() %></title>
%= $self->styles(sub {
<style>
%= shift
</style>
% })
</head>
<body>
<%= $content %>
</body>
</html>
And an inheriting view:
package Example::View::Inherit;
use Moose;
use MooseX::MethodAttributes;
extends 'Example::View::Base';
has 'name' => (is => 'ro', isa => 'Str', export=>1);
sub title :Helper { 'Inherited Title' }
__PACKAGE__->meta->make_immutable;
__DATA__
# Style content
% content_for('css', sub {
p { color: red; }
% });
# Main content
<p>hello <%= $name %></p>
When called from a controller like this:
sub inherit :Local {
my ($self, $c) = @_;
return $c->view('Inherit', name=>'joe')->http_ok;
}
Produced output similar to:
<html>
<head>
<title>Inherited Title</title>
<style>
p { color: red; }
</style>
</head>
<body>
<p>hello joe</p>
</body>
</html>
DEFAULT HELPERS
The following default helpers are available in all templates, in addition to helpers that are default in Template::EmbeddedPerl itself (see "HELPER-FUNCTIONS" in Template::EmbeddedPerl for more information):
Note: Just to be clear, you don't have to write a helper for every method you want to call in your template. You always get $self
and $c
in your template so you can call methods on the view object and the context object directly. Personally my choice is to have helpers for things that are in my base view which are shared across all views and then call $self for things that are specific to the view. This makes it easier for people to debug and understand the code IMHO.
view
view($view_name, @args)
Renders another view and returns its content. Useful for including the output of one view inside another.
content
content($name, $content)
Captures a block of content for later use.
content_for
content_for($name, $content)
Captures a block of content for later use, appending to any existing content.
content_append
content_append($name, $content)
Appends content to a previously captured block.
content_prepend
content_prepend($name, $content)
Prepends content to a previously captured block.
content_replace
content_replace($name, $content)
Replaces a previously captured block with new content.
get_styles
get_styles()
Returns all the styles that have been pushed to the 'css' block.
Example
$self->push_style(sub {
p { color: red; }
});
my $styles = $self->get_styles;
Or in a template:
<%= get_styles() %>
%= push_style(sub {
p { color: red; }
%})
Produces output like:
<style>
p { color: red; }
</style>
push_style
push_style($content)
push_style($name, $content)
Pushes content to the 'css' block. If a name is provided, the content will only be pushed if it has not already been pushed with that name. Useful for ensuring that styles are only included once in the output.
get_scripts
get_scripts()
Returns all the scripts that have been pushed to the 'js' block.
Example
$self->push_script(sub {
alert('hello');
});
my $scripts = $self->get_scripts;
Or in a template:
<%= get_scripts() %>
%= push_script(sub {
alert('hello');
%})
Produces output like:
<script>
alert('hello');
</script>
push_script
push_script($content)
push_script($name, $content)
Pushes content to the 'js' block. If a name is provided, the content will only be pushed if it has not already been pushed with that name. Useful for ensuring that scripts are only included once in the output.
attr
attr($attribute, $value)
Returns an HTML attribute string. Example usage:
<a href="<%= attr('href', $url) %>">Link</a>
style
style($style)
Returns a style
attribute string. Example usage:
<div <%= style('color: red;') %>>Content</div>
class
class($class)
Returns a class
attribute string. Example usage:
<div <%= class('important') %>>Content</div>
checked
checked($checked)
Returns a checked
attribute string. Example usage:
<input type="checkbox" <%= checked($checked) %> />
selected
selected($selected)
Returns a selected
attribute string. Example usage:
<select>
<option value="1" <%= selected($selected) %>>One</option>
<option value="2" <%= selected($selected) %>>Two</option>
</select>
disabled
disabled($disabled)
Returns a disabled
attribute string. Example usage:
<input type="text" <%= disabled($disabled) %> />
readonly
readonly($readonly)
Returns a readonly
attribute string. Example usage:
<input type="text" <%= readonly($readonly) %> />
required
required($required)
Returns a required
attribute string. Example usage:
<input type="text" <%= required($required) %> />
href
href(@href_parts)
Returns an href
attribute string. Example usage:
<a <%= href('/path/to/page') %>>Link</a>
src
src(@src_parts)
Returns a src
attribute string. Example usage:
<img <%= src('/path/to/image.jpg') %> />
data
data($data)
Returns a data
attribute string. Example usage:
<div <%= data({'id', '123'}) %>>Content</div>
over
my $result = $self->over($iterable, sub {
my ($item, $info) = @_;
return "<p>Item: $item, Index: " . $info->current . "</p>";
});
In a template:
<%= over($iterable, sub($item, $info) {
<p>Item: <%= $item %>, Index: <%= $info->current %></p>
}) %>
(Remember in the template calling helpers via $self
is your option)
Executes a callback for each item in an iterable, providing metadata about the iteration through a Catalyst::View::EmbeddedPerl::PerRequest::EachInfo object.
Although you can use standard Perl looping constructs in your templates, the over
helper provides the addional metadata about the iteration that can be useful in some situations related to display logic, such as changing the CSS based on even/odd rows or tracking the item number.
Arguments
$iterable
- An iterable source. This can be:An array reference (e.g.,
[1, 2, 3]
).A coderef that returns the next value when called (e.g.,
sub { ... }
).An object with a
next
method and optionallylength
orcount
methods.A scalar value or single item.
$cb
- A callback subroutine. The callback will be called for each item in the iterable and receives the following arguments:$item
- The current item in the iterable.$info
- An instance of Catalyst::View::EmbeddedPerl::PerRequest::EachInfo containing metadata about the current iteration.
Returns
A concatenated string of the results returned by the callback for each item.
Examples
Iterating over an array reference
my $array = [qw(foo bar baz)];
my $result = $self->over($array, sub {
my ($item, $info) = @_;
return "<p>Item: $item, Index: " . $info->current . "</p>";
});
# Output:
# <p>Item: foo, Index: 0</p>
# <p>Item: bar, Index: 1</p>
# <p>Item: baz, Index: 2</p>
Using a coderef as an iterator
my $iterator = sub {
state $count = 0;
return $count < 5 ? ++$count : undef;
};
my $result = $self->over($iterator, sub {
my ($item, $info) = @_;
return "<p>Item: $item, Is Even: " . $info->is_even . "</p>";
});
# Output:
# <p>Item: 1, Is Even: 0</p>
# <p>Item: 2, Is Even: 1</p>
# <p>Item: 3, Is Even: 0</p>
# <p>Item: 4, Is Even: 1</p>
# <p>Item: 5, Is Even: 0</p>
Iterating over an object with next
and length
package MyIterator;
use Moo;
has items => (is => 'rw', default => sub { [1, 2, 3] });
sub next { shift @{ shift->items } }
sub length { scalar @{ shift->items } }
my $obj = MyIterator->new(items => [qw(alpha beta gamma)]);
my $result = $self->over($obj, sub {
my ($item, $info) = @_;
return "<p>Item: $item, Total: " . $info->total . "</p>";
});
# Output:
# <p>Item: alpha, Total: 3</p>
# <p>Item: beta, Total: 3</p>
# <p>Item: gamma, Total: 3</p>
Passing a scalar value
my $result = $self->over("single value", sub {
my ($item, $info) = @_;
return "<p>Item: $item, Is First: " . $info->is_first . "</p>";
});
# Output:
# <p>Item: single value, Is First: 1</p>
Iterating over multiple arguments
my $result = $self->over(qw(apple banana cherry), sub {
my ($item, $info) = @_;
return "<p>Item: $item, Is Last: " . $info->is_last . "</p>";
});
# Output:
# <p>Item: apple, Is Last: 0</p>
# <p>Item: banana, Is Last: 0</p>
# <p>Item: cherry, Is Last: 1</p>
TEMPLATE LOCATIONS
Templates are searched in the following order:
- 1.
__DATA__
section of the view module. -
If your view module has a
__DATA__
section, the template will be read from there. - 2. File system based on the view's class name.
-
If not found in
__DATA__
, the template file is looked up in the file system, following the view's class name path. The file will be a 'snake case' version of the view module name with a '.epl' extension. - 3. default_template method
-
If no template is found, the
default_template
method is called to get a default template. This method should return a string containing the template.The default template is:
<%= $content %>
Which is handy for when you are making a base view that others will inherit from. But you can override this method in your view module to provide a different default.
COOKBOOK
Some ideas about how to use this view well
Avoid complex logic in the view
Instead of putting complex logic in the view, you can define a method on the view which accepts a callback to render the content. This way you can keep the logic in the controller or model where it belongs.
package MyApp::View::MyView;
use Moose
extends 'Catalyst::View::EmbeddedPerl::PerRequest';
has 'person' => (is => 'ro', required => 1);
sub person_data {
my ($self, $content_cb) = @_;
my $content = $content_cb->($self->person->name, $self->person->age);
return "....@{[ $self->trim($content_cb->()) ]}....";
}
__PACKAGE__->meta->make_immutable;
In your template:
%# Person info
<%= $self->person_data(sub($name, $age) {
<p>Name: <%= $name %></p>
<p>Age: <%= $age %></p>
}) %>
Use a base view class
If you have a lot of views that share common features, you can create a base view class that contains those features. This way you can avoid repeating yourself and keep your code DRY.
package MyApp::View;
use Moose;
extends 'Catalyst::View::EmbeddedPerl::PerRequest';
sub helpers {
my ($class) = @_;
return (
format_date => sub {
my ($self, $c, $date) = @_;
return $date->strftime('%Y-%m-%d');
},
);
}
# Other shared view features such as methods, attributes, etc.
__PACKAGE__->meta->make_immutable;
__PACKAGE__->config(
prepend => 'use v5.40',
content_type=>'text/html; charset=UTF-8'
);
In your view modules:
package MyApp::View::MyView;
use Moose;
extends 'MyApp::View';
__PACKAGE__->meta->make_immutable;
Using 'tags' for HTML snippets in helpers
Sometimes you want a helper that can construct safe HTML:
sub hello_name :Helper ($self, $name) {
my $t = $self->tags;
return $t->div({class=>"container"}, sub {
return $t->h1($self->text("hello ${name}!")),
$t->hr;
}),
}
Used in a template like:
%= hello_name('John')
Results in:
<div class='container'>Hello John!<div>
See Valiant::HTML::Util::TagBuilder whose methods are proxied by this class.
SEE ALSO
-
The Catalyst web framework.
Catalyst::View::BasePerRequest
The base class for per-request views in Catalyst.
-
Module used for processing embedded Perl templates.
-
A postmodern object system for Perl 5.
AUTHOR
John Napiorkowski <jjnapiork@cpan.org>
LICENSE
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.