1package Dancer::Template::Abstract; 2our $AUTHORITY = 'cpan:SUKRIA'; 3#ABSTRACT: abstract class for Dancer's template engines 4$Dancer::Template::Abstract::VERSION = '1.3513'; 5use strict; 6use warnings; 7use Carp; 8 9use Dancer::Logger; 10use Dancer::Factory::Hook; 11use Dancer::FileUtils 'path'; 12use Dancer::Exception qw(:all); 13 14use base 'Dancer::Engine'; 15 16Dancer::Factory::Hook->instance->install_hooks( 17 qw/before_template_render after_template_render before_layout_render after_layout_render/ 18); 19 20# overloads this method to implement the rendering 21# args: $self, $template, $tokens 22# return: a string of $template's content processed with $tokens 23sub render { confess "render not implemented" } 24 25sub default_tmpl_ext { "tt" } 26 27# Work out the template names to look for; this will be the view name passed 28# as-is, and also the view name with the default template extension appended, if 29# it did not already end in that. 30sub _template_name { 31 my ( $self, $view ) = @_; 32 my @views = ( $view ); 33 my $def_tmpl_ext = $self->config->{extension} || $self->default_tmpl_ext(); 34 push @views, $view .= ".$def_tmpl_ext" if $view !~ /\.\Q$def_tmpl_ext\E$/; 35 return @views; 36} 37 38sub view { 39 my ($self, $view) = @_; 40 41 my $views_dir = Dancer::App->current->setting('views'); 42 43 for my $template ($self->_template_name($view)) { 44 my $view_path = path($views_dir, $template); 45 return $view_path if -f $view_path; 46 } 47 48 # No matching view path was found 49 return; 50} 51 52sub layout { 53 my ($self, $layout, $tokens, $content) = @_; 54 55 my $layouts_dir = path(Dancer::App->current->setting('views'), 'layouts'); 56 my $layout_path; 57 for my $layout_name ($self->_template_name($layout)) { 58 $layout_path = path($layouts_dir, $layout_name); 59 last if -e $layout_path; 60 } 61 62 my $full_content; 63 if (-e $layout_path) { 64 $full_content = Dancer::Template->engine->render( 65 $layout_path, {%$tokens, content => $content}); 66 } else { 67 $full_content = $content; 68 Dancer::Logger::error("Defined layout ($layout) was not found!"); 69 } 70 $full_content; 71} 72 73sub apply_renderer { 74 my ($self, $view, $tokens) = @_; 75 76 ($tokens, undef) = _prepare_tokens_options($tokens); 77 78 $view = $self->view($view); 79 80 Dancer::Factory::Hook->execute_hooks('before_template_render', $tokens); 81 82 my $content; 83 try { 84 $content = $self->render($view, $tokens); 85 } continuation { 86 my ($continuation) = @_; 87 # If we have a Route continuation, run the after hook, then 88 # propagate the continuation 89 Dancer::Factory::Hook->execute_hooks('after_template_render', \$content); 90 $continuation->rethrow(); 91 }; 92 93 Dancer::Factory::Hook->execute_hooks('after_template_render', \$content); 94 95 # make sure to avoid ( undef ) in list context return 96 defined $content 97 and return $content; 98 return; 99} 100 101sub apply_layout { 102 my ($self, $content, $tokens, $options) = @_; 103 104 ($tokens, $options) = _prepare_tokens_options($tokens, $options); 105 106 # If 'layout' was given in the options hashref, use it if it's a true value, 107 # or don't use a layout if it was false (0, or undef); if layout wasn't 108 # given in the options hashref, go with whatever the current layout setting 109 # is. 110 my $layout = 111 exists $options->{layout} 112 ? ($options->{layout} ? $options->{layout} : undef) 113 : Dancer::App->current->setting('layout'); 114 115 defined $content or return; 116 117 defined $layout or return $content; 118 119 Dancer::Factory::Hook->execute_hooks('before_layout_render', $tokens, \$content); 120 121 my $full_content; 122 123 try { 124 $full_content = $self->layout($layout, $tokens, $content); 125 } continuation { 126 my ($continuation) = @_; 127 # If we have a Route continuation, run the after hook, then 128 # propagate the continuation 129 Dancer::Factory::Hook->execute_hooks('after_layout_render', \$full_content); 130 $continuation->rethrow(); 131 }; 132 133 Dancer::Factory::Hook->execute_hooks('after_layout_render', \$full_content); 134 135 # make sure to avoid ( undef ) in list context return 136 defined $full_content 137 and return $full_content; 138 return; 139} 140 141sub _prepare_tokens_options { 142 my ($tokens, $options) = @_; 143 144 $options ||= {}; 145 146 # these are the default tokens provided for template processing 147 $tokens ||= {}; 148 $tokens->{perl_version} = $]; 149 $tokens->{dancer_version} = $Dancer::VERSION; 150 $tokens->{settings} = Dancer::Config->settings; 151 152 # If we're processing a request, also add the request object, params and 153 # vars as tokens: 154 if (my $request = Dancer::SharedData->request) { 155 $tokens->{request} = $request; 156 $tokens->{params} = $request->params; 157 $tokens->{vars} = Dancer::SharedData->vars; 158 } 159 160 Dancer::App->current->setting('session') 161 and $tokens->{session} = Dancer::Session->get; 162 163 return ($tokens, $options); 164} 165 166sub template { 167 my ($class, $view, $tokens, $options) = @_; 168 my ($content, $full_content); 169 170 my $engine = Dancer::Template->engine; 171 172 # it's important that $tokens is not undef, so that things added to it via 173 # a before_template in apply_renderer survive to the apply_layout. GH#354 174 $tokens ||= {}; 175 $options ||= {}; 176 177 if ($view) { 178 # check if the requested view exists 179 my $view_path = $engine->view($view) || ''; 180 if ($engine->view_exists($view_path)) { 181 $content = $engine->apply_renderer($view, $tokens); 182 } else { 183 Dancer::Logger::error( 184 "Supplied view ($view) not found - $view_path does not exist" 185 ); 186 return Dancer::Error->new( 187 code => 500, 188 message => 'view not found', 189 )->render(); 190 } 191 } else { 192 $content = delete $options->{content}; 193 } 194 195 defined $content and $full_content = 196 $engine->apply_layout($content, $tokens, $options); 197 198 defined $full_content 199 and return $full_content; 200 201 Dancer::Error->new( 202 code => 404, 203 message => "Page not found", 204 )->render(); 205} 206 207sub view_exists { return defined $_[1] && -f $_[1] } 208 2091; 210 211__END__ 212 213=pod 214 215=encoding UTF-8 216 217=head1 NAME 218 219Dancer::Template::Abstract - abstract class for Dancer's template engines 220 221=head1 VERSION 222 223version 1.3513 224 225=head1 DESCRIPTION 226 227This class is provided as a base class for each template engine. Any template 228engine must inherit from it and provide a set of methods described below. 229 230=head1 TEMPLATE TOKENS 231 232By default Dancer injects some tokens (or variables) to templates. The 233available tokens are: 234 235=over 4 236 237=item C<perl_version> 238 239The current running Perl version. 240 241=item C<dancer_version> 242 243The current running Dancer version. 244 245=item C<settings> 246 247Hash to access current application settings. 248 249=item C<request> 250 251Hash to access your current request. 252 253=item C<params> 254 255Hash to access your request parameters. 256 257=item C<vars> 258 259Hash to access your defined variables (using C<vars>). 260 261=item C<session> 262 263Hash to access your session (if you have session enabled) 264 265=back 266 267=head1 INTERFACE 268 269=over 4 270 271=item B<init()> 272 273The template engine can overload this method if some initialization stuff has to 274be done before the template engine is used. 275 276The base class provides a plain init() method that only returns true. 277 278=item B<default_tmpl_ext()> 279 280Template class that inherits this class should override this method to return a default template 281extension, example: for Template::Toolkit it returns "tt" and for HTML::Mason it returns "mason". 282So when you call C<template 'index';> in your dispatch code, Dancer will look for a file 'index.tt' 283or 'index.mason' based on the template you use. 284 285Note 1: when returning the extension string, please do not add a dot in front of the extension 286as Dancer will do that. 287 288Note 2: for backwards compatibility abstract class returns "tt" instead of throwing 289an exception 'method not implemented'. 290 291User would be able to change the default extension using the 292C<<extension>> configuration variable on the template 293configuration. For example, for the default (C<Simple>) engine: 294 295 template: "simple" 296 engines: 297 simple: 298 extension: 'tmpl' 299 300=item B<view($view)> 301 302The default behavior of this method is to return the path of the given view, 303appending the default template extension (either the value of the C<extension> 304setting in the configuration, or the value returned by C<default_tmpl_ext>) if 305it is not present in the view name given and no layout template with that exact 306name existed. (In other words, given a layout name C<main>, if C<main> exists 307in the layouts dir, it will be used; if not, C<main.tmpl> (where C<tmpl> is the 308value of the C<extension> setting, or the value returned by C<default_tmpl_ext>) 309will be looked for.) 310 311=item B<view_exists($view_path)> 312 313By default, Dancer::Template::Abstract checks to see if it can find the 314view file calling C<view_exists($path_to_file)>. If not, it will 315generate a nice error message for the user. 316 317If you are using extending Dancer::Template::Abstract to use a template 318system with multiple document roots (like L<Text::XSlate> or 319L<Template>), you can override this method to always return true, and 320therefore skip this check. 321 322=item B<layout($layout, $tokens, $content)> 323 324The default behavior of this method is to merge a content with a layout. The 325layout file is looked for with similar logic as per C<view> - an exact match 326first, then attempting to append the default template extension, if the view 327name given did not already end with it. 328 329=item B<render($self, $template, $tokens)> 330 331This method must be implemented by the template engine. Given a template and a 332set of tokens, it returns a processed string. 333 334If C<$template> is a reference, it's assumed to be a reference to a string that 335contains the template itself. If it's not a reference, it's assumed to be the 336path to template file, as a string. The render method will then have to open it 337and read its content (Dancer::FileUtils::read_file_content does that job). 338 339This method's return value must be a string which is the result of the 340interpolation of C<$tokens> in C<$template>. 341 342If an error occurs, the method should trigger an exception with C<die()>. 343 344Examples : 345 346 # with a template as a file 347 $content = $engine->render('/my/template.txt', { var => 42 }; 348 349 # with a template as a scalar 350 my $template = "here is <% var %>"; 351 $content = $engine->render(\$template, { var => 42 }); 352 353=back 354 355=head1 AUTHOR 356 357This module has been written by Alexis Sukrieh, see L<Dancer> for details. 358 359=head1 AUTHOR 360 361Dancer Core Developers 362 363=head1 COPYRIGHT AND LICENSE 364 365This software is copyright (c) 2010 by Alexis Sukrieh. 366 367This is free software; you can redistribute it and/or modify it under 368the same terms as the Perl 5 programming language system itself. 369 370=cut 371