Выпуск 9. Ноябрь 2013

От редактора | Содержание | Обзор изменений Perl 5.19.5

Использование HTML::FormFu при работе с Catalyst

Введение в использование HTML::FormFu под Catalyst. Очень простые примеры, комментарии. Руководство для начинающих. Как создать Catalyst-приложение с нуля.

Все примеры были выполнены специально для публикации, в Windows-среде, под Strawberry Perl. В среде UNIX развернуть все необходимое и заставить работать должно быть даже проще. Приведенные примеры кода будут работать и в том, и в другом случае — они достаточно простые, чтобы не зависеть от тонкостей использования среды.

Для того, чтобы получить более полное представление о работе HTML::FormFu в Catalyst, создадим Catalyst-приложение с нуля. Более того, установим Catalyst под Windows. Можно было пойти более простым путем, и использовать уже готовое рабочее UNIX-окружение, но тогда оставалась вероятность, что в описании забудется какой-нибудь важный модуль, или аспект, без внимания к которому подключить HTML::FormFu будет не просто.

Введение

Что такое Catalyst?

Catalyst — это фреймворк для создания веб-приложений. Поддерживает концепцию MVC (Model-View-Controller).

Catalyst поставляется вместе со своим собственным HTTP-сервером, который можно использовать для разработки и тестирования. В боевых условиях, его используют в связке nginx + FastCGI, или Apache + mod_perl. Можно использовать другие серверы, но такие решения встречаются значительно реже.

Несмотря на регулярную критику, Catalyst остается самым мощным и популярным фреймворком в Perl-среде.

Что такое HTML::FormFu?

HTML::FormFu — это менеджер форм для Perl-приложений. Гибкий и мощный инструмент. Поддерживает все этапы работы с формами: создание форм, их валидацию, вывод ошибок, сохранение данных в БД. HTML::FormFu считается одной из самых мощных и функциональных систем для работы с формами в Perl.

Как установить Catalyst под Windows

Запускаем cpan в Perl-консоли. Потом вводим первую команду:

force install Catalyst::Runtime

На этом этапе может случится первый fail — если пытаться установить Catalyst (ну правильно, документацию читают только слабаки) вот так:

force install Catalyst

cpan что-то долго думает, что-то закачивает, устанавливает, но в итоге все заканчивается ошибкой. Устанавливать надо Catalyst::Runtime, а не Catalyst!

force install нужен, чтобы запретить cpan слишком много думать и обращать внимание на результаты тестов. При установке Perl-модулей под Windows — это необходимо.

Потом устанавливаем еще немного дополнительных модулей, без которых сложно построить минимальное Catalyst-приложение или не будет работать HTML::FormFu.

force install Catalyst::Controller::HTML::FormFu
force install Catalyst::Devel
force install DBD::mysql
force install DBI
force install DBIx::Class
force install Catalyst::Model::DBIC::Schema
force install Catalyst::View::TT
force install DBIx::Class::Schema::Loader

Для того, чтобы беспроблемно установить DBIx::Class::Schema::Loader — желательно прописать в переменных окружения путь к вашему локальному mysql-демону (во время тестирования использовалась локальная mysql-БД).

force install MooseX::MarkAsMethods
force install Catalyst::Plugin::Unicode::Encoding

Как создать каркас Catalyst-приложения под Windows

Создаем директорию, в которой будет расположен проект. Например: C:\Documents and Settings\username\www.

Совет: создавая директорию, следует избегать русскоязычных имен в пути к вашему проекту. В дальнейшем это может привести к возникновению проблем.

Переходим в созданную директорию. В командной строке Strawberry Perl вводим:

catalyst.pl MyApp

В результате, в каталоге www будет создана директория с именем MyApp, содержащая каркас Catalyst-приложения.

Как создать контроллер

Создаем контроллер Admin.pm, в дальнейшем он нам пригодится. Для этого в Perl-консоли выполняем команду:

perl script/myapp_create.pl controller Admin

Остальные контроллеры создаем вручную.

Как создать View

В отличие от контроллера, view вручную лучше не создавать. В Perl-консоли выполняем команду:

perl script/myapp_create.pl view TT TT

Команда создаст файл TT.pm в директории /lib/MyApp/View/TT.pm. Модуль TT.pm требуется отредактировать, добавив настройки:

