Catalyst et FormFu

user_icon admin | icon2 Catalyst | icon4 4/9/2007 23h19| Type doc: article| Type File: txt| icon3 No Comment

Catalyst et FormFu


1. Qu'est ce que c'est

HTML::FormFu est un module Perl qui assure sous Catalyst la création de formulaire HTML , la validation et le rendu.

Attention

Il s'agit de code en version Beta

HTML::FormFu nous fourni deux méthodes pour créer des formulaires. Avec du code ou par fichier de config YAML.

La création et le rendu ne posant pas de problème insumontable, arrêtons nous un instant sur la validation.

La validation du formulaire se fait à travers les étapes suivantes:

  • filter

  • constraint

  • inflate

  • validate

  • transform

Chaque étape est exécuter si seulement la précédente s'est correctement terminée. Chacune , excepté filter peuvent lever des exceptions qui seront affichées sous forme d'erreur.

L'étape Filter permet de récupérer les flux des entrées ( paramètres CGI ). Ce flux peut être nettoyé au moyen du filtre 'TrimEdges'. Le filtre 'NonNumeric' sera utilisé pour supprimer tous les espaces et trait d'union dans les champs carte de crédit par exemple.

L'étape Constraint quand à elle nous servira à valider le contenu des entrées vérifiant certaines règles. ( Requiered, size, Integer ...)

Le But d' Inflate est de transformé les données. Par exemple transformer une date en objet DateTime de Perl ou encore transformer un fichier téléchargé en objet Imager.

Validate est le niveau supérieur de vérification de règles. Par exemple 'Est-ce que cette date est antérieure à celle-ci", Est ce que le champ A est coché lorsque les champs B et C le sont.

Transform est fournie comme un moyen permettant de faire toutes les autres choses nécessaires après la validation des données.

Tout cela n'est certes pas très simple, rien ne vaut la pratique. (les sources étant fournies dans le dernier chapitre)

2. Installation

La dernière mise à jour de FormFu est téléchargeable à partir de :


svn checkout http://html-formfu.googlecode.com/svn/trunk/ trunk

Et ensuite installer HTML-FormFu et Catalyst-Controller-HTML-FormFu.

Bien entendu Catalyst devra être installé.

3. Un squelette pour les tests

La création d'une application simpliste va nous permettre de tester ces libraires.

L'application stockée dans /var/www/catalyst se nommera ' monapp' . Son modèle de donnée sera une base SQLite comportant trois tables (page, article, page_article). Ce modèle ne sera utilisé que plus tard dans la suite de cet article.

Tout dabord créeons normalement l'application.


mkdir /var/www/catalyst
cd /var/www/catalyst
catalyst.pl monapp
cd monapp

Le fichier monapp.sql de définition de la base de donnée :

cat > monapp.sql << __EOF__

CREATE TABLE page (
   id_page INTEGER PRIMARY KEY,
   titre VARCHAR(40)
);

CREATE TABLE article(
   id_article INTEGER PRIMARY KEY,
   titre VARCHAR(40),
   contenu VARCHAR(2000)
);


CREATE TABLE page_article(
   id INTEGER PRIMARY KEY,
   page INTEGER REFERENCES page,
   article INTEGER REFERENCES article
);


INSERT INTO page VALUES(1, 'Titre de la premiere page');
INSERT INTO page values(2, 'Titre de la seconde page');


INSERT INTO article values(1, 'Titre Article 1', 'contenu article 1');
INSERT INTO article values(2, 'Titre Article 2', 'contenu article 2');


INSERT INTO article values(3, 'Titre Article 3', 'contenu article 3');
INSERT INTO article values(4, 'Titre Article 4', 'contenu article 4');


INSERT INTO page_article values(1,'1','1');
INSERT INTO page_article values(2,'1','2');


INSERT INTO page_article values(3,'2','3');
INSERT INTO page_article values(4,'2','4');
__EOF__

Construisons de suite notre base monapp.db.


sqlite3 monapp.db < monapp.sql

Maintenant il nous faut une vue TT et un modèle de données.

perl script/monapp_create.pl view TT TT
perl script/monapp_create.pl model MonModel DBIC::Schema monapp::Schema create=static dbi:SQLite:/var/www/catalyst/monapp/monapp.db

