Améliorer l'expérience utilisateur avec le module Paragraphs sur Drupal 8

une vieille machine à écrire

Le module Paragraphs est une très bonne alternative à un éditeur WYSIWYG pour qui veut permettre aux utilisateurs d'un site Drupal de réaliser des agencements de pages complexes, mixant texte, images, vidéos, diaporamas, citation, blocs de présentation, ou toute autre composant évolué de mise en forme.

Plutôt que laisser l'utilisateur se débattre tant bien que mal avec son éditeur de texte pour faire des mises en pages évoluées, mais sans jamais pouvoir atteindre le niveau de rendu possible avec Paragraphs, nous pouvons lui proposer de composer sa page avec des éléments de contenu structurés, chacun de ces composants se chargeant de rendre le contenu, selon les options sélectionnées, selon une mise en forme maîtrisée.

Citons un exemple parmi des dizaines d'autres (les possibilité sont infinies). Plutôt que de proposer une simple liste à puces depuis l'éditeur de texte, nous pouvons créer un composant qui pourra générer une liste à puces plus aboutie : chaque élément de la liste à puces pourrait par exemple disposer d'un pictogramme, d'un titre, d'une brève description, et d'un lien éventuel, et l'éditeur de contenu pourrait simplement sélectionner le nombre d'élément qu'il souhaite par rangée. Pour obtenir par exemple un rendu de ce genre.

Une liste à puces mise en forme avec paragraphs

Proposer ces différents composants permet à un utilisateur non averti de créer des agencements de page complexe, avec pour seule contrainte de se concentrer sur son contenu, et uniquement son contenu.

Les différents modes d'éditions possibles des composants paragraph

Nous disposons de plusieurs options pour afficher, dans le formulaire d'édition de son contenu, les différents composants créés avec Paragraphs. nous pouvons les afficher :

  • En mode ouvert : le formulaire d'édition du composant Paragraph est ouvert par défaut
  • En mode fermé :  le formulaire d'édition du composant Paragraph est fermé par défaut
  • En mode prévisualisation : le composant paragraph est affiché tel qu'il est rendu sur la front office

Paramètres d'affichage du formulaire d'un composant paragraph

