Eigene Elemente erstellen.

Learn DXPR BuilderExtendBouw erop voort

Jedes DXPR-Builder-Element ist ein JavaScript-Objekt, das auf window.dxprBuilder.dxpr_elements gepusht wird. Der Editor macht aus jedem Objekt eine Elementklasse, mit bereits angehängten Tabs Style und Animation. Diese Seite zeigt, wie die mitgebrachten Elemente in dxpr_builder/dxpr_elements.js definiert sind, wie ein Modul eine eigene Definition in den Editor einbringt und wie das Ergebnis-HTML aussieht. Es gibt keine serverseitigen Twig-Vorlagen pro Element; Elemente rendern im Browser.

Wie der Editor Elemente registriert

Der Editor lädt sechs Skripte in dieser Reihenfolge, definiert in dxpr_builder_library_info_build() in dxpr_builder.module als Bibliothek dxpr_builder/editor.builder:

  1. dxpr_templates.js: clientseitige twig.js-Vorlagen, kompiliert aus dxpr_builder/templates/.
  2. dxpr_global.js: geteilte Helfer und die CKEditor-5-Integration.
  3. dxpr_events.js
  4. dxpr_param_types.js: pusht je ein Objekt pro Eingabetyp auf window.dxprBuilder.dxpr_param_types.
  5. dxpr_elements.js: erzeugt das Array window.dxprBuilder.dxpr_elements und pusht eine Definition pro Element.
  6. dxpr_builder.js: der Editor. Wenn diese Datei ausführt, ruft sie create_dxpr_elements() auf, das über window.dxprBuilder.dxpr_elements loopt und pro Definition:
    • ruft register_animated_element(base, is_container, Class) auf, sodass die Klasse AnimatedElement erweitert (Tab Style, Tab Animation, HTML-ID, HTML-Klassen und Platzierungsparameter);
    • die geerbten Parameter an die params der Definition anhängt;
    • jeden Parameter durch make_param_type() wandelt, das param.type gegen die registrierten Parametertypen matcht;
    • die Eigenschaften der Definition in den Klassenprototyp mischt.

Eine Definition muss daher nach dxpr_elements.js und vor dxpr_builder.js auf dem Array liegen. Alle sechs Dateien laden mit dem defer-Attribut, daher ist die Dokumentreihenfolge der Skript-Tags die Ausführungsreihenfolge.

Die Elementdefinition

Dies ist das Alert-Element aus dxpr_builder/dxpr_elements.js (Quelle: dxpr_builder/build/dxpr-elements/elements/az_alert.js), gestutzt auf die Eigenschaften, die jedes Element braucht. Das Original setzt außerdem el_class, einen onCancel-Handler und einen focusout-Listener.

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"));
  },
});
Eigenschaft Zweck
base Systemname. Er wird als data-azb="az_alert" in das gespeicherte HTML geschrieben und genutzt, um die Klasse zu finden, wenn die Seite erneut geparst wird. Den az_-Präfix behalten.
name, nameEn Bezeichnung in der Elementauswahl und ihre unübersetzte Form.
icon CSS-Klassen für das Auswahl-Icon. Die mitgebrachten Icons sind in dxpr_builder/sass/dxpr-builder-backend/components/_custom-icons.scss definiert; ein eigenes Element braucht eigenes CSS für seine Icon-Klasse.
category Auswahl-Tab. Die Layout-Elemente nutzen Drupal.t("Layout"); Blöcke und Ansichten nutzen "CMS". Weglassen für den Standard-Inhalts-Tab.
is_container true, wenn das Element andere Elemente hält.
largeSettingsWindow Öffnet den Einstellungsdialog in der größeren Größe.
hidden Verbirgt das Element vor der Auswahl, hält es aber renderbar.
params Einstellungen, die im Dialog gezeigt werden. Siehe unten.
show_settings_on_create Öffnet den Einstellungsdialog, sobald das Element abgelegt wird.
has_content Das Element hat einen bearbeitbaren Inhaltsbereich (dom_content_element).
render(...args) Baut this.dom_element aus this.attrs und ruft dann das Basis-render auf.
save_history() Pusht einen Rückgängig-Eintrag mit Bezeichnung.
parse_attrs_from_dom(element) Optional. Liest Parameterwerte aus gespeichertem Markup zurück, genutzt zusammen mit skip_data_azb und skip_params von Elementen wie Image, die Werte in den <img>-Attributen statt data-azat-* speichern.

Parameter

Jeder Eintrag in params hat:

Schlüssel Bedeutung
type Einer der registrierten Parametertypen (nächster Abschnitt).
heading Feldbezeichnung.
param_name Schlüssel in this.attrs und, im gespeicherten HTML, das Attribut data-azat-<param_name>. content ist besonders: Es ist der Element-Body, kein Attribut.
value Standardwert. Für dropdown ist es ein Optionsobjekt mit getText-, getValue- und getDefault-Callbacks.
description Hilfetext unter dem Feld.
tab Gruppiert das Feld in ein benanntes Akkordeon, zum Beispiel Drupal.t("SEO").
can_be_empty Leere Zeichenkette speichern, statt das Attribut zu droppen.
safe URL-Codierung des gespeicherten Werts überspringen.

Die Parametertypen sind die type-Werte, die in dxpr_builder/dxpr_param_types.js auf window.dxprBuilder.dxpr_param_types gepusht werden: boxmodel, checkbox, checkboxes, colorpicker, datetime, dropdown, dxb_slider, html, icon, image, images, link, links, numericfield, row_layout, saved_datetime, social_links, textarea, textfield und video. Ein Typ, den Sie nicht registrieren, fällt auf eine schlichte Texteingabe zurück (BaseParamType).

Nutzen Sie keinen param_name wieder, den jedes Element erbt, etwa color, background, el_class, hash oder die an_*-Animationsnamen. Der Tab Style besitzt color bereits, daher wird ein zweiter Parameter mit diesem Namen nie nach data-azat-color geschrieben.

Was gespeichert wird

update_data() in dxpr_builder.js schreibt das Element nach HTML:

  • data-azb="<base>" auf dem Wurzelelement;
  • data-azat-<param_name>="<value>" für jeden Parameter, dessen Wert vom Standard abweicht, URL-codiert, außer der Parameter setzt safe: true;
  • die Style-Tab-Parameter als Inline-style und Klassen, nie als data-azat-*-Attribute;
  • data-azcnt="true" auf dem Inhaltselement, wenn has_content gesetzt ist.

Betrachtende, die den Editor nicht erhalten, bekommen dieses HTML ohne seine data-az*-Attribute: DxprBuilderService::parseDocumentForCleanup() streift sie, außer einer festen Liste, die es auf animierten Elementen und auf eingebauten dynamischen Elementen wie Carousel behält. Wenn das Element JavaScript oder CSS für Besuchende braucht, legen Sie ein data-dxpr-builder-libraries="<key>"-Attribut auf seine Wurzel: parseDocumentForTemplateLibrary() hängt dxpr_builder/elements.<key> für jeden Schlüssel ohne Schrägstrich an. Die Schlüssel sind die elements.*-Bibliotheken in dxpr_builder.libraries.yml; ein Schlüssel mit Schrägstrich gilt nur, wenn er die library eines registrierten Icon-Sets ist.

Minimal lauffähiges Beispiel

Das Beispiel fügt ein „Notice“-Element hinzu, das einen Absatz mit gewählter Farbe rendert. Es baut sein DOM direkt, weil die kompilierten twig.js-Vorlagen in dxpr_templates.js Teil des Editor-Bundles sind und von einem anderen Modul nicht erweitert werden können.

  1. Erstellen Sie 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. Fügen Sie die Datei zwischen dxpr_elements.js (Gewicht -4) und dxpr_builder.js (Gewicht -3) in die Editor-Bibliothek ein, in 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 und defer passen zu den Einträgen, die das Modul für seine eigenen Dateien definiert, daher behält die Datei ihren Platz in der Reihenfolge, ob Editor-Assets vom CDN oder aus einem lokalen Build kommen.

  3. Leeren Sie den Cache und öffnen Sie eine Builder-Seite:

    drush cr
    
  4. Fügen Sie das Element aus der Auswahl hinzu, setzen Sie Text und Farbe und speichern Sie. Das Feld speichert:

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

    Gäste erhalten dasselbe Markup ohne die data-az*-Attribute. Der Absatz behält seine Inline-Farbe, daher braucht dieses Element keine Besucherbibliothek.

Zu kennende Grenzen

  • Profile listen eigene Elemente nicht. Die Element-Checkliste in einem Builder-Profil stammt aus einer festen Liste in src/Form/DxprBuilderProfileForm.php und src/Service/Handler/ProfileHandler.php. Elemente außerhalb dieser Liste verbirgt kein Profil je.
  • Icons für die Auswahl brauchen CSS aus Ihrem Modul; die az-custom-icon-Klassen des Builders decken nur die mitgebrachten Elemente ab.
  • DxprBuilderService::getDxprElementsFolders() und sein hook_dxpr_builder_elements_folders_alter() liefern eine Liste von elements-Ordnern in Themes, aber nichts im Editor liest diese Liste. Es ist keine Registrierungsroute.
  • Style- und Animation-Tabs werden von register_animated_element() für Sie hinzugefügt; definieren Sie diese Parameter nicht neu.

Wie geht es weiter?

  • Hooks-API für die PHP-Hooks, die Dienstklassen und Icon-Sets hinzufügen.
  • Architektur: wie Editor und Feld-Formatierer zusammenpassen.