On peux remarquer au passage que les tables ont été découverte. Leur classes sont stockées dans lib/monappli/Schema/*.pm .

FormFu utilise des fichiers TT pour la création des formulaires. On les insère immédiatement avec l'aide du Helper.


Pour accéder à notre application il lui manque encore le 'controller'. On va l'appeler ' test' et on y accédera via l'url http://localhost:3000/test. Se sera donc la methode index qui sera appelée. Plutôt que d'utiliser le 'Helper' de catalyst, je le crééerai manuellement.

cat > lib/monapp/Controller/test.pm << __EOF__
package monapp::Controller::test;

sub index : Local : FormConfig {
        my ( $self, $c ) = @_;

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

1;
__EOF__

Si l'on fait un man de Catalyst::Controller::HTML::FormFu on remarque que:


               # using the FormConfig attribute is equivalent to:
               #
               #
               # $form->load_config_file(’root/forms/my/controller/bar.yml’);
               #
               # $c->stash->{form}   = $form;
               #

               # so you only need to do the following...

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

Et en effet si l'on démarre l'application et que l'on se connecte sur l'url test l'erreur suivante est retournée.

Caught exception in monapp::Controller::test->index "file not found: '/var/www/catalyst/monapp/root/forms/test/index.yml' at
 /usr/local/share/perl/5.8.8/Catalyst/Controller/HTML/FormFu/Action/Config.pm line 35"

Naturellement il nous manque le fichier YAML de définition du formulaire. Le fichier suivant nous permet de teser le fonctionnement de FormFu.


mkdir -p root/forms/test/
cat > root/forms/test/index.yml << __EOF__
elements:
      - type: Text
        name: user
        constraints:
          - Required
      - type: Password
        name: pass
        constraints:
          - Required
      - type: Submit
__EOF__

Et si on redémarre, nous avons à nouveau une erreur:

Coldn't render template "file error - test/index.tt: not found"

Mais sûr, il nous faut un fichier template pour afficher le rendu du formulaire.

mkdir root/test
cat > root/test/index.tt << __EOF__
[% form %]
__EOF__

Ouf ... nous avons enfin notre formulaire de demande de login/password. La validation du formulaire fonctionne correctement. (voir contraints dans fichier yml )

Pour résumer si nous souhaitons profiter de la puissance de FormFu pour créer un formulaire il nous faut :

  • Définir le controleur et une méthode pour accéder au formulaire ( ... normal )

  • Créer le fichier YAML dans root/forms/Nom du Controller/Nom Methode.yml

  • Créer le fichier Template toolkit ( contenant au moins [% form %] ) dans root/Nom_du_controller/Nom_Methode.tt

Les prochains chapitres décrivent les divers éléments disponibles dans HTHML::Formfu et les contraintes liés à la validation de ceux-ci.

4. Les éléments des formulaires

Dans ce chapitre nous verrons les divers éléments qu'il est possible d'insérer dans un formulaire FormFu.

4.1. Emplacement

Par défaut le fichier de définition d'un formulaire est stocké dans root/forms/Nom_du_Controller/Nom_Methode.yml.

4.2. Type "Block"

Pour grouper des éléments dans un bloc nous devons utiliser le type 'block'

Modifions par exemple notre fichier index.yml de manière à ce qu'il ressemble à :

elements:
      - type: Block
        elements:
          - type: Password
            name: pass

      - type: Block
        tag: span
        content: Le contenu du span ...

4.3. Type 'Fieldset'

Pour regrouper des éléments nous devons utiliser le type 'fieldset' et indiquer auto_fieldset=1

auto_fieldset: 1
elements:
      - type: Text
        name: user
        constraint:
          - Required
      - type: Password
        name: pass
        constraint:
          - Required
      - type: Submit
      - type: Fieldset
      - type: Text
        name: nom
        label: Votre nom
      - type: Fieldset
      - type: Text
        name: toto

Trois set sont ainsi créés.

4.4. Type 'Text'

Une zone de saisie uniligne est construite comme ci-dessous:

elements:
      - type: Text
        name: zonesaisie
        size: 60

4.5. Type 'Textarea'

Une zone de saisie multiligne est construite comme ci-dessous:

element_defaults:
  Textarea:
    cols: 120
    rows: 10
    add_attributes:
      class: bigbox
elements:
      - type: Textarea
        name: zonesaisie

4.6. Type 'Password'

Allons un peu plus loin avec le type 'password'.

Dans ce cas le champ utilisateur devra être une adresse mail, et le mot de passe devra être entré deux fois.

    elements:
      - type: Text
        name: username
        label: Email
        constraint:
          - Email
          - type: Required

      - type: Password
        name: password
        label: Mot de passe
        constraint:
          - type: Required
          - type: Equal
            others: repeat-password

      - type: Password
        label: Mot de passe
        name: repeat-password

      - type: Submit

4.7. Type 'Radiogroup'

Il s'agit d'un groupe de bouton 'radio'

elements:
  - type: Radiogroup
    name: rg1
    label: Groupe de bouton 'radio'
    values: [un, deux, trois]

4.8. Type 'Checkbox'

Il s'agit des cases à cocher.

elements:
  - type: Checkbox
    name: cb1
    label: Case à cocher

4.9. Type 'Select'

Selection parmi une liste

elements:
  - type: Select
    name: s2
    values: [un, deux, trois]

Ou encore avec optgroup

elements:
  - type: Select
    name: opt1
    label: Un 'Select' avec optgroups
    options:
      - group: [[1, un], [2, deux]]
        label: opt1
      - [3, non-optgroup]
      - group: [[4, quatre], [5, cinq]]
        label: opt2

Dans un intervalle

      - type: Select
        name: Annee
        value_range: [1900, 2000]

4.10. Type 'Image'

elements:
  - type: Image
    name: i1
    src: http://localhost:3000/feedback/captcha
    label: une Image

4.11. Type 'Hidden'

elements:
  - type: Hidden
    name: h1

5. Les contraintes

Sur chaque champ du formulaire il est possible d'associer des contraintes.

Nous voulons par exemple que dans un champ soit insérer une adresse mail, ou que le nombre entré soit inférieur à 20 ou .... Tout cela est possible avec les 'contraints'

Ci-dessous la liste des contraintes actuellement utilisable:

AllOrNone.pm     Callback.pm  Length.pm        _others.pm    Set.pm
ASCII.pm         DependOn.pm  MaxLength.pm     Printable.pm  SingleValue.pm
AutoSet.pm       Email.pm     MinLength.pm     Range.pm      Word.pm
Bool.pm          Equal.pm     MinMaxFields.pm  Regex.pm
CallbackOnce.pm  Integer.pm   Number.pm        Required.pm

Il est possible de personnaliser la sortie d'erreur lorsque la contrainte n'est pas respectée. (message)

    elements:
      - type: Text
        name: test
        constraints:
          - type: Length
            min: 8
            message: Doit comporter au moins 8 caractères

5.1. AutoSet

Avec AutoSet on s'assure que l'entrée fournie appartient bien au entrées proposées. Ne fonctionne qu'avec select et radiogroup.

  - type: Select
    values: [yes, no]
    constraints: [AutoSet]

... est identique à:

  - type: Select
    values: [yes, no]
    constraints:
      - type: Set
        set: [yes, no]

5.2. Regex

La contraintes Regex nous permet de valider une entrée répondant à une expression régulière. (Regexp::Common)

codé:
$field->constraint('Regex')->common([ 'URI', 'HTTP', { -scheme => 'https?' } ]);

ou dans le fichier yaml:

elements:
  - type: Text
    name: foo
    constraints:
      - type: Regex
        common: [URI, HTTP, {-scheme, https?}]

6. DBIx::Class::FormFu

note: perl script/myapp_create.pl HTML::FormFu a déjà été exécuté précédement.

Le module DBIx::Class::FormFu nous facilite grandement la vie lorsqu'il s'agit de préremplir un formulaire avec des données provenant d'une base ou encore d'enregistrer en base les données d'un formulaire.

Le nom des champs du formulaire et celui des colonnes de la base doit être identique.

Nous avons à notre disposition la base Sqlite monapp.db précédement créee qui contient les tables:

  • page

  • article

  • page_article

Dans ce chapitre la table ' Article' sera utilisée, première chose à faire : remplir les champs d'un formulaire avec un enregistrement de la table.

6.1. Les données de l'enregistrement en formulaire (fill_formfu_values)

Pour cela je créer un controleur ' Article'


perl script/monapp_create.pl controller Article

Ensuite il suffit de modifier la méthode ' index' de notre controleur et le module ' Catalyst::Controller::HTML::FormFu' dont hérite notre controleur comme ceci:

cat lib/monapp/Controller/Article.pm

...

...

sub index : Local : FormConfig {
    my ( $self, $c ) = @_;

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

    my $result =  $c->model('Article')->find(1);
    $result->fill_formfu_values( $form );
}

Bien sur il nous faut , comme précédement, créer le fichier template du formulaire ( root/forms/article/index.yml ) et son fichier TT ( root/article/index.tt )


cat root/forms/article/index.yml

elements:
      - type: Text
        name: id_article
      - type: Text
        name: titre
      - type: Text
        name: contenu

cat root/article/index.tt
[% form %]

Pour finir il faut aussi que notre table 'article' charge le composant ' HTML::Formfu". Dans le fichier lib/monapp/Schema/Article.pm nous ajoutons le composant :

Et voilà, en se connectant à http://localhost:3000/article les données de l'enregistrement 1 ont bien remplies le formulaire.

6.2. Ajout d'un enregistrement (populate_from_formfu)

Si les données sont fournies (le paramètre CGI 'titre' est fourni) alors créer le nouvel enregistrement sinon afficher le formulaire


sub index : Local : FormConfig {
    my ( $self, $c ) = @_;

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

    if ( $c->req->params->{titre} ){
       my $row =  $c->model('Article')->new({});
       $row->populate_from_formfu( $form );
    }
}

6.3. Synthèse

Nous avons maintenant tous les outils pour visualiser/créer/supprimer des enregistrements d'une table.

Pour mettre en pratique les connaissances acquises je vais créer une interface de visualisation de la table ' Article' à partir de zéro. (voir 'Un squelette pour les test').

Celle-ci disposera d'une vue principale qui listera tous les articles, d'une page d'ajout, d'une page de modification et enfin d'une page suppression.

Toutes les actions se feront à travers les méthodes ' list', ' add', ' update' et ' delete' du controleur ' Article'. Une vue sera spécifique à chaque action. De plus pour les actions de modification ' add', ' update' et ' delete' nous disposerons d'un formulaire spécifique.

Soit en résumé:

lib/monapp/Controler/Article
                        - list
                        - add
                        - update
                        - delete

root/article/list.tt
             add.tt
             update
             delete.tt

root/forms/article/add.yml
                   update.yml
                   delete.yml

Je commence par créer le controleur ' Article'

perl script/monapp_create.pl controller Article

Comme précédement il doit hériter de ' Catalyst::Controller::HTML::FormFu'. Ne pas oublier de charger le composant ' HTML::Formfu" au schema de la table 'Article'

La méthode par défaut ' index' du controleur ' Article' redirige les requetes vers 'list'

sub index : Private {
    my ( $self, $c ) = @_;

    $c->forward('list');
}

Pour lister tous les enregistrements de la table ' Article' je créer la méthode 'list' dans le même controleur.

sub list : Local {
    my ( $self, $c ) = @_;

    $c->stash->{template} = 'article/list.tt';
    $c->stash->{articles} = [$c->model('Article')->all];
}

Et son template d'affichage ' root/article/list.tt'


<table>
<tr><th>ID</th><th>Titre</th><th>Contenu</th></tr>
[% FOREACH article IN articles -%]
<tr>

     <td><a href="[% c.uri_for('update/') _ article.id_article %]">[% article.id_article %]</a></td>
     <td>[% article.titre %]</td>
     <td>[% article.contenu %]</td>

     <td><a href="[% c.uri_for('delete/') _ article.id_article %]">Supprimer</a></td>
</tr>
[% END -%]
</table>
 <a href="[% c.uri_for('add') %]">Nouveau</a>

Si nous lançons l'application nous sommes redirigé vers ' list' qui affiche :

Jusque là tout va bien :)

Ajoutons maintenant la méthode 'add' . Celle-ci utilise ' populate_from_formfu' vu précédement :


sub add : Local : FormConfig {
    my ( $self, $c ) = @_;

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

    if ( $c->req->params->{titre} ){
       my $row =  $c->model('Article')->new({});
       $row->populate_from_formfu( $form );
       $c->forward('list');
    }
}

Ainsi si le ' titre' n'est pas founi c'est le formulaire d'ajout qui apparait sinon l'enregistrement est effectué et nous retournons à la liste des articles.

Encore une fois crééons le fomulaire YAML et le template TT.


cat root/forms/article/add.yml

elements:
      - type: Text
        name: id_article
      - type: Text
        name: titre
      - type: Text
        name: contenu
      - type: Submit

cat root/article/add.tt

[ %form %]

Nouvel enregistrement :

Retour à la liste:

Ok le nouvel enregistrement est bien créé :)

La suppression se fait tout aussi facilement :


sub delete : Local {
    # $id = clé primaire de l'article
    my ($self, $c, $id) = @_;

    $c->model('Article')->search({id => $id})->delete_all;

    $c->forward('list');
}

Et pour fini la mise à jour d'un article :


sub update : Local : FormConfig {
    my ( $self, $c, $id ) = @_;

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

    my $result =  $c->model('Article')->find($id);
    $result->fill_formfu_values( $form );

    if ( $form->submitted &amp;&amp; !$form->has_errors ) {
        $result->populate_from_formfu( $form );
        $c->forward('list');
    }
}

Dans un premier temps les données sont chargées dans le formulaire et s'il y a validation après modification alors populate_from_formfu est appelé. Nous sommes ensuite redirigé vers ' list'.

Pour que cette dernière action fonctionne il nous faut créer les fichier update.yml et update.tt (qui sont identique à add.yml et add.tt)

Et c'est tout :)

Les sources de ce chapitre sont diponibles ICI.

7. Localisation

Jusqu'à maintenant les erreurs de validation retournées par FormFu sont en anglais. De plus si je regarde dans lib/HTML/FormFu/I18N/ de HTML::FormFu j'apprend que le langage français n'est pas présent. Qu'à cela ne tienne on va l'ajouter.

Nota: Le fichier fr.pm est présent dans la dernière version d'HTML::FormFu. Il peut il manquer certaines traduction mais il est très simple de le mettre à jour.

Je copie le modele anglais et le traduit.


... traduction approximative ...

use strict;
use warnings;


our %Lexicon = (
    form_error_message     => 'Il y a des erreurs, voir dessous pour plus de détials',
    form_default_error     => 'Entrée invalide',
    form_allornone_error   => 'Erreur',
    form_ascii_error       => 'Champ contenant des caractères invalides',
    form_autoset_error     => 'Champ contenant un choix invalide',
    form_bool_error        => 'Champ doit contenir une valeur booléèenne',
    form_dependon_error    => 'Erreur',
    form_email_error       => 'Ce champ doit contenir une adresse mail',
    form_equal_error       => 'Erreur',
    form_integer_error     => 'Ce champ doit être un entier',
    form_length_error      => 'Entrée invalide',
    form_minlength_error   => 'Doit comporter au moins [_1] caractères',
    form_maxlength_error   => 'Ne doit pas comporter plus de [_1] caractères',
    form_number_error      => 'Ce champ doit être un nombre',
    form_printable_error   => 'Champ conteant des caractères invalides',
    form_range_error       => 'Entrée invalide',
    form_regex_error       => 'Entrée invalide',
    form_required_error    => 'Ce champ est requis',
    form_set_error         => 'Champ contenant un choix invalide',
    form_singlevalue_error => 'Ce champ accepte seulement une valeur simple',
    form_word_error        => 'Champ conteant des caractères invalides',
);

1;

Le formulaire doit connaitre la langue à utiliser ajoutons à notre controleur test les lignes suivantes:


        my $form = $c->stash->{form};
        $form->languages(['fr']);

Et c'est tout :) Les messages d'erreur de validation des formulaires sont maintenant en français.

8. Les sources de cet article

Chapitre DBIx::Class::FormFu


Add a comment

Validator_logo
Catapulse v0.06
( 0.09259 s)