/ Accueil / English
CakePHP

CakePHP : soumettre un formulaire en Ajax et retourner les erreurs de validations

CakePHP, soumettre un formulaire en Ajax et retourner les erreurs de validations

Afin d’améliorer l’expérience utilisateur, de plus en plus d’applications utilisent la technologie Ajax, nous allons voir dans ce tutoriel une manière de soumettre un formulaire avec Cakephp sans recharger la page. De nombreux articles présentent déjà des solutions mais la plupart conseille de désactiver le composant CSRF ce qui est dommageable pour la sécurité de votre site. De plus nous allons aussi retourner les erreurs de validation directement dans le formulaire.

Installation

La version de CakePHP utilisée est la 3.2 On utilisera Bootstrap 3, Jquery et Jquery UI Afin d’utiliser facilement Bootstrap, on installe également l’extension cakephp3-bootstrap-helpers On ajoute donc la ligne "holt59/cakephp3-bootstrap-helpers": "dev-master" au fichier composer.json et on lance un ‘composer update’. Voici la partie ‘require’ de composer.json

"require": {
 "php": ">=5.5.9",
 "cakephp/cakephp": "~3.2",
 "mobiledetect/mobiledetectlib": "2.", "cakephp/migrations": "~1.0", "cakephp/plugin-installer": "",
 "holt59/cakephp3-bootstrap-helpers": "dev-master"
 }

Afin d’utiliser les helpers de cette extension, il faut ajouter dans /src/AppController.php

public $helpers = array(
 'Html' => [
 'className' => 'Bootstrap.BootstrapHtml'
 ],
 'Form' => [
 'className' => 'Bootstrap.BootstrapForm',
 ],
 'Paginator' => [
 'className' => 'Bootstrap.BootstrapPaginator'
 ],
 'Modal' => [
 'className' => 'Bootstrap.BootstrapModal'
 ],
 'Paginator'
 );

Bien sur dans config/bootstrap.php, on n’oublie pas d’ajouter

Plugin::load('Bootstrap');

Base de données

Concernant la base de données, on part sur une table ‘users’ Voici le code SQL si vous souhaitez l’installer

