Aangepaste elementen maken.

Learn DXPR BuilderExtendBouw erop voort

Elk DXPR Builder-element is een JavaScript-object gepusht op window.dxprBuilder.dxpr_elements. De editor maakt van elk object een elementklasse met de tabbladen Style en Animation er al aan. Deze pagina toont hoe de meegeleverde elementen zijn gedefinieerd in dxpr_builder/dxpr_elements.js, hoe een module zijn eigen definitie aan de editor toevoegt en hoe de resulterende HTML eruitziet. Er zijn geen Twig-sjablonen per element aan de serverkant; elementen renderen in de browser.

Hoe de editor elementen registreert

De editor laadt zes scripts in deze volgorde, gedefinieerd in dxpr_builder_library_info_build() in dxpr_builder.module als de dxpr_builder/editor.builder-library:

  1. dxpr_templates.js: twig.js-sjablonen aan de clientkant gecompileerd uit dxpr_builder/templates/.
  2. dxpr_global.js: gedeelde helpers en de CKEditor 5-integratie.
  3. dxpr_events.js
  4. dxpr_param_types.js: pusht één object per inputtype op window.dxprBuilder.dxpr_param_types.
  5. dxpr_elements.js: maakt de array window.dxprBuilder.dxpr_elements en pusht één definitie per element.
  6. dxpr_builder.js: de editor. Wanneer dit bestand wordt uitgevoerd roept het create_dxpr_elements() aan, dat over window.dxprBuilder.dxpr_elements loopt en voor elke definitie:
    • roept register_animated_element(base, is_container, Class) aan, zodat de klasse AnimatedElement uitbreidt (tabblad Style, tabblad Animation, HTML ID, HTML classes en plaatsingsparameters);
    • hangt de overgeërfde parameters aan de params van de definitie;
    • converteert elke parameter via make_param_type(), dat param.type matcht met de geregistreerde parametertypen;
    • mixt de eigenschappen van de definitie in het klasseprototype.

Een definitie moet dus op de array staan nadat dxpr_elements.js heeft gedraaid en voordat dxpr_builder.js draait. Alle zes bestanden laden met het attribuut defer, dus de documentvolgorde van de scripttags is de uitvoeringsvolgorde.

De elementdefinitie

Dit is het Alert-element van dxpr_builder/dxpr_elements.js (bron: dxpr_builder/build/dxpr-elements/elements/az_alert.js), ingekort tot de eigenschappen die elk element nodig heeft. Het origineel stelt ook el_class, een onCancel-handler en een focusout-listener in.

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"));
  },
});
Eigenschap Doel
base Machinenaam. Hij wordt in de opgeslagen HTML geschreven als data-azb="az_alert" en gebruikt om de klasse te vinden wanneer de pagina opnieuw wordt geparsed. Houd de prefix az_ aan.
name, nameEn Label in de elementkiezer en zijn niet-vertaalde vorm.
icon CSS-klassen voor het kiezerpictogram. De meegeleverde pictogrammen zijn gedefinieerd in dxpr_builder/sass/dxpr-builder-backend/components/_custom-icons.scss; een aangepast element heeft eigen CSS nodig voor zijn pictogramklasse.
category Kiezer-tabblad. De lay-outelementen gebruiken Drupal.t("Layout"); blokken en views gebruiken "CMS". Laat het weg voor het standaard inhoudstabblad.
is_container true wanneer het element andere elementen bevat.
largeSettingsWindow Opent het instellingendialoogvenster in de grotere maat.
hidden Verbergt het element voor de kiezer terwijl het renderbaar blijft.
params Instellingen getoond in het dialoogvenster. Zie hieronder.
show_settings_on_create Opent het instellingendialoogvenster zodra het element wordt neergezet.
has_content Het element heeft een bewerkbaar inhoudsgebied (dom_content_element).
render(...args) Bouwt this.dom_element uit this.attrs, en roept daarna de basis-render aan.
save_history() Pusht een undo-vermelding met een label.
parse_attrs_from_dom(element) Optioneel. Leest parameterwaarden terug uit opgeslagen markup, samen gebruikt met skip_data_azb en skip_params door elementen zoals Image die waarden in de <img>-attributen bewaren in plaats van data-azat-*.

Parameters

Elke vermelding in params heeft:

Sleutel Betekenis
type Een van de geregistreerde parametertypen (volgende sectie).
heading Veldlabel.
param_name Sleutel in this.attrs en, in opgeslagen HTML, het attribuut data-azat-<param_name>. content is speciaal: het is de elementbody, geen attribuut.
value Standaardwaarde. Voor dropdown is het een object met opties met getText-, getValue- en getDefault-callbacks.
description Hulptekst onder het veld.
tab Groepeert het veld in een benoemde accordeon, bijvoorbeeld Drupal.t("SEO").
can_be_empty Bewaar een lege string in plaats van het attribuut te laten vallen.
safe Sla URL-codering van de opgeslagen waarde over.

De parametertypen zijn de type-waarden gepusht op window.dxprBuilder.dxpr_param_types in 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 en video. Een type dat je niet registreert valt terug op een gewone tekstinput (BaseParamType).

Hergebruik geen param_name die elk element overerft, zoals color, background, el_class, hash of de an_*-animatienamen. Het tabblad Style bezit color al, dus een tweede parameter met die naam wordt nooit naar data-azat-color geschreven.

Wat wordt opgeslagen

update_data() in dxpr_builder.js schrijft het element naar HTML:

  • data-azb="<base>" op het rootelement;
  • data-azat-<param_name>="<value>" voor elke parameter waarvan de waarde afwijkt van zijn standaard, URL-gecodeerd tenzij de parameter safe: true zet;
  • de Style-tabbladparameters als inline style en klassen, nooit als data-azat-*-attributen;
  • data-azcnt="true" op het inhoudselement wanneer has_content is ingesteld.

Kijkers die de editor niet krijgen ontvangen deze HTML zonder zijn data-az*-attributen: DxprBuilderService::parseDocumentForCleanup() stript ze, behalve een vaste lijst die hij behoudt op geanimeerde elementen en op ingebouwde dynamische elementen zoals Carousel. Als het element JavaScript of CSS nodig heeft voor bezoekers, zet je een data-dxpr-builder-libraries="<key>"-attribuut op zijn root: parseDocumentForTemplateLibrary() koppelt dxpr_builder/elements.<key> voor elke key zonder slash. De keys zijn de elements.*-libraries in dxpr_builder.libraries.yml; een key met een slash wordt alleen gehonoreerd wanneer het de library is van een geregistreerde pictogrammenset.

Minimaal werkend voorbeeld

Het voorbeeld voegt een "Notice"-element toe dat een alinea met een gekozen kleur rendert. Het bouwt zijn DOM direct omdat de gecompileerde twig.js-sjablonen in dxpr_templates.js deel zijn van de editorbundle en niet door een andere module kunnen worden uitgebreid.

  1. Maak 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. Voeg het bestand in de editorlibrary in tussen dxpr_elements.js (gewicht -4) en dxpr_builder.js (gewicht -3) 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 en defer matchen de vermeldingen die de module definieert voor zijn eigen bestanden, zodat het bestand zijn plek in de volgorde behoudt of editorassets nu uit het CDN of uit een lokale build komen.

  3. Wis de cache en open een builderpagina:

    drush cr
    
  4. Voeg het element uit de kiezer toe, stel zijn tekst en kleur in en sla op. Het veld bewaart:

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

    Een bezoeker krijgt dezelfde markup zonder de data-az*-attributen. De alinea behoudt zijn inline kleur, dus voor dit element is geen bezoekerslibrary nodig.

Limieten om te kennen

  • Profielen vermelden geen aangepaste elementen. De elementchecklist in een builderprofiel komt uit een vaste lijst in src/Form/DxprBuilderProfileForm.php en src/Service/Handler/ProfileHandler.php. Elementen buiten die lijst worden nooit door een profiel verborgen.
  • Pictogrammen voor de kiezer hebben CSS nodig van je module; de az-custom-icon-klassen meegeleverd door de builder dekken alleen de meegeleverde elementen.
  • DxprBuilderService::getDxprElementsFolders() en zijn hook_dxpr_builder_elements_folders_alter() geven een lijst met elements-mappen in thema's terug, maar niets in de editor leest die lijst. Het is geen registratiroute.
  • Style- en Animation-tabbladen worden voor je toegevoegd door register_animated_element(); herdefinieer die parameters niet.

Wat nu?

  • Hooks API voor de PHP-hooks die utility-klassen en pictogrammensets toevoegen.
  • Architectuur voor hoe de editor en de veldformatter in elkaar passen.