package MyApp::View::TT;
use Moose;
use namespace::autoclean;

extends 'Catalyst::View::TT';

__PACKAGE__->config(
    TEMPLATE_EXTENSION => '.tt',
    render_die => 1,
    CATALYST_VAR => 'c',
    ENCODING     => 'utf-8',

);

1;

Далее, открываем файл MyApp.pm и добавляем еще немного конфигурационных данных для TT в блоке __PACKAGE__->config():

__PACKAGE__->config(
    # ...

    'View::TT' => {
        INCLUDE_PATH => [
            __PACKAGE__->path_to( 'root', 'src' ),
        ],
    },
);

Кроме того, в блок use Catalyst qw/.../; добавляем Unicode::Encoding. Это делается для того, чтобы Catalyst смог нормально обрабатывать русскоязычные символы в шаблонах.

use Catalyst qw/
    # ...
    Unicode::Encoding
/;

Если Unicode::Encoding не подключить, в дальнейшем можно будет увидеть в логах ошибку:

[error] Caught exception in engine "Wide character in syswrite at C:/strawberry/ perl/lib/IO/Handle.pm line 474."
Terminating on signal SIGINT(2)

Настройки для HTML::FormFu

В файле MyApp.pm в блок __PACKAGE__->config() добавляем настройки:

__PACKAGE__->config(
    # ...

    'Controller::HTML::FormFu' => {
        'model_stash' => {
            schema => 'DB'
        },
        constructor => {
            tt_args => {
                ENCODING => 'UTF-8',
            }
        }
    }
);

Как создать модель

В Perl-консоли выполняем команду:

perl script/myapp_create.pl model DB DBIC::Schema MyApp::Schema::DB create=static "dbi:mysql:test" "root" ""

После этого в блоке connect_info файла lib/MyApp/Model/DB.pm добавляем параметр mysql_enable_utf8:

connect_info => {
    dsn => 'dbi:mysql:test',
    user => 'root',
    password => '',
    mysql_enable_utf8 => 1
}

Этот параметр позволит корректно отображать данные из таблиц БД. Разумеется, если данные хранятся в кодировке UTF-8.

Как запустить сервер Catalyst под Windows

В консоли выполняем команду:

perl script/myapp_server.pl

Это позволит запустить специальный web-сервер. Для боевого использования он не подходит, а для тестирования новых возможностей — вполне. В эту же консоль будет печататься вывод отладочной информации сервера.

В браузере вводим адрес:

http://localhost:3000

Создаем интерфейс администратора с помощью HTML::FormFu и Catalyst

Панель администратора — это скрытый от обычных посетителей блок сайта, который позволяет владельцу осуществлять управление контентом сайта и принципами его отображения. Например, настроить список используемых виджетов, добавить новые публикации, разместить баннеры и т.п.

Почти вся админка — это бесконечное число разнообразных форм. Использование менеджера форм тут более чем оправдано.

Поэтому в качестве простого примера создадим примитивный интерфейс администратора:

  • главную страницу панели администратора;
  • страницу со списком публикаций на сайте;
  • страницу для редактирования статей (на основе HTML::FormFu);
  • страницу для создания новой публикации (на основе HTML::FormFu).

Кроме того, позволим пользователю удалять статьи.

Дамп БД

Для начала нам потребуется база данных. Ниже — дамп БД, которая использовалась для приведенных примеров. Установить БД, если ее у вас нет, лучше еще до начала установки Catalyst с его модулями для создания моделей.

Теперь просто создаем таблицу и следим за кодировками, дабы избежать проблем в будущем. Как современные люди, мы выбираем UTF-8.

CREATE DATABASE IF NOT EXISTS `test`;
USE `test`;

CREATE TABLE IF NOT EXISTS `articles` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `full` text,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

INSERT INTO `articles` (`id`, `name`, `full`) VALUES
    (1, 'name', 'text'),
    (2, 'name2', 'текст');

Главная страница панели администратора

Для максимально удобной работы с HTML::FormFu в Catalyst, существует модуль Catalyst::Controller::HTML::FormFu. Если планируем в создаваемом контроллере использовать менеджер форм, подключаем Catalyst::Controller::HTML::FormFu с помощью extends.

