Sablier en bois
  • 22/09/2019
  • 1 commentaire

Lorsque l'on utilise l'Ajax API de Drupal 8, on peut être amené à modifier/remplacer le loader qui s'affiche lorsqu'une requête est en cours d'exécution. Pour personnaliser cette barre de progression, cette article présentera les étapes et les différents bouts de codes nécessaires à cette fonctionnalité.

Après avoir vu comment personnaliser le rendu d'un formulaire et dynamiser un champ grâce à l’auto-complétion, attaquons nous maintenant à la personnalisation du loader lorsqu'une requête Ajax est envoyée.

Avant de commencer, je voulais juste faire un point sur le vocabulaire utilisé. Je parle de loader, barre de progression, indicateur de progression ... Tout ceci est la même chose. Le terme d'origine est indicateur de progression mais si on affiche un gif on parle alors de loader ou si on affiche une barre qui affiche des pourcentages pour matérialiser l'avancée on parle alors de barre de progression. Tout dépend de l'aspect graphique que vous souhaitez mettre en place.

Les sources de l'exemple présent dans cette article sont disponibles sur Gitlab. Pour tester l'exemple complet, il faut installer le module dans le dossier modules/custom de votre projet Drupal. En activant le module, une route (/example-form) sera disponible et affichera 5 champs textes ayant chacun une barre de progression (ou loader). Le dernier exemple correspond à la barre de progression personnalisée.

Passons maintenant aux différentes étapes pour personnaliser un indicateur de progression de l'Ajax API de Drupal 8.

Création d'un formulaire personnalisé

Comment dans d'autres billets, pour accéder facilement au formulaire et avoir un exemple rapidement disponible, il faut tout d'abord créer une route qui affichera ce formulaire dans une page dédiée. Il est tout à fait possible de créer un Bloc qui affichera ce formulaire pour le placer dans une page existante.

Déclaration de la route

La première étape à cette fonctionnalité est de déclarer la route avec ses options de path, permissions ...  Une route se déclare dans le fichier se nommant monmodule.routing.yml  disponible à la racine du module. La première partie du nom de fichier 'monmodule' doit être adaptée avec le vrai nom machine du module dans lequel se fichier sera placé.

example_ajax.custom_form:
  path: '/example-form'
  defaults:
    _form: '\Drupal\custom_ajax_progress_bar\Form\ExampleForm'
    _title: 'Example Form'
  requirements:
    _permission: 'access content'

La première ligne de ce fichier est le nom machine de la route, il doit être unique au projet.

L'option path définit l'url de la page. Il sera possible, par la suite, de créer des alias avec le module path_auto.

L'option defaults déclare le contrôleur ou le formulaire qui sera appelé afin de générer la réponse de la requête. L'attribut _form permet de saisir le namespace du formulaire. Il est composé de la racine Drupal, puis du nom machine du module, suivi du nom de dossier dans lequel sera placé le fichier gérant le formulaire. La dernière partie correspond au nom de la classe qui doit être le même nom que le fichier.

Création de la classe gérant le formulaire

Toutes les informations ont été données ci-dessus. Une classe portant le nom de ExampleForm doit être écrite dans le fichier ExampleForm.php et elle se situe dans le dossier src/Form de votre module.

La classe doit héritée de la classe Drupal\Core\Form\FormBase, mais il est tout à fait possible de se baser sur une classe un peu plus fournie comme Drupal\Core\Form\ConfigFormBase.

Voici le code minimal d'un formulaire :

<?php

namespace Drupal\custom_ajax_progress_bar\Form;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;

class ExampleForm extends FormBase {

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'example_ajax_form';
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    $form['textfield'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Field with ajax progress bar'),
    ];

    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
  }

}

La méthode getFormId() permet de définir l'identifiant de votre formulaire. Dans certains cas, comme un rendu depuis un bloc, cet identifiant sera utilisé pour charger le formulaire.

Le buildForm() est l'élément central de la classe car c'est ici qu'il faudra déclarer tout les éléments (inputs, checkboxes, buttons) qui composeront le formulaire.

Pour finir, la méthode submitForm() est la callback une fois le formulaire soumis.

Passez maintenant à l'activité la plus récurrente de tout développeur ou web master Drupal ... le vidage de Cache !

Ajout d'un événement Ajax

Comme vu dans le billet parlant de l'auto-complétion, pour utiliser l'Ajax API il suffit de déclarer la clé #ajax sur un input (ou sur un submit avec une callback) pour que le système fonctionne. Voici la mise à jour de l'input textfield.

