Créer des éléments personnalisés.

Apprendre DXPR BuilderÉtendreDévelopper dessus

Chaque élément DXPR Builder est un objet JavaScript empilé sur window.dxprBuilder.dxpr_elements. L’éditeur transforme chaque objet en classe d’élément avec les onglets Style et Animation déjà attachés. Cette page montre comment les éléments livrés sont définis dans dxpr_builder/dxpr_elements.js, comment un module ajoute sa propre définition à l’éditeur, et à quoi ressemble le HTML résultant. Il n’y a pas de modèles Twig côté serveur par élément ; les éléments sont rendus dans le navigateur.

Comment l’éditeur enregistre les éléments

L’éditeur charge six scripts dans cet ordre, définis dans dxpr_builder_library_info_build() dans dxpr_builder.module comme la bibliothèque dxpr_builder/editor.builder :

  1. dxpr_templates.js : modèles twig.js côté client compilés depuis dxpr_builder/templates/.
  2. dxpr_global.js : utilitaires partagés et l’intégration CKEditor 5.
  3. dxpr_events.js
  4. dxpr_param_types.js : empile un objet par type d’entrée sur window.dxprBuilder.dxpr_param_types.
  5. dxpr_elements.js : crée le tableau window.dxprBuilder.dxpr_elements et empile une définition par élément.
  6. dxpr_builder.js : l’éditeur. Quand ce fichier s’exécute, il appelle create_dxpr_elements(), qui boucle sur window.dxprBuilder.dxpr_elements et pour chaque définition :
    • appelle register_animated_element(base, is_container, Class), pour que la classe étende AnimatedElement (onglet Style, onglet Animation, HTML ID, HTML classes et paramètres de placement) ;
    • ajoute les paramètres hérités aux params de la définition ;
    • convertit chaque paramètre via make_param_type(), qui fait correspondre param.type aux types de paramètres enregistrés ;
    • mélange les propriétés de la définition dans le prototype de la classe.

Une définition doit donc être sur le tableau après l’exécution de dxpr_elements.js et avant celle de dxpr_builder.js. Les six fichiers se chargent avec l’attribut defer, donc l’ordre des balises de script dans le document est l’ordre d’exécution.

La définition d’élément

Voici l’élément Alert de dxpr_builder/dxpr_elements.js (source : dxpr_builder/build/dxpr-elements/elements/az_alert.js), réduit aux propriétés requises par chaque élément. L’original définit aussi el_class, un gestionnaire onCancel et un écouteur focusout.

window.dxprBuilder.dxpr_elements.push({
  base: "az_alert",
  name: Drupal.t("Alert"),
  nameEn: "Alert",
  icon: "az-custom-icon az-alert-icon",
  largeSettingsWindow: true,
  hidden: false,
  params: [
    {
      type: "textarea",
      heading: Drupal.t("Message"),
      param_name: "content",
      value: Drupal.t(
        "This is a placeholder text. Your actual content will go here. Edit this to include your own information.",
      ),
    },
    {
      type: "dropdown",
      heading: Drupal.t("Type"),
      param_name: "type",
      description: Drupal.t("Select message type."),
      getText: (opt) => opt.text,
      getValue: (opt) => opt.value,
      getDefault: (param) => param.value["alert-success"].value,
      value: {
        "alert-success": { text: Drupal.t("Success"), value: "alert-success" },
        "alert-info": { text: Drupal.t("Info"), value: "alert-info" },
        "alert-warning": { text: Drupal.t("Warning"), value: "alert-warning" },
        "alert-danger": { text: Drupal.t("Danger"), value: "alert-danger" },
      },
      passthrough: true,
    },
  ],
  show_settings_on_create: true,
  has_content: true,
  render(...args) {
    const state = {
      class: `az-element az-alert alert ${this.attrs.type} ${this.get_el_classes()} ${this.get_content_classes()}`,
      style: this.attrs.style,
      message: this.attrs.content,
    };
    this.dom_element = this.baseclass.prototype.render_template(
      "elements/alert/alert",
      state,
    );
    this.baseclass.prototype.render.apply(this, args);
  },
  save_history() {
    const container = this.get_my_container();
    container.save_container_history(Drupal.t("Edit alert"));
  },
});
Propriété Rôle
base Nom machine. Il est écrit dans le HTML enregistré comme data-azb="az_alert" et sert à retrouver la classe quand la page est de nouveau analysée. Gardez le préfixe az_.
name, nameEn Libellé dans le sélecteur d’éléments et sa forme non traduite.
icon Classes CSS pour l’icône du sélecteur. Les icônes livrées sont définies dans dxpr_builder/sass/dxpr-builder-backend/components/_custom-icons.scss ; un élément personnalisé a besoin de son propre CSS pour sa classe d’icône.
category Onglet du sélecteur. Les éléments de mise en page utilisent Drupal.t("Layout") ; blocs et vues utilisent "CMS". Omettez-le pour l’onglet de contenu par défaut.
is_container true quand l’élément contient d’autres éléments.
largeSettingsWindow Ouvre le dialogue de réglages en grande taille.
hidden Masque l’élément du sélecteur tout en le gardant rendable.
params Réglages montrés dans le dialogue. Voir ci-dessous.
show_settings_on_create Ouvre le dialogue de réglages dès que l’élément est déposé.
has_content L’élément a une zone de contenu modifiable (dom_content_element).
render(...args) Construit this.dom_element depuis this.attrs, puis appelle le render de base.
save_history() Empile une entrée d’annulation avec un libellé.
parse_attrs_from_dom(element) Optionnel. Relit les valeurs de paramètres depuis le balisage enregistré, utilisé avec skip_data_azb et skip_params par des éléments comme Image qui stockent les valeurs dans les attributs <img> au lieu de data-azat-*.