Создать форму можно либо определив ее параметры в непосредственно в коде, либо — по конфигурационному файлу. Конфигурационный файл предпочтительней. Во-первых, это все-таки элементы HTML-кода, и уже давно стало хорошим правилом отделять разметку от кода. Во-вторых, при создании панели администратора в боевых условиях форм будет очень много, и удобнее хранить их отдельно.

Чтобы создать форму, нужно задать в атрибутах Catalyst action:FormConfig. Если не указать путь к форме в атрибуте :FormConfig, система решит, что в качестве имени формы следует использовать имя action. Поиск файла будет осуществляться примерно по такому пути: root/forms/controller_name/action_name.yml.

Наличие атрибута :FormConfig говорит системе, что требуется создать объект формы и разместить его в хранилище Catalyst$c->stash->{form}. Чтобы вывести форму на html-странице, достаточно добавить в шаблон инструкцию: [% form %]. form — это полностью готовый блок HTML-кода формы.

Проверить, была ли отправлена форма и корректны ли ее данные можно с помощью метода $form->submitted_and_valid.

Другой метод позволит получить данные из полей формы: $form->param_value('field_name'). Кроме того, можно получить значения полей формы с помощью $c->req->param('field_name').

Модуль /lib/MyApp/Controller/Admin.pm

package MyApp::Controller::Admin;
use Moose;
use namespace::autoclean;

BEGIN { extends 'Catalyst::Controller'; }

sub index :Path :Args(0) {
    my ( $self, $c ) = @_;

        $c->stash->{template} = 'admin/index.tt';
        $c->forward('View::TT');
}

__PACKAGE__->meta->make_immutable;

1;

Шаблон /root/src/admin/index.tt

Переходим в директорию root. Создаем в ней каталог src. В каталоге src создаем каталог admin.

Не забываем, что все шаблоны должны быть в кодировке UTF-8 .

<h1>Панель администратора</h1>
<ul>
<li><a href="[% c.uri_for( 'articles' ).path_query %]">Список публикаций</a></li>
</ul>

Для наглядности TT-шаблоны не содержат никакого HTML-кода, кроме самого необходимого.

Работа с публикациями

Модуль /lib/MyApp/Controller/Admin/Articles.pm

Один модуль будет отвечать и за отображение списка публикаций, и за создание новых, редактирование старых, и за удаление статей.

package MyApp::Controller::Admin::Articles;
use Moose;
use namespace::autoclean;

BEGIN { extends qw/
    Catalyst::Controller::HTML::FormFu
/ }

=head2 index

Отображение страницы со списком статей

=cut

sub index :Chained('') :Path :Args(0) {
    my ( $self, $c ) = @_;

    $c->stash->{articles_list} = [$c->model('DB::Article')->search(
        {}
    )->all];

    $c->stash->{template} = 'admin/articles.tt';
    $c->forward('View::TT');
}

=head2 remove

Удаление статьи по ее идентификатору в БД

=cut

sub remove :Chained('') :Path('remove') :Args(1) {
    my ( $self, $c, $id ) = @_;

    $c->model('DB::Article')->find({ id => $id})->delete;
    $c->res->redirect($c->uri_for('/admin/articles'));
}

=head2 update

Данный блок кода отвечает за отображение страницы для редактирования статьи,
и за обновление статьи в БД.

=cut

sub update :Chained('') :Path('update') :Args(1) :FormConfig('article/update.yml') {
    my ( $self, $c, $id ) = @_;

    my $form = $c->stash->{form};

    if ($form->submitted_and_valid) {
        eval {
            my $obj = $c->model('DB::Article')->update_or_create({
                id => $form->param_value('id'),
                name => $form->param_value('name') || undef,
                full => $form->param_value('full') || undef
            });
        };

        $c->warn('DB error') if $@;

    } else {
        my $article = $c->model('DB::Article')->search( {
            id => $id,
        } )->first;

        $c->stash->{form}->default_values({
            'name' => $article->name,
            'full' => $article->full,
            'id' => $article->id
        });
    }

    $c->stash->{template} = 'admin/article/update.tt';
    $c->forward('View::TT');
}

=head2 create

Страница для создания новой статьи. Если пользователь нажал кнопку "Сохранить",
статья отправляется в БД, затем клиента перенаправляют на страницу со списком статей.

=cut

