Mojolicious::Guides::Rendering
НАЗВАНИЕ
Mojolicious::Guides::Rendering — Рендеринг
ОБЗОР
Генерация контента с использованием механизма рендеринга Mojolicious
ОСНОВНЫЕ ИДЕИ
Основное, что должен знать каждый разработчик на Mojolicious.
Рендерер
Рендерер - это крошечный черный ящик, преобразующий данные приложения в реальный ответ. При этом используются различные системы шаблонов и модули кодирования данных.
{text => 'Hello!'} -> 200 OK, text/html, 'Hello!' {json => {x => 3}} -> 200 OK, application/json, '{"x":3}' {text => 'Oops!', status => '410'} -> 410 Gone, text/html, 'Oops!'
Шаблон может быть выбран автоматически, если для этого разрабочиком либо
маршрутом предоставлено достаточно информации. Наименование шаблона должно
соответствовать следующему формату имя_шаблона.формат.обработчик
(name.format.handler
), где name
по умолчанию соответствует
controller/action
или имени маршрута, format
по умолчанию соответствует
html
, handler
по умолчанию соответствует ep
.
{controller => 'users', action => 'list'} -> 'users/list.html.ep' {name => 'foo', format => 'txt'} -> 'foo.txt.ep' {name => 'foo', handler => 'epl'} -> 'foo.html.epl'
Все шаблоны должны быть размещены в директории templates
приложения
или в секции DATA
класса main
.
__DATA__ @@ time.html.ep <!doctype html><html> <head><title>Time</title></head> <body><%= localtime time %></body> </html> @@ hello.txt.ep ...
Рендерер может быть легко расширен для поддержки дополнительных шаблонизаторов, с помощью расширений, но об этом позже.
Embedded Perl
Mojolicious изначально включает простую, но очень мощную систему шаблонов,
называемую Embedded Perl или ep
для краткости. Это позволяет встраивать код
на Perl прямо в содержимое, используя небольшой набор специальных меток и
символов начала строки.
<% Строчный Perl %> <%= Выражение Perl, заменяемое результатом с XML экранированием %> <%== Выражение Perl, заменяемое результатом без какой-либо обработки %> <%# Комментарий, полезно для отладки %> % Строка Perl %= Строка выражения Perl, заменяемое результатом с XML экранированием %== Строка выражения Perl, заменяемое результатом без какой-либо обработки %# Строка комментария, полезно для отладки
Для вставки Perl кода используется простейший способ. Теги и строки работают одинаково, но в зависимости от контекста что-то будет выглядеть лучше.
<% my $i = 10; %> Текст до цикла <% for my $j (1 .. $i) { %> <%= $j %> <% } %> Текст после цикла % my $i = 10; Текст до цикла % for my $j (1 .. $i) { %= $j % } Текст после цикла
Вы также можете вставлять результаты выполнения Perl-кода, используя выражения.
По умолчанию символы <
, >
, &
, '
и "
будут экранированы,
чтобы предотвратить XSS-атаки на ваше приложение. Ко всем выражениям
автоматически будут добавлены точки с запятой.
<%= 'lalala' %> <%== '<p>test</p>' %>
Только объекты Mojo::ByteStream не экранируются автоматически.
<%= b('<p>test</p>') %>
Вы также можете добавить дополнительный знак '=' в конец тега, чтобы автоматически удалить все лишние пробелы в начале и конце, это позволяет делать отступы, не портя результат.
<% for (1 .. 3) { %> <%= $foo =%> <% } %>
Теги комментариев очень полезны для исключения некоторого кода в целях отладки.
% my $foo = 'lalala'; <%# if ($foo) { %> <%= $foo =%> <%# } %>
Значения stash, которые не содержат недопустимых символов в своих именах,
автоматически инициализируются как обычные переменные в шаблоне, а экземпляр
контроллера как $self
.
$r->route('/foo/:name') ->to(controller => 'foo', action => 'bar', name => 'tester'); Hello <%= $name %>.
Есть также множество встроенных вспомогательных функций, таких как url_for, которые позволяют создавать URL определенного маршрута по его имени.
$r->route('/foo/:name')->to('foo#bar')->name('some_route'); <%= url_for 'some_route' %>
ОСНОВЫ
Наиболее часто используемые возможности, которые должен знать каждый Mojolicious-разработчик.
Автоматический рендеринг
Рендеринг может быть инициирован "вручную", вызовом метода render
контроллера, но обычно в этом нет необходимости, потому что рендеринг
произойдет автоматически если ничего не будет отображено после того как
диспетчер роутов завершит свою работу. Это так же означает, что можно создать
маршрут, указывающий только на шаблон, минуя действие контроллера.
$self->render;
Однако есть одна большая разница: вызывая render
вручную, можно быть
уверенным, что будет использоваться текущий экземпляр контроллера, а не
контроллер по умолчанию, определенный атрибутом controller_class
в классе
приложения.
Рендеринг Шаблонов (template)
Рендерер всегда будет пытаться автоматически определить нужный шаблон, но вы
можете определить в stash значение template
чтобы рендерить указанный вами
шаблон.
$self->render(template => 'foo/bar');
Выбор соответствующего формата format
и обработчика handler
очень прост.
$self->render(template => 'foo/bar', format => 'txt', handler => 'epl');
Поскольку обработка шаблона - часто используемая задача, она так же имеет сокращенную запись.
$self->render('foo/bar');
Все значения, переданные при вызове render
, назначаются в stash временно и
сбрасываются после окончания рендеринга.
Рендеринг встроенных шаблонов (inline)
Некоторые рендереры, такие как ep
, позволяют использовать шаблоны в строке.
$self->render(inline => 'The result is <%= 1 + 1%>!');
Так как авто определение зависит от пути, вы можете указать обработчик handler
.
$self->render(inline => "<%= shift->param('foo') %>", handler => 'epl');
Рендеринг Текста (text)
Perl символы могут быть визуализированы stash значением text
, переданный
текст будет автоматически закодирован в байты.
$self->render(text => 'Hello Wörld!');
Рендеринг Данных (data)
Бинарные данные можно обрабтаывать с помощью stash значения data
,
перекодирование в этом случае не производится.
$self->render(data => $octets);
Формирование JSON (json)
Значение stash json
позволяет передавать рендереру структуры данных Perl,
которые будут преобразованы непосредственно в JSON.
$self->render(json => {foo => [1, 'test', 3]});
Частичный рендеринг (partial)
Иногда вам может понадобиться получить результат рендеринга, например для
создания сообщений электронной почты. Это можно сделать с помощью значения
stash partial
.
my $html = $self->render('mail', partial => 1);
Код Ответа (status)
Возвращаемые коды ответа могут быть изменены посредством stash-ключа status
$self->render(text => 'Oops!', status => 500);
Тип содержимого (Content Type) (format)
Заголовок Content-Type
в ответе создается на основе соответствия MIME-типа,
указанного в stash значении format
.
$self->render(text => 'Hello!', format => 'txt');
Эти соответствия могут быть расширены или изменены.
# Application package MyApp; use Mojo::Base 'Mojolicious'; sub startup { my $self = shift; # Добавить новый MIME тип $self->types->type(txt => 'text/plain; charset=utf-8'); } 1;
Данные Stash
Данные могут быть переданы в шаблоны через stash
в любом из нативных
типов данных Perl.
$self->stash(author => 'Sebastian'); $self->stash(frameworks => [qw/Catalyst Mojolicious/]); $self->stash(examples => {tweetylicious => 'a microblogging app'}); <%= $author %> <%= $frameworks->[1] %> <%= $examples->{tweetylicious} %>
Это работает, так как все является просто обычными структурами Perl.
<% for my $framework (@$frameworks) { %> <%= $framework %> was written by <%= $author %>. <% } %> <% while (my ($app, $description) = each %$examples) { %> <%= $app %> is a <%= $description %>. <% } %>
Вспомогательные функции
Вспомогательные функции - это небольшие функции, you can use in templates and controller code.
<%= dumper [1, 2, 3] %> my $serialized = $self->dumper([1, 2, 3]);
Вспомогательная функция dumper
,например, будет использовать Data::Dumper для
сериализации структуры данных, которую вы передадите ему, это может быть очень
полезным для отладки.
Мы различаем помощников по умолчанию
которые имеют общее назначение, как
dumper
и помощников для тэгов
, которые специфичны для шаблонов и в
основном используются для генерации тэгов HTML
.
<%= javascript '/script.js' %> <%= javascript begin %> var a = 'b'; <% end %>
Все эти вспомогательные функции содержатся в плагинах Mojolicious::Plugin::DefaultHelpers и Mojolicious::Plugin::TagHelpers.
Общий шаблон (Layouts)
В большинстве случаев при использовании шаблонов ep
вы захотите поместить
ваш генерируемый контент в HTML-"обертку", благодаря общим шаблонам это
делается очень просто.
@@ foo/bar.html.ep % layout 'mylayout'; Hello World! @@ layouts/mylayout.html.ep <!doctype html><html> <head><title>MyApp!</title></head> <body><%= content %></body> </html>
Вы можете просто выбрать подходящий шаблон c помощью хэлпера layout
и
поместить результат текущего шаблона хэлпером content
.
Также можно передать обычное stash значение в хэлпер layout
.
@@ foo/bar.html.ep % layout 'mylayout', title => 'Hi there!'; Hello World! @@ layouts/mylayout.html.ep <!doctype html><html> <head><title><%= $title %></title></head> <body><%= content %></body> </html>
Включение составных шаблонов
Как и большинство вспомогательные функций include
- это просто сокращение,
которое сделает вашу жизнь немного проще.
@@ foo/bar.html.ep <!doctype html><html> <%= include 'header' %> <body>Bar!</body> </html> @@ header.html.ep <head><title>Здорово!</title></head>
Вместо include
можно также вызывать render
с аргументом partial
.
@@ foo/bar.html.ep <!doctype html><html> <%= $self->render('header', partial => 1) %> <body>Bar!</body> </html> @@ header.html.ep <head><title>Здорово!</title></head>
Но есть одна небольшая разница между двумя этими способами, если передать
в include
значения stash, они будут автоматически локализованы и
доступны только в частичном шаблоне.
@@ foo/bar.html.ep <!doctype html><html> <%= include 'header', title => 'Hello!' %> <body>Bar!</body> </html> @@ header.html.ep <head><title><%= $title %></title></head>
Повторно используемые блоки в шаблонах
Не очень весело повторять код дважды, поэтому можно строить блоки в ep
шаблонах для их повторного использования. Они работают аналогично обычным
функциям в Perl.
<% my $block = begin %> <% my $name = shift; %> Привет <%= $name %>. <% end %> <%= $block->('Sebastian') %> <%= $block->('Sara') %>
Блоки всегда отделяются ключевыми словами begin
и end
.
% my $block = begin % my $name = shift; Привет <%= $name %>. % end % for (1 .. 10) { %= $block->('Sebastian') % }
Простое преобразование в равнозначный Perl-код может выглядеть следующим образом.
my $output = ''; my $block = sub { my $name = shift; my $output = ''; $output .= "Привет $name."; return $output; } for (1 .. 10) { $output .= $block->('Sebastian'); } print $output;
Блоки содержимого
Блоки и помощник content_for
могут быть использованы, чтобы передать
целые секции шаблона в общий шаблон.
@@ foo/bar.html.ep % layout 'mylayout'; <% content_for header => begin %> <title>MyApp!</title> <% end %> Hello World! <% content_for header => begin %> <meta http-equiv="Pragma" content="no-cache"> <% end %> @@ layouts/mylayout.html.ep <!doctype html><html> <head><%= content_for 'header' %></head> <body><%= content %></body> </html>
Наследование шаблонов
Наследование продвигает еще на один шаг концепцию шаблонов, о котором говорилось выше.
В отличие от content_for
хэлпер content
не позволяет добавления к существующим
значениям, это дает возможность перезагружать целые разделы шаблонов. Единственное
различие между layout
и extends
в том, что расширенные шаблоны(extended temlates)
не содержат префиксы layouts/
.
@@ first.html.ep %# "<div>Первый заголовок!Первый футер!</div>" <div> <%= content header => begin %> Первый заголовок! <% end %> <%= content footer => begin %> Первый футер! <% end %> </div> @@ second.html.ep %# "<div>Второй заголовок!Первый футер!</div>" % extends 'first'; <% content header => begin %> Второй заголовок! <% end %> @@ third.html.ep %# "<div>Второй заголовок!Третий футер!</div>" % extends 'second'; <% content footer => begin %> Третий футер! <% end %>
Эта цепочка может продолжаться и продолжаться, позволяя многократное использование шаблонов.
Кэширование блоков шаблона
Скомпилированные шаблоны всегда кэшируются в памяти, но с хэлпером memorize
вы можете пойти на один шаг вперед и предотвратить блоки шаблонов от
выполнения более одного раза.
<%= memorize begin %> Этот шаблон был скомпилирован в <%= localtime time %>. <% end %>
Выбор режимов exception и not_found шаблонов
Хотя встроенные шаблоны exception
и not_found
очень полезны
в процессе разработки, вы, скорее всего, хотите показать своим пользователям
нечто большее, связанное с вашей программой в продакшене.
Вот почему Mojolicious
будет всегда пытаться to render exception.$mode.html.*
или not_found.$mode.html.*
прежде чем отступать нв встроенные
по умолчанию шаблоны.
@@ exception.production.html.ep <!doctype html><html> <head><title>Ошибка сервера</title></head> <body>Случилось что-то плохое!</body> </html> @@ not_found.production.html.ep <!doctype html><html> <head><title>Страница не найдена</title></head> <body>Кажется, такой страницы не существует.</body> </html>
ДОПОЛНИТЕЛЬНО
Более мощные, но реже используемые возможности.
Chunked Transfer Encoding
Для очень динамичного контента вы можете не знать ответа Content-Length
заранее, тот, где chunked
Transfer-Encoding
очень удобно. Общим использованием
будет отправлять секцию head
HTML документа в браузер заранее и ускорить
предварительную загрузку изображений и стилей по имеющимся ссылкам.
$self->write_chunk('<html><head><title>Example</title>'); $self->write_chunk('<link href="example.css" rel="stylesheet"'); $self->write_chunk(' type="text/css"></head>', sub { my $self = shift; $self->write_chunk('<body>Example</body></html>'); $self->write_chunk(''); });
Необязательный drain callback гарантирует, что все предыдущие куски были написаны до продолжения обработки. Пустой кусок отмечает завершение потока.
22 <html><head><title>Example</title> 29 <link href="example.css" rel="stylesheet" 17 type="text/css"></head> 1C <body>Example</body></html> 0
Особенно в сочетании с большой задержкой соединения, это может быть
очень полезным для Comet (long polling
).
Из-за ограничений на некоторых веб-серверах это может не работать идеально
во всех средах развертывания.
Кодировка
Шаблоны хранятся в файлах, по умолчанию в UTF-8
, но это легко можно
изменить.
# Application package MyApp; use Mojo::Base 'Mojolicious'; sub startup { my $self = shift; # Другая кодировка $self->renderer->encoding('koi8-r'); } 1;
Все шаблоны из секции DATA имеют ту же кодировку, что и сам Perl-скрипт, поэтому не забывайте использовать прагму utf8 при необходимости.
Base64 кодированные данные в секции DATA
Закодированные в Base64 статические файлы, такие как изображения, можно
легко хранить в секции DATA
вашей программы, подобно шаблонам.
@@ favicon.ico (base64) ...base64 encoded image...
Разворачивание шаблонов из DATA
Шаблоны, хранящиеся в файлах, имеют больший приоритет перед шаблонами из секции
DATA
. Это позволяет вам включить набор шаблонов по умолчанию в ваше
приложение, которые пользователь позже может кастомизировать. Команда
inflate
запишет все шаблоны из секции DATA
в отдельные файлы в каталог
templates
.
$ ./myapp.pl inflate
Кастомизация синтаксиса шаблонов
Можно легко изменить весь синтаксис шаблонов, подключая расширение
ep_renderer
с желаемой конфигурацией.
use Mojolicious::Lite; plugin ep_renderer => { name => 'mustache', template => { tag_start => '{{', tag_end => '}}' } }; get '/' => 'index'; app->start; __DATA__ @@ index.html.mustache Hello {{= $name }}.
Mojo::Template содержит весь список возможных опций.
Добавление своих хелперов
Добавлять и переопределять хелперы очень просто, а использовать их можно для чего угодно.
use Mojolicious::Lite; helper debug => sub { my ($self, $string) = @_; $self->app->log->debug($string); }; get '/' => sub { my $self = shift; $self->debug('action'); } => 'index'; app->start; __DATA__ @@ index.html.ep % debug 'template';
Хэлперы могут также принимать блоки шаблона как последний аргумент, это например позволяет очень очень приятно в использовании хэлперы тегов и фильтров.
use Mojolicious::Lite; use Mojo::ByteStream; helper trim_newline => sub { my ($self, $block) = @_; my $result = $block->(); $result =~ s/\n//g; return Mojo::ByteStream->new($result); }; get '/' => 'index'; app->start; __DATA__ @@ index.html.ep <%= trim_newline begin %> Какой-то текст. <%= 1 + 1 %> Еще текст. <% end %>
Делать обертку для результата хэлпера в Mojo::ByteStream объект может предотвратить двойное экранирование
Добавление своей системы шаблонов
Возможно вы предпочитаете другой шаблонизатор вместо ep
, все что вам нужно
для его подключения - добавить новый обработчик handler
.
use Mojolicious::Lite; app->renderer->add_handler( mine => sub { my ($r, $c, $output, $options) = @_; # Один раз использовать встроенный шаблон my $inline = $options->{inline}; # Сформировать относительный путь шаблона my $name = $r->template_name($options); # Попытка найти подходящий шаблон в секции DATA my $content = $r->get_data_template($options, $name); # Сформировать абсолютный путь шаблона my $path = $r->template_path($options); # Эта часть зависит от вас и вашей системы шаблонов :) ... # Передать rendered результат обратно в рендерер $$output = 'The rendered result!'; } ); get '/' => 'index'; app->start; __DATA__ @@ index.html.mine ...
Поскольку большинство систем шаблонов не поддерживают шаблоны в
секции DATA
, рендереры предоставляют методы, чтобы помочь вам в этом.
ДОПОЛНИТЕЛЬНО
Вы можете продолжить перейдя на страницу Mojolicious::Guides или на wiki-страничку Mojolicious http://github.com/kraih/mojo/wiki, в котором содержится много документации и примеров от различных авторов.