Clonage de champs produits WooCommerce. Part. 1
1- Les prérequis
Vous allez devoir développer des fonctions, au sein de votre thème enfant, pour offrir la possibilité à vos visiteurs de saisir des informations personnalisées à un produit en fonction de la quantité demandée.
Nous chercherons également à afficher des informations complémentaires associées à ces produits par le biais de champs personnalisés, grâce au plugin ACF (Advanced Custom Fields).
Pour cela vous allez avoir besoin :
- Du plugin ACF
- D’activer la bibliothèque jQuery (déjà présente dans WordPress)
- De bien connaître la structure des HOOK WordPress, Storefront et WooCommerce
- De connaître les fonctions et classes de WooCommerce
Je vais prendre l’exemple d’une réservation pour un vol Paris – Berlin où l’utilisateur devra renseigner les nom et prénom du ou des voyageurs. Le sélecteur de quantité du produit WooCommerce servira de choix pour le nombre de voyageur.
Sur ce même principe, vous pourrez l’adapter facilement à tout type de personnalisation : saisir un message à imprimer sur un t-shirt ou autre.
2- Faire apparaître le ou les champs de saisi
La première étape consiste à développer une fonction qui va ajouter des champs INPUT au formulaire d’ajout au panier d’une fiche produit. Ces champs s’ajouteront ainsi automatiquement à la variable $_REQUEST[ ] pour être traités par WooCommerce.
Pour travailler proprement, nous allons créer un dossier INC à la racine du thème enfant. Dans ce dossier vous allez créer un fichier add_product_data.php. C’est dans ce fichier que nous développerons notre système.
Afin qu’il soit pris en compte par notre thème, nous devons l’inclure dans le fichier functions.php. Pour cela inscrivez le code ci-dessous dans functions.php de votre thème enfant :
require_once 'inc/add_product_data.php';
Une fois fait revenez au fichier add_product_data.php pour y inscrire la ligne de code ci-dessous :
add_action('woocommerce_before_add_to_cart_button','my_custom_fields');
Nous faisons donc appel au HOOK “woocommerce_before_add_to_cart_button” de WooCommerce pour venir positionner notre appel de fonction my_custom_fields() juste avant l’affichage du bouton d’ajout au panier.
Dans cette fonction my_custom_fields() nous allons gérer l’affichage de la structure HTML qui accueillera le premier champ INPUT et qui servira de conteneur pour écrire dynamiquement les autres champs en JS, lors de l’action sur la quantité.
Contenu de notre fonction my_custom_fields() :
<?php
function my_custom_fields()
{
global $product;
ob_start();
?>
<section id="add_custom_fields" title="coucou">
<div class="wdm-custom-fields" id="group_voyageurs">
<div class="group_info" id="info_1">
<h4>Voyageur 1</h4>
<input type="text" name="wdm_name_1" required placeholder="Nom">
<input type="text" name="wdm_lastname_1" required placeholder="Prénom">
</div>
</div>
</section>
<div class="clear"></div>
<?php
$content = ob_get_contents();
ob_end_flush();
return $content;
}
Les fonctions ob_start(), ob_get_contents(), ob_end_flush() permettent de gérer le Output Buffering. C’est à dire que l’on temporise les traitements du serveur le temps que tout le document à renvoyer soit prêt.
Au centre le code HTML de nos premiers champs :
- La structure <div class=”group_info”> accueille la mise en forme du titre Voyageur 1 et le champs Nom et Prénom. Vous remarquerez que l’ID du DIV et les valeurs NAME des champs comporte une numérotation type “_1”.
Elle sera importante par la suite, car elle nous permettra, sur ce même format, d’ajouter dynamiquement les futurs champs type info_2, info_3, etc. en répétant la structure HTML du <div class=”group_info”>
Mettre en forme les champs
Je vous invite à créer un dossier ASSETS à la racine de votre thème dans lequel vous allez créer deux autres dossiers :
- CSS
- JS
Ils accueilleront par la suite nos CSS et SCRIPTS personnalisés.
Créez un fichier main.css dans assets/css/ puis associez cette nouvelle feuille de style dans functions.php via le code de votre fonction qui appelle le style.css de votre thème parent :
add_action( 'wp_enqueue_scripts', 'ajout_css_theme_parent' );
function ajout_css_theme_parent() {
wp_enqueue_style( 'parent-style', get_template_directory_uri() . '/style.css' );
wp_enqueue_style( 'child-style', get_stylesheet_directory_uri() . '/assets/css/main.css' );
}
Vous n’avez plus qu’à saisir du code CSS dans votre fichier main.css pour créer une mise en forme de vos premiers champs comme dans l’exemple ci-dessous :
3- Gérer les champs en jQuery / JS
Nous devons à présent ajouter ou supprimer des champs de saisi en fonction de la modification de la quantité.
Commencez par créer un fichier main.js dans assets/js/ de votre thème enfant.
Puis ajouter le code ci-dessous dans functions.php, pour l’associer à votre thème enfant et activer le jQuery de WordPress :
function theme_scripts() {
wp_enqueue_script('jquery');
wp_enqueue_script( 'myscript', get_stylesheet_directory_uri() . '/assets/js/main.js' );
}
add_action('wp_enqueue_scripts', 'theme_scripts');
Tester si jQuery est bien fonctionnel
Dans votre fichier main.js saisissez le code ci-dessous puis allez dans la console de l’inspecteur d’élément de votre navigateur pour en vérifier l’affichage :
jQuery( document ).ready(function($) {
console.log( "ready! " );
});
L’instruction jQuery( document ).ready(function($) permet de vérifier si le document est bien chargé et de créer l’objet/fonction $ qui vous sera utile pour sélectionner les éléments HTML de votre fiche produit.
Nous allons maintenant ajouter un événement sur l’INPUT type number avec la fonction .bind() de jQuery.
Si vous inspectez le code du champs quantité, vous remarquerez la présence des classes CSS .input-text et .qty. Nous allons nous en servir comme sélecteur d’élément avec jQuery. Attention pas d’espace entre les deux car, comme en CSS, l’espace est l’opérateur pour désigner la classe de l’élément enfant.
jQuery( document ).ready(function($) {
$(".input-text.qty").bind('keyup mouseup', function () {
var nbv = $(this).attr('value');
console.log( "ready! "+nbv );
});
});
Détail du code ci-dessus :
- En paramètre de on() nous utilisons “keyup mouseup” pour déclencher l’événement lors du clic ou d’une touche relevée sur le clavier. Les champs number en HTML peuvent effectivement être modifié via les valeurs numériques ou les touches flèches du clavier.
- Le second paramètre de on() est la fonction à exécuter lorsque l’événement est déclenché.
- On récupère donc dans la variable nbv la valeur du champs quantité. $(this) s’adresse à l’élément sur lequel l’événement à lieu, donc bien notre champs quantité : $(« .input-text.qty »).
- On concatène en suite la variable nbv dans le message de console pour s’assurer que tout fonctionne.
Vérifiez maintenant que les messages de consoles sont corrects et que la valeur du champ caché change bien via l’inspecteur d’élément du navigateur.
Étude des cas à traiter
Avant d’aller plus loin, il nous faut réfléchir aux différents cas qui peuvent se présenter lors de la réservation d’un vol :
- Par défaut : l’utilisateur arrive sur une fiche produit, où le premier groupe de champs pour le voyageur 01 est présent. Normal, puisqu’il faut au moins un voyageur pour réserver. Dans le cas de la diminution de “quantité”, il faudra que ce dernier soit toujours visible.
- Augmentation du nombre de voyageurs : lorsque l’utilisateur augmentera le nombre de voyageur, notre système devra vérifier le nombre de groupes de champs actifs pour pouvoir nommer correctement les nouveaux groupes à ajouter, mais aussi déterminer si il doit agir ou non (événement sur touche du clavier).
- Diminution du nombre de voyageurs : Dans ce cas il faudra également faire la différence entre le nombre de groupes de champs actifs et le nombre de voyageurs désiré, afin de supprimer correctement les champs en trop sans écraser les données qui resteront afficher dans les autres champs.
Voici la structure que je vous propose :
jQuery( document ).ready(function($) {
$(".input-text.qty").on('keyup mouseup', function () {
var nbv = $(this).attr('value');
update_champs(nbv);
});
function update_champs(nbv) {
//compter les éléments group_info puis comparer avec la quantité saisie
var nb_champs = $('.group_info').length;
if(nb_champs != nbv){
//cas ou le nombre de champs actif est supérieur, il faut en supprimer
if(nb_champs > nbv){
console.log( "Supprime" );
} //fin if supprime champs
//cas ou le nombre de champs actif est inférieur, il faut en ajouter
if(nb_champs < nbv){
console.log( "ajoute" );
} //fin if ajoute champs
}//fin if le nombre de champs actif est différent du nombre de voyageur demandé
}//fin function update_champs
});
Nous allons commencer à remplacer le console.log() précédent par un appel de fonction update_champs().
Nous la déclarons juste après l’événement on() vu précédemment.
La structure principale de notre fonction est simple :
- Grâce à $(‘.group_info’).length on récupère le nombre d’élément associé à la classe .group_info, qui est la classe CSS des conteneurs des champs nom et prénom de chaque voyageur.
- La structure conditionnelle principale if(), teste si le nombre de groupes de champs actifs est différent du champs quantité présent dans la variable nbv. On ne fait rien si ces deux valeurs sont égales.
- Si elles sont différentes, alors on teste dans un premier temps si il y a plus de groupes de champs actifs que de quantité puis le contraire. Cela fonctionne avec un SINON, else, naturellement. Mais là je fais exprès de décomposer l’algorithme. Dans le premier cas il faudra supprimer des champs, dans le second en ajouter.
Concentrons nous sur la partie AJOUT DE CHAMPS :
//cas ou le nombre de champs actif est inférieur, il faut en ajouter
if(nb_champs < nbv){
console.log( "ajoute" );
} //fin if ajoute champs
Nous allons devoir écrire dynamiquement, à la suite du conteneur <div class=”group_info” id=”info_1”>, autant de fois que nécessaire, la même structure de conteneur en changeant seulement la valeur numérique de chaque id des éléments qui la composent, ainsi que l’intitulé du Voyageur :
<!-- Rappel de la structure HTML de nos champs d'enregistrement qui sont dans add_product_data.php -->
<div class="group_info" id="info_1">
<h4>Voyageur 1</h4>
<input type="text" name="wdm_name_1" required placeholder="Nom">
<input type="text" name="wdm_lastname_1" required placeholder="Prénom">
</div>
Saisissez le code ci-dessous à la suite du console.log( « ajoute » ); :
var nb_ajout = nbv - nb_champs; //calcul du nombre de champs à ajouter
var offset = nb_champs+1; //détermine le nombre de départ pour la dénomination des ID
for(var i = 0; i < nb_ajout; i++) {
var cpt = i + offset;
var champ = '<div class="group_info" id="info_' + cpt + '"><h4>Voyageur ' + cpt + '</h4><input type="text" name="wdm_name_' + cpt + '" required placeholder="Nom"><input type="text" name="wdm_lastname_' + cpt + '" required placeholder="Prénom"></div>';
$(champ).appendTo('#group_voyageurs');
} //fin du for
Détaillons le code d’ajout de champs dans l’ordre :
- On affecte à nb_ajout la différence entre la quantité saisie et le nombre de groupes de champs voyageurs déjà présents (actifs). On sait combien de nouveaux groupes on doit créer.
- On affecte à offset le nombre de groupes de champs voyageurs présent plus une unité pour déterminer à quel numéro nous allons commencer l’identification des champs.
- On utilise un boucle de comptage for(), pour ajouter autant de nouveaux groupes de champs que nécessaire grâce à la variable nb_ajout.
- En commençant le comptage avec la variable i à 0, on peut créer la variable cpt qui à chaque tour de boucle recevra le numéro de début d’identification, offset, ajouté au comptage de i.
- On affecte ensuite à la variable champ, le code HTML du groupe de champs voyageur à ajouter en concaténant la variable cpt pour identifier correctement tous les id et l’intitulé du voyageur en <h4></h4>. ATTENTION le code HTML doit être écrit sans retour chariot, saut de ligne ou autre.
- On termine avec l’utilisation de la fonction appendTo(), qui va ajouter la valeur contenue dans champ à la suite du code HTML contenu dans le <div id=”group_voyageurs”>.
Tester le résultat de votre avancement, en utilisant l’inspecteur d’élément, pour vous assurer que tout fonctionne correctement.
A présent, concentrons nous sur la partie SUPPRESSION DE CHAMPS :
Toujours dans votre fichier main.js, écrivez le code ci-dessous juste après console.log( « Supprime » ); :
var nb_supp = nb_champs - nbv; //calcul du nombre de champs à ajouter
var offset = parseInt(nbv)+1; //détermine à partir de quel numéro de champs on supprime
for(var i = 0; i < nb_supp; i++) {
var cpt = i + offset;
$('#info_' + cpt).remove();
} //fin boucle de suppression FOR
Détaillons le code de suppression de champs dans l’ordre :
- On affecte cette fois ci à la variable nb_supp, la différence du nombre de groupes de champs voyageurs avec le nombre du champs quantité. On connaît ainsi le nombre de champs à supprimer.
- Comme pour l’ajout, on affecte à la variable offset le nombre de voyageurs, champs quantité, plus une unité. On utilise la fonction JS parseInt() pour changer le type de la variable nbv, qui est un caractère, en nombre entier.
- On a plus qu’à faire le comptage du nombre de groupes de champs à supprimer avec une boucle for().
- On utilise encore une fois la variable cpt pour incrémenter l’id du sélecteur à supprimer avec la fonction jQuery remove().
Vous pouvez tester dès maintenant le résultat de votre script avant de passer à l’enregistrement des données lors de l’ajout au panier.