Документация Perl 5

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, в котором содержится много документации и примеров от различных авторов.