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.
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');
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
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:
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');
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
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>
Dans le prochain tuto, nous verrons comment afficher un message de confirmation une fois qu’un utilisateur est bien enregistré.
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.
WordPress est un système de gestion de contenu (CMS en anglais). Voici les 10 avantages de WordPress pour créer votre prochain site Web
Vous souhaitez atteindre vos prospects à moindre frais et optimiser votre coût d’acquisition client? Combien ça coûte avec Google Adwords?
Il est pertinent de porter une attention particulière sur la sélection de vos couleurs lors de la création de votre image et votre site Web.
thank you for your post, I have a question, where is the content of « contentWrapAddUsers » or add.ctp
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.
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