HTML::FormHandler

Gabriel Andrade Santana
Publicado em 01/01/2010

HTML::FormHandler

HTML::FormHandler é um módulo de processamento de formulários web feito em Moose, suas origens remontam ao Form::Processor e destaca-se pela automatização das trabalhosas e repetitivas etapas intrínsecas ao processamento de formulários. A renderização do formulário em HTML, validação e tratamento dos dados submetidos são excutados convenientemente e sem complicações pelo HTML::FormHandler

Ao extender o HTML::FormHandle::Model::DBIC torna-se possível usar o DBIx::Class como modelo para o formulário mapeando os métodos acessores e relacionamentos do DBIC em campos HTML no formulário. Por exemplo, um relacionamento do tipo belongs_to pode se transformar num <<select> onde cada linha da tabela relacionada seria usada pra compor a lista de <option>.

Os formulários do HTML::FormHandler são classes de Perl, portanto, a mesma flexibilidade e extensibilidade da linguagem estão presentes na construção de seus formulários. Você pode compor suas classes de formulários com unidades lógicas reponsáveis por tarefas específicas como validação, transformações ou processamento do formulário.

Montado no alce

Por ter sido escrito usando Moose, o HTML::FormHandler nos permite lançar mão de seus recursos simplificando o processo de criação dos formulários. A validação dos campos pode ser feita através da aplicação de type constraints, e usando a sintaxe intuitiva provida pelo Moose é possível criar novos formulários a partir de herança ou composição de roles além do uso dos method modifiers (before, after, around), que são uma mão na roda quando é necessária uma certa customização no comportamento de certas classes de formulários.

A seguir o exemplo de um formulário usando HTML::FormHandler:

   package MyApp::Form::Foo;
   use HTML::FormHandler::Moose;
   extends 'HTML::FormHandler';

   use Moose::Util::TypeConstraints;

   subtype 'Maioridade'
     => as 'Int'
     => where { $_ >= 18 }
     => message { "$_ !? Não é permitido menores aqui. Meia volta, Mantovani!"};

   no Moose::Util::TypeConstraints;

   has_field 'nome' => (
     type => 'Text',
     label => 'Nome do usuário',
   );

   has_field 'idade' => (
     type => 'Integer',
     label => 'Idade',
     apply => ['Maioridade']
   );

   has_field submit => ( type => 'Submit' );

   around 'update_model' => sub {...};

   no HTML::FormHandler::Moose;

   #
   #

   package MyApp::Bar;

   use MyApp::Form::Foo;

   my $form = MyApp::Form::Foo->new;

   $form->render; # Renderizando form em html

   $form->process(params => \%params);

   if ($form->validated){
      ...
   }

Nessa classe estão presentes os atributos e palavras-chave mais comumente utilizadas na criação de um formulário.

A palavra-chave has_field é usada para declarar os campos dos formulários, já o atributo type define o tipo do campo. label define o rótulo que deve aparecer em cada campo no formulário HTML.

O HTML::FormHandler já traz consigo uma série de tipos pré-definidos que especificam qual deve ser carregado para renderizar e processar cada campo. Dentre eles destacam-se o Text e Integer< para strings simples e inteiros, respectivamente, Select para <input do tipo <select> e Email para validação de endereços de emails, dentre outros como Money, Password, Date, etc. Toda essa gama de opções não impede que você possa criar seus próprios tipos para servir aos seus propósitos mais particulares, que por sinal é uma tarefa bem simples, instruções mais detalhadas podem ser encontradas nos manuais do HTML::FormHandler.

Além da validação default presente nos campos você implementar a sua própria de diversas maneiras, uma delas é ilustrada nesse exemplo onde é passado ao atributo apply uma lista de TypeConstraints que será aplicada aos valores submetidos no formulário. Nesse exemplo, a mensagem definida em Maioriadde será mostrada no formulário caso os valores não "passem" na validação.

Mão na massa com o HTML::FormHandler

Vamos agora fazer alguma coisa útil com esse maravilhoso módulo. Nosso exemplo constitui parte de uma aplicação web fictícia em Catalyst e DBIx::Class que estará usando o HTML::FormHandler para lidar com os formulários de postagem de notícias no Equinócio.