Paramètres

Chaque entrée de params a :

Clé Sens
type L’un des types de paramètres enregistrés (section suivante).
heading Libellé du champ.
param_name Clé dans this.attrs et, dans le HTML enregistré, l’attribut data-azat-<param_name>. content est spécial : c’est le corps de l’élément, pas un attribut.
value Valeur par défaut. Pour dropdown c’est un objet d’options avec les callbacks getText, getValue et getDefault.
description Texte d’aide sous le champ.
tab Regroupe le champ dans un accordéon nommé, par exemple Drupal.t("SEO").
can_be_empty Stocke une chaîne vide au lieu de retirer l’attribut.
safe Ignore l’encodage URL de la valeur stockée.

Les types de paramètres sont les valeurs type empilées sur window.dxprBuilder.dxpr_param_types dans dxpr_builder/dxpr_param_types.js : boxmodel, checkbox, checkboxes, colorpicker, datetime, dropdown, dxb_slider, html, icon, image, images, link, links, numericfield, row_layout, saved_datetime, social_links, textarea, textfield et video. Un type non enregistré retombe sur un champ de texte brut (BaseParamType).

Ne réutilisez pas un param_name hérité par chaque élément, comme color, background, el_class, hash ou les noms d’animation an_*. L’onglet Style possède déjà color, donc un second paramètre avec ce nom n’est jamais écrit dans data-azat-color.

Ce qui est enregistré

update_data() dans dxpr_builder.js écrit l’élément en HTML :

  • data-azb="<base>" sur l’élément racine ;
  • data-azat-<param_name>="<value>" pour chaque paramètre dont la valeur diffère de son défaut, encodé en URL sauf si le paramètre définit safe: true ;
  • les paramètres de l’onglet Style comme style en ligne et classes, jamais comme attributs data-azat-* ;
  • data-azcnt="true" sur l’élément de contenu quand has_content est défini.

Les visiteurs sans éditeur reçoivent ce HTML sans ses attributs data-az* : DxprBuilderService::parseDocumentForCleanup() les retire, sauf une liste fixe conservée sur les éléments animés et sur les éléments dynamiques intégrés comme Carousel. Si l’élément a besoin de JavaScript ou de CSS pour les visiteurs, posez un attribut data-dxpr-builder-libraries="<key>" sur sa racine : parseDocumentForTemplateLibrary() attache dxpr_builder/elements.<key> pour chaque clé sans barre oblique. Les clés sont les bibliothèques elements.* dans dxpr_builder.libraries.yml ; une clé avec une barre oblique n’est honorée que si elle est la library d’un jeu d’icônes enregistré.

