Criar elementos personalizados.

Learn DXPR BuilderExtendBouw erop voort

Cada elemento do DXPR Builder é um objeto JavaScript empurrado para window.dxprBuilder.dxpr_elements. O editor transforma cada objeto numa classe de elemento com os separadores Style e Animation já anexados. Esta página mostra como os elementos incluídos se definem em dxpr_builder/dxpr_elements.js, como um módulo adiciona a própria definição ao editor, e como fica o HTML resultante. Não há modelos Twig de servidor por elemento; os elementos apresentam-se no navegador.

Como o editor regista elementos

O editor carrega seis scripts por esta ordem, definidos em dxpr_builder_library_info_build() em dxpr_builder.module como a biblioteca dxpr_builder/editor.builder:

  1. dxpr_templates.js: modelos twig.js do lado do cliente compilados de dxpr_builder/templates/.
  2. dxpr_global.js: auxiliares partilhados e a integração com CKEditor 5.
  3. dxpr_events.js
  4. dxpr_param_types.js: empurra um objeto por tipo de entrada para window.dxprBuilder.dxpr_param_types.
  5. dxpr_elements.js: cria o array window.dxprBuilder.dxpr_elements e empurra uma definição por elemento.
  6. dxpr_builder.js: o editor. Quando este ficheiro executa, chama create_dxpr_elements(), que percorre window.dxprBuilder.dxpr_elements e para cada definição:
    • chama register_animated_element(base, is_container, Class), para a classe estender AnimatedElement (separador Style, separador Animation, HTML ID, classes HTML e parâmetros de colocação);
    • anexa os parâmetros herdados aos params da definição;
    • converte cada parâmetro através de make_param_type(), que compara param.type com os tipos de parâmetro registados;
    • mistura as propriedades da definição no protótipo da classe.

Uma definição tem por isso de estar no array depois de dxpr_elements.js correr e antes de dxpr_builder.js correr. Os seis ficheiros carregam com o atributo defer, por isso a ordem dos script tags no documento é a ordem de execução.

A definição de elemento

Este é o elemento Alert de dxpr_builder/dxpr_elements.js (origem: dxpr_builder/build/dxpr-elements/elements/az_alert.js), reduzido às propriedades de que cada elemento precisa. O original também define el_class, um manipulador onCancel e um ouvinte 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"));
  },
});
Property Purpose
base Nome de sistema. Escreve-se no HTML guardado como data-azb="az_alert" e usa-se para encontrar a classe quando a página é analisada outra vez. Mantenha o prefixo az_.
name, nameEn Etiqueta no seletor de elementos e a forma não traduzida.
icon Classes CSS para o ícone do seletor. Os ícones incluídos definem-se em dxpr_builder/sass/dxpr-builder-backend/components/_custom-icons.scss; um elemento personalizado precisa do próprio CSS para a classe de ícone.
category Separador do seletor. Os elementos de esquema usam Drupal.t("Layout"); blocos e vistas usam "CMS". Omita para o separador de conteúdo predefinido.
is_container true quando o elemento contém outros elementos.
largeSettingsWindow Abre o diálogo de definições no tamanho maior.
hidden Esconde o elemento do seletor mantendo-o apresentável.
params Definições mostradas no diálogo. Ver abaixo.
show_settings_on_create Abre o diálogo de definições assim que o elemento é largado.
has_content O elemento tem uma área de conteúdo editável (dom_content_element).
render(...args) Constrói this.dom_element a partir de this.attrs e depois chama o render base.
save_history() Empurra uma entrada de anulação com etiqueta.
parse_attrs_from_dom(element) Opcional. Lê valores de parâmetro de volta da marcação guardada, usado com skip_data_azb e skip_params por elementos como Image que guardam valores nos atributos <img> em vez de data-azat-*.

Parâmetros

Cada entrada em params tem:

Key Meaning
type Um dos tipos de parâmetro registados (próxima secção).
heading Etiqueta do campo.
param_name Chave em this.attrs e, no HTML guardado, o atributo data-azat-<param_name>. content é especial: é o corpo do elemento, não um atributo.
value Valor predefinido. Para dropdown é um objeto de opções com retornos getText, getValue e getDefault.
description Texto de ajuda sob o campo.
tab Agrupa o campo num acordeão nomeado, por exemplo Drupal.t("SEO").
can_be_empty Guarda uma cadeia vazia em vez de largar o atributo.
safe Omite a codificação de URL do valor guardado.

Os tipos de parâmetro são os valores type empurrados para window.dxprBuilder.dxpr_param_types em 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 e video. Um tipo sem registo recua para uma entrada de texto simples (BaseParamType).

Não reutilize um param_name que cada elemento herda, como color, background, el_class, hash ou os nomes de animação an_*. O separador Style já detém color, por isso um segundo parâmetro com esse nome nunca é escrito em data-azat-color.

O que fica guardado

update_data() em dxpr_builder.js escreve o elemento em HTML:

  • data-azb="<base>" no elemento raiz;
  • data-azat-<param_name>="<value>" para cada parâmetro cujo valor difere da predefinição, codificado para URL salvo se o parâmetro define safe: true;
  • os parâmetros do separador Style como style em linha e classes, nunca como atributos data-azat-*;
  • data-azcnt="true" no elemento de conteúdo quando has_content está definido.

Os visitantes sem editor recebem este HTML sem os atributos data-az*: DxprBuilderService::parseDocumentForCleanup() remove-os, exceto uma lista fixa que mantém em elementos animados e em elementos dinâmicos incluídos como Carousel. Se o elemento precisa de JavaScript ou CSS para visitantes, ponha um atributo data-dxpr-builder-libraries="<key>" na raiz: parseDocumentForTemplateLibrary() anexa dxpr_builder/elements.<key> para cada chave sem barra. As chaves são as bibliotecas elements.* em dxpr_builder.libraries.yml; uma chave com barra só é aceite quando é a library de um conjunto de ícones do registo.

Exemplo mínimo funcional

O exemplo adiciona um elemento "Notice" que apresenta um parágrafo com uma cor escolhida. Constrói o DOM diretamente porque os modelos twig.js compilados em dxpr_templates.js fazem parte do pacote do editor e não podem ser estendidos por outro módulo.

  1. Crie 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. Insira o ficheiro na biblioteca do editor entre dxpr_elements.js (peso -4) e dxpr_builder.js (peso -3) em 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 e defer correspondem às entradas que o módulo define para os próprios ficheiros, por isso o ficheiro mantém o lugar na sequência quer os recursos do editor venham da CDN ou de uma compilação local.

  3. Limpe a cache e abra uma página do construtor:

    drush cr
    
  4. Adicione o elemento a partir do seletor, defina o texto e a cor, e guarde. O 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>
    

    Um visitante recebe a mesma marcação sem os atributos data-az*. O parágrafo mantém a cor em linha, por isso não é precisa biblioteca de visitantes para este elemento.

Limites a conhecer

  • Profiles do not list custom elements. A lista de verificação de elementos num perfil do construtor vem de uma lista fixa em src/Form/DxprBuilderProfileForm.php e src/Service/Handler/ProfileHandler.php. Elementos fora dessa lista nunca são escondidos por um perfil.
  • Icons for the picker precisam de CSS do módulo; as classes az-custom-icon incluídas no construtor abrangem apenas os elementos incluídos.
  • DxprBuilderService::getDxprElementsFolders() e o hook_dxpr_builder_elements_folders_alter() devolvem uma lista de pastas elements em temas, mas nada no editor lê essa lista. Não é uma rota de registo.
  • Style and Animation tabs são adicionados por register_animated_element(); não redefina esses parâmetros.

O que se segue?

  • API de hooks para os hooks PHP que adicionam classes utilitárias e conjuntos de ícones.
  • Arquitetura para saber como o editor e o formatador de campo combinam.