--
 -- Table structure for table users
 CREATE TABLE IF NOT EXISTS users (
 id int(11) NOT NULL,
 name varchar(255) NOT NULL,
 email varchar(255) NOT NULL,
 created datetime NOT NULL,
 modified datetime NOT NULL
 ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=latin1;
 --
 -- Indexes for table users
 ALTER TABLE users
 ADD PRIMARY KEY (id), ADD KEY id (id);

Afin de créer le model, controller et les templates rapidement, on utilise en ligne de commande

$ bin/cake bake model users
$ bin/cake bake controller users
$ bin/cake bake template users

Fenêtre modale

Voyons comment ouvrir une fenêtre modale avec le formulaire d’ajout d’utilisateur à l’intérieur Dans /src/Template/Users/index.ctp, il faut ajouter une class sur le lien qui permet d’ajouter un utilisateur Ce lien doit charger l’action ‘call_modal’ du controller Users

<li><?= $this->Html->link(__('New User'), ['action' => 'call_modal'], ['class' => 'overlay-add-user']) ?></li>

Toujours dans le fichier index, tout à la fin on ajoute le code afin de créer la structure de la fenêtre modale

<div class="modal fade" id="dialogModalAddUsers" role="dialog">
    <div class="contentWrapAddUsers"></div>
</div>

Le contenu va être insérer en Javascript dans la div ‘contentWrapAddUsers’ On crée un fichier Javascript dans /webroot/js

// ------------
// Création modal Add Category
// ------------
$(document).on("click", ".overlay-add-user", function(event){ //(1)
 event.preventDefault();
 $('.contentWrapAddUsers').load($(this).attr("href")); //(2)
 $('#dialogModalAddUsers').modal('show'); //(3)
});

Explications:

  1. Detecte le click sur le lien qui a la class ‘overlay-add-user’
  2. Charge le contenu de la page dans la div contentWraloAddUsers
  3. Et affiche la fenêtre modale

Controller Users

Voici le contenu du UsersController.php

<?php
 namespace AppController;
 use AppControllerAppController;
 use CakeEventEvent;
 class UsersController extends AppController
 {
 public function initialize(){
 parent::initialize();
 $this->loadComponent('Security');
 $this->loadComponent('Csrf');
 }
 public function beforeFilter(Event $event){
 parent::beforeFilter($event);
 $this->Security->config('unlockedActions', ['add']);
 }
 public function index(){
 $users = $this->paginate($this->Users);
 $this->set(compact('users'));
 $this->set('_serialize', ['users']);
 }
 public function callModal(){
 $this->viewBuilder()->layout('ajax');
 if($this->request->is('ajax')){
 $user = $this->Users->newEntity();
 $this->set(compact('user'));
 $this->set('_serialize', ['user']);
 }
 }
 public function add(){
 $user = $this->Users->newEntity();
 $this->autoRender = false;
 if($this->request->is('ajax')){
 $user = $this->Users->patchEntity($user, $this->request->data);
 if ($this->Users->save($user)) {
 $response['status'] = 'success';
 $response['message'] = ("User Saved"); $response['data'] = $this->request->data; $response['id'] = $user->id; echo json_encode($response); } else { $errors = $user->errors(); $response['status'] = 'error'; $response['message'] = ("The User could not be saved. Please, try again.");
 $response['data'] = compact('errors');
 echo json_encode($response);
 }
 }
 }
?>

Explication:

$this->Security->config('unlockedActions', ['add']);

On désactive le component security pour l’action add puisqu’on va l’appeler avec une requête Ajax et ce composant va la bloquer. Voir Désactiver le Component Security pour des Actions Spécifiques

public function callModal(){
 $this->viewBuilder()->layout('ajax');
 if($this->request->is('ajax')){
 $user = $this->Users->newEntity();
 $this->set(compact('user'));
 $this->set('_serialize', ['user']);
 }
}

La méthode callModal va s’implement afficher le contenu de la fenètre modale avec le formulaire.

public function add(){
 $user = $this->Users->newEntity();
 $this->autoRender = false;
 if($this->request->is('ajax')){
 $user = $this->Users->patchEntity($user, $this->request->data);
 if ($this->Users->save($user)) { // (1)
 $response['status'] = 'success';
 $response['message'] = ("User Saved"); $response['data'] = $this->request->data; $response['id'] = $user->id; echo json_encode($response); } else { // (2) $errors = $user->errors(); $response['status'] = 'error'; $response['message'] = ("The User could not be saved. Please, try again.");
 $response['data'] = compact('errors');
 echo json_encode($response);
 }
 }
}

La méthode add va recevoir la requète du formulaire quand on va cliquer sur le bouton Submit Si la sauvegarde se passe correctement (1), une réponse sous la forme Json est renvoyé avec les données qui viennent d’être enregistrées ‘$response[‘data’]’ et l’id ‘$response[‘id’]’ de cet enregistrement. On renvoit aussi un status et un message qui seront traité par le javascript Si la sauvegarde échoue (2), on envoit aussi une réponse Json avec message, status et surtout les erreurs

$response['data'] = compact('errors');

Javascript

On reprend maintenant notre fichier javascript déjà créé, script_users.js

csrf_token = $("input[name='_csrfToken']").val();
 // ------------
 // Validation de la modal d'ajout de nouveau user
 // ------------
 $(document).on('click','#SubmitUserNew', function(e){
 var formSerialize = $('#formUserAdd').serialize();
 $.ajax({
 beforeSend: function(xhr) {
 xhr.setRequestHeader('X-CSRF-Token', csrf_token);
 $('#SubmitUserNew').text('Saving…');
 $('#SubmitUserNew').attr('disabled', true);
 $(".error-message").remove();
 $(".has-error").removeClass('has-error');
 },
 url: 'users/add/',
 data: formSerialize,
 type: "POST",
 dataType: "JSON",
 async: true,
 success: function (a){
 if (a.status === 'success'){
 $('#SubmitUserNew').text('Submit');
 $('#SubmitUserNew').attr('disabled', false);
 $('#dialogModalAddUsers').modal('hide');
 }
 if (a.status === 'error'){
 $('#SubmitUserNew').text('Submit');
 $('#SubmitUserNew').attr('disabled', false);
 $.each(a.data, function(model, errors) {
 for (var fieldName in this) {
 var element = $("[name='"+fieldName+"']");
 $.each(this[fieldName], function(label, text){
 text_error = text ;
 });
 var create = $(document.createElement('span')).insertAfter(element);
 create.addClass('error-message help-block').text(text_error);
 create.parent().addClass('has-error');
 }
 });
 }
 }
 });
});

Explications:

csrf_token = $("input[name='_csrfToken']").val();

On stocke le _csrfToken dans une variable afin de l’envoyer avec le formulaire. Sans ça, le requète sera bloqué par le component Csrf

$(document).on('click','#SubmitUserNew', function(e){
 var formSerialize = $('#formUserAdd').serialize();
 $.ajax({
 beforeSend: function(xhr) {
 xhr.setRequestHeader('X-CSRF-Token', csrf_token);
 $('#SubmitUserNew').text('Saving…');
 $('#SubmitUserNew').attr('disabled', true);
 $(".error-message").remove();
 $(".has-error").removeClass('has-error');
 },
 url: 'users/add/',
 data: formSerialize,
 type: "POST",
 dataType: "JSON",
 async: true,

Au click sur le bouton Submit, les valeurs du formulaire sont sérialisés puis envoyé en Ajax à l’action add du controller Users

xhr.setRequestHeader('X-CSRF-Token', csrf_token);

Permet d’envoyer le Csrf Token dans l’entête de la requète

success: function (a){
 if (a.status === 'success'){
 $('#SubmitUserNew').text('Submit');
 $('#SubmitUserNew').attr('disabled', false);
 $('#dialogModalAddUsers').modal('hide');
 }

Dans la variable [cci_javascript]a[/cci_javascript], on retrouve le Json renvoyé par la methode add() du controller Users. Si la réponse est un succès, on remet ‘Submit’ comme texte dans le bouton et on ferme la fenètre Modale

if (a.status === 'error'){
 $('#SubmitUserNew').text('Submit');
 $('#SubmitUserNew').attr('disabled', false);
 $.each(a.data, function(model, errors) {
 for (var fieldName in this) {
 var element = $("[name='"+fieldName+"']");
 $.each(this[fieldName], function(label, text){
 text_error = text ;
 });
 var create = $(document.createElement('span')).insertAfter(element);
 create.addClass('error-message help-block').text(text_error);
 create.parent().addClass('has-error');
 }
 });
 }
 }

Si la réponse est un échec, on récupère les erreurs de validation qui sont dans a.data C’est un tableau qui est comme ceci:

[
 'errors' => [
 'name' => [
 '_empty' => 'This field cannot be left empty'
 ],
 'email' => [
 '_empty' => 'This field cannot be left empty'
 ]
 ]
 ]

La boucle for permet de récupérer tous les ‘name’ des champs du formulaire (ici nous en avons deux: name et email), puis

$.each(this[fieldName], function(label, text){
 text_error = text ;
 });

permet de récupérer les messages d’erreurs (This field cannot be left empty) par exemple et de le mettre dans text_error.

var create = $(document.createElement('span')).insertAfter(element);
 create.addClass('error-message help-block').text(text_error);
 create.parent().addClass('has-error');

On crée ensuite un span dans lequel on met chaque élément avec la class ‘has_error’ histoire d’avoir les messages et les champs qui passent au rouge

Model

Pour info, voici le modèle utilisé: Userstable/php

namespace AppModelTable;
 use AppModelEntityUser;
 use CakeORMQuery;
 use CakeORMRulesChecker;
 use CakeORMTable;
 use CakeValidationValidator;
 /**
 Users Model
 *
 */
 class UsersTable extends Table
 { 
 /**
 Initialize method
 *
 @param array $config The configuration for the Table.
 @return void
 */
 public function initialize(array $config)
 {
 parent::initialize($config); 
 $this->table('users');
 $this->displayField('name');
 $this->primaryKey('id');
 $this->addBehavior('Timestamp');
 }
 /**
 Default validation rules.
 *
 @param CakeValidationValidator $validator Validator instance.
 @return CakeValidationValidator
 */
 public function validationDefault(Validator $validator)
 {
 $validator
 ->add('id', 'valid', ['rule' => 'numeric'])
 ->allowEmpty('id', 'create'); 
 $validator
 ->requirePresence('name', 'create')
 ->notEmpty('name');
 $validator
 ->email('email')
 ->requirePresence('email', 'create')
 ->notEmpty('email');
 return $validator;
 }
 /**
 Returns a rules checker object that will be used for validating
 application integrity.
 *
 @param CakeORMRulesChecker $rules The rules object to be modified.
 @return CakeORMRulesChecker
 */
 public function buildRules(RulesChecker $rules)
 {
 $rules->add($rules->isUnique(['email']));
 return $rules;
 }
 } 

Voici le contenu du fichier add.ctp

<nav id="actions-sidebar" class="large-3 medium-4 columns"></nav>
 <div class="users form large-9 medium-8 columns content"></div>
 <nav id="actions-sidebar" class="large-3 medium-4 columns"></nav>
 <div class="users form large-9 medium-8 columns content">
 <fieldset>
 <legend><!--?= __('Add User') ?--></legend></fieldset>
 </div>

Suite…

Dans le prochain tuto, nous verrons comment afficher un message de confirmation une fois qu’un utilisateur est bien enregistré.

Auteur.e de l'article

Gilles Duquerroy

Flexocodeur
Gilles, c’est d’abord et avant tout un parcours hors du commun. Passionné par les ordinateurs depuis sa plus tendre enfance, il devient professeur de technologies et part enseigner dans divers lycées français d’Afrique pendant plus de 15 ans. Autodidacte, il commence à créer des sites Web dès qu’intervient la démocratisation d’internet. En 2013, après avoir complété sa formation universitaire au Conservatoire National des Arts et Métiers d’Amiens, en France, il décide de se consacrer à ses premières amours : les langages de programmation Web!

À découvrir sur notre blogue

2017-01-05

CakePHP, ajouter l’affichage d’un message Flash

Gilles Duquerroy / Flexocodeur

Pour la suite de notre tutoriel précédent, on va maintenant afficher un message flash si l’ajout d’utilisateur a bien réussi.

Lire la suite
2024-04-11

Installer un thème Shopify : guide pratique pour débutants

Marie-Michel Tremblay / Intégratrice Web, Graphiste

Shopify, l’une des plateformes de commerce électronique les plus populaires, offre une variété de thèmes pour personnaliser votre boutique.

Lire la suite
2021-06-01

Refonte d’un site Web : à quels moments est-ce une bonne idée de la faire ?

Émilie Demers Moreau / Designer graphique

La refonte est une étape nécessaire dans la vie de votre site Web pour que celui-ci continue à servir vos objectifs de développement.

Lire la suite
2024-05-02

Tutoriel Ubersuggest, ChatGPT et Google Ads pour la recherche de mots-clés SEO

Davyd Quintal / Directeur des solutions novatrices

Que vous soyez un débutant ou un professionnel du marketing numérique, la recherche de mots-clés est une pierre angulaire du SEO.

Lire la suite

Laisser un commentaire

Votre adresse courriel ne sera pas publiée. Les champs obligatoires sont indiqués avec *

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

The reCAPTCHA verification period has expired. Please reload the page.

3 commentaires pour “CakePHP : soumettre un formulaire en Ajax et retourner les erreurs de validations”

  1. Humbert dit:

    thank you for your post, I have a question, where is the content of « contentWrapAddUsers » or add.ctp

    1. Gilles dit:

      Sorry for the delay in responding. I added the content of add.ctp at the end of the article. It’s the basic form to add a user.

      1. Humbert dit:

        Thank you for your response, I have searched on this topic and only found this post on the all internet, I hope to make it work, but when I call callModal, it does not work.

        https://uploads.disquscdn.com/images/c66a6f1c9f2931619def1b47a1a912561f4e9054c3a444bee8fde97593d282d1.png