Exemple minimal fonctionnel

L’exemple ajoute un élément « Notice » qui rend un paragraphe avec une couleur choisie. Il construit son DOM directement car les modèles twig.js compilés dans dxpr_templates.js font partie du paquet de l’éditeur et ne peuvent pas être étendus par un autre module.

  1. Créez mymodule/js/notice-element.js :

    window.dxprBuilder.dxpr_elements.push({
      base: "az_notice",
      name: Drupal.t("Notice"),
      nameEn: "Notice",
      icon: "az-custom-icon az-alert-icon",
      params: [
        {
          type: "textfield",
          heading: Drupal.t("Text"),
          param_name: "text",
          value: Drupal.t("Notice text"),
        },
        {
          type: "colorpicker",
          heading: Drupal.t("Colour"),
          param_name: "notice_color",
          value: "#0d6efd",
        },
      ],
      show_settings_on_create: true,
      render(...args) {
        const el = document.createElement("div");
        el.className = `az-element az-notice ${this.get_el_classes()}`;
        if (this.attrs.style) {
          el.setAttribute("style", this.attrs.style);
        }
        const p = document.createElement("p");
        p.style.color = this.attrs.notice_color;
        p.textContent = this.attrs.text;
        el.appendChild(p);
        this.dom_element = el;
        this.baseclass.prototype.render.apply(this, args);
      },
      save_history() {
        this.get_my_container().save_container_history(Drupal.t("Edit notice"));
      },
    });
    
  2. Insérez le fichier dans la bibliothèque de l’éditeur entre dxpr_elements.js (poids -4) et dxpr_builder.js (poids -3) dans mymodule.module :

    /**
     * Implements hook_library_info_alter().
     */
    function mymodule_library_info_alter(array &$libraries, string $extension): void {
      if ($extension === 'dxpr_builder' && isset($libraries['editor.builder'])) {
        $path = '/' . \Drupal::service('extension.list.module')->getPath('mymodule');
        $libraries['editor.builder']['js'][$path . '/js/notice-element.js'] = [
          'weight' => -3.5,
          'preprocess' => FALSE,
          'attributes' => ['defer' => TRUE],
        ];
      }
    }
    

    preprocess: FALSE et defer correspondent aux entrées que le module définit pour ses propres fichiers, pour que le fichier garde sa place dans la séquence, que les ressources de l’éditeur viennent du CDN ou d’un build local.

  3. Videz le cache et ouvrez une page du builder :

    drush cr
    
  4. Ajoutez l’élément depuis le sélecteur, définissez son texte et sa couleur, et enregistrez. Le champ stocke :

    <div class="az-element az-notice" id="..." data-azb="az_notice"
         data-azat-text="Hello" data-azat-notice_color="%23198754">
      <p style="color: #198754;">Hello</p>
    </div>
    

    Un visiteur reçoit le même balisage sans les attributs data-az*. Le paragraphe garde sa couleur en ligne, donc aucune bibliothèque visiteur n’est requise pour cet élément.

Limites à connaître

  • Les profils ne listent pas les éléments personnalisés. La liste d’éléments d’un profil du builder vient d’une liste fixe dans src/Form/DxprBuilderProfileForm.php et src/Service/Handler/ProfileHandler.php. Les éléments hors de cette liste ne sont jamais masqués par un profil.
  • Les icônes pour le sélecteur ont besoin de CSS depuis votre module ; les classes az-custom-icon livrées par le builder ne couvrent que les éléments livrés.
  • DxprBuilderService::getDxprElementsFolders() et son hook_dxpr_builder_elements_folders_alter() renvoient une liste de dossiers elements dans les thèmes, mais rien dans l’éditeur ne lit cette liste. Ce n’est pas une voie d’enregistrement.
  • Les onglets Style et Animation sont ajoutés pour vous par register_animated_element() ; ne redéfinissez pas ces paramètres.

Et ensuite ?

  • API des hooks pour les hooks PHP qui ajoutent des classes utilitaires et des jeux d’icônes.
  • Architecture pour l’articulation entre l’éditeur et le formateur de champ.