
7 Tricks zur Erstellung eines Drupal-Themas, von denen Sie vielleicht noch nie gehört haben
Die Erstellung von Themes in Drupal ist nicht so einfach, wie es scheint. Daher gibt es hier Raum, um Ihre Arbeit zu erleichtern. Oft gehen wir jedoch Abkürzungen ein, die nicht immer die gewünschten Ergebnisse bringen oder gegen die Philosophie von Drupal und allgemein anerkannte Best Practices verstoßen.
Viele von Ihnen wissen wahrscheinlich, dass wir in Drupal, wie in jedem anderen CMS oder Framework, die gleichen Ziele erreichen können, indem wir unterschiedliche Wege wählen, um sie zu erreichen. Im Laufe meiner Erfahrung bin ich wiederholt auf häufige Fehler von Drupal-Anfängern gestoßen und habe es geschafft, viele Codes von neuen und erfahrenen Drupal-Entwicklern zu verfolgen. Ich habe gesehen, wie unterschiedlich die Ansätze sind, die sie verwenden. Im Rahmen dieses Artikels möchte ich 7 Tricks (oder einfach Abkürzungen) vorstellen, die die Arbeit an der Erstellung eines Themes erleichtern und von vielen Programmierern, von denen ich nie gehört habe.
Da mit jeder Unterversion von Drupal verschiedene bedeutende Änderungen einhergehen, möchte ich die hier besprochenen Beispiele hervorheben, die mit der Kernversion 8.9.x kompatibel sind. Alle vorgestellten Tricks sind, obwohl sie manchmal umstritten sein mögen, vollständig konsistent mit der Kunst und können für Projekte verwendet werden.
1. Verwendung des #context-Schlüssels in der Rendering-Array
Eines der häufigen Probleme bei der Erstellung von Templates ist die Möglichkeit, Daten zwischen ihnen zu übergeben. Dies erfordert normalerweise, dass Programmierer komplexe Operationen durchführen, z. B. bei der Verarbeitung von Templates. Die Herausforderung wird noch schwieriger, wenn einige Hooks bereits zwischengespeichert wurden und das Hochladen von Daten noch erforderlich war. Es mag viele Ideen zur Lösung des Problems geben, aber mit der Drupal-Version 8.8 wird das Problem durch den #context-Schlüssel gelöst.
Angenommen, wir haben hook_preprocess_node implementiert, in dem wir mit einem dedizierten Template ein zusätzliches Element hinzufügen, aber dennoch Zugriff auf das Node-Objekt haben möchten. Hier kommt der #context-Schlüssel ins Spiel.
function my_module_preprocess_node(array &$variables) {
$node = $variables['node'];
$variables['my_custom_element'] = [
'#theme' => 'my_custom_theme',
'#title' => t('Mein benutzerdefinierter Theme-Titel'),
'#context' => [
'entity' => $node,
],
];
}
Dadurch ist es möglich, beliebige Daten von einer Rendering-Tabelle zu einer anderen zu übergeben. Sie können alles darin unterbringen, z. B. eine Instanz eines Objekts, das wir in der weiteren Verarbeitung benötigen werden:
function my_module_preprocess_my_custom_theme(array &$variables) {
$node = $variables['#context']['entity'];
// Machen Sie, was Sie mit der Node-Entity in Ihrem benutzerdefinierten Theme benötigen...
}
Es ist erwähnenswert, dass diese Variable auch in hook_theme_suggestion_alter verfügbar ist, was zusätzliche Vorschläge für kontextabhängige Templatenamen erleichtern kann.
2. Unterstützung von Caching in Twig-Templates
Immer anspruchsvollere Grafikdesigns von Websites und die Seitenoptimierung (hinsichtlich der Menge des zurückgegebenen HTML-Codes) zwingen uns dazu, einige Abkürzungen zu verwenden, z. B. das direkte Extrahieren des Feldwerts im Entity-Template (Node-Typ).
Lösungen wie
{{ content.field_my_reference_field.0 }}
die nur ein einziges Feld statt der Inhaltsvariable anzeigen, können katastrophale Folgen in Form von Cache-Problemen haben. In den Rendering-Arrays gibt es neben dem endgültigen HTML-Code auch Schlüssel, die für die Speicherung solcher Cache-Metadaten verantwortlich sind: Tag, Context und Max-Age. Wenn Sie sicherstellen möchten, dass die Metadaten korrekt vom Renderer-Dienst gesammelt werden, fügen Sie einfach eine magische Zeile im Template hinzu:
{% set rendered = content|render %}
Im Beispiel eines Node-Templates wird es die Inhaltsvariable sein, es kann jedoch jede "Haupt"-Variable Ihres Templates sein. Die Übergabe eines solchen Haupt-Rendering-Arrays durch die Renderfilter ermöglicht es Ihnen, alle Cache-Metadaten korrekt zu sammeln, ohne die gesamte Inhaltsvariable anzeigen zu müssen. Gleichzeitig hat die Lösung keine negativen Auswirkungen auf die Leistung der Code-Ausführung, solange wir Cache-Mechanismen verwenden.
3. Thema-Vorschläge ohne Verwendung von hook_theme_suggestion_
Ja, das klingt nach etwas völlig Verrücktem. Diese Lösung ist jedoch "out of the box" verfügbar. Es genügt, den Vorschlag zu den im #theme-Schlüssel oder theme_hook_original versteckten Werten in einem beliebigen Rendering-Array (z. B. in hook_preprocess_HOOK) hinzuzufügen und dabei die Namenskonvention für Templates einzuhalten.
Als Ergebnis setzt der in dieser Weise festgelegte Variablenwert
function my_module_preprocess_paragraph(array &$variables) {
$paragraph = $variables['paragraph'];
if ($paragraph->bundle() === 'my_awesome_bundle') {
$variables['theme_hook_original'] = 'paragraph__my_custom_theme_suggestion';
}
}
einen Vorschlag für ein gegebenes Basistemplate (Paragraph) und ermöglicht es Ihnen, eine Template-Datei mit dem Namen paragraph-my-custom-theme-suggestion.html.twig zu erstellen. Dennoch würde ich diese Lösung auf Ausnahmefälle beschränken. Für den allgemeinen Gebrauch empfehle ich einen für diesen Zweck dedizierten Hook.
4. Sicherer Filterersatz | raw
Dieser Filter wird im Allgemeinen als potenziell gefährlich angesehen (er erhöht die Möglichkeit erfolgreicher XSS-Angriffe) und wird in Einzelfällen verwendet. Es sollte jedoch vermieden werden, soweit es möglich ist. Ich schlage vor, nach einem besseren Weg zu suchen, besonders da einer von ihnen extrem einfach zu verwenden ist, wie Sie in einem Beispiel sehen werden.
Gehen wir davon aus, dass wir ein Feld haben, das letztendlich das Bild anzeigen soll, aber aus irgendeinem Grund möchten wir den gesamten unterwegs generierten HTML-Code auslassen.
Der einfachste Weg, dies zu tun, ist:
{{ content.field_my_image_field|render|striptags('<img>')|trim }}
Erfahrene Drupal-Entwickler wissen, dass eine solche Lösung anstelle eines Bildes auf der Seite den gesamten Inhalt als Klartext darstellt, der in HTML-Entitäten kodiert ist. Ja, Sie können einen raw-Filter hinzufügen, aber Sie können es anders machen, indem Sie die Render-API von Drupal mit dem #markup-Schlüssel verwenden:
{{ {'#markup': content.field_my_image_field|render|striptags('<img>')|trim} }}
Die Methode ist vielleicht etwas weniger lesbar als mit dem Filter, aber sie ist sicher. HTML-Code wird hier weiterhin gefiltert und nur erlaubte sichere Tags werden übergeben.
5. Referenzierung einer Template-Datei aus einem anderen Theme/Modul
Wenn Sie die Mechanismen von Twig wie include, embed oder extend in Ihren Template-Dateien noch nicht verwendet haben, schlage ich vor, die DRY-Regel in allen Aspekten der Programmierung (oder Webentwicklung im Allgemeinen) zu berücksichtigen. Themes sind die Orte, die am meisten unter dupliziertem Code leiden, sowohl in Template-Dateien als auch in Stylesheets.
Viele Male bin ich auf das gesamte Template-Datei gestoßen, das kopiert wurde, z. B. von einem geerbten Theme oder einem Modul, nur um ein wenig umgebend HTML-Code hinzuzufügen oder einen einzelnen Template-Block zu ändern. Derselbe Effekt kann erzielt werden, indem auf eine übergeordnete Datei mit den Aliasen @module_name @theme_name verwiesen wird. Angenommen, wir haben eine Template-Datei (Card) in unserem Beispielmodul namens custom_card
<div class="card">
{% block card_header %}
<div class="card__header">{{ header }}</div>
{% endblock %}
{% block card_content %}
<div class="card__content">{{ content }}</div>
{% endblock %}
{% block card__footer %}
<div class="card__footer">{{ footer }}</div>
{% endblock %}
</div>
Und jetzt in unserem Theme müssten wir einige zusätzliche Elemente im Header-Block hinzufügen, z. B. ein Icon. Das häufigste geschieht ist, den gesamten Template-Code zu kopieren, aber das kann einfacher und ohne Wiederholung des Codes gemacht werden:
{% extends '@custom_card/card.html.twig' %}
{% block header %}
<div class="card__header">
<div class="icon">{{ icon }}</div>
{{ header }}
</div>
{% endblock %}
6. Neu laden von Ansichten mit InvokeCommand
Wie Sie wissen, ermöglicht das Views-Modul die Erstellung von Ansichten, die AJAX-Technologie verwenden. Dies geschieht, um dynamisch zwischen den verschiedenen Seiten der Ansicht zu wechseln, aber auch, um die Ergebnisse der Antwort auf Wertänderungen aus dem bereitgestellten Formular anzuzeigen. Aber was tun, wenn der Inhalt der Ansicht aufgrund einiger Aktionen außerhalb davon aktualisiert werden muss, z. B. nach dem Senden einer Anfrage an den Controller von einem anderen Block? Oder um ein Formular auf derselben Seite abzusenden? Nun, im Kern von Drupal gibt es zwei sehr praktische Funktionalitäten.
Die erste ist das Standardereignis RefreshView, das in der Ansicht registriert ist und dazu führt, dass die Ansicht mit AJAX aktualisiert wird (ich empfehle, es in der Browser-Konsole in Ihrer Beispielansicht einzugeben und den Effekt zu beobachten).
Der zweite Mechanismus ist InvokeCommand, das es ermöglicht, in der AJAX-Antwort den Aufruf der jQuery-Methode zusammen mit den angegebenen Parametern auf dem DOM-Element zurückzugeben. Zusammen ergeben sich daraus diese Beispiel-Codes:
class MyCustomController extends ControllerBase {
/**
* Führen Sie die Aktualisierung durch und aktualisieren Sie die Ansicht.
*
* @return \Drupal\Core\Ajax\AjaxResponse
* Ajax-Antwort.
*/
public function doUpdateAndRefresh(): AjaxResponse {
// Führen Sie eine Art Logik aus, die für Ihren Controller benötigt wird.
$this->doUpdateOperations();
// Aktualisieren Sie die Ansicht.
$response = new AjaxResponse();
$response->addCommand(new InvokeCommand('#my-view-block', 'trigger', ['RefreshView']));
return $response;
}
}
In meinem Beispiel gab es Formulare zur Benutzerbestätigung innerhalb der generierten Ansicht. Als Antwort auf den Eintrag eines solchen Formulars musste ich eine aktualisierte Liste anzeigen. Wie Sie sehen, hat im Grunde eine zusätzliche Zeile den ganzen Zauber gemacht, ohne zusätzlichen Aufwand.
7. Verwendung von Drupal.debounce
Zum Schluss möchte ich etwas beschreiben, das nicht so sehr ein Trick als eine gute Praxis ist. Die Underscore.js-Bibliothek ist im Kern von Drupal enthalten, aber viele Entwickler nutzen sie überhaupt nicht. Sie enthält eine Reihe von Funktionalitäten. Einer der häufigsten Fälle, in denen die Möglichkeiten dieser Bibliothek nicht genutzt werden, ist das sogenannte Debouncing, das heißt die Verhinderung mehrfacher Codeausführung als Reaktion auf ein bestimmtes Ereignis - meist ist es eine Änderung der Breite des Browserfensters.
Ich bin viele Male auf Code getroffen, der wie folgt aussah:
function onResize(callback) {
var timer;
return function (event) {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(callback,100, event);
};
}
$(window).resize(onResize(function(e) {
// Resize-Implementierung geht hier hin.
}));
Natürlich ist dies eine beispielhafte und einfachste Implementierung, und solcher Code wird korrekt ausgeführt. Die Frage ist: Warum das Rad neu erfinden? Die Antwort: Um die Drupal-Kompatibilität im Kern von Drupal zu verbessern und auch die Bequemlichkeit der Arbeit für Entwickler zu erhöhen - es gibt einen Debouncer-Port aus der Underscore-Bibliothek.
Fügen Sie einfach eine Abhängigkeit zu Ihrer Bibliothek hinzu
- core/drupal.debounce
und fügen Sie folgende Implementierung im JS-Code hinzu:
$(window).resize(Drupal.debounce(function () {
// Resize-Implementierung geht hier hin.
}, 100));
Ist das nicht einfacher? Und vor allem verwenden wir etwas, das schon einmal gut geschrieben wurde.
Zusammenfassung
Obwohl ich auf viele interessante Ansätze beim Arbeiten mit Themes in unserer Drupal-Agentur gestoßen bin, muss betont werden, dass viele Dinge mit Erfahrung kommen. Besonders neue Entwickler sollten von Anfang an der Philosophie von Drupal folgen. Sie sollten verstehen, warum sie etwas auf eine bestimmte Weise tun. Dadurch können wir die Möglichkeiten von Drupal leichter erkennen und lernen, sie zu nutzen, um den Arbeitskomfort und die Effizienz zu steigern, ohne die Sauberkeit des Codes zu verlieren.