Table des matières
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)
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é.
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.
Dans ce chapitre nous verrons les divers éléments qu'il est possible d'insérer dans un formulaire FormFu.
Par défaut le fichier de définition d'un formulaire est stocké
dans
root/forms/Nom_du_Controller/Nom_Methode.yml.
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 ...
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.
Une zone de saisie uniligne est construite comme ci-dessous:
elements:
- type: Text
name: zonesaisie
size: 60
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
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
Il s'agit d'un groupe de bouton 'radio'
elements:
- type: Radiogroup
name: rg1
label: Groupe de bouton 'radio'
values: [un, deux, trois]
Il s'agit des cases à cocher.
elements:
- type: Checkbox
name: cb1
label: Case à cocher
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]
elements:
- type: Image
name: i1
src: http://localhost:3000/feedback/captcha
label: une Image
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
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]
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?}]
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.
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.

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 );
}
}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 && !$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.
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.