J'ai tendance à privilégier de retenir le mode fermé par défaut afin d'améliorer l'ergonomie du formulaire d'édition, car si la page se compose de nombreux composants alors, en mode ouvert, le formulaire d'édition a tendance à effrayer l'utilisateur tant le nombre de formulaire peut être important, et rend également très difficile la réorganisation des différents composants (changement d'ordre), tandis que le mode pré-visualisation implique soit d'intégrer le rendu également sur le thème d'administration, soit d'opter pour l'utilisation du thème principal du site pour l'édition des contenus.

L'inconvénient de l'utilisation du mode fermé pour l'édition des composants

L'utilisation du mode fermé permet d'avoir une vue d'ensemble des différents composants de la page (du contenu), et permet de les réorganiser très facilement par simple glisser / déposer. La modification des différents composants de la page se fait alors en dépliant / repliant à la demande ces derniers.

Formulaire d'édition d'un contenu composé de paragraphs

Avec ce mode d'édition, les différents composants du contenu sont listés et ont pour titre le type de paragraphe utilisé. Ce qui peut être un inconvénient majeur si le contenu utilise de nombreux composants du même type, l'éditeur ne disposant pas de repères immédiats pour distinguer à quel contenu se rapporte chaque composant.

Modifier le label des composants paragraphe du contenu

Nous pouvons pallier à ce problème en créant un petit module, qui va se charger de modifier le label de chaque composant en récupérant le contenu de certains champs de nos paragraphes. 

L'idée générale est d'altérer le formulaire d'édition du contenu, de détecter si ce contenu contient des champs correspondant à des paragraphes, et dans l'affirmative, de récupérer pour chacun des paragraphes la valeur d'un champ (par exemple un champ dont le nom machine contient le mot title), puis de modifier le label utilisé dans le formulaire d'édition pour chaque paragraphe avec cette valeur.

Passons à la pratique et au snippet PHP. Nous allons implémenter hook_form_alter().


/**
 * Implements hook_form_alter().
 */
function MYMODULE_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  $form_object = $form_state->getFormObject();

  // Paragraphs are only set on ContentEntityForm object.
  if (!$form_object instanceof ContentEntityForm) {
    return;
  }

  /** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */
  $entity = $form_object->getEntity();
  // We check that the entity fetched is fieldable.
  if (!$entity instanceof FieldableEntityInterface) {
    return;
  }

  // Check if an entity reference revision field is attached to the entity.
  $field_definitions = $entity->getFieldDefinitions();
  /** @var \Drupal\Core\Field\FieldDefinitionInterface $field_definition */
  foreach ($field_definitions as $field_name => $field_definition) {
    if ($field_definition instanceof FieldConfigInterface && $field_definition->getType() == 'entity_reference_revisions') {
      // Fetch the paragrahs entities referenced.
      $entities_referenced = $entity->{$field_name}->referencedEntities();
      /** @var \Drupal\Core\Entity\FieldableEntityInterface $entity_referenced */
      foreach ($entities_referenced as $key => $entity_referenced) {
        
        $fields = $entity_referenced->getFieldDefinitions();
        $title = '';
        $text = '';
        
        foreach ($fields as $name => $field) {
          if ($field instanceof FieldConfigInterface && $field->getType() == 'string') {
            if (strpos($name, 'title') !== FALSE) {
              $title = $entity_referenced->{$name}->value;
            }
            // Fallback to text string if no title field found.
            elseif (strpos($name, 'text') !== FALSE) {
              $text = $entity_referenced->{$name}->value;
            }
          }
        }
        // Fallback to $text if $title is empty.
        $title = $title ? $title : $text;
        // Override paragraph label only if a title has been found.
        if ($title) {
          $title = (strlen($title) > 50) ? substr($title, 0, 50) . ' (...)' : $title;
          $form[$field_name]['widget'][$key]['top']['paragraph_type_title']['info']['#markup'] = '<strong>' . $title . '</strong>';
        }
      }

    }
  }

}

Passons en revue plus en détails ce que nous faisons dans cette altération.

Dans un premier temps nous vérifions que nous sommes bien sur un formulaire d'édition de contenu, et que l'entité que nous sommes en train d'éditer dispose bien de champs.

$form_object = $form_state->getFormObject();

// Paragraphs are only set on ContentEntityForm object.
if (!$form_object instanceof ContentEntityForm) {
  return;
}

/** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */
$entity = $form_object->getEntity();
// We check that the entity fetched is fieldable.
if (!$entity instanceof FieldableEntityInterface) {
  return;
}

Nous parcourons alors tous les champs de cette entité (un noeud, un block de contenu, ou tout autre entité de contenu) et ne traitons que les champs de type entity_reference_revisions qui correspondent aux champs implémentés et utilisés par le module paragraphs.

// Check if an entity reference revision field is attached to the entity.
$field_definitions = $entity->getFieldDefinitions();
/** @var \Drupal\Core\Field\FieldDefinitionInterface $field_definition */
foreach ($field_definitions as $field_name => $field_definition) {
  if ($field_definition instanceof FieldConfigInterface && $field_definition->getType() == 'entity_reference_revisions') {
    // Fetch the paragrahs entities referenced.
    $entities_referenced = $entity->{$field_name}->referencedEntities();
    /** @var \Drupal\Core\Entity\FieldableEntityInterface $entity_referenced */
    foreach ($entities_referenced as $key => $entity_referenced) {

      // Stuff.

    }

  }
}

Pour chacune des entités Paragraph détectées nous allons alors récupérer la valeur d'un champ. Dans notre exemple, nous testons d'abord s'il s'agit d'un champ de type texte court (string), puis testons si son nom machine contient le mot title, ou encore le mot text qui nous servira de solution de repli si aucun champ ne contient le mot title dans son nom machine. 

