source: main/trunk/greenstone2/perllib/cpan/Mojolicious/Guides/Rendering.pod@ 32205

Last change on this file since 32205 was 32205, checked in by ak19, 6 years ago

First set of commits to do with implementing the new 'paged_html' output option of PDFPlugin that uses using xpdftools' new pdftohtml. So far tested only on Linux (64 bit), but things work there so I'm optimistically committing the changes since they work. 2. Committing the pre-built Linux binaries of XPDFtools for both 32 and 64 bit built by the XPDF group. 2. To use the correct bitness variant of xpdftools, setup.bash now exports the BITNESS env var, consulted by gsConvert.pl. 3. All the perl code changes to do with using xpdf tools' pdftohtml to generate paged_html and feed it in the desired form into GS(3): gsConvert.pl, PDFPlugin.pm and its parent ConvertBinaryPFile.pm have been modified to make it all work. xpdftools' pdftohtml generates a folder containing an html file and a screenshot for each page in a PDF (as well as an index.html linking to each page's html). However, we want a single html file that contains each individual 'page' html's content in a div, and need to do some further HTML style, attribute and structure modifications to massage the xpdftool output to what we want for GS. In order to parse and manipulate the HTML 'DOM' to do this, we're using the Mojo::DOM package that Dr Bainbridge found and which he's compiled up. Mojo::DOM is therefore also committed in this revision. Some further changes and some display fixes are required, but need to check with the others about that.

File size: 43.5 KB
Line 
1
2=encoding utf8
3
4=head1 NAME
5
6Mojolicious::Guides::Rendering - Rendering content
7
8=head1 OVERVIEW
9
10This document explains content generation with the L<Mojolicious> renderer.
11
12=head1 CONCEPTS
13
14Essentials every L<Mojolicious> developer should know.
15
16=head2 Renderer
17
18The renderer is a tiny black box turning stash data into actual responses
19utilizing multiple template systems and data encoding modules.
20
21 {text => 'Hello.'} -> 200 OK, text/html, 'Hello.'
22 {json => {x => 3}} -> 200 OK, application/json, '{"x":3}'
23 {text => 'Oops.', status => '410'} -> 410 Gone, text/html, 'Oops.'
24
25Templates can be automatically detected if enough information is provided by
26the developer or routes. Template names are expected to follow the
27C<template.format.handler> scheme, with C<template> defaulting to
28C<controller/action> or the route name, C<format> defaulting to C<html> and
29C<handler> to C<ep>.
30
31 {controller => 'users', action => 'list'} -> 'users/list.html.ep'
32 {template => 'foo', format => 'txt'} -> 'foo.txt.ep'
33 {template => 'foo', handler => 'epl'} -> 'foo.html.epl'
34
35The C<controller> value gets converted from C<CamelCase> to C<snake_case> using
36L<Mojo::Util/"decamelize"> and C<-> characters replaced with C</>.
37
38 {controller => 'My::Users', action => 'add'} -> 'my/users/add.html.ep'
39 {controller => 'my-users', action => 'show'} -> 'my/users/show.html.ep'
40
41All templates should be in the C<templates> directories of the application,
42which can be customized with L<Mojolicious::Renderer/"paths">, or one of the
43the C<DATA> sections from L<Mojolicious::Renderer/"classes">.
44
45 __DATA__
46
47 @@ time.html.ep
48 % use Time::Piece;
49 % my $now = localtime;
50 <!DOCTYPE html>
51 <html>
52 <head><title>Time</title></head>
53 <body>The time is <%= $now->hms %>.</body>
54 </html>
55
56 @@ hello.txt.ep
57 ...
58
59The renderer can be easily extended to support additional template systems with
60plugins, but more about that later.
61
62=head2 Embedded Perl
63
64L<Mojolicious> includes a minimalistic but very powerful template system out of
65the box called Embedded Perl or C<ep> for short. It is based on
66L<Mojo::Template> and allows the embedding of Perl code right into actual
67content using a small set of special tags and line start characters. For all
68templates L<strict>, L<warnings>, L<utf8> and Perl 5.10 L<features|feature> are
69automatically enabled.
70
71 <% Perl code %>
72 <%= Perl expression, replaced with XML escaped result %>
73 <%== Perl expression, replaced with result %>
74 <%# Comment, useful for debugging %>
75 <%% Replaced with "<%", useful for generating templates %>
76 % Perl code line, treated as "<% line =%>" (explained later)
77 %= Perl expression line, treated as "<%= line %>"
78 %== Perl expression line, treated as "<%== line %>"
79 %# Comment line, useful for debugging
80 %% Replaced with "%", useful for generating templates
81
82Tags and lines work pretty much the same, but depending on context one will
83usually look a bit better. Semicolons get automatically appended to all
84expressions.
85
86 <% my $i = 10; %>
87 <ul>
88 <% for my $j (1 .. $i) { %>
89 <li>
90 <%= $j %>
91 </li>
92 <% } %>
93 </ul>
94
95 % my $i = 10;
96 <ul>
97 % for my $j (1 .. $i) {
98 <li>
99 %= $j
100 </li>
101 % }
102 </ul>
103
104Aside from differences in whitespace handling, both examples generate similar
105Perl code, a naive translation could look like this.
106
107 my $output = '';
108 my $i = 10;
109 $output .= '<ul>';
110 for my $j (1 .. $i) {
111 $output .= '<li>';
112 $output .= xml_escape scalar + $j;
113 $output .= '</li>';
114 }
115 $output .= '</ul>';
116 return $output;
117
118An additional equal sign can be used to disable escaping of the characters
119C<E<lt>>, C<E<gt>>, C<&>, C<'> and C<"> in results from Perl expressions, which
120is the default to prevent XSS attacks against your application.
121
122 <%= 'I ♥ Mojolicious!' %>
123 <%== '<p>I ♥ Mojolicious!</p>' %>
124
125Only L<Mojo::ByteStream> objects are excluded from automatic escaping.
126
127 <%= b('<p>I ♥ Mojolicious!</p>') %>
128
129Whitespace characters around tags can be trimmed by adding an additional equal
130sign to the end of a tag.
131
132 <% for (1 .. 3) { %>
133 <%= 'Trim all whitespace characters around this expression' =%>
134 <% } %>
135
136Newline characters can be escaped with a backslash.
137
138 This is <%= 1 + 1 %> a\
139 single line
140
141And a backslash in front of a newline character can be escaped with another
142backslash.
143
144 This will <%= 1 + 1 %> result\\
145 in multiple\\
146 lines
147
148A newline character gets appended automatically to every template, unless the
149last character is a backslash. And empty lines at the end of a template are
150ignored.
151
152 There is <%= 1 + 1 %> no newline at the end here\
153
154At the beginning of the template, stash values that don't have invalid
155characters in their name get automatically initialized as normal variables, and
156the controller object as both C<$self> and C<$c>.
157
158 $c->stash(name => 'tester');
159
160 Hello <%= $name %> from <%= $c->tx->remote_address %>.
161
162A prefix like C<myapp.*> is commonly used for stash values that you don't want
163to expose in templates.
164
165 $c->stash('myapp.name' => 'tester');
166
167There are also many helper functions available, but more about that later.
168
169 <%= dumper {foo => 'bar'} %>
170
171=head1 BASICS
172
173Most commonly used features every L<Mojolicious> developer should know about.
174
175=head2 Automatic rendering
176
177The renderer can be manually started by calling the method
178L<Mojolicious::Controller/"render">, but that's usually not necessary, because
179it will get automatically called if nothing has been rendered after the router
180finished its work. This also means you can have routes pointing only to
181templates without actual actions.
182
183 $c->render;
184
185There is one big difference though, by calling it manually you can make sure
186that templates use the current controller object, and not the default
187controller specified with the attribute L<Mojolicious/"controller_class">.
188
189 $c->render_later;
190
191You can also disable automatic rendering with the method
192L<Mojolicious::Controller/"render_later">, which can be very useful to delay
193rendering when a non-blocking operation has to be performed first.
194
195=head2 Rendering templates
196
197The renderer will always try to detect the right template, but you can also use
198the C<template> stash value to render a specific one. Everything before the
199last slash will be interpreted as the subdirectory path in which to find the
200template.
201
202 # foo/bar/baz.*.*
203 $c->render(template => 'foo/bar/baz');
204
205Choosing a specific C<format> and C<handler> is just as easy.
206
207 # foo/bar/baz.txt.epl
208 $c->render(template => 'foo/bar/baz', format => 'txt', handler => 'epl');
209
210Because rendering a specific template is the most common task it also has a
211shortcut.
212
213 $c->render('foo/bar/baz');
214
215If you're not sure in advance if a template actually exists, you can also use
216the method L<Mojolicious::Controller/"render_maybe"> to try multiple
217alternatives.
218
219 $c->render_maybe('localized/baz') or $c->render('foo/bar/baz');
220
221=head2 Rendering to strings
222
223Sometimes you might want to use the rendered result directly instead of
224generating a response, for example, to send emails, this can be done with
225L<Mojolicious::Controller/"render_to_string">.
226
227 my $html = $c->render_to_string('mail');
228
229No encoding will be performed, making it easy to reuse the result in other
230templates or to generate binary data.
231
232 my $pdf = $c->render_to_string('invoice', format => 'pdf');
233 $c->render(data => $pdf, format => 'pdf');
234
235All arguments passed will get localized automatically and are only available
236during this render operation.
237
238=head2 Template variants
239
240To make your application look great on many different devices you can also use
241the C<variant> stash value to choose between different variants of your
242templates.
243
244 # foo/bar/baz.html+phone.ep
245 # foo/bar/baz.html.ep
246 $c->render('foo/bar/baz', variant => 'phone');
247
248This can be done very liberally since it only applies when a template with the
249correct name actually exists and falls back to the generic one otherwise.
250
251=head2 Rendering inline templates
252
253Some renderers such as C<ep> allow templates to be passed C<inline>.
254
255 $c->render(inline => 'The result is <%= 1 + 1 %>.');
256
257Since auto-detection depends on a path you might have to supply a C<handler>
258too.
259
260 $c->render(inline => "<%= shift->param('foo') %>", handler => 'epl');
261
262=head2 Rendering text
263
264Characters can be rendered to bytes with the C<text> stash value, the given
265content will be automatically encoded with L<Mojolicious::Renderer/"encoding">.
266
267 $c->render(text => 'I ♥ Mojolicious!');
268
269=head2 Rendering data
270
271Bytes can be rendered with the C<data> stash value, no encoding will be
272performed.
273
274 $c->render(data => $bytes);
275
276=head2 Rendering JSON
277
278The C<json> stash value allows you to pass Perl data structures to the renderer
279which get directly encoded to JSON with L<Mojo::JSON>.
280
281 $c->render(json => {foo => [1, 'test', 3]});
282
283=head2 Status code
284
285Response status codes can be changed with the C<status> stash value.
286
287 $c->render(text => 'Oops.', status => 500);
288
289=head2 Content type
290
291The C<Content-Type> header of the response is actually based on the MIME type
292mapping of the C<format> stash value.
293
294 # Content-Type: text/plain
295 $c->render(text => 'Hello.', format => 'txt');
296
297 # Content-Type: image/png
298 $c->render(data => $bytes, format => 'png');
299
300These mappings can be easily extended or changed with L<Mojolicious/"types">.
301
302 # Add new MIME type
303 $app->types->type(md => 'text/markdown');
304
305=head2 Stash data
306
307Any of the native Perl data types can be passed to templates as references
308through the L<Mojolicious::Controller/"stash">.
309
310 $c->stash(description => 'web framework');
311 $c->stash(frameworks => ['Catalyst', 'Mojolicious']);
312 $c->stash(spinoffs => {minion => 'job queue'});
313
314 %= $description
315 %= $frameworks->[1]
316 %= $spinoffs->{minion}
317
318Since everything is just Perl normal control structures just work.
319
320 % for my $framework (@$frameworks) {
321 <%= $framework %> is a <%= $description %>.
322 % }
323
324 % if (my $description = $spinoffs->{minion}) {
325 Minion is a <%= $description %>.
326 % }
327
328For templates that might get rendered in different ways and where you're not
329sure if a stash value will actually be set, you can just use the helper
330L<Mojolicious::Plugin::DefaultHelpers/"stash">.
331
332 % if (my $spinoffs = stash 'spinoffs') {
333 Minion is a <%= $spinoffs->{minion} %>.
334 % }
335
336=head2 Helpers
337
338Helpers are little functions you can use in templates as well as application
339and controller code.
340
341 # Template
342 %= dumper [1, 2, 3]
343
344 # Application
345 my $serialized = $app->dumper([1, 2, 3]);
346
347 # Controller
348 my $serialized = $c->dumper([1, 2, 3]);
349
350We differentiate between default helpers, which are more general purpose like
351L<Mojolicious::Plugin::DefaultHelpers/"dumper">, and tag helpers like
352L<Mojolicious::Plugin::TagHelpers/"link_to">, which are template specific and
353mostly used to generate HTML tags.
354
355 %= link_to Mojolicious => 'https://mojolicious.org'
356
357In controllers you can also use the method L<Mojolicious::Controller/"helpers">
358to fully qualify helper calls and ensure that they don't conflict with existing
359methods you may already have.
360
361 my $serialized = $c->helpers->dumper([1, 2, 3]);
362
363A list of all built-in helpers can be found in
364L<Mojolicious::Plugin::DefaultHelpers> and L<Mojolicious::Plugin::TagHelpers>.
365
366=head2 Content negotiation
367
368For resources with different representations and that require truly RESTful
369content negotiation you can also use L<Mojolicious::Controller/"respond_to">
370instead of L<Mojolicious::Controller/"render">.
371
372 # /hello (Accept: application/json) -> "json"
373 # /hello (Accept: application/xml) -> "xml"
374 # /hello.json -> "json"
375 # /hello.xml -> "xml"
376 # /hello?format=json -> "json"
377 # /hello?format=xml -> "xml"
378 $c->respond_to(
379 json => {json => {hello => 'world'}},
380 xml => {text => '<hello>world</hello>'}
381 );
382
383The best possible representation will be automatically selected from the
384C<format> C<GET>/C<POST> parameter, C<format> stash value or C<Accept> request
385header and stored in the C<format> stash value. To change MIME type mappings for
386the C<Accept> request header or the C<Content-Type> response header you can use
387L<Mojolicious/"types">.
388
389 $c->respond_to(
390 json => {json => {hello => 'world'}},
391 html => sub {
392 $c->content_for(head => '<meta name="author" content="sri">');
393 $c->render(template => 'hello', message => 'world')
394 }
395 );
396
397Callbacks can be used for representations that are too complex to fit into a
398single render call.
399
400 # /hello (Accept: application/json) -> "json"
401 # /hello (Accept: text/html) -> "html"
402 # /hello (Accept: image/png) -> "any"
403 # /hello.json -> "json"
404 # /hello.html -> "html"
405 # /hello.png -> "any"
406 # /hello?format=json -> "json"
407 # /hello?format=html -> "html"
408 # /hello?format=png -> "any"
409 $c->respond_to(
410 json => {json => {hello => 'world'}},
411 html => {template => 'hello', message => 'world'},
412 any => {text => '', status => 204}
413 );
414
415And if no viable representation could be found, the C<any> fallback will be
416used or an empty C<204> response rendered automatically.
417
418 # /hello -> "html"
419 # /hello (Accept: text/html) -> "html"
420 # /hello (Accept: text/xml) -> "xml"
421 # /hello (Accept: text/plain) -> undef
422 # /hello.html -> "html"
423 # /hello.xml -> "xml"
424 # /hello.txt -> undef
425 # /hello?format=html -> "html"
426 # /hello?format=xml -> "xml"
427 # /hello?format=txt -> undef
428 if (my $format = $c->accepts('html', 'xml')) {
429 ...
430 }
431
432For even more advanced negotiation logic you can also use the helper
433L<Mojolicious::Plugin::DefaultHelpers/"accepts">.
434
435=head2 Rendering C<exception> and C<not_found> pages
436
437By now you've probably already encountered the built-in C<404> (Not Found) and
438C<500> (Server Error) pages, that get rendered automatically when you make a
439mistake. Those are fallbacks for when your own exception handling fails, which
440can be especially helpful during development. You can also render them manually
441with the helpers L<Mojolicious::Plugin::DefaultHelpers/"reply-E<gt>exception">
442and L<Mojolicious::Plugin::DefaultHelpers/"reply-E<gt>not_found">.
443
444 use Mojolicious::Lite;
445 use Scalar::Util 'looks_like_number';
446
447 get '/divide/:dividend/by/:divisor' => sub {
448 my $c = shift;
449
450 my $dividend = $c->param('dividend');
451 my $divisor = $c->param('divisor');
452
453 # 404
454 return $c->reply->not_found
455 unless looks_like_number $dividend && looks_like_number $divisor;
456
457 # 500
458 return $c->reply->exception('Division by zero!') if $divisor == 0;
459
460 # 200
461 $c->render(text => $dividend / $divisor);
462 };
463
464 app->start;
465
466You can also change the templates of those pages, since you most likely want to
467show your users something more closely related to your application in
468production. The renderer will always try to find C<exception.$mode.$format.*>
469or C<not_found.$mode.$format.*> before falling back to the built-in default
470templates.
471
472 use Mojolicious::Lite;
473
474 get '/dies' => sub { die 'Intentional error' };
475
476 app->start;
477 __DATA__
478
479 @@ exception.production.html.ep
480 <!DOCTYPE html>
481 <html>
482 <head><title>Server error</title></head>
483 <body>
484 <h1>Exception</h1>
485 <p><%= $exception->message %></p>
486 <h1>Stash</h1>
487 <pre><%= dumper $snapshot %></pre>
488 </body>
489 </html>
490
491The hook L<Mojolicious/"before_render"> makes even more advanced customizations
492possible by allowing you to intercept and modify the arguments passed to the
493renderer.
494
495 use Mojolicious::Lite;
496
497 hook before_render => sub {
498 my ($c, $args) = @_;
499
500 # Make sure we are rendering the exception template
501 return unless my $template = $args->{template};
502 return unless $template eq 'exception';
503
504 # Switch to JSON rendering if content negotiation allows it
505 $args->{json} = {exception => $args->{exception}} if $c->accepts('json');
506 };
507
508 get '/' => sub { die "This sho...ALL GLORY TO THE HYPNOTOAD!\n" };
509
510 app->start;
511
512=head2 Layouts
513
514Most of the time when using C<ep> templates you will want to wrap your
515generated content in an HTML skeleton, thanks to layouts that's absolutely
516trivial.
517
518 use Mojolicious::Lite;
519
520 get '/' => {template => 'foo/bar'};
521
522 app->start;
523 __DATA__
524
525 @@ foo/bar.html.ep
526 % layout 'mylayout';
527 Hello World!
528
529 @@ layouts/mylayout.html.ep
530 <!DOCTYPE html>
531 <html>
532 <head><title>MyApp</title></head>
533 <body><%= content %></body>
534 </html>
535
536You just select the right layout template with the helper
537L<Mojolicious::Plugin::DefaultHelpers/"layout"> and place the result of the
538current template with the helper
539L<Mojolicious::Plugin::DefaultHelpers/"content">. You can also pass along
540normal stash values to the C<layout> helper.
541
542 use Mojolicious::Lite;
543
544 get '/' => {template => 'foo/bar'};
545
546 app->start;
547 __DATA__
548
549 @@ foo/bar.html.ep
550 % layout 'mylayout', title => 'Hi there';
551 Hello World!
552
553 @@ layouts/mylayout.html.ep
554 <!DOCTYPE html>
555 <html>
556 <head><title><%= $title %></title></head>
557 <body><%= content %></body>
558 </html>
559
560Instead of the C<layout> helper you could also just use the C<layout> stash
561value, or call L<Mojolicious::Controller/"render"> with the C<layout> argument.
562
563 $c->render(template => 'mytemplate', layout => 'mylayout');
564
565To set a C<layout> stash value application-wide you can use
566L<Mojolicious/"defaults">.
567
568 $app->defaults(layout => 'mylayout');
569
570Layouts can also be used with L<Mojolicious::Controller/"render_to_string">,
571but the C<layout> value needs to be passed as a render argument (not a stash
572value).
573
574 my $html = $c->render_to_string('reminder', layout => 'mail');
575
576=head2 Partial templates
577
578You can break up bigger templates into smaller, more manageable chunks. These
579partial templates can also be shared with other templates. Just use the helper
580L<Mojolicious::Plugin::DefaultHelpers/"include"> to include one template into
581another.
582
583 use Mojolicious::Lite;
584
585 get '/' => {template => 'foo/bar'};
586
587 app->start;
588 __DATA__
589
590 @@ foo/bar.html.ep
591 <!DOCTYPE html>
592 <html>
593 %= include '_header', title => 'Howdy'
594 <body>Bar</body>
595 </html>
596
597 @@ _header.html.ep
598 <head><title><%= $title %></title></head>
599
600You can name partial templates however you like, but a leading underscore is a
601commonly used naming convention.
602
603=head2 Reusable template blocks
604
605It's never fun to repeat yourself, that's why you can build reusable template
606blocks in C<ep> that work very similar to normal Perl functions, with the
607C<begin> and C<end> keywords. Just be aware that both keywords are part of the
608surrounding tag and not actual Perl code, so there can only be whitespace after
609C<begin> and before C<end>.
610
611 use Mojolicious::Lite;
612
613 get '/' => 'welcome';
614
615 app->start;
616 __DATA__
617
618 @@ welcome.html.ep
619 <% my $block = begin %>
620 % my $name = shift;
621 Hello <%= $name %>.
622 <% end %>
623 <%= $block->('Wolfgang') %>
624 <%= $block->('Baerbel') %>
625
626A naive translation of the template to Perl code could look like this.
627
628 my $output = '';
629 my $block = sub {
630 my $name = shift;
631 my $output = '';
632 $output .= 'Hello ';
633 $output .= xml_escape scalar + $name;
634 $output .= '.';
635 return Mojo::ByteStream->new($output);
636 };
637 $output .= xml_escape scalar + $block->('Wolfgang');
638 $output .= xml_escape scalar + $block->('Baerbel');
639 return $output;
640
641While template blocks cannot be shared between templates, they are most
642commonly used to pass parts of a template to helpers.
643
644=head2 Adding helpers
645
646You should always try to keep your actions small and reuse as much code as
647possible. Helpers make this very easy, they get passed the current controller
648object as first argument, and you can use them to do pretty much anything an
649action could do.
650
651 use Mojolicious::Lite;
652
653 helper debug => sub {
654 my ($c, $str) = @_;
655 $c->app->log->debug($str);
656 };
657
658 get '/' => sub {
659 my $c = shift;
660 $c->debug('Hello from an action!');
661 } => 'index';
662
663 app->start;
664 __DATA__
665
666 @@ index.html.ep
667 % debug 'Hello from a template!';
668
669Helpers can also accept template blocks as last argument, this for example,
670allows very pleasant to use tag helpers and filters. Wrapping the helper result
671into a L<Mojo::ByteStream> object can prevent accidental double escaping.
672
673 use Mojolicious::Lite;
674 use Mojo::ByteStream;
675
676 helper trim_newline => sub {
677 my ($c, $block) = @_;
678 my $result = $block->();
679 $result =~ s/\n//g;
680 return Mojo::ByteStream->new($result);
681 };
682
683 get '/' => 'index';
684
685 app->start;
686 __DATA__
687
688 @@ index.html.ep
689 %= trim_newline begin
690 Some text.
691 %= 1 + 1
692 More text.
693 % end
694
695Similar to stash values, you can use a prefix like C<myapp.*> to keep helpers
696from getting exposed in templates as functions, and to organize them into
697namespaces as your application grows. Every prefix automatically becomes a
698helper that returns a proxy object containing the current controller object and
699on which you can call the nested helpers.
700
701 use Mojolicious::Lite;
702
703 helper 'cache_control.no_caching' => sub {
704 my $c = shift;
705 $c->res->headers->cache_control('private, max-age=0, no-cache');
706 };
707
708 helper 'cache_control.five_minutes' => sub {
709 my $c = shift;
710 $c->res->headers->cache_control('public, max-age=300');
711 };
712
713 get '/news' => sub {
714 my $c = shift;
715 $c->cache_control->no_caching;
716 $c->render(text => 'Always up to date.');
717 };
718
719 get '/some_older_story' => sub {
720 my $c = shift;
721 $c->cache_control->five_minutes;
722 $c->render(text => 'This one can be cached for a bit.');
723 };
724
725 app->start;
726
727While helpers can also be redefined, this should only be done very carefully to
728avoid conflicts.
729
730=head2 Content blocks
731
732The helper L<Mojolicious::Plugin::DefaultHelpers/"content_for"> allows you to
733pass whole blocks of content from one template to another. This can be very
734useful when your layout has distinct sections, such as sidebars, where content
735should be inserted by the template.
736
737 use Mojolicious::Lite;
738
739 get '/' => 'foo';
740
741 app->start;
742 __DATA__
743
744 @@ foo.html.ep
745 % layout 'mylayout';
746 % content_for header => begin
747 <meta http-equiv="Content-Type" content="text/html">
748 % end
749 <div>Hello World!</div>
750 % content_for header => begin
751 <meta http-equiv="Pragma" content="no-cache">
752 % end
753
754 @@ layouts/mylayout.html.ep
755 <!DOCTYPE html>
756 <html>
757 <head><%= content 'header' %></head>
758 <body><%= content %></body>
759 </html>
760
761=head2 Forms
762
763To build HTML forms more efficiently you can use tag helpers like
764L<Mojolicious::Plugin::TagHelpers/"form_for">, which can automatically select a
765request method for you if a route name is provided. And since most browsers
766only allow forms to be submitted with C<GET> and C<POST>, but not request
767methods like C<PUT> or C<DELETE>, they are spoofed with an C<_method> query
768parameter.
769
770 use Mojolicious::Lite;
771
772 get '/' => 'form';
773
774 # PUT /nothing
775 # POST /nothing?_method=PUT
776 put '/nothing' => sub {
777 my $c = shift;
778
779 # Prevent double form submission with redirect
780 my $value = $c->param('whatever');
781 $c->flash(confirmation => "We did nothing with your value ($value).");
782 $c->redirect_to('form');
783 };
784
785 app->start;
786 __DATA__
787
788 @@ form.html.ep
789 <!DOCTYPE html>
790 <html>
791 <body>
792 % if (my $confirmation = flash 'confirmation') {
793 <p><%= $confirmation %></p>
794 % }
795 %= form_for nothing => begin
796 %= text_field whatever => 'I ♥ Mojolicious!'
797 %= submit_button
798 % end
799 </body>
800 </html>
801
802The methods L<Mojolicious::Controller/"flash"> and
803L<Mojolicious::Controller/"redirect_to"> are often used together to prevent
804double form submission, allowing users to receive a confirmation message that
805will vanish if they decide to reload the page they've been redirected to.
806
807=head2 Form validation
808
809You can use L<Mojolicious::Controller/"validation"> to validate C<GET> and
810C<POST> parameters submitted to your application. All unknown fields will be
811ignored by default, so you have to decide which should be required or optional
812before you can perform checks on their values. Every check is performed right
813away, so you can use the results immediately to build more advanced validation
814logic with methods like L<Mojolicious::Validator::Validation/"is_valid">.
815
816 use Mojolicious::Lite;
817
818 get '/' => sub {
819 my $c = shift;
820
821 # Check if parameters have been submitted
822 my $v = $c->validation;
823 return $c->render unless $v->has_data;
824
825 # Validate parameters ("pass_again" depends on "pass")
826 $v->required('user')->size(1, 20)->like(qr/^[a-z0-9]+$/);
827 $v->required('pass_again')->equal_to('pass')
828 if $v->optional('pass')->size(7, 500)->is_valid;
829
830 # Render confirmation if validation was successful
831 $c->render('thanks') unless $v->has_error;
832 } => 'index';
833
834 app->start;
835 __DATA__
836
837 @@ index.html.ep
838 <!DOCTYPE html>
839 <html>
840 <head>
841 <style>
842 label.field-with-error { color: #dd7e5e }
843 input.field-with-error { background-color: #fd9e7e }
844 </style>
845 </head>
846 <body>
847 %= form_for index => begin
848 %= label_for user => 'Username (required, 1-20 characters, a-z/0-9)'
849 <br>
850 %= text_field 'user', id => 'user'
851 %= submit_button
852 <br>
853 %= label_for pass => 'Password (optional, 7-500 characters)'
854 <br>
855 %= password_field 'pass', id => 'pass'
856 <br>
857 %= label_for pass_again => 'Password again (equal to the value above)'
858 <br>
859 %= password_field 'pass_again', id => 'pass_again'
860 % end
861 </body>
862 </html>
863
864 @@ thanks.html.ep
865 <!DOCTYPE html>
866 <html><body>Thank you <%= validation->param('user') %>.</body></html>
867
868Form elements generated with tag helpers from
869L<Mojolicious::Plugin::TagHelpers> will automatically remember their previous
870values and add the class C<field-with-error> for fields that failed validation
871to make styling with CSS easier.
872
873 <label class="field-with-error" for="user">
874 Username (required, only characters e-t)
875 </label>
876 <input class="field-with-error" type="text" name="user" value="sri">
877
878For a full list of available checks see also
879L<Mojolicious::Validator/"CHECKS">.
880
881=head2 Adding form validation checks
882
883Validation checks can be registered with L<Mojolicious::Validator/"add_check">
884and return a false value if they were successful. A true value may be used to
885pass along additional information which can then be retrieved with
886L<Mojolicious::Validator::Validation/"error">.
887
888 use Mojolicious::Lite;
889
890 # Add "range" check
891 app->validator->add_check(range => sub {
892 my ($v, $name, $value, $min, $max) = @_;
893 return $value < $min || $value > $max;
894 });
895
896 get '/' => 'form';
897
898 post '/test' => sub {
899 my $c = shift;
900
901 # Validate parameters with custom check
902 my $v = $c->validation;
903 $v->required('number')->range(3, 23);
904
905 # Render form again if validation failed
906 return $c->render('form') if $v->has_error;
907
908 # Prevent double form submission with redirect
909 $c->flash(number => $v->param('number'));
910 $c->redirect_to('form');
911 };
912
913 app->start;
914 __DATA__
915
916 @@ form.html.ep
917 <!DOCTYPE html>
918 <html>
919 <body>
920 % if (my $number = flash 'number') {
921 <p>Thanks, the number <%= $number %> was valid.</p>
922 % }
923 %= form_for test => begin
924 % if (my $err = validation->error('number')) {
925 <p>
926 %= 'Value is required.' if $err->[0] eq 'required'
927 %= 'Value needs to be between 3 and 23.' if $err->[0] eq 'range'
928 </p>
929 % }
930 %= text_field 'number'
931 %= submit_button
932 % end
933 </body>
934 </html>
935
936=head2 Cross-site request forgery
937
938CSRF is a very common attack on web applications that trick your logged in
939users to submit forms they did not intend to send, with something as mundane as
940a link. All you have to do, to protect your users from this, is to add an
941additional hidden field to your forms with
942L<Mojolicious::Plugin::TagHelpers/"csrf_field">, and validate it with
943L<Mojolicious::Validator::Validation/"csrf_protect">.
944
945 use Mojolicious::Lite;
946
947 get '/' => {template => 'target'};
948
949 post '/' => sub {
950 my $c = shift;
951
952 # Check CSRF token
953 my $v = $c->validation;
954 return $c->render(text => 'Bad CSRF token!', status => 403)
955 if $v->csrf_protect->has_error('csrf_token');
956
957 my $city = $v->required('city')->param('city');
958 $c->render(text => "Low orbit ion cannon pointed at $city!")
959 unless $v->has_error;
960 } => 'target';
961
962 app->start;
963 __DATA__
964
965 @@ target.html.ep
966 <!DOCTYPE html>
967 <html>
968 <body>
969 %= form_for target => begin
970 %= csrf_field
971 %= label_for city => 'Which city to point low orbit ion cannon at?'
972 %= text_field 'city', id => 'city'
973 %= submit_button
974 %= end
975 </body>
976 </html>
977
978For Ajax requests and the like, you can also generate a token directly with the
979helper L<Mojolicious::Plugin::DefaultHelpers/"csrf_token">, and then pass it
980along with the C<X-CSRF-Token> request header.
981
982=head1 ADVANCED
983
984Less commonly used and more powerful features.
985
986=head2 Template inheritance
987
988Inheritance takes the layout concept above one step further, the helpers
989L<Mojolicious::Plugin::DefaultHelpers/"content"> and
990L<Mojolicious::Plugin::DefaultHelpers/"extends"> allow you to build skeleton
991templates with named blocks that child templates can override.
992
993 use Mojolicious::Lite;
994
995 # first > mylayout
996 get '/first' => {template => 'first', layout => 'mylayout'};
997
998 # third > second > first > mylayout
999 get '/third' => {template => 'third', layout => 'mylayout'};
1000
1001 app->start;
1002 __DATA__
1003
1004 @@ layouts/mylayout.html.ep
1005 <!DOCTYPE html>
1006 <html>
1007 <head><title>Hello</title></head>
1008 <body><%= content %></body>
1009 </html>
1010
1011 @@ first.html.ep
1012 %= content header => begin
1013 Default header
1014 % end
1015 <div>Hello World!</div>
1016 %= content footer => begin
1017 Default footer
1018 % end
1019
1020 @@ second.html.ep
1021 % extends 'first';
1022 % content header => begin
1023 New header
1024 % end
1025
1026 @@ third.html.ep
1027 % extends 'second';
1028 % content footer => begin
1029 New footer
1030 % end
1031
1032This chain could go on and on to allow a very high level of template reuse.
1033
1034=head2 Serving static files
1035
1036Static files are automatically served from the C<public> directories of the
1037application, which can be customized with L<Mojolicious::Static/"paths">, or one
1038of the C<DATA> sections from L<Mojolicious::Static/"classes">. And if that's not
1039enough you can also serve them manually with
1040L<Mojolicious::Plugin::DefaultHelpers/"reply-E<gt>static"> and
1041L<Mojolicious::Plugin::DefaultHelpers/"reply-E<gt>file">.
1042
1043 use Mojolicious::Lite;
1044
1045 get '/' => sub {
1046 my $c = shift;
1047 $c->reply->static('index.html');
1048 };
1049
1050 get '/some_download' => sub {
1051 my $c = shift;
1052 $c->res->headers->content_disposition('attachment; filename=bar.png;');
1053 $c->reply->static('foo/bar.png');
1054 };
1055
1056 get '/leak' => sub {
1057 my $c = shift;
1058 $c->reply->file('/etc/passwd');
1059 };
1060
1061 app->start;
1062
1063=head2 Custom responses
1064
1065Most response content, static as well as dynamic, gets served through
1066L<Mojo::Asset::File> and L<Mojo::Asset::Memory> objects. For somewhat static
1067content, like cached JSON data or temporary files, you can create your own and
1068use the helper L<Mojolicious::Plugin::DefaultHelpers/"reply-E<gt>asset"> to
1069serve them while allowing content negotiation to be performed with C<Range>,
1070C<If-Modified-Since> and C<If-None-Match> headers.
1071
1072 use Mojolicious::Lite;
1073 use Mojo::Asset::File;
1074
1075 get '/leak' => sub {
1076 my $c = shift;
1077 $c->res->headers->content_type('text/plain');
1078 $c->reply->asset(Mojo::Asset::File->new(path => '/etc/passwd'));
1079 };
1080
1081 app->start;
1082
1083For even more control you can also just skip the helper and use
1084L<Mojolicious::Controller/"rendered"> to tell the renderer when you're done
1085generating a response.
1086
1087 use Mojolicious::Lite;
1088 use Mojo::Asset::File;
1089
1090 get '/leak' => sub {
1091 my $c = shift;
1092 $c->res->headers->content_type('text/plain');
1093 $c->res->content->asset(Mojo::Asset::File->new(path => '/etc/passwd'));
1094 $c->rendered(200);
1095 };
1096
1097 app->start;
1098
1099=head2 Helper plugins
1100
1101Some helpers might be useful enough for you to share them between multiple
1102applications, plugins make that very simple.
1103
1104 package Mojolicious::Plugin::DebugHelper;
1105 use Mojo::Base 'Mojolicious::Plugin';
1106
1107 sub register {
1108 my ($self, $app) = @_;
1109 $app->helper(debug => sub {
1110 my ($c, $str) = @_;
1111 $c->app->log->debug($str);
1112 });
1113 }
1114
1115 1;
1116
1117The C<register> method will be called when you load the plugin. And to add your
1118helper to the application, you can use L<Mojolicious/"helper">.
1119
1120 use Mojolicious::Lite;
1121
1122 plugin 'DebugHelper';
1123
1124 get '/' => sub {
1125 my $c = shift;
1126 $c->debug('It works!');
1127 $c->render(text => 'Hello!');
1128 };
1129
1130 app->start;
1131
1132A skeleton for a full CPAN compatible plugin distribution can be automatically
1133generated.
1134
1135 $ mojo generate plugin DebugHelper
1136
1137And if you have a C<PAUSE> account (which can be requested at
1138L<http://pause.perl.org>), you are only a few commands away from releasing it
1139to CPAN.
1140
1141 $ perl Makefile.PL
1142 $ make test
1143 $ make manifest
1144 $ make dist
1145 $ mojo cpanify -u USER -p PASS Mojolicious-Plugin-DebugHelper-0.01.tar.gz
1146
1147=head2 Bundling assets with plugins
1148
1149Assets such as templates and static files can be easily bundled with your
1150plugins, even if you plan to release them to CPAN.
1151
1152 $ mojo generate plugin AlertAssets
1153 $ mkdir Mojolicious-Plugin-AlertAssets/lib/Mojolicious/Plugin/AlertAssets
1154 $ cd Mojolicious-Plugin-AlertAssets/lib/Mojolicious/Plugin/AlertAssets
1155 $ mkdir public
1156 $ echo 'alert("Hello World!");' > public/alertassets.js
1157 $ mkdir templates
1158 $ echo '%= javascript "/alertassets.js"' > templates/alertassets.html.ep
1159
1160Just give them reasonably unique names, ideally based on the name of your
1161plugin, and append their respective directories to the list of search paths
1162when C<register> is called.
1163
1164 package Mojolicious::Plugin::AlertAssets;
1165 use Mojo::Base 'Mojolicious::Plugin';
1166
1167 use Mojo::File 'path';
1168
1169 sub register {
1170 my ($self, $app) = @_;
1171
1172 # Append "templates" and "public" directories
1173 my $base = path(__FILE__)->sibling('AlertAssets');
1174 push @{$app->renderer->paths}, $base->child('templates')->to_string;
1175 push @{$app->static->paths}, $base->child('public')->to_string;
1176 }
1177
1178 1;
1179
1180Both will work just like normal C<templates> and C<public> directories once
1181you've installed and loaded the plugin, with slightly lower precedence.
1182
1183 use Mojolicious::Lite;
1184
1185 plugin 'AlertAssets';
1186
1187 get '/alert_me';
1188
1189 app->start;
1190 __DATA__
1191
1192 @@ alert_me.html.ep
1193 <!DOCTYPE html>
1194 <html>
1195 <head>
1196 <title>Alert me!</title>
1197 %= include 'alertassets'
1198 </head>
1199 <body>You've been alerted.</body>
1200 </html>
1201
1202And it works just the same for assets bundled in the C<DATA> section of your
1203plugin.
1204
1205 package Mojolicious::Plugin::AlertAssets;
1206 use Mojo::Base 'Mojolicious::Plugin';
1207
1208 sub register {
1209 my ($self, $app) = @_;
1210
1211 # Append class
1212 push @{$app->renderer->classes}, __PACKAGE__;
1213 push @{$app->static->classes}, __PACKAGE__;
1214 }
1215
1216 1;
1217 __DATA__
1218
1219 @@ alertassets.js
1220 alert("Hello World!");
1221
1222 @@ alertassets.html.ep
1223 %= javascript "/alertassets.js"
1224
1225=head2 Post-processing dynamic content
1226
1227While post-processing tasks are generally very easy with the hook
1228L<Mojolicious/"after_dispatch">, for content generated by the renderer it is a
1229lot more efficient to use L<Mojolicious/"after_render">.
1230
1231 use Mojolicious::Lite;
1232 use IO::Compress::Gzip 'gzip';
1233
1234 hook after_render => sub {
1235 my ($c, $output, $format) = @_;
1236
1237 # Check if "gzip => 1" has been set in the stash
1238 return unless $c->stash->{gzip};
1239
1240 # Check if user agent accepts gzip compression
1241 return unless ($c->req->headers->accept_encoding // '') =~ /gzip/i;
1242 $c->res->headers->append(Vary => 'Accept-Encoding');
1243
1244 # Compress content with gzip
1245 $c->res->headers->content_encoding('gzip');
1246 gzip $output, \my $compressed;
1247 $$output = $compressed;
1248 };
1249
1250 get '/' => {template => 'hello', title => 'Hello', gzip => 1};
1251
1252 app->start;
1253 __DATA__
1254
1255 @@ hello.html.ep
1256 <!DOCTYPE html>
1257 <html>
1258 <head><title><%= title %></title></head>
1259 <body>Compressed content.</body>
1260 </html>
1261
1262=head2 Streaming
1263
1264You don't have to render all content at once, the method
1265L<Mojolicious::Controller/"write"> can also be used to stream a series of
1266smaller chunks.
1267
1268 use Mojolicious::Lite;
1269
1270 get '/' => sub {
1271 my $c = shift;
1272
1273 # Prepare body
1274 my $body = 'Hello World!';
1275 $c->res->headers->content_length(length $body);
1276
1277 # Start writing directly with a drain callback
1278 my $drain;
1279 $drain = sub {
1280 my $c = shift;
1281 my $chunk = substr $body, 0, 1, '';
1282 $drain = undef unless length $body;
1283 $c->write($chunk, $drain);
1284 };
1285 $c->$drain;
1286 };
1287
1288 app->start;
1289
1290The drain callback will be executed whenever the entire previous chunk of data
1291has actually been written.
1292
1293 HTTP/1.1 200 OK
1294 Date: Sat, 13 Sep 2014 16:48:29 GMT
1295 Content-Length: 12
1296 Server: Mojolicious (Perl)
1297
1298 Hello World!
1299
1300Instead of providing a C<Content-Length> header you can also call
1301L<Mojolicious::Controller/"finish"> and close the connection manually once you
1302are done.
1303
1304 use Mojolicious::Lite;
1305
1306 get '/' => sub {
1307 my $c = shift;
1308
1309 # Prepare body
1310 my $body = 'Hello World!';
1311
1312 # Start writing directly with a drain callback
1313 my $drain;
1314 $drain = sub {
1315 my $c = shift;
1316 my $chunk = substr $body, 0, 1, '';
1317 length $chunk ? $c->write($chunk, $drain) : $c->finish;
1318 };
1319 $c->$drain;
1320 };
1321
1322 app->start;
1323
1324While this is rather inefficient, as it prevents keep-alive, it is sometimes
1325necessary for EventSource and similar applications.
1326
1327 HTTP/1.1 200 OK
1328 Date: Sat, 13 Sep 2014 16:48:29 GMT
1329 Connection: close
1330 Server: Mojolicious (Perl)
1331
1332 Hello World!
1333
1334=head2 Chunked transfer encoding
1335
1336For very dynamic content you might not know the response content length in
1337advance, that's where the chunked transfer encoding and
1338L<Mojolicious::Controller/"write_chunk"> come in handy. A common use would be
1339to send the C<head> section of an HTML document to the browser in advance and
1340speed up preloading of referenced images and stylesheets.
1341
1342 use Mojolicious::Lite;
1343
1344 get '/' => sub {
1345 my $c = shift;
1346 $c->write_chunk('<html><head><title>Example</title></head>' => sub {
1347 my $c = shift;
1348 $c->finish('<body>Example</body></html>');
1349 });
1350 };
1351
1352 app->start;
1353
1354The optional drain callback ensures that all previous chunks have been written
1355before processing continues. To end the stream you can call
1356L<Mojolicious::Controller/"finish"> or write an empty chunk of data.
1357
1358 HTTP/1.1 200 OK
1359 Date: Sat, 13 Sep 2014 16:48:29 GMT
1360 Transfer-Encoding: chunked
1361 Server: Mojolicious (Perl)
1362
1363 29
1364 <html><head><title>Example</title></head>
1365 1b
1366 <body>Example</body></html>
1367 0
1368
1369Especially in combination with long inactivity timeouts this can be very useful
1370for Comet (long polling). Due to limitations in some web servers this might not
1371work perfectly in all deployment environments.
1372
1373=head2 Encoding
1374
1375Templates stored in files are expected to be C<UTF-8> by default, but that can
1376be easily changed with L<Mojolicious::Renderer/"encoding">.
1377
1378 $app->renderer->encoding('koi8-r');
1379
1380All templates from the C<DATA> section are bound to the encoding of the Perl
1381script.
1382
1383 use Mojolicious::Lite;
1384
1385 get '/heart';
1386
1387 app->start;
1388 __DATA__
1389
1390 @@ heart.html.ep
1391 I ♥ Mojolicious!
1392
1393=head2 Base64 encoded DATA files
1394
1395Base64 encoded static files such as images can be easily stored in the C<DATA>
1396section of your application, similar to templates.
1397
1398 use Mojolicious::Lite;
1399
1400 get '/' => {text => 'I ♥ Mojolicious!'};
1401
1402 app->start;
1403 __DATA__
1404
1405 @@ favicon.ico (base64)
1406 ...base64 encoded image...
1407
1408=head2 Inflating DATA templates
1409
1410Templates stored in files get preferred over files from the C<DATA> section,
1411this allows you to include a default set of templates in your application that
1412the user can later customize. The command L<Mojolicious::Command::inflate> will
1413write all templates and static files from the C<DATA> section into actual files
1414in the C<templates> and C<public> directories.
1415
1416 $ ./myapp.pl inflate
1417
1418=head2 Customizing the template syntax
1419
1420You can easily change the whole template syntax by loading
1421L<Mojolicious::Plugin::EPRenderer> with a custom configuration.
1422
1423 use Mojolicious::Lite;
1424
1425 plugin EPRenderer => {
1426 name => 'mustache',
1427 template => {
1428 tag_start => '{{',
1429 tag_end => '}}'
1430 }
1431 };
1432
1433 get '/:name' => {name => 'Anonymous'} => 'index';
1434
1435 app->start;
1436 __DATA__
1437
1438 @@ index.html.mustache
1439 Hello {{= $name }}.
1440
1441L<Mojo::Template> contains the whole list of available options.
1442
1443=head2 Adding your favorite template system
1444
1445Maybe you would prefer a different template system than C<ep>, which is provided
1446by L<Mojolicious::Plugin::EPRenderer>, and there is not already a plugin on CPAN
1447for your favorite one. All you have to do, is to add a new C<handler> with
1448L<Mojolicious::Renderer/"add_handler"> when C<register> is called.
1449
1450 package Mojolicious::Plugin::MyRenderer;
1451 use Mojo::Base 'Mojolicious::Plugin';
1452
1453 sub register {
1454 my ($self, $app) = @_;
1455
1456 # Add "mine" handler
1457 $app->renderer->add_handler(mine => sub {
1458 my ($renderer, $c, $output, $options) = @_;
1459
1460 # Check for one-time use inline template
1461 my $inline_template = $options->{inline};
1462
1463 # Check for appropriate template in "templates" directories
1464 my $template_path = $renderer->template_path($options);
1465
1466 # Check for appropriate template in DATA sections
1467 my $data_template = $renderer->get_data_template($options);
1468
1469 # This part is up to you and your template system :)
1470 ...
1471
1472 # Pass the rendered result back to the renderer
1473 $$output = 'Hello World!';
1474
1475 # Or just die if an error occurs
1476 die 'Something went wrong with the template';
1477 });
1478 }
1479
1480 1;
1481
1482An C<inline> template, if provided by the user, will be passed along with the
1483options. You can use L<Mojolicious::Renderer/"template_path"> to search the
1484C<templates> directories of the application, and
1485L<Mojolicious::Renderer/"get_data_template"> to search the C<DATA> sections.
1486
1487 use Mojolicious::Lite;
1488
1489 plugin 'MyRenderer';
1490
1491 # Render an inline template
1492 get '/inline' => {inline => '...', handler => 'mine'};
1493
1494 # Render a template from the DATA section
1495 get '/data' => {template => 'test'};
1496
1497 app->start;
1498 __DATA__
1499
1500 @@ test.html.mine
1501 ...
1502
1503=head2 Adding a handler to generate binary data
1504
1505By default the renderer assumes that every C<handler> generates characters that
1506need to be automatically encoded, but this can be easily disabled if you're
1507generating bytes instead.
1508
1509 use Mojolicious::Lite;
1510 use Storable 'nfreeze';
1511
1512 # Add "storable" handler
1513 app->renderer->add_handler(storable => sub {
1514 my ($renderer, $c, $output, $options) = @_;
1515
1516 # Disable automatic encoding
1517 delete $options->{encoding};
1518
1519 # Encode data from stash value
1520 $$output = nfreeze delete $c->stash->{storable};
1521 });
1522
1523 # Set "handler" value automatically if "storable" value is set already
1524 app->hook(before_render => sub {
1525 my ($c, $args) = @_;
1526 $args->{handler} = 'storable'
1527 if exists $args->{storable} || exists $c->stash->{storable};
1528 });
1529
1530 get '/' => {storable => {i => '♥ mojolicious'}};
1531
1532 app->start;
1533
1534The hook L<Mojolicious/"before_render"> can be used to make stash values like
1535C<storable> special, so that they no longer require a C<handler> value to be set
1536explicitly.
1537
1538 # Explicit "handler" value
1539 $c->render(storable => {i => '♥ mojolicious'}, handler => 'storable');
1540
1541 # Implicit "handler" value (with "before_render" hook)
1542 $c->render(storable => {i => '♥ mojolicious'});
1543
1544=head1 MORE
1545
1546You can continue with L<Mojolicious::Guides> now or take a look at the
1547L<Mojolicious wiki|http://github.com/kraih/mojo/wiki>, which contains a lot more
1548documentation and examples by many different authors.
1549
1550=head1 SUPPORT
1551
1552If you have any questions the documentation might not yet answer, don't
1553hesitate to ask on the
1554L<mailing list|http://groups.google.com/group/mojolicious> or the official IRC
1555channel C<#mojo> on C<irc.perl.org>
1556(L<chat now!|https://chat.mibbit.com/?channel=%23mojo&server=irc.perl.org>).
1557
1558=cut
Note: See TracBrowser for help on using the repository browser.