$form['textfield'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Autocomplete field'),
      '#ajax' => [
        'callback' => '::myAjaxCallback',
        'event' => 'keydown',
      ]
];

L'option #ajax permet de définir une callback, ici myAjaxCallback(). L'option event définit l'événement qui déclenchera la requête ajax. Ici keydown permet de lancer un appel ajax à chaque fois que l'internaute appuie sur une touche lors d'une saisie dans le champ textfield.

Pour que la requête Ajax n'échoue pas il faut définir une callback. Pour cet article je déclare juste la fonction avec une pause de 5 secondes et un objet réponse (sinon cela provoque des erreurs) pour avoir le temps de bien voir le loader.

La callback est une simple méthode portant le nom donné à l'option callback ci-dessus.

  public function myAjaxCallback(array $form, FormStateInterface $form_state){
    $response = new AjaxResponse();
    sleep(5);
    return $response;
  }

Voici un aperçu du site lorsque l'événement keydown est déclenché.

Lancement de la requête Ajax. Affichage de la progresse bar.

On y arrive tout doucement mais on y arrive. Voici enfin un élément en lien avec le titre de l'article ! La fameuse barre de progression (ici un loader) lors d'une requête Ajax.

Et c'est cette dernière que nous allons personnaliser. Mais d'abord faisons un tour sur les options et les valeurs par défaut de cette fonctionnalité.

Options existantes de la progress bar

Avant de rentrer dans les détails voici le lien vers la documentation officielle. Jetez-y un œil ça vaut toujours le coup !

Exemple lors d'une utilisation de la barre de progression.

        'progress' => [
          'type' => 'throbber',
          'message' => 'Waiting please !'
        ],
  • type: Indique le type de la barre de progression. Par défaut le loader (throbber) qui est utilisé avec un message par défaut. (Voir image ci-dessus.) Il existe des alternatives comme 'bar' ou 'fullscreen'.
    Voici le rendu de la barre de progression 'bar' :

    Pour la faire avancer, il faut utiliser les options url et interval, voir ci-dessous.
     Mise à jour de la progresse bar avec pourcentageAfin de mettre à jour le pourcentage il faut créer une route avec un contrôleur, puis renseigner l'option 'url'.
    Voici un exemple, très simple, du retour attendu. Il est possible de passer des arguments par l'url et de les récupérer dans le contrôleur comme pour une route normale.
      public function progress($arg1) {
        $progress = [
          'message' => 'Argument passé à la méthode = ' . $arg1,
          'percentage' => rand(0,100),
        ];
    
        return new JsonResponse($progress);
      }
    

    Voici le rendu de la barre de progression 'fullscreen' :Affiche un loader au milieu de l'écran.
  • message: Contient le message affiché à l'écran.
  • url: Cette option est utilisé pour le type 'bar' afin de mettre à jour l'état (pourcentage) et le message de la barre de progression.
  • interval: Cette option est liée à l'option url, elle permet de définir l’intervalle de temps entre chaque appel à 'url' afin de mettre à jour les informations affichées à l'écran. Unité en milliseconde !

    Exemple :
    'progress' => [
        'type' => 'bar',
        'url' => Url::fromRoute('custom_ajax_progress_bar.ajax_progress', ['arg1' => rand(0,10)]),
        'interval' => 1000
    ]

Définition d'une progress bar custom

Comme vous pouvez le voir, Drupal propose nativement pas mal de choix pour afficher et gérer la barre de progression d'une requête Ajax. Il est possible d'aller plus loin en définissant sa propre progress bar avec un template et des options.

Déclarer l'utilisation d'une nouvelle barre de progression

Pour déclarer l'utilisation d'une barre de progression il faut modifier la clé 'type' de l'élément 'progress' comme ci-dessous.

  $form['field_custom_progress_bar'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Field with custom progress bar'),
      '#ajax' => [
        'callback' => '::myAjaxCallback',
        'event' => 'keydown',
        'progress' => [
          'type' => 'myCustomProgressBar',
          'message' => 'Waiting and envoy my progress bar !',
          'option_custom' => 'Oh yeah !'
        ],
      ],
    ];

Seul l'attribut type est obligatoire, les autres clés ne sont que des variables passées pour la génération du HTML. Vous pouvez donc mettre autant de variables que vous le souhaitez à cet endroit.