$fields = $entity_referenced->getFieldDefinitions();
$title = '';
$text = '';
foreach ($fields as $name => $field) {
  if ($field instanceof FieldConfigInterface && $field->getType() == 'string') {
    if (strpos($name, 'title') !== FALSE) {
      $title = $entity_referenced->{$name}->value;
    }
    // Fallback to text string if no title field found.
    elseif (strpos($name, 'text') !== FALSE) {
      $text = $entity_referenced->{$name}->value;
    }
  }
}

Cet exemple est bien sûr à adapter en fonction de votre propre contexte. Nous aurions pu par exemple cibler précisément un champ précis en fonction du type de paragraphe détecté. Par exemple :

$bundle = $entity_referenced->bundle();
$title = '';
$text = '';
switch ($bundle) {
  case 'paragraph_imagetext':
    $title = $entity_referenced->field_paragraph_imagetext_title->value;
    break;
  case 'other_paragraph_type':
    $title = $entity_referenced->another_field->value;
    break;
  default:
    break;
}

Et enfin, nous remplaçons le label utilisé par le type de paragraph si nous avons bien obtenu une valeur pour notre nouveau label.

// Fallback to $text if $title is empty.
$title = $title ? $title : $text;
// Override paragraph label only if a title has been found.
if ($title) {
  $title = (strlen($title) > 50) ? substr($title, 0, 50) . ' (...)' : $title;
  $form[$field_name]['widget'][$key]['top']['paragraph_type_title']['info']['#markup'] = '<strong>' . $title . '</strong>';
}

Un utilisateur heureux

Le résultat obtenu nous permet alors de proposer aux éditeurs de contenu un formulaire d'édition compact et lisible où il peuvent immédiatement repérer à quel contenu se réfère un type de paragraphe.

Formulaire amélioré d'édition d'un contenu composé de paragraphes

Cette petite altération de l'affichage des différents composants d'une page la rend immédiatement plus lisible et plus compréhensible. Elle traduit une information technique, plutôt orientée site builder, en une information de contenu orientée utilisateur, lui donnant ainsi une meilleure appréhension et un meilleur confort.

Je me demande dans quelle mesure cette fonctionnalité pourrait être implémenté au moyen d'un module contribué (ou encore dans le module Paragraphs lui-même), la plus grande difficulté résidant ici dans la capacité d'un champ Entity Reference Revisions de cibler une infinité de type de paragraphs, eux-même contenant une possible infinité de champs. Si vous avez une idée je suis preneur ?

Vous avez besoin d'un freelance spécialiste Drupal pour des problématiques d'ergonomie ou autre ? N'hésitez pas à me consulter.

 

 

Commentaires

Soumis par Qazema (non vérifié) le 20/10/2016 à 10:25 - Permalien

Ah voila un sujet sur lequel j'avais pensé écrire ! En effet Paragraph est super pratique pour se débarrasser des CKEditor et consorts, il permet d'avoir vraiment une approche modulaire du contenu !
En complément très intéressant : Classy Paragraph. Ce module permet d'ajouter des classes spécifiques à son paragraphe et évites ainsi d'avoir à créer des types de paragraph en "doublon".
Exemple concret que j'utilise fréquemment : "Illustrated paragraph" qui est composé d'un titre, d'une image, d'un texte long et d'une classe (classy paragraph est un champ). Ce dernier champ permet à l'utilisateur de choisir entre trois classes différentes qui offriront pour chacune d'elle un affichage différent :
- image au dessus du texte
- image flottante à gauche
- image flottante à droite.

Un très bon article en tout cas, et très sympa ce petit module custom pour modifier le label des composants paragraphe du contenu ! Merci à toi ;)

Merci pour la référence à Classy Paragraphs. J'ai dû faire un truc du genre sur un projet de site 100% paragraphs mais du coup j'avais géré ça avec un champ "Style" de type liste et j'avais fait la conversion en classes CSS manuellement dans le code. C'est suffisamment léger pour pouvoir se passer d'un module en plus mais pour les gens qui n'ont pas forcément les compétences de dev ça a l'air d'être un bon module (avec son UI en bonus).

Ajouter un commentaire