Nosso Schema

  # Usando PostgreSQL como RDBMS

  package Equinocio::Web::Schema::Result::Noticia;
  use Moose;
  extends 'DBIx::Class::Core';
  __PACKAGE__->table("noticia");
  __PACKAGE__->add_columns(
    "id",
    {
      data_type     => "integer",
      default_value => "nextval('noticia_id_seq'::regclass)",
      is_nullable   => 0,
      size          => 4,
    },
    "titulo",
    {
      data_type     => "character varying",
      default_value => undef,
      is_nullable   => 0,
      size          => 255,
    },
    "categoria",
    {
      data_type     => "integer",
      default_value => undef,
      is_nullable   => 0,
      size          => 4
    },
    "conteúdo",
    {
      data_type     => "text",
      default_value => undef,
      is_nullable   => 0,
      size          => undef,
    }
    );
    __PACKAGE__->set_primary_key("id");
    __PACKAGE__->add_unique_constraint( "noticia_pkey", ["id"] );

    __PACKAGE__->belongs_to(
      "categoria",
      "Equinocio::Web::Schema::Result::Categoria",
      { id => "categoria" },
    );

    __PACKAGE__->many_to_many( tags => 'noticia_tags', 'tag' );

    #
    #

    package Equinocio::Web::Schema::Result::Categoria;
    use Moose;
    extends 'DBIx::Class::Core';
    __PACKAGE__->table("noticia");
    __PACKAGE__->add_columns(
      "id",
      {
        data_type     => "integer",
        default_value => "nextval('noticia_id_seq'::regclass)",
        is_nullable   => 0,
        size          => 4,
      },
      "nome",
      {
        data_type     => "character varying",
        default_value => undef,
        is_nullable   => 0,
        size          => 255,
      }
    );

Nossa classe de formulário

  package Equinocio::Web::Form::Noticia;
  use HTML::FormHandler::Moose;
  extends 'HTML::FormHandler::Model::DBIC';

  use utf8;
  use namespace::autoclean;
  use Moose::Util::TypeConstraints;

  subtype 'TextoCurto'
    => as 'Str'
    => where { length $_ <1000 }=""> message { 'Texto muito longo' };

  has '+item_class' => ( default => 'DB::Noticia' );

  has_field titulo => (
    type           => 'Text',
    label          => 'Título',
    unique         => 1,
    unique_message => 'Opa! Já existe uma entrada com esse título.'
  );

  has_field categoria => (
    type         => 'Select',
    label        => 'Categoria',
    column_label => 'name'
  );

  has_field conteúdo => (
    type             => 'TextArea',
    label            => 'Conteúdo',
    required         => 1,
    required_message => 'Não acha que está esquecendo de alguma coisa?',
    apply            => ['TextoCurto']
  );

  has_field enviar => ( type => 'Submit' );

  1;







O que é que tá acontecendo aqui??

Como extende HTML::FormHandler::Model::DBIC e por ter definido o atributo +item_class, o nosso form ao fim de uma validação com suceso, vai inserir ou atualizar uma Row de Noticia em nosso banco de dados. Percebam a restrição de unicidade explicitada pelo atributo unique no campo titulo. Antes de inserir/atualizar os dados o HTML::FormHandler::Model::DBIC verifica se já existe outra notícia com o mesmo título, caso exista, a mensagem definida em unique_message será exibida.

No campo categoria definimos através do atributo column_label qual coluna do registro será usada como label no <select> do formulário.

Já o campo conteúdo possui o atributo required setado, que faz com que o HTML::FormHandler exiba a mensagem definida em required_message se o campo for submetido vazio. Uma constraint denominada TextoCurto é declarada e aplicada ao valor submetido no campo, TextoCurto é usado para restringir o tamanho do texto a 1000 caracteres.

Por fim um campo do tipo Submit que renderizará o nosso botão para submeter o formulário.

Enquanto isso... num Controller qualquer.

  package Equinocio::Web::Controller::Noticia;
  use Moose;

  BEGIN { extends 'Catalyst::Controller'}

  use aliased 'Equinocio::Web::Form::Noticia' => 'FormNoticia';

  sub base :Chained('/') :PathParth('noticia'): CaptureArgs(0) {
    my ($self, $c) = @_;
    $c->stash->{collection} = $c->model('DB::Noticia');
  }

  sub create :Chained('base') :PathPart('nova') :Args(0){
    my ($self, $c) = @_;

    $nova_noticia = $c->stash->{collection}->new_result({});
    my $form = FormNoticia->new( item => $nova_noticia );
    $c->stash( form => $form , template => 'noticia/nova.tt');

    return unless $c->req->method eq 'POST');

    $form->process(params => $c->req->params);

    return unless $form->validated;

    $c->res->redirect(
      $c->uri_for_action( $c->controller->action_for('list') )
    );
  }




Por fim, nossa template

  [% form.render %]

Exatamente, só isso. Em nossa Action create criamos o formulário passando como parâmetro o novo registro onde iremos persistir os dados. Ao ser invocado em nossa template o método render vai fazer com que todos os campos do nosso fomulário sejam renderizados e apresentados na página. Na próxima vez em que o formulário for preenchido e submetido, ou seja, quando o método da requisição HTTP for um c<POST>, os parâmetros serão processados no formulário. Caso algum erro de validação ocorra o formulário é retornado ao usuário agora contendo as mensagens de erro em seus respectivos campos inválidos, caso contrário o fluxo de execução prossegue e, nesse caso, somos redirecionados para a página de listagem de notícias.

O formulário renderizado

Viu só como foi fácil construir um formulário? Rápido, simples, sem tocar em simplesmente uma única linha de HTML e acima de tudo usando os recursos mais avançados de Perl.

Até a próxima, e aproveitem o Equinócio!

AUTHOR

Gabriel Andrade <gabiruh  gmail • com> é desenvolvedor Perl na Aware TI (www.aware.com.br).

blog comments powered by Disqus