Attention à la valeur 'type' : Cette valeur sera reprise pour établir la fonction JS qui sera appelée lors de la génération. Retenez bien que toutes les lettres de 'type' seront mises en minuscules sauf la première. Donc évitez de faire comme dans mon exemple et privilégiez une clé de type my_custom_progress_bar.

Déclaration de la librairie qui générera le HTML / CSS

La suite de la déclaration d'une barre de progression personnalisée se fait dans la partie JS. C'est pour cette raison qu'il faut déclarer une librairie et l'attacher au formulaire afin que la barre de progression s'affiche.

Voici un exemple de déclaration de librairie. Seul le JS est obligatoire dans la création d'une barre de progression.

ajax_progress_bar.myAwesomeProgressBar:
  css:
    theme:
      css/ajax.myAwesomeProgressBar.css: {}
  js:
    js/ajax.myAwesomeProgressBar.js: {}
  dependencies:
    - core/jquery
    - core/drupal

Ne pas oublier d'attacher cette librairie au formulaire !

$form['#attached']['library'][] = 'custom_ajax_progress_bar/ajax_progress_bar.myAwesomeProgressBar';

Il y a sûrement une meilleur façon de faire afin d'attacher la librairie à chaque fois que la barre de progression 'myCustomProgressBar' est appelée. Je ne vais pas la décrire ici car ce billet est déjà trop long, mais si vous l'avez déjà fait ou que vous connaissez une meilleure façon de faire, n'hésitez pas à me le mettre en commentaire ou via twitter.

Génération du HTML

On est presque au bout du sujet, un peu de JS et emballé c'est pesé ! Nous voilà maintenant fin prêt à écrire le HTML qui sera utilisé lorsqu'une requête ajax sera envoyée et qu'on attendra la réponse.

À l'intérieur du fichier JS, il faut définir la function callback qui générera le HTML. La fonction se place dans l'objet Drupal.Ajax.prototype et se nomme 'setProgressIndicator' + le nom définit dans #ajax -> progress -> type.

Je reviens  à l'avertissement de tout à l'heure en ce qui concerne le nom de la barre de progression. La première lettre est mise en majuscule et tout le reste en minuscule. C'est pour cela que le nom dans l'exemple ci-dessus  (myCustomProgressBar) se transforme en Mycustomprogressbar dans le nom de la fonction JS. Donc pour ne pas perdre de temps lors d'un débogage aussi frustrant, suivez cette recommandation : TOUT EN MINUSCULE dans la déclaration du type de progress bar !

 Drupal.Ajax.prototype.setProgressIndicatorMycustomprogressbar = function () {
    this.progress.element = $(Drupal.theme('ajaxProgressBarMycustomprogressbar', this.progress.message, this.progress.option_custom));
    $(this.element).after(this.progress.element);
  };

Dans la fonction JS on hérite de l'objet this qui contient un attribut progress qui permet de récupérer tous les paramètres passés depuis le formulaire. Dans l'exemple ci-dessus, on récupère les variables message et option_custom.

Pour reprendre le fonctionnement du core, un thème est utilisé pour séparer la partie code et template. Il est ensuite injecté dans this.progress.element, il est possible de renseigner directement du HTML dans ce dernier, cependant la bonne pratique veux que l'on passe par un thème.

Voici l'exemple d'un thème en javascript avec Drupal 8.

  Drupal.theme.ajaxProgressBarMycustomprogressbar = function (message, option_custom) {
    var throbber = '<span class="throbber"><img src="https://loading.io/spinners/color-bar/index.colorful-progress-loader.gif" /></span>';

    return '<span class="ajax-progress ajax-progress-custom">' + throbber + message + '  --- <span>Custom option : ' + option_custom + '</span>' + throbber +'</div>';
  };

Une fois le HTML généré il faut maintenant l'ajouter au DOM. Il est possible de cibler l'input auquel l'appel ajax est attachée grâce à l'objet this.element. Il ne reste plus qu'à faire un .after() ou .before() pour que le template soit affiché.

Résultat

Et voici le résultat final. Vous avez maintenant tous les éléments à votre disposition pour créer de superbes barre de progression ou des loaders qui mettrons des paillettes dans votre vie !

Barre de progression personnalisée

 

Si vous souhaitez aller plus loin dans la personnalisation et l'utilisation de l'API Ajax de Drupal 8 allez jeter un œil à la documentation officielle.

Vous recherchez un Expert Drupal 8 pour de la maintenance, du développement, formation ou autre ? Contactez moi et nous étudierons ensemble votre besoin.