sub create :Chained('') :Path('create') :Args(0) :FormConfig('article/create.yml') {
    my ( $self, $c, $id ) = @_;

    my $form = $c->stash->{form};

    if ($form->submitted_and_valid) {
        eval {
            my $obj = $c->model('DB::Article')->create({
                name => $form->param_value('name') || undef,
                full => $form->param_value('full') || undef
            });
        };

        $c->warn('DB error') if $@;

        $c->res->redirect($c->uri_for('/admin/articles'));
    } else {
        $c->stash->{template} = 'admin/article/create.tt';
        $c->forward('View::TT');
    }
}

__PACKAGE__->meta->make_immutable;

1;

default_values — позволяет задать значения по умолчанию полям формы, то, что увидит пользователь, если откроет страницу с формой.

Можно использовать метод $form->submitted, чтобы понять, была форма отправлена или нет. Форма может быть отправлена, но не пройти валидацию. Метод $form->submitted_and_valid учитывает и отправку, и благополучное прохождение валидации.

Шаблон /root/src/admin/articles.tt

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Формочка</title>
    <link rel="stylesheet" type="text/css" href="/static/style.css" />
</head>
<body>

<table cellspacing="0" id="result_list">
[% FOREACH article = articles_list %]
    <tr>
    <td>
        <a href="[% c.uri_for( 'update', article.id ).path_query %]">[% article.name %]</a>
        <a href="[% c.uri_for( 'remove', article.id ).path_query %]">Удалить</a>
    </td>
    </tr>
[% END %]
</table>
<a href="[% c.uri_for( 'create' ).path_query %]">Добавить новую статью</a>

</body>
</html>

Шаблон /root/src/admin/article/update.tt

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Формочка</title>
    <link rel="stylesheet" type="text/css" href="/static/style.css" />
</head>
<body>

Update article

[% form %]

</body>
</html>

Шаблон /root/src/admin/article/create.tt

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Формочка</title>
    <link rel="stylesheet" type="text/css" href="/static/style.css" />
</head>
<body>

Create new article

[% form %]

</body>
</html>

CSS-стили для формы /root/static/style.css

Добавим совсем простую CSS-таблицу, которая позволяет аккуратно вывести элементы формы. В дальнейшем, CSS можно усложнить, создавая с его помощью профессиональное оформление всей панели администратора.

form {
  width: 40em;
}

.submit {
  display: block;
}

label {
  display: block;
}

YAML-конфиг для формы /root/forms/article/update.yml

По умолчанию Catalyst-приложение будет искать формы в директории root/forms. Для работы с конфигурационными файлами Catalyst::Controller::HTML::FormFu использует Config::Any. Соответственно, хранить конфигурационную информацию о формах кроме YAML-формата можно в XML, JSON, конфигах в стиле Apache, Perl-коде и т.п.

Переходим в директорию root. Создаем в ней каталог forms. В каталоге forms создаем каталог article и файл update.yml.

---
attributes:
    id: element-form

auto_fieldset:
    attributes:
        class: module aligned

---
elements:
    - type: Hidden
      name: id

    - type: Text
      name: name
      label: Название статьи

    - type: Textarea
      name: full
      label: Текст статьи

    - type: Submit
      name: submit
      value: Сохранить

YAML-конфиг для формы /root/forms/article/create.yml

---
attributes:
    id: element-form

auto_fieldset:
    attributes:
        class: module aligned

---
elements:
    - type: Text
      name: name
      label: Название статьи

    - type: Textarea
      name: full
      label: Текст статьи

    - type: Submit
      name: submit
      value: Сохранить

Вот и все. Простой прототип панели администратора с использованием HTML::FormFu готов. Теперь на основе полученных результатов можно пробовать усложнять формы, вводить в работу сложные поля и правила валидации, добавлять JavaScript для работы со сложными элементами, CSS для создания современного интерфейса.

В данном руководстве ничего не сказано про работу HTML::FormFu с БД своими средствами. Честно говоря, автора эти возможности не впечатлили. Возможно, примеры, которые встречались в сети, были слишком простыми. Но пока не видно никакой разницы, использовать для сохранения в БД средства HTML::FormFu или стандартный запрос с помощью DBIx.

Наталья Анисимова


От редактора | Содержание | Обзор изменений Perl 5.19.5
Нас уже 1393. Больше подписчиков — лучше выпуски!

Комментарии к статье