Skip to content

Commit

Permalink
added before_render hook
Browse files Browse the repository at this point in the history
  • Loading branch information
kraih committed Jan 29, 2014
1 parent 8c1e908 commit b26a795
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 48 deletions.
2 changes: 2 additions & 0 deletions Changes
@@ -1,5 +1,7 @@

4.72 2014-01-29
- Added template_for and template_handler methods to Mojolicious::Renderer.
- Added before_render hook.
- Fixed bug in Mojo::Transaction::WebSocket that prevented decompression
errors from being handled gracefully.

Expand Down
14 changes: 14 additions & 0 deletions lib/Mojolicious.pm
Expand Up @@ -312,6 +312,20 @@ differently. (Passed a callback leading to the next hook, the current
controller object, the action callback and a flag indicating if this action is
an endpoint)
=head2 before_render
Emitted before content is generated by the renderer. Note that this hook can
trigger out of order due to its dynamic nature, and with embedded applications
will only work for the application that is rendering.
$app->hook(before_render => sub {
my ($c, $args) = @_;
...
});
Mostly used for pre-processing arguments passed to the renderer. (Passed the
current controller object and the render arguments)
=head2 after_render
Emitted after content has been generated by the renderer that is not partial.
Expand Down
17 changes: 10 additions & 7 deletions lib/Mojolicious/Controller.pm
Expand Up @@ -152,10 +152,11 @@ sub render {
# Template may be first argument
my ($template, $args) = (@_ % 2 ? shift : undef, {@_});
$args->{template} = $template if $template;
my $maybe = delete $args->{'mojo.maybe'};
my $app = $self->app;
my $plugins = $app->plugins->emit_hook(before_render => $self, $args);
my $maybe = delete $args->{'mojo.maybe'};

# Render
my $app = $self->app;
my ($output, $format) = $app->renderer->render($self, $args);
return defined $output ? Mojo::ByteStream->new($output) : undef
if $args->{partial};
Expand All @@ -164,7 +165,7 @@ sub render {
return $maybe ? undef : !$self->render_not_found unless defined $output;

# Prepare response
$app->plugins->emit_hook(after_render => $self, \$output, $format);
$plugins->emit_hook(after_render => $self, \$output, $format);
my $headers = $self->res->body($output)->headers;
$headers->content_type($app->types->type($format) || 'text/plain')
unless $headers->content_type;
Expand Down Expand Up @@ -687,10 +688,12 @@ Prepare a C<302> redirect response, takes the same arguments as L</"url_for">.
my $bool = $c->render('foo/index');
my $output = $c->render('foo/index', partial => 1);
Render content using L<Mojolicious::Renderer/"render"> and emit hook
L<Mojolicious/"after_render"> unless the result is C<partial>. If no template
is provided a default one based on controller and action or route name will be
generated, all additional values get merged into the L</"stash">.
Render content using L<Mojolicious::Renderer/"render"> and emit hooks
L<Mojolicious/"before_render"> as well as L<Mojolicious/"after_render"> if the
result is not C<partial>. If no template is provided a default one based on
controller and action or route name will be generated with
L<Mojolicious::Renderer/"template_for">, all additional values get merged into
the L</"stash">.
=head2 render_exception
Expand Down
96 changes: 55 additions & 41 deletions lib/Mojolicious/Renderer.pm
Expand Up @@ -91,7 +91,7 @@ sub render {

# Template or templateless handler
else {
$options->{template} ||= $self->_generate_template($c);
$options->{template} ||= $self->template_for($c);
return unless $self->_render_template($c, \$output, $options);
}

Expand All @@ -114,6 +114,43 @@ sub render {
return $output, $options->{format};
}

sub template_for {
my ($self, $c) = @_;

# Normal default template
my $stash = $c->stash;
my ($controller, $action) = @$stash{qw(controller action)};
return join '/', split(/-/, decamelize($controller)), $action
if $controller && $action;

# Try the route name if we don't have controller and action
return undef unless my $endpoint = $c->match->endpoint;
return $endpoint->name;
}

sub template_handler {
my ($self, $options) = @_;

# Templates
return undef unless my $file = $self->template_name($options);
unless ($self->{templates}) {
s/\.(\w+)$// and $self->{templates}{$_} ||= $1
for map { sort @{Mojo::Home->new($_)->list_files} } @{$self->paths};
}
return $self->{templates}{$file} if exists $self->{templates}{$file};

# DATA templates
unless ($self->{data}) {
my $loader = Mojo::Loader->new;
my @templates = map { sort keys %{$loader->data($_)} } @{$self->classes};
s/\.(\w+)$// and $self->{data}{$_} ||= $1 for @templates;
}
return $self->{data}{$file} if exists $self->{data}{$file};

# Default
return $self->default_handler;
}

sub template_name {
my ($self, $options) = @_;
return undef unless my $template = $options->{template};
Expand Down Expand Up @@ -146,57 +183,18 @@ sub _add {

sub _bundled { $TEMPLATES{"@{[pop]}.html.ep"} }

sub _detect_handler {
my ($self, $options) = @_;

# Templates
return undef unless my $file = $self->template_name($options);
unless ($self->{templates}) {
s/\.(\w+)$// and $self->{templates}{$_} ||= $1
for map { sort @{Mojo::Home->new($_)->list_files} } @{$self->paths};
}
return $self->{templates}{$file} if exists $self->{templates}{$file};

# DATA templates
unless ($self->{data}) {
my $loader = Mojo::Loader->new;
my @templates = map { sort keys %{$loader->data($_)} } @{$self->classes};
s/\.(\w+)$// and $self->{data}{$_} ||= $1 for @templates;
}
return $self->{data}{$file} if exists $self->{data}{$file};

# Nothing
return undef;
}

sub _extends {
my ($self, $stash) = @_;
my $layout = delete $stash->{layout};
$stash->{extends} ||= join('/', 'layouts', $layout) if $layout;
return delete $stash->{extends};
}

sub _generate_template {
my ($self, $c) = @_;

# Normal default template
my $stash = $c->stash;
my $controller = $stash->{controller};
my $action = $stash->{action};
return join '/', split(/-/, decamelize($controller)), $action
if $controller && $action;

# Try the route name if we don't have controller and action
return undef unless my $endpoint = $c->match->endpoint;
return $endpoint->name;
}

sub _render_template {
my ($self, $c, $output, $options) = @_;

# Find handler and render
my $handler = $options->{handler} || $self->_detect_handler($options);
$options->{handler} = $handler ||= $self->default_handler;
my $handler = $options->{handler} ||= $self->template_handler($options);
if (my $renderer = $self->handlers->{$handler}) {
return 1 if $renderer->($self, $c, $output, $options);
}
Expand Down Expand Up @@ -337,6 +335,22 @@ Get a C<DATA> section template by name, usually used by handlers.
Render output through one of the renderers. See
L<Mojolicious::Controller/"render"> for a more user-friendly interface.
=head2 template_for
my $name = $renderer->template_for(Mojolicious::Controller->new);
Generate default template name for L<Mojolicious::Controller> object.
=head2 template_handler
my $handler = $renderer->template_handler({
template => 'foo/bar',
format => 'html'
});
Detect handler based on an options hash reference with C<template> and
C<format>.
=head2 template_name
my $template = $renderer->template_name({
Expand Down
2 changes: 2 additions & 0 deletions t/mojolicious/app.t
Expand Up @@ -60,6 +60,8 @@ is $t->app->static->file('hello.txt')->slurp,
"Hello Mojo from a development static file!\n", 'right content';
is $t->app->moniker, 'mojolicious_test', 'right moniker';
is $t->app->secrets->[0], $t->app->moniker, 'secret defaults to moniker';
is $t->app->renderer->template_handler(
{template => 'foo/bar/index', format => 'html'}), 'epl', 'right handler';

# Missing methods and functions (AUTOLOAD)
eval { $t->app->missing };
Expand Down
12 changes: 12 additions & 0 deletions t/mojolicious/exception_lite_app.t
Expand Up @@ -21,6 +21,13 @@ app->log->on(message => sub { shift; $log .= join ':', @_ });

helper dead_helper => sub { die "dead helper!\n" };

# Custom rendering for missing "txt" template
hook before_render => sub {
my ($self, $args) = @_;
$args->{text} = 'Missing template.'
if ($args->{template} // '') eq 'not_found' && $args->{format} eq 'txt';
};

get '/logger' => sub {
my $self = shift;
my $level = $self->param('level');
Expand Down Expand Up @@ -194,6 +201,11 @@ $t->get_ok('/missing_template.json')->status_is(404)
->content_type_is('text/html;charset=UTF-8')
->content_like(qr/Page not found/);

# Missing template with custom rendering
$t->get_ok('/missing_template.txt')->status_is(404)
->content_type_is('text/plain;charset=UTF-8')
->content_is('Missing template.');

# Missing template (failed rendering)
$t->get_ok('/missing_template/too')->status_is(404)
->header_is('X-Not-Found' => 1)->content_type_is('text/html;charset=UTF-8')
Expand Down
4 changes: 4 additions & 0 deletions t/mojolicious/renderer.t
Expand Up @@ -49,6 +49,10 @@ is $r->render($c), undef, 'return undef for unrecognized handler';
like $log, qr/No handler for "not_defined" available\./, 'right message';
$c->app->log->unsubscribe(message => $cb);

# Default template name
$c->stash(controller => 'foo', action => 'bar');
is $c->app->renderer->template_for($c), 'foo/bar', 'right template name';

# Big cookie
$log = '';
$cb = $c->app->log->on(message => sub { $log .= pop });
Expand Down

0 comments on commit b26a795

Please sign in to comment.