CakePHP : validation Ajax

Tagged:  •  

Toujours dans l'idée d'améliorer mon site et mon expérience de CakePHP, je me suis laissé tenté par la validation Ajax d'un formulaire. J'ai vu passer pas mal de discussions portant sur ce sujet, ça me démangeait depuis un moment d'essayer.

Après recherche (pas très approfondie, j'admets, j'ai peu de temps libre pour le dev), j'ai trouvé un seul résultat qui m'a paru intéressant (Cake 1.2, avec prototype...) : Validate single fields trough AJAX with CakePHP.
La personne y présente une implé possible, de A à Z.

Dans mon cas, je voulais appliquer ça à un formulaire de contact. Adapter la solution exposée dans le billet signalé plus haut impliquait donc :
- à la soumission, requête ajax vers la méthode de validation
- celle-ci applique les validations du modèle aux données reçues
- elle récupère les erreurs qui ont été levées et les messages d'erreur
- elle génère un flux XML qui liste les champs en erreur et pour chacun, les messages d'erreurs à signaler à l'utilisateur
- un code JS réceptionne tout ça, parcourt le XML, fait le lien avec les champs de formulaire, crée les éléments XHTML nécessaires pour chaque erreur

fiouuu, ça va être long tout ça, et ça fait écrire à la mano des comportements que CakePHP fait normalement à ma place (affichage des erreurs surtout)

bon, je passe en mode "lazy" : je vais plutôt faire un appel Ajax qui va me retourner le code XHTML complet du formulaire (champs, erreurs...), il suffira d'écraser le formulaire déjà présent par celui fraichement reçu

Ci-dessous, le code nécessaire à cette réalisation. On notera :
- pas besoin de répéter le code du formulaire
- on garde les comportements automatiques de CakePHP sur l'affichage des erreurs de chaque champ
- on n'écrit pas de nouvelle fonction, on ne fait que rajouter 2-3 "if"
- ok, il y a plus propre mais c'est DRY, rapide à mettre en place et ça marche...

CODE :

contrôleur, avant :

public $components = array('Email');

public function index() {
if (!empty($this->data)) {
$this->__cleanDatas();

$this->Contact->create($this->data);

if ($this->Contact->validates()) {
if ($this->__sendMail()) {
$this->Session->setFlash('Votre message a bien été envoyé, merci.');
$this->redirect(array(Configure::read('Routing.admin') => false, 'controller' => 'contact', 'action' => 'index'));
} else {
$this->Session->setFlash("Votre message n'a pas pu être envoyé, peut-être à cause d'un problème technique.");
}
} else {
$this->Session->setFlash('Veuillez corriger les erreurs ci-dessous.');
}
}
}

vue, avant :

// affichage du formulaire, avec le view helper Form

contrôleur, après : on rajoute un component et des conditions suivant que l'action est appelée via Ajax ou pas

public $components = array('Email', 'RequestHandler');

public function index() {
$isAjax = $this->RequestHandler->isAjax();

if (!empty($this->data)) {
$this->__cleanDatas();

$this->Contact->create($this->data);

if ($this->Contact->validates()) {
if ($this->__sendMail()) {
$this->Session->setFlash('Votre message a bien été envoyé, merci.');
if (!$isAjax) {
$this->redirect(array(Configure::read('Routing.admin') => false, 'controller' => 'contact', 'action' => 'index'));
} else {
$this->data = array(); // on vide les données pour afficher un formulaire vierge
}
} else {
$this->Session->setFlash("Votre message n'a pas pu être envoyé, peut-être à cause d'un problème technique.");
}
} else {
$this->Session->setFlash('Veuillez corriger les erreurs ci-dessous.');
}
}

if ($isAjax) {
$this->layout = 'ajax-flash';
}
}

vous aurez peut-être noté le $this->layout = 'ajax-flash';
pourquoi pas juste "ajax" ? tout simple : dans mon code je fais beaucoup d'appels aux messages flash, il faut donc qu'ils soient aussi mis à jour côté client, comme le formulaire
dans mon layout principal j'ai ça :

// ... du code
< div id="content_for_layout" >
< ? php
if ($session->check('Message.flash')) {
$session->flash();
}
? >

< ? php echo $content_for_layout ? >
< / div >
// du code ...

j'ai donc créé un nouveau layout ajax-flash :

< ? php
if ($session->check('Message.flash')) {
$session->flash();
echo $javascript->codeBlock('highlightFlashMessage();');
}
? >

< ? php echo $content_for_layout ? >

explication sur l'appel JavaScript : j'applique un effet visuel aux flashMessages, en JS, ceci est géré au chargement de la page (dom:loaded). Là, on affiche un flashMessage mais la page a déjà été chargée, donc on force l'appel à la méthode qui va appliquer l'effet visuel

vue, après :

// affichage du formulaire, avec le view helper Form
< ? php echo $javascript->codeBlock('observeContactFormSubmition();') ? >

explication sur l'appel JS : on appelle la méthode qui va surveiller la soumission du formulaire et faire la requête Ajax, on doit mettre cet appel dans la vue car à chaque requête Ajax, on renvoie le code XHTML d'un formulaire, on ne peut donc pas se contenter de surveiller le formulaire seulement lors du premier affichage de la page

fichier JS :

function observeContactFormSubmition() {
$('ContactAddForm').observe('submit', validateFormViaAjax);
}

function validateFormViaAjax(event) {
event.stop();

new Ajax.Updater(
'content_for_layout',
_url_root + 'contact',
{
parameters: $('ContactAddForm').serialize(true),
evalScripts: true
}
);
}

on dit au script de mettre à jour le contenu du div qui contient les flashMessages et le code de la vue
les paramètres sont les valeurs des champs du formulaire, mis au format JSON automatiquement par prototype
et on dit d'évaluer les balises script, puisque dans la vue, on a un appel à une fonction JS

Indente ton code !!! C'est

Indente ton code !!! C'est illisible

...

nous sommes sur un blog de développeurs, donc de personnes qui sont quand même habituées à lire du code, donc qui savent qu'il faut indenter, donc si ce n'est pas fait c'est qu'il doit y avoir une raison

le code est indenté mais les espaces ne sont pas rendus à l'affichage, ou alors il faut que je m'amuse à mettre des points à la place des espaces, peut être que ça marcherait

tu peux aussi copier coller ça dans un éditeur et utiliser l'indentation auto

Dia

utilise la base HTML pre

utilise la base HTML pre

 
     mon code indente

utilise la balise HTML

utilise la balise HTML pre

<pre>
mon code indente
</pre>

 
     mon code indente

Merci, c'est exactement ce

Merci, c'est exactement ce que je recherchais. :-)