Erstellen eines WordPress Custom Control mit Underscore.js- (JavaScript-)Template für den Theme-Customizer

Vor einiger Zeit wollte ich mein WordPress Theme “Tikva” mit einem Custom Control ausstatten. Dabei handelt es sich um eines derjenigen Elemente im Admin-Bereich, d.h. innerhalb des Customizers, die nicht per Default von WordPress mitgeliefert werden. Dabei handelte es sich um einen “Repeater”, d.h. ein Control, das eine beliebige Anzahl von Elementen beinhalten kann. Dieser Repeater sollte nicht einerseits flexibel und erweiterbar sein, jedoch ohne Abhängigkeiten zu anderen Plugins wie Redux Framework oder Kirki Toolkit. Denn das Problem bei derartigen Abhängigkeiten ist, dass diese Libraries entweder direkt dem Theme hinzu gepackt werden müssen, oder vom Nutzer hinzu installiert werden müssen. Dabei besagt die WordPress-Policy, dass die Abhängigkeit nur als “Empfehlung” ausgesprochen werden kann, so dass der Nutzer wiederum die Wahl hat, das eigentlich benötigte Plugin nicht zu installieren, infolge dessen würden Teile des Theme-Customizers wiederum nicht funktionieren. 

Da bereits der Customizer auf recht viel JavaScript basiert, und insbesondere beim dynamischen Hinzufügen oder Entfernen von Elementen keine Server-Interaktion stattfindet bzw. stattfinden sollte, hatte ich mich dazu entschieden, möglichst viele Aktivitäten auf Client-Ebene durchzuführen. Nun ist ein Custom Control eigentlich einfach zu erstellen, aber letztlich noch immer eine Mischung zwischen PHP und HTML wie in längst vergangenen Zeiten. Nicht zuletzt beziehen sich die meisten Beiträge zur Programmierung auf den klassischen Weg, ein Artikel findet sich z.B. bei wpmudev

WordPress bietet jedoch ebenfalls die Unterstützung von JavaScript-Templates, basierend auf der bekannten JavaScript-Libary underscore.js. Dazu finden sich jedoch wesentlich weniger Tutorials bzw. Hinweise zur Programmierung, weshalb das Repeater-Control mit viel Trial-and-Error entstanden ist. Die fertige Fassung ist übrigens in der aktuellen Version des Tikva-Themes enthalten und wird dort genutzt, um den Inhalt der Aufmacher-Bereich (Lead Area) festzulegen. 

Letztlich ist das grundlegende Prinzip eines derartigen Custom Controls mit Underscore.js-Templates auch relativ einfach, vorausgesetzt, man hat es einmal verstanden. Um dabei ein wenig zu helfen, ist dieser Blog-Eintrag entstanden. 

Das Beispiel

Bei dem folgenden Beispiel handelt es sich nicht um das Repeater-Control, sondern um ein neues Font-Control, was ab der Version 0.5.4 des Tikva-Themes Platz findet. Das Font-Control bietet eine Auswahl von Schriftarten und – in der aktuellen Version – einer Schriftgröße. Dabei werden Schriftart und -größe gemeinsam verwaltet, es ist somit nicht notwendig, die Schriftart z.B. per Select-Feld zu speichern und die Schriftgröße in einem davon unabhängigen Textfeld.

Dieses einfachere Beispiel habe ich daher gewählt, weil die Konzepte identisch sind, aber die Komplexität wesentlich geringer ausfällt. Im Beispiel zeige ich die Anwendung und den Aufbau des Controls, wobei – das als Warnung vorweg – der hier dargestellte Code größtenteils nicht per Copy&Paste übernommen werden kann bzw. sollte, da Stellen verkürzt oder weg gelassen sind, falls diese nicht nötig sind, um die Struktur zu erkennen bzw. das Prinzip zu verstehen. Letztlich finden sich die vollständigen Controls-Klassen auch frei zugänglich in der veröffentlichten Version des Themes Tikva. Ebenso werde ich nicht alle Komponenten des Theme-Customizers erläutern, eine Einführung dazu findet sich z.B. in einem Artikel bei t3n

Hinzufügen einer Custom Control Klasse

Wenn die Custom Control Klasse existiert, kann sie in der Customizer-Definition verwendet werden. Von “außen” betrachtet wird das Control einfach wie andere “Advanced”-Controls, z.B. File-Upload- oder Image-Control hinzugefügt:

Hier wird unter dem Bezeichner “setting_typography_headline” das entsprechende Setting definiert, was sich im Control “control_typography_headline” befindet. Die weiteren Optionen des Controls dienen als Ausgaben für den Nutzer sowie zur Platzierung innerhalb des Customizers, d.h. innerhalb der “section_theme_options_typography”. Inzwischen achte ich bei sämtlichen Controls bzw. Settings auf ein einheitliches Namensschema. 

Damit ist die Einbindung auch bereits abgeschlossen, weitere Angaben sind nicht notwendig, da die Klasse “Tikva_Custom_Font_Control” weiß, welche Elemente sie beinhaltet bzw. wie diese zu speichern sind. Zwar kann noch ein Default-Wert hinzugefügt werden, aber der betreffende Code befindet sich aktuell noch in Entwicklung bzw. ist für das Beispiel zunächst nicht notwendig. 

Die Bestandteile eines Custom Controls

Das Custom Control besteht aus zwei Komponenten – einerseits der PHP-Klasse, die von WP_Customize_Control abgeleitet wurde, andererseits aus der dazu gehörigen JavaScript-Datei, deren Hauptaufgabe darin besteht, die aus den einzelnen Formularfeldern eingelesenen Daten an das Control zu übermitteln, so dass geänderte Werte als Theme-Option gespeichert werden können. 

Tatsächlich findet die meiste Verarbeitung in der JavaScript-Datei statt, während die PHP-Klasse nur dafür zuständig ist, den notwendigen HTML-Code initial darzustellen bzw. die in der Datenbank gespeicherten Werte entgegen zu nehmen und dem Control bereit zu stellen. Insgesamt ist der WordPress-Customizer bereits eine Single Page Application (SPA), ohne dies ausdrücklich so zu bezeichnen.

Das hört sich theoretischer an, als es ist, daher folgen nun einige Erläuterungen am Beispiel, was ich zum leichteren Verständnis etwas gekürzt habe. 

Das Custom Control

Die PHP-Klasse für das Custom Control sieht (ein wenig gekürzt) wie folgt aus:

 

Wichtig ist meines Erachtens das Verständnis der Methoden

  • enqueue()
  • to_json()
  • content_template() bzw. render_content()

enqueue()

Die enqueue()-Methode dient dazu, ggf. notwendige JavaScript- und CSS-Dateien zu laden bzw. in den Verarbeitungsprozess von WordPress zu integrieren. Datei wird mit wp_enqueue_script() im Beispiel die JavaScript-Datei custom-font.js geladen. Dazu später mehr.

Des Weiteren wird dafür gesorgt, dass die im WordPress-Standardumfang enthaltene JavaScript-Library namens underscore.js eingebunden wird. Dabei ist zu beachten, dass viele der bekannten JavaScript-Bibliotheken bereits mit WordPress ausgeliefert werden, was aber nicht heißt, dass sie zu jeder Zeit aktiv sind. Das ist insofern von Vorteil, als dass im Detail ausgewählt werden kann, welche der Libraries relevant sind und aktiviert werden müssen. Damit muss nicht jedes Theme oder Plugin eine eigene Version der notwendigen Libraries ausliefern, sondern kann auf zentrale Bestandteile zurück greifen. Von underscore.js wird das JavaScript-Template-System eingesetzt. 

to_json()

Die Methode to_json() ist laut WordPress-Dokumentation dafür zuständig, Parameter, die an JavaScript übergeben wurden, via JSON zu aktualisieren. Das ist zwar richtig, erklärt aber noch nicht viel. 

Letztlich geht es darum: Wenn der WordPress-Admin in den Customizer geht, und somit die SPA startet, müssen alle in der Datenbank gespeicherten Konfigurationsoptionen des Themes sowie Daten aus der Definition der Controls wie z.B. Default-Werte an die JavaScript-Seite übergeben werden. Dazu wird das assoziative Array $this->json[] verwendet. Im JavaScript sind diese Werte anschließend unter der Variable data verfügbar, siehe bei den Hinweisen zu content_template()

Neben den Daten, die standardmäßig enthalten sind, können auch eigene Daten hinzu gefügt werden – ein Beispiel wäre die Verwendung des Keys “identifier” als Abkürzung für den Werte, der in $this->json['settings']['default'] enthalten ist, dabei handelt es sich um den Namen des Settings, das wäre “setting_typography_headline” aus obigem Beispiel. Wichtig ist, dass die in Parameter aus der Definition des Controls standardmäßig zur Verfügung stehen, sofern zu Beginn parent::to_json() aufgerufen wurde. 

Die Werte, die für das Control in der Datenbank abgelegt werden sind letztlich entscheidend zur Konfiguration des Themes. Diese Angaben werden zunächst ausgelesen mit $this->values(), anschließend dekodiert, da in der Datenbank serialisiert im JSON-Format gespeichert, und danach unter dem Key “value” verfügbar gemacht. 

content_template() / render_content()

Als ich mit der Entwicklung des ersten Controls, basierend auf JavaScript-Underscore.js-Templates begonnen habe, haben mich diese beiden Methoden zunächst etwas verwirrt. Letztlich ist es so, dass diese Methoden mehr oder minder dasselbe machen, jedoch auf unterschiedlichen Ebenen. Die Methoden sind dafür zuständig, die Ausgabe zu generieren, die den Admin-Bereich des Custom Controls darstellt, d.h. Titel, Formularelemente zur Eingabe, Beschreibung usw.. Wenn kein Underscore-JavaScript-Template verwendet wird, befindet sich der dafür zuständige HTML- bzw. PHP-Code in render_content(). Dabei kann PHP beendet und reines HTML gezeichnet werden, wie etwa im Beispiel der Code-Reference. Falls jedoch – wie hier beabsichtigt – ein Undersocre-JavaScript-Template für die Ausgabe sorgen soll, wird content_template() verwendet und render_template() muss vorhanden, aber leer sein. Somit wird die render_template()-Methode der abgeleiteten Klasse überschrieben. Die Ausgaben von content_template() werden zur Client-Seite geleitet, darin ist dann die Verwendung von Underscore-Template möglich. 

Natürlich können auch in content_template() auch PHP-Funktionen aufgerufen werden bzw. Ausgaben von PHP generiert werden, was jedoch nicht gerade zur Übersichtlichkeit beiträgt. Diese Möglichkeit habe ich an den Stellen benutzt, an denen Ausgaben zur Internationalisierung vorhanden sind, da dies per JavaScript bei WordPress noch etwas umständlich ist. 

Die content_template()-Methode dient somit dazu, das Control zu kreieren, unter Benutzung der underscore.js-Template-Library. Dabei können – wie im Beispiel vorhanden – JavaScript-Fragmente enthalten sein. JavaScript wird mittels der Symbole “<#” eingeleitet und durch “#>” beendet. Dabei können die Start- und End-Zeichen ähnlich wie in PHP verwendet werden und sich insbesondere auch über mehrere Zeilen erstrecken, siehe folgendes Beispiel:

Innerhalb des Blocks der if-Anweisung wird hier HTML ausgegeben, der Block endet erst im darauf folgenden JavaScript-Abschnitt. Ebenfalls zeigt das Beispiel die Ausgabe von JavaScript-Variablen, die in geschweiften Klammern erfolgt. Der Einfachheit halber ist es dabei nicht notwendig, die Start- und End-Symbole wie bei der if-Anweisung zu verwenden. 

Der weitere Code im Beispiel baut ein Select-Feld auf, wobei eine Vorauswahl erfolgt, falls die entsprechende Variable bereits gesetzt ist. Wichtiger als das Verständnis von jeder Zeile des Beispiels erscheint mir hier eine Übersicht des Prinzips der Verwendung der Underscore.js-Templates. Der gezeigte Code ist Bestandteil des Themes Tikva in der aktuellen Version und dient zur Auswahl der Schriftart. 

Reaktion und Verarbeitung mittels JavaScript

Die zweite Komponente dient zur Auswertung der Eingaben und wird mittels JavaScript realisiert. Dazu ist die bereits Datei custom-font.js zuständig, deren Inhalt ich hier gekürzt zeigen möchte.

 

Zunächst ist die Ableitung von wp.customize.Control wichtig. Die ready()-Funktion wird von WordPress bei der Initialisierung aufgerufen. Letztlich ist die Zeile entscheidend, die den gewählten Wert in das Control-Objekt füllt:

Damit werden die Daten, die sich in der Variablen “settingData” befinden, zur weiteren Verarbeitung WordPress übergeben, d.h. der Admin kann dann die geänderten Werte beispielsweise speichern. Ebenso weiß WordPress erst aufgrund dieser Zeile, dass sich die Werte geändert haben und sorgt somit für einen Reload der Vorschau bzw. bei Verwendung der Live-Preview für eine entsprechende Aktualisierung. 

Wie kommen nun die geänderten Werte in diese Variable? Dazu ist ein wenig Event-Handling nötig. Innerhalb von initFontControl() befindet wird mit dem change-Event des Select-Feldes eine Aktion verknüpft, so dass die Methode onChangeSelectUpdate() aufgerufen wird. Die Aktionen könnte man auch direkt in innerhalb der Event-Definition ausführen, aber der Übersichtlichkeit halber und da dieselben Aktionen mehrfach verwendet werden, habe ich dazu onChangeSelectUpdate() erstellt. 

Darin wird aus dem Select-Feld zunächst der gewähnte Wert gelesen. Anhand dessen erfolgt die weitere Verarbeitung, die hier nicht näher dargestellt wird. Letztlich werden alle notwendigen Informationen wie Schriftart, Schriftgröße, ein Flag, ob es sich um einen Google-Font handelt usw., in einem Objekt namens “elementData” gespeichert. Diese Variable wird der Methode updateCurrentDataField() übergeben. Darin wird das Objekt als JSON serialisiert und in einem Element des Controls abgespeichert. Danach wird der Event “event_font_updated” ausgelöst. Die Reaktion auf dieses Event wurde zu Beginn definiert. Dabei wird der Wert aus dem entsprechenden Feld ausgelesen und dem Control übergeben. Durch die Serialisierung im JSON-Format können somit auch komplexere Strukturen gespeichert werden. Korrespondierend dazu erfolgt in der weiter oben besprochenen content_template()-Methode ein Parsen der Daten im JSON-Format, so dass die Ausgabe entsprechend angepasst werden kann. So wird etwa die gewählte Schriftart innerhalb des Select-Feldes angezeigt, wenn der Admin an die entsprechende Stelle im Customizer gelangt. 

Auch hier erscheint mir das Verständnis des Prinzips wichtiger als die Beschäftigung mit jeder Codezeile. Das Prinzip lautet – noch einmal kurz beschrieben – wie folgt:

Der Admin ändert einen Wert im Control. Durch die Änderung wird ein Event ausgelöst. Dieser Event muss vorab definiert werden. Die Reaktion auf den Event sorgt dafür, dass die geänderten Daten gelesen, verarbeitet und gespeichert, d.h. dem Control übergeben werden. Es empfiehlt sich die Verwendung des JSON-Formates, da damit nicht nur singuläre Werte gespeichert werden können. 

Davon mehr oder minder unabhängig ist die HTML-Ebene, d.h. das Control, was mit Hilfe des Underscore.js-Templates dargestellt wird. Relevant werden die gespeicherten Werte dabei, sofern eine Anpassung der Ausgabe notwendig ist. Das dürfte jedoch in den meisten Fällen notwendig sein, da der Admin-User beim neuerlichen Benutzen des Customizers auch die geänderten Angaben vorfinden möchte. Daher werden die zum Control gehörigen Daten unter dem Index “value” übergeben, so dass eine Verarbeitung stattfinden kann, siehe dazu Zeile 106 und Zeilen 127ff. in der weiter oben angegebenen Datei Tikva_Custom_Font_Control

Fazit

Insbesondere bei intensiver Nutzung von JavaScript innerhalb von Controls erscheint mir die Verwendung der Underscore.js-Library eine sehr gute Alternative zum Rendern des Controls in PHP. Es “fühlt” sich “natürlicher” an, z.B. auf dieselbe Datenstruktur in JSON zugreifen zu können bzw. allgemein die bei WordPress meines Erachtens zu häufige Mischung zwischen PHP und HTML nicht einsetzen zu müssen. Zwar ist das Underscore.js-Template noch immerhalb einer PHP-Datei, aber insgesamt geht der Ansatz bereits in die richtige Richtung. Natürlich wird nach wie vor die PHP-basierte Controls-API unterstützt, jedoch dringt JavaScript in immer mehr Bereiche vor. Das soll nicht heißen, dass JavaScript innerhalb von WordPress PHP ersetzen kann oder soll – dafür ist es auch gar nicht gedacht. Jedoch würde ich mir persönlich ein wenig mehr Konsequenz wünschen, beispielsweise bei neueren Entwicklungen wie dem Customizer stellt sich die Frage, wie lange die Kompatibilität zur PHP-basierten API überhaupt noch vorhanden sein muss. Mit dem einen oder anderen Bruch wird auch WordPress leben müssen, um sich der Zukunft zu stellen. 

 

 

Ähnliche Beiträge

Tags:
Kategorien:

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.