add files
|
1 |
package Mojolicious::Renderer; |
2 |
use Mojo::Base -base; |
|
3 | ||
4 |
use File::Spec::Functions 'catfile'; |
|
5 |
use Mojo::Cache; |
|
6 |
use Mojo::JSON; |
|
7 |
use Mojo::Home; |
|
8 |
use Mojo::Loader; |
|
9 |
use Mojo::Util qw(decamelize encode slurp); |
|
10 | ||
11 |
has cache => sub { Mojo::Cache->new }; |
|
12 |
has classes => sub { ['main'] }; |
|
13 |
has default_format => 'html'; |
|
14 |
has 'default_handler'; |
|
15 |
has encoding => 'UTF-8'; |
|
16 |
has handlers => sub { |
|
17 |
{ |
|
18 |
data => sub { ${$_[2]} = $_[3]{data} }, |
|
19 |
text => sub { ${$_[2]} = $_[3]{text} }, |
|
20 |
json => sub { ${$_[2]} = Mojo::JSON->new->encode($_[3]{json}) } |
|
21 |
}; |
|
22 |
}; |
|
23 |
has helpers => sub { {} }; |
|
24 |
has paths => sub { [] }; |
|
25 | ||
26 |
# Bundled templates |
|
27 |
my $HOME = Mojo::Home->new; |
|
28 |
$HOME->parse( |
|
29 |
$HOME->parse($HOME->mojo_lib_dir)->rel_dir('Mojolicious/templates')); |
|
30 |
my %TEMPLATES = map { $_ => slurp $HOME->rel_file($_) } @{$HOME->list_files}; |
|
31 | ||
32 |
sub add_handler { shift->_add(handlers => @_) } |
|
33 |
sub add_helper { shift->_add(helpers => @_) } |
|
34 | ||
35 |
sub get_data_template { |
|
36 |
my ($self, $options) = @_; |
|
37 | ||
38 |
# Index DATA templates |
|
39 |
my $loader = Mojo::Loader->new; |
|
40 |
unless ($self->{index}) { |
|
41 |
my $index = $self->{index} = {}; |
|
42 |
for my $class (reverse @{$self->classes}) { |
|
43 |
$index->{$_} = $class for keys %{$loader->data($class)}; |
|
44 |
} |
|
45 |
} |
|
46 | ||
47 |
# Find template |
|
48 |
my $template = $self->template_name($options); |
|
49 |
return $loader->data($self->{index}{$template}, $template); |
|
50 |
} |
|
51 | ||
52 |
sub render { |
|
53 |
my ($self, $c, $args) = @_; |
|
54 |
$args ||= {}; |
|
55 | ||
56 |
# Localize "extends" and "layout" to allow argument overrides |
|
57 |
my $stash = $c->stash; |
|
58 |
local $stash->{layout} = $stash->{layout} if exists $stash->{layout}; |
|
59 |
local $stash->{extends} = $stash->{extends} if exists $stash->{extends}; |
|
60 |
delete @{$stash}{qw(layout extends)} if my $partial = $args->{partial}; |
|
61 | ||
62 |
# Merge stash and arguments |
|
63 |
@{$stash}{keys %$args} = values %$args; |
|
64 | ||
65 |
my $options = { |
|
66 |
encoding => $self->encoding, |
|
67 |
handler => $stash->{handler}, |
|
68 |
template => delete $stash->{template} |
|
69 |
}; |
|
70 |
my $inline = $options->{inline} = delete $stash->{inline}; |
|
71 |
$options->{handler} //= $self->default_handler if defined $inline; |
|
72 |
$options->{format} = $stash->{format} || $self->default_format; |
|
73 | ||
74 |
# Data |
|
75 |
my $output; |
|
76 |
if (defined(my $data = delete $stash->{data})) { |
|
77 |
$self->handlers->{data}->($self, $c, \$output, {data => $data}); |
|
78 |
return $output, $options->{format}; |
|
79 |
} |
|
80 | ||
81 |
# JSON |
|
82 |
elsif (my $json = delete $stash->{json}) { |
|
83 |
$self->handlers->{json}->($self, $c, \$output, {json => $json}); |
|
84 |
return $output, 'json'; |
|
85 |
} |
|
86 | ||
87 |
# Text |
|
88 |
elsif (defined(my $text = delete $stash->{text})) { |
|
89 |
$self->handlers->{text}->($self, $c, \$output, {text => $text}); |
|
90 |
} |
|
91 | ||
92 |
# Template or templateless handler |
|
93 |
else { |
|
94 |
$options->{template} ||= $self->_generate_template($c); |
|
95 |
return unless $self->_render_template($c, \$output, $options); |
|
96 |
} |
|
97 | ||
98 |
# Extends |
|
99 |
my $content = $stash->{'mojo.content'} ||= {}; |
|
100 |
local $content->{content} = $output if $stash->{extends} || $stash->{layout}; |
|
101 |
while ((my $extends = $self->_extends($stash)) && !defined $inline) { |
|
102 |
$options->{handler} = $stash->{handler}; |
|
103 |
$options->{format} = $stash->{format} || $self->default_format; |
|
104 |
$options->{template} = $extends; |
|
105 |
$self->_render_template($c, \$output, $options); |
|
106 |
$content->{content} = $output |
|
107 |
if $content->{content} !~ /\S/ && $output =~ /\S/; |
|
108 |
} |
|
109 | ||
110 |
# Encoding |
|
111 |
$output = encode $options->{encoding}, $output |
|
112 |
if !$partial && $options->{encoding} && $output; |
|
113 | ||
114 |
return $output, $options->{format}; |
|
115 |
} |
|
116 | ||
117 |
sub template_name { |
|
118 |
my ($self, $options) = @_; |
|
119 |
return undef unless my $template = $options->{template}; |
|
120 |
return undef unless my $format = $options->{format}; |
|
121 |
my $handler = $options->{handler}; |
|
122 |
return defined $handler ? "$template.$format.$handler" : "$template.$format"; |
|
123 |
} |
|
124 | ||
125 |
sub template_path { |
|
126 |
my $self = shift; |
|
127 | ||
128 |
# Nameless |
|
129 |
return undef unless my $name = $self->template_name(shift); |
|
130 | ||
131 |
# Search all paths |
|
132 |
for my $path (@{$self->paths}) { |
|
133 |
my $file = catfile($path, split '/', $name); |
|
134 |
return $file if -r $file; |
|
135 |
} |
|
136 | ||
137 |
# Fall back to first path |
|
138 |
return catfile($self->paths->[0], split '/', $name); |
|
139 |
} |
|
140 | ||
141 |
sub _add { |
|
142 |
my ($self, $attr, $name, $cb) = @_; |
|
143 |
$self->$attr->{$name} = $cb; |
|
144 |
return $self; |
|
145 |
} |
|
146 | ||
147 |
sub _bundled { $TEMPLATES{"@{[pop]}.html.ep"} } |
|
148 | ||
149 |
sub _detect_handler { |
|
150 |
my ($self, $options) = @_; |
|
151 | ||
152 |
# Templates |
|
153 |
return undef unless my $file = $self->template_name($options); |
|
154 |
unless ($self->{templates}) { |
|
155 |
s/\.(\w+)$// and $self->{templates}{$_} ||= $1 |
|
156 |
for map { sort @{Mojo::Home->new($_)->list_files} } @{$self->paths}; |
|
157 |
} |
|
158 |
return $self->{templates}{$file} if exists $self->{templates}{$file}; |
|
159 | ||
160 |
# DATA templates |
|
161 |
unless ($self->{data}) { |
|
162 |
my $loader = Mojo::Loader->new; |
|
163 |
my @templates = map { sort keys %{$loader->data($_)} } @{$self->classes}; |
|
164 |
s/\.(\w+)$// and $self->{data}{$_} ||= $1 for @templates; |
|
165 |
} |
|
166 |
return $self->{data}{$file} if exists $self->{data}{$file}; |
|
167 | ||
168 |
# Nothing |
|
169 |
return undef; |
|
170 |
} |
|
171 | ||
172 |
sub _extends { |
|
173 |
my ($self, $stash) = @_; |
|
174 |
my $layout = delete $stash->{layout}; |
|
175 |
$stash->{extends} ||= join('/', 'layouts', $layout) if $layout; |
|
176 |
return delete $stash->{extends}; |
|
177 |
} |
|
178 | ||
179 |
sub _generate_template { |
|
180 |
my ($self, $c) = @_; |
|
181 | ||
182 |
# Normal default template |
|
183 |
my $stash = $c->stash; |
|
184 |
my $controller = $stash->{controller}; |
|
185 |
my $action = $stash->{action}; |
|
186 |
return join '/', split(/-/, decamelize($controller)), $action |
|
187 |
if $controller && $action; |
|
188 | ||
189 |
# Try the route name if we don't have controller and action |
|
190 |
return undef unless my $endpoint = $c->match->endpoint; |
|
191 |
return $endpoint->name; |
|
192 |
} |
|
193 | ||
194 |
sub _render_template { |
|
195 |
my ($self, $c, $output, $options) = @_; |
|
196 | ||
197 |
# Find handler and render |
|
198 |
my $handler = $options->{handler} || $self->_detect_handler($options); |
|
199 |
$options->{handler} = $handler ||= $self->default_handler; |
|
200 |
if (my $renderer = $self->handlers->{$handler}) { |
|
201 |
return 1 if $renderer->($self, $c, $output, $options); |
|
202 |
} |
|
203 | ||
204 |
# No handler |
|
205 |
else { $c->app->log->error(qq{No handler for "$handler" available.}) } |
|
206 |
return undef; |
|
207 |
} |
|
208 | ||
209 |
1; |
|
210 | ||
211 |
=encoding utf8 |
|
212 | ||
213 |
=head1 NAME |
|
214 | ||
215 |
Mojolicious::Renderer - Generate dynamic content |
|
216 | ||
217 |
=head1 SYNOPSIS |
|
218 | ||
219 |
use Mojolicious::Renderer; |
|
220 | ||
221 |
my $renderer = Mojolicious::Renderer->new; |
|
222 |
push @{$renderer->classes}, 'MyApp::Foo'; |
|
223 |
push @{renderer->paths}, '/home/sri/templates'; |
|
224 | ||
225 |
=head1 DESCRIPTION |
|
226 | ||
227 |
L<Mojolicious::Renderer> is the standard L<Mojolicious> renderer. |
|
228 | ||
229 |
See L<Mojolicious::Guides::Rendering> for more. |
|
230 | ||
231 |
=head1 ATTRIBUTES |
|
232 | ||
233 |
L<Mojolicious::Renderer> implements the following attributes. |
|
234 | ||
235 |
=head2 cache |
|
236 | ||
237 |
my $cache = $renderer->cache; |
|
238 |
$renderer = $renderer->cache(Mojo::Cache->new); |
|
239 | ||
240 |
Renderer cache, defaults to a L<Mojo::Cache> object. |
|
241 | ||
242 |
=head2 classes |
|
243 | ||
244 |
my $classes = $renderer->classes; |
|
245 |
$renderer = $renderer->classes(['main']); |
|
246 | ||
247 |
Classes to use for finding templates in C<DATA> sections, first one has the |
|
248 |
highest precedence, defaults to C<main>. |
|
249 | ||
250 |
# Add another class with templates in DATA section |
|
251 |
push @{$renderer->classes}, 'Mojolicious::Plugin::Fun'; |
|
252 | ||
253 |
=head2 default_format |
|
254 | ||
255 |
my $default = $renderer->default_format; |
|
256 |
$renderer = $renderer->default_format('html'); |
|
257 | ||
258 |
The default format to render if C<format> is not set in the stash. |
|
259 | ||
260 |
=head2 default_handler |
|
261 | ||
262 |
my $default = $renderer->default_handler; |
|
263 |
$renderer = $renderer->default_handler('ep'); |
|
264 | ||
265 |
The default template handler to use for rendering in cases where auto |
|
266 |
detection doesn't work, like for C<inline> templates. |
|
267 | ||
268 |
=head2 encoding |
|
269 | ||
270 |
my $encoding = $renderer->encoding; |
|
271 |
$renderer = $renderer->encoding('koi8-r'); |
|
272 | ||
273 |
Will encode generated content if set, defaults to C<UTF-8>. Note that many |
|
274 |
renderers such as L<Mojolicious::Plugin::EPRenderer> also use this value to |
|
275 |
determine if template files should be decoded before processing. |
|
276 | ||
277 |
=head2 handlers |
|
278 | ||
279 |
my $handlers = $renderer->handlers; |
|
280 |
$renderer = $renderer->handlers({epl => sub {...}}); |
|
281 | ||
282 |
Registered handlers, by default only C<data>, C<text> and C<json> are already |
|
283 |
defined. |
|
284 | ||
285 |
=head2 helpers |
|
286 | ||
287 |
my $helpers = $renderer->helpers; |
|
288 |
$renderer = $renderer->helpers({url_for => sub {...}}); |
|
289 | ||
290 |
Registered helpers. |
|
291 | ||
292 |
=head2 paths |
|
293 | ||
294 |
my $paths = $renderer->paths; |
|
295 |
$renderer = $renderer->paths(['/home/sri/templates']); |
|
296 | ||
297 |
Directories to look for templates in, first one has the highest precedence. |
|
298 | ||
299 |
# Add another "templates" directory |
|
300 |
push @{$renderer->paths}, '/home/sri/templates'; |
|
301 | ||
302 |
=head1 METHODS |
|
303 | ||
304 |
L<Mojolicious::Renderer> inherits all methods from L<Mojo::Base> and |
|
305 |
implements the following new ones. |
|
306 | ||
307 |
=head2 add_handler |
|
308 | ||
309 |
$renderer = $renderer->add_handler(epl => sub {...}); |
|
310 | ||
311 |
Register a new handler. |
|
312 | ||
313 |
=head2 add_helper |
|
314 | ||
315 |
$renderer = $renderer->add_helper(url_for => sub {...}); |
|
316 | ||
317 |
Register a new helper. |
|
318 | ||
319 |
=head2 get_data_template |
|
320 | ||
321 |
my $template = $renderer->get_data_template({ |
|
322 |
template => 'foo/bar', |
|
323 |
format => 'html', |
|
324 |
handler => 'epl' |
|
325 |
}); |
|
326 | ||
327 |
Get a C<DATA> section template by name, usually used by handlers. |
|
328 | ||
329 |
=head2 render |
|
330 | ||
331 |
my ($output, $format) = $renderer->render(Mojolicious::Controller->new); |
|
332 |
my ($output, $format) = $renderer->render(Mojolicious::Controller->new, { |
|
333 |
template => 'foo/bar', |
|
334 |
foo => 'bar' |
|
335 |
}); |
|
336 | ||
337 |
Render output through one of the renderers. See |
|
338 |
L<Mojolicious::Controller/"render"> for a more user-friendly interface. |
|
339 | ||
340 |
=head2 template_name |
|
341 | ||
342 |
my $template = $renderer->template_name({ |
|
343 |
template => 'foo/bar', |
|
344 |
format => 'html', |
|
345 |
handler => 'epl' |
|
346 |
}); |
|
347 | ||
348 |
Builds a template name based on an options hash reference with C<template>, |
|
349 |
C<format> and C<handler>, usually used by handlers. |
|
350 | ||
351 |
=head2 template_path |
|
352 | ||
353 |
my $path = $renderer->template_path({ |
|
354 |
template => 'foo/bar', |
|
355 |
format => 'html', |
|
356 |
handler => 'epl' |
|
357 |
}); |
|
358 | ||
359 |
Builds a full template path based on an options hash reference with |
|
360 |
C<template>, C<format> and C<handler>, usually used by handlers. |
|
361 | ||
362 |
=head1 SEE ALSO |
|
363 | ||
364 |
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>. |
|
365 | ||
366 |
=cut |