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 :

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 :

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 :

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 :

Créer des champs de produits personnalisés dans WooCommerce

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 :

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 :

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 :

  1. 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.
  2. 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.
  3. 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 :

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 :

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.