Creación de elementos personalizados.

Learn DXPR BuilderExtendBouw erop voort

Cada elemento de DXPR Builder es un objeto JavaScript puesto en window.dxprBuilder.dxpr_elements. El editor convierte cada objeto en una clase de elemento con las pestañas Style y Animation ya adjuntas. Esta página muestra cómo se definen los elementos incluidos en dxpr_builder/dxpr_elements.js, cómo un módulo añade su propia definición al editor y cómo es el HTML resultante. No hay plantillas Twig de servidor por elemento; los elementos se renderizan en el navegador.

Cómo el editor registra elementos

El editor carga seis scripts en este orden, definidos en dxpr_builder_library_info_build() en dxpr_builder.module como la biblioteca dxpr_builder/editor.builder:

  1. dxpr_templates.js: plantillas twig.js de cliente compiladas desde dxpr_builder/templates/.
  2. dxpr_global.js: ayudas compartidas y la integración CKEditor 5.
  3. dxpr_events.js
  4. dxpr_param_types.js: pone un objeto por tipo de entrada en window.dxprBuilder.dxpr_param_types.
  5. dxpr_elements.js: crea el array window.dxprBuilder.dxpr_elements y pone una definición por elemento.
  6. dxpr_builder.js: el editor. Cuando este archivo se ejecuta llama a create_dxpr_elements(), que recorre window.dxprBuilder.dxpr_elements y para cada definición:
    • llama a register_animated_element(base, is_container, Class), así que la clase extiende AnimatedElement (pestaña Style, pestaña Animation, HTML ID, HTML classes y parámetros de colocación);
    • añade los parámetros heredados a los params de la definición;
    • convierte cada parámetro con make_param_type(), que cruza param.type con los tipos de parámetro registrados;
    • mezcla las propiedades de la definición en el prototipo de la clase.

Una definición tiene que estar en el array después de que dxpr_elements.js se ejecutó y antes de que dxpr_builder.js se ejecute. Los seis archivos se cargan con el atributo defer, así que el orden de documento de las etiquetas de script es el orden de ejecución.

La definición de elemento

Este es el elemento Alert de dxpr_builder/dxpr_elements.js (fuente: dxpr_builder/build/dxpr-elements/elements/az_alert.js), recortado a las propiedades que cada elemento necesita. El original también fija el_class, un manejador onCancel y un listener 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"));
  },
});
Propiedad Propósito
base Nombre de máquina. Se escribe en el HTML guardado como data-azb="az_alert" y se usa para encontrar la clase cuando la página se analiza de nuevo. Conserva el prefijo az_.
name, nameEn Etiqueta en el selector de elementos y su forma sin traducir.
icon Clases CSS para el icono del selector. Los iconos incluidos se definen en dxpr_builder/sass/dxpr-builder-backend/components/_custom-icons.scss; un elemento personalizado necesita su propio CSS para su clase de icono.
category Pestaña del selector. Los elementos de diseño usan Drupal.t("Layout"); bloques y vistas usan "CMS". Omítelo para la pestaña de contenido predeterminada.
is_container true cuando el elemento contiene otros elementos.
largeSettingsWindow Abre el diálogo de ajustes en el tamaño grande.
hidden Oculta el elemento del selector manteniéndolo renderizable.
params Ajustes mostrados en el diálogo. Ver abajo.
show_settings_on_create Abre el diálogo de ajustes en cuanto el elemento se suelta.
has_content El elemento tiene un área de contenido editable (dom_content_element).
render(...args) Construye this.dom_element desde this.attrs, y luego llama al render base.
save_history() Pone una entrada de deshacer con etiqueta.
parse_attrs_from_dom(element) Opcional. Lee valores de parámetros de vuelta desde el marcado guardado, usado junto con skip_data_azb y skip_params por elementos como Image que guardan valores en los atributos <img> en lugar de data-azat-*.

Parámetros

Cada entrada en params tiene:

Clave Significado
type Uno de los tipos de parámetro registrados (siguiente sección).
heading Etiqueta del campo.
param_name Clave en this.attrs y, en el HTML guardado, el atributo data-azat-<param_name>. content es especial: es el cuerpo del elemento, no un atributo.
value Valor predeterminado. Para dropdown es un objeto de opciones con callbacks getText, getValue y getDefault.
description Texto de ayuda bajo el campo.
tab Agrupa el campo en un acordeón con nombre, por ejemplo Drupal.t("SEO").
can_be_empty Guarda una cadena vacía en lugar de quitar el atributo.
safe Omite la codificación URL del valor guardado.

Los tipos de parámetro son los valores type puestos en window.dxprBuilder.dxpr_param_types en 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 y video. Un tipo que no registras vuelve a una entrada de texto simple (BaseParamType).

No reutilices un param_name que cada elemento hereda, como color, background, el_class, hash o los nombres de animación an_*. La pestaña Style ya posee color, así que un segundo parámetro con ese nombre nunca se escribe en data-azat-color.

Qué se guarda

update_data() en dxpr_builder.js escribe el elemento a HTML:

  • data-azb="<base>" en el elemento raíz;
  • data-azat-<param_name>="<value>" para cada parámetro cuyo valor difiere de su predeterminado, codificado en URL salvo que el parámetro fija safe: true;
  • los parámetros de la pestaña Style como style en línea y clases, nunca como atributos data-azat-*;
  • data-azcnt="true" en el elemento de contenido cuando has_content está fijado.

Quienes no reciben el editor reciben este HTML sin sus atributos data-az*: DxprBuilderService::parseDocumentForCleanup() los quita, salvo una lista fija que conserva en elementos animados y en elementos dinámicos integrados como Carousel. Si el elemento necesita JavaScript o CSS para visitas, pon un atributo data-dxpr-builder-libraries="<key>" en su raíz: parseDocumentForTemplateLibrary() adjunta dxpr_builder/elements.<key> por cada clave sin barra. Las claves son las bibliotecas elements.* en dxpr_builder.libraries.yml; una clave con barra solo se respeta cuando es el library de un conjunto de iconos registrado.

Ejemplo mínimo funcional

El ejemplo añade un elemento "Notice" que renderiza un párrafo con un color elegido. Crea su DOM directamente porque las plantillas twig.js compiladas en dxpr_templates.js son parte del paquete del editor y otro módulo no puede extenderlas.

  1. Crea 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. Inserta el archivo en la biblioteca del editor entre dxpr_elements.js (peso -4) y dxpr_builder.js (peso -3) en 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 y defer coinciden con las entradas que el módulo define para sus propios archivos, así que el archivo conserva su lugar en la secuencia vengan los recursos del editor de la CDN o de una compilación local.

  3. Limpia la caché y abre una página del constructor:

    drush cr
    
  4. Añade el elemento desde el selector, fija su texto y color, y guarda. El campo guarda:

    <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>
    

    Una visita recibe el mismo marcado sin los atributos data-az*. El párrafo conserva su color en línea, así que este elemento no necesita biblioteca de visitas.

Límites que debes conocer

  • Los perfiles no enumeran elementos personalizados. La lista de elementos en un perfil del constructor viene de una lista fija en src/Form/DxprBuilderProfileForm.php y src/Service/Handler/ProfileHandler.php. Los elementos fuera de esa lista nunca los oculta un perfil.
  • Icons for the picker: los iconos del selector necesitan CSS de tu módulo. Las clases az-custom-icon incluidas solo cubren los elementos incluidos.
  • DxprBuilderService::getDxprElementsFolders() y su hook_dxpr_builder_elements_folders_alter() devuelven una lista de carpetas elements en temas, pero nada en el editor lee esa lista. No es una ruta de registro.
  • Style and Animation tabs las añade por ti register_animated_element(); no redefinas esos parámetros.

¿Qué sigue?

  • API de hooks para los hooks PHP que añaden clases de utilidades y conjuntos de iconos.
  • Arquitectura para ver cómo encajan el editor y el formato de campo.