Integration des WordPress Color Pickers in die Widget-Admin-UI

Mit dem Color Picker lässt sich innerhalb von WordPress auf einfache Art und Weise eine Farbe auswählen. Meist wird dieses UI-Element in der Admin-Oberfläche von Themes oder im Customizer bei der Anpassung der Farbgestaltung des verwendeten Themes genutzt. Da es sich um ein von WordPress bereit gestelltes Eingabeelement handelt, erschien es mir nur konsequent, es auch in den Einstellungen eines Widgets zu verwenden.

Zur Integration des WordPress-Color-Pickers in Themes und Plugins finden sich viele Tutorials und Anleitungen, im Detail mitunter etwas abweichend voneinander. Beispielsweise wären da “WordPress’ Color Picker in Plugins (und Themes) nutzen” oder auch “Die WordPress Color Picker API Richtig Verwenden“, um nur zwei zu nennen.

Vorbemerkungen

Bemerkenswerterweise hatte ich bei meinen Recherchen jedoch wesentlich weniger, um nicht zu sagen fast nichts über die Einbindung in die Admin-UI eines Widgets gefunden. Letztlich sollte das Ergebnis innerhalb der Widget-Admin-UI wie folgt aussehen:

Integration des WordPress Color Pickers in die Widget-Admin-UI 1

Ebenso sollte der Color Picker im Widget-Bereich des Customizers lauffähig sein:

Integration des WordPress Color Pickers in die Widget-Admin-UI 2

Die Integration ist eigentlich auch ganz einfach und funktioniert wie in den Tutorials und Anleitungen beschrieben. Naja, fast…

Ich beschreibe hier nun meine Lösung, bin mir aber sicher, dass es weitere Möglichkeiten gibt, die ebenfalls zu einer erfolgreichen Integration des WordPress Color Pickers führen.

Schritt 1: Integration der Color Picker CSS- und JavaScript-Dateien

Das Widget wird von einem Plugin bereit gestellt. Daher sind die Schritte zur Aktivierung des Color Pickers zunächst dieselben wie bei einem Plugin.

Üblicherweise besitzt ein Plugin ein Verzeichnis mit einem eindeutigen Namen unterhalb von “wp-content/plugins/“. Beispielsweise soll das Plugin “azb-mein-plugin” heißen. Damit ergibt sich folgende Struktur:

wp-content/plugins/
                   azb-mein-plugin/
                                   azb-mein-plugin.php

Innerhalb dieser Datei findet die Initialisierung statt, im Falle des Widgets wird dieses registriert und damit der WordPress-Instanz signalisiert, dass es verfügbar ist. Da ich einen Namensraum bei der von WP_Widget abgeleiteten Widget-Klasse nutze, sieht die Registrierung wie folgt aus:

[...]
require( plugin_dir_path( __FILE__ ) . 'src/Azbalac/Widget/WidgetName.php');
[...]


add_action( 'widgets_init', function() {
    register_widget('\Azbalac\Widget\WidgetName'); // Main widget class
    azbMeinPluginWidgetStyle();
    azbMeinPluginWidgetJavaScript();
});

Zunächst wird die Widget-Klasse eingebunden. Anschließend wird das Widget registriert, hier unter Verwendung des Namensraumes “\Azbalac\Widget“. Die daraufhin aufgerufenen Funktionen sind für die Einbindung der CSS- und JavaScript-Dateien zuständig. Da der Aufruf der Funktionen bei jeder Registrierung des Widgets stattfindet, d.h. sobald das Plugin in WordPress aktiviert ist und geladen wird, die Verwendung des WordPress Color Pickers zumindest bei diesem Plugin bzw. Widget nur in der Admin-UI notwendig ist, werden die Funktionen zur Einbindung des Color Pickers noch in einem is_admin()-Anweisungsblock gekapselt:

function azbMeinWidgetWidgetJavaScript()
{
   
    wp_enqueue_script(
        'azbalac-mein-widget-javascript-head',            //Give the script an ID
        plugins_url('/js/mein-widget-head.js',__FILE__), //Point to file
        array( 'jquery'),  //Define dependencies
        '',                         //Define a version (optional)
        false                        //Put script in footer?
    );

    wp_enqueue_script(
        'azbalac-mein-widget-javascript',            //Give the script an ID
        plugins_url('/js/mein-widget.js',__FILE__), //Point to file
        array( 'jquery'),  //Define dependencies
        '',                         //Define a version (optional)
        true                        //Put script in footer?
    );

    
    if (is_admin()) {
        wp_enqueue_style( 'wp-color-picker' );
        wp_enqueue_script('wp-color-picker');
    }


}

Entscheidend zur Einbindung des Color Pickers sind die beiden Zeilen innerhalb des is_admin()-Anweisungsblocks. Streng genommen wäre es angebracht, die Einbindung des notwendigen CSS-Teils in die Funktion azbMeinPluginWidgetStyle() zu verlagern, andererseits wäre es auch möglich, eine Action zu verwenden, die nur im Admin-Bereich ausgeführt wird… Wie erwähnt führen hier einige Wege zum Ziel, wichtig ist jedoch, dass die wp_enqueue_*-Funktionen genutzt werden, damit WordPress die Möglichkeit hat, die Abhängigkeiten zu verwalten. Dabei ist “wp-color-picker” ein bereits von WordPress im Standardumfang angebotenes Handle. WordPress bietet etliche JavaScript-Elemente an, eine Liste befindet sich in der Dokumentation. Falls möglich, empfiehlt es sich, darauf zurück zu greifen, man erspart sich somit das Anbieten und somit auch die Verwaltung von JavaScript- bzw. CSS-Libraries innerhalb des jeweiligen Plugins, damit einher gehende Konflikte und andere Unwägbarkeiten. Selbstverständlich können eigene CSS- und JavaScript-Dateien eingebunden werden, wie im o.g. Beispiel gezeigt.

Schritt 2: Erstellen der Input-Felder

Ein WordPress-Widget besteht in der einfachsten Form aus einer Klasse, die von WP_Widget abgeleitet ist. In der form()-Methode wird das Formular erstellt, mit dem in der Widget-Admin-UI bzw. im Customizer die Widget-Instanz konfiguriert werden kann. Weitere Informationen dazu finden sich in einer Vielzahl von Tutorials, aber auch in der WordPress-Dokumentation.

Für den Color Picker wird ein Input-Feld vom Typ “text” verwendet:

<p>        
    <label for="<?php echo $this->get_field_id( 'ticker_bgcol_even' ); ?>"><?php _e( 'Background color in even rows', 'azbalacmeinplugin' ); ?>:</label>
    <input type="text" id="<?php echo $this->get_field_id( 'ticker_bgcol_even' ); ?>" name="<?php echo $this->get_field_name( 'ticker_bgcol_even' ); ?>" value="<?php echo esc_attr( $instance['ticker_bgcol_even'] ); ?>" class="azbalac-mein-plugin-color-picker" />
</p>

So oder ähnlich könnte das Eingabefeld aussehen. Der einzige Unterschied gegenüber anderen Text-Feldern ist die CSS-Klasse “azbalac-mein-plugin-color-picker“. Dies dient zur Identifizierung derjenigen Felder, die den Color Picker erhalten sollen.

Schritt 3: Aktivieren des Color Pickers

Der WordPress Color Picker muss nun noch aktiviert werden, damit anstatt des normalen Text-Feldes die Benutzerschnittstelle zur Farbwahl angezeigt wird. Dazu ist nur eine Zeile JavaScript notwendig:

Update

Leider funktioniert das hier beschriebene Verfahren nur dann, wenn ein Widget bereits einmal gespeichert wurde. Beim Neuanlegen eines Widgets hingegen wird zwar der Button des Color-Pickers angezeigt, aber wenn man auf den Button klickt, passiert rein gar nichts. Warum auch immer… Der JavaScript-Code wird zwar ausgeführt, aber zumindest das Event-Handling nicht korrekt initialisiert. Nachdem dass neu erzeugte Widget einmal gespeichert wurde, lässt sich der Color-Picker-Button hingegen anklicken, so dass eine Farbauswahl möglich ist. Erklären dürfte sich das Verhalten dadurch, dass alle Widgets in der Admin-UI bereits im DOM vorliegen, somit zuvor ausgegeben wurden und beim Drag&Drop möglicherweise kopiert werden, wobei der Platzhalter der Widget-ID dann überschrieben wird. Ich habe aber den konkreten JavaScript-Code nicht weiter analysiert, da ich auch keine Dokumentation gefunden habe, wie man in diese Vorgänge auf JavaScript-Ebene eingreifen könnte. Vorstellbar wäre etwa ein Event, der nach erfolgreichem Abschluss des Hinzufügens eines Widgets getriggert würde, und an den man einen Handler binden könnte. In dem Fall hätte man die Möglichkeit, gewisse Aktionen wie die Initialisierung des Color Pickers im Anschluss daran auszuführen. Aber erstens hatte ich inzwischen schon viel zu viel Aufwand für dieses Standard-Element betrieben und wollte daher nicht noch mehr Zeit investieren. Und zweitens ist die Widget-UI eine der älteren Admin-Bereiche der WordPress-Welt, so dass ich die Hoffnung hege, dass hier bald eine Überarbeitung erfolgt…

Der folgende Teil ist somit obsolet, im Anschluss daran erfolgt die Beschreibung des Workarounds.

 $('.azbalac-mein-plugin-color-picker').wpColorPicker();

Da die entsprechenden Input-Felder mit der CSS-Klasse “azbalac-mein-plugin-color-picker” markiert worden waren, genügt dieser Funktionsaufruf, um den Color Picker einzurichten. Interessanter als der Funktionsaufruf ist jedoch die Frage, wann dies erfolgen muss bzw. an welcher Stelle das JavaScript eingebunden werden kann. Denn Widgets können in der Admin-UI hinzugefügt werden, die beim Laden der UI noch nicht vorhanden waren. Insofern müssen diese dem DOM-Tree neu hinzugefügten Input-Felder ebenfalls initialisiert werden.

Ich habe hierfür eine Lösung gewählt, die ich in einem anderen Plugin gefunden habe, die aber nicht ganz die reine Lehre darstellt. Der Kommentar ist ebenfalls fast im Original übernommen. Und zwar wird am Ende der form()-Methode der Widget-Klasse eine weitere Methode aufgerufen, die JavaScript ausgibt:

public function form( $instance ) 
{

//[...output of formular fields...] 

    // FIXME echoing script in form is not very clean
    // but it does not work if enqueued properly :
    $this->adminJavaScript();
}

Die Methode “adminJavaScript()” sorgt nun für alles Weitere:

/**
 * Add javascript to control the widget options
 *
 * @since 1.0
 */
public function adminJavaScript() {
    ?>
    <script type='text/javascript'>
    //<![CDATA[
    jQuery( document ).ready( function( $ ) {
        
        $('.azbalac-mein-plugin-color-picker').wpColorPicker(); 
    } );
    //]]>
    </script>
    <?php
}

Zugegeben – so richtig gut gefällt mir dies auch nicht, immerhin wird für jedes neue Widget wiederum dasselbe JavaScript eingebaut. Vielleicht wäre es sinnvoller, sich hierbei z.B. Events zu bedienen, anhand derer erkannt wird, dass ein neues Widget vorhanden ist, so dass daraufhin die Aktivierung des Color-Picker-Elements stattfinden kann. Aber Raum für Verbesserungen gibt es bekanntlich immer.

Workaround

Die Frage war nun, wie die Input-Felder mit dem Color-Picker bestückt werden konnten, und zwar erst, nachdem das Widget erstellt, d.h. die DOM-Elemente an ihr Ziel kopiert worden waren und das Widget bereits eine ID erhalten hatte. Die Aktion des Funktionsaufrufs von “wpColorPicker()” musste somit verzögert erfolgen. Ich habe dies mit einem aufklappbaren Bereich gelöst, so dass erst bei Klick auf den entsprechenden Button die Input-Felder und somit die Color-Picker-Elemente sichtbar werden. Bei der Initialisierung des Widgets, d.h. Ausgabe des Admin-Bereiches mit der form()-Methode wird nur noch der click-Handler auf den Button definiert. Im Screenshot sieht diese Lösung wie folgt aus, zunächst der betreffende Widget-Bereich im nicht ausgeklappten Zustand:

Integration des WordPress Color Pickers in die Widget-Admin-UI 3

Nach einem Klick auf den Button werden beide Input-Felder angezeigt, während quasi gleichzeitig die Initialisierung des Color-Pickers erfolgt. Das Ergebnis zeigt folgendes Bild:

Integration des WordPress Color Pickers in die Widget-Admin-UI 4

Damit war es nun möglich, die Farbwahl mittels Color-Picker zu nutzen:

Integration des WordPress Color Pickers in die Widget-Admin-UI 5

Vom Code her hatte sich gar nicht so viel geändert. Erstens bekamen die Input-Felder ein umschließendes div-Element sowie den Button zum Auf- bzw. Zuklappen. Der Bereich der beiden Input-Felder für die Farben ist dabei standardmäßig, d.h. beim Erzeugen des Widgets nicht sichtbar:

<div id="<?php echo $this->get_field_id('widget_id'); ?>_togglecolorcnt">
    <p>
        <button type="button" id="<?php 
            echo $this->get_field_id('widget_id'); ?>_togglecolor"><?php 
            _e( 'Toggle color options', 'azbalacmeinplugin' ); ?></button>
    </p>
    <div class="azbalac-mein-plugin-rows-color" style="display: none;">

      <!-- [...Formularelemente wie zuvor definiert...] -->       

    </div>
</div>

Zum anderen musste das der JavaScript-Teil modifiziert werden, der am Ende der form()-Methode aufgerufen wird:

jQuery( document ).ready( function( $ ) {
        
        var widgetId = '<?php echo $this->get_field_id('widget_id'); ?>';
        var widgetFieldColorOdd = '<?php echo $this->get_field_id( 'row_bgcol_odd' ); ?>';
        var widgetFieldColorEven = '<?php echo $this->get_field_id( 'row_bgcol_even' ); ?>';
       

        var colorPickerOptions = {
            // definition as described below
        };

        $(document).on('click','#' + widgetId + '_togglecolor',function() {  
            $('#' + widgetId + '_togglecolorcnt').find('.azbalac-mein-plugin-rows-color').toggle();
            $('#' + widgetFieldColorEven).wpColorPicker(colorPickerOptions);
            $('#' + widgetFieldColorOdd).wpColorPicker(colorPickerOptions);
          
        });

    } );

Zunächst werden die IDs der Input-Felder in JavaScript-Variablen gespeichert, dasselbe passiert mit der Widget-ID. Zum Schluss wird ein “click“-Event-Handler auf der ID des Buttons definiert, so dass bei einem Klick das div-Element, in dem sich die Input-Felder befinden, aus- oder eingeklappt wird. Zwar müsste beim Zuklappen die wpColorPicker-Initialisierung nicht erneut stattfinden, aber ich konnte bei ersten Tests auch kein nachteiliges Verhalten feststellen. Auch im Customizer funktioniert das Anlegen und Ändern des Widgets jetzt wie gewünscht.

Einzig stört mich an der Lösung, dass sie ein Workaround ist. Denn der “Toggle“-Button wurde einzig aus technischen Gründen implementiert, nicht jedoch aufgrund von möglicherweise verbesserter Benutzerfreundlichkeit. Aber wie war das noch mit dem in der ersten Fassung angegebenen “Raum für Verbesserungen”..?

Schritt 4: Hürden umschiffen

Denn nach dem Einbau und der Aktivierung war das Resultat zunächst vielversprechend – der Color Picker wurde angezeigt, so dass der Benutzer die Farbwahl vornehmen konnte. Nach dem Ändern von Werten in den Konfigurationsoptionen eines Widgets unterscheiden sich die dadurch hervorgerufenen Änderungen je nach dem, in welchem Bereich man sich befindet.

  • In der Admin-UI wird bei einer Änderung der “Speichern“-Button aktiviert, was wiederum das Speichern der Widget-Einstellungen ermöglicht. Wenn der Benutzer noch keine Änderung durchgeführt hat, bleibt der Button deaktiviert, somit ist keine Speicherung möglich.
  • Im Customizer werden Änderungen nach einer Benutzereingabe umgehend gesichert, ein explizites Speichern ist nicht notwendig. Damit einher gehend ist jedoch die Aktivierung des “Veröffentlichen“-Buttons in der Customizer-UI.

Letztlich sollten die Widget-Einstellungen also wie folgt aussehen:

Integration des WordPress Color Pickers in die Widget-Admin-UI 6

Nur genau dies war nicht der Fall. Zwar funktionierte die Farbwahl, d.h. nach dem Schließen des Color Pickers war die Farbe bei “Farbe auswählen” vorhanden, aber falls nur diese eine Option geändert worden war, wurde der “Speichern”-Button nicht aktiviert und war weiterhin ausgegraut. Im folgenden Screenshot wurde beispielsweise die Farbe editiert, der Button verharrt jedoch im Zustand “Gespeichert” (aus einer vorherigen Speichern-Aktion) bzw. lässt sich nicht anklicken.

Integration des WordPress Color Pickers in die Widget-Admin-UI 7

Beim Ändern vom Titel oder anderen Optionen hingegen war ein Speichern dank des sofort aktivierten Speichern-Buttons problemlos möglich.

Im Customizer zeigte sich ein ähnliches Bild – es fand keine automatische Speicherung der editierten Farbe statt.

Nun ist das “change“-Event auf dem jeweiligen Eingabefeld im Formular dafür zuständig, dass WordPress bzw. das entsprechende JavaScript-Modul über die Änderungen informiert wird. Anscheinend wurde dieses Event auf dem Input-Feld des Color Pickers nicht ausgeführt? Der Color Picker schien hierbei anders zu reagieren, was mir zwar unlogisch vorkam, aber vielleicht waren die Erläuterungen daher nur auf reine Plugins und Themes bezogen.

Natürlich lassen sich Events in JavaScript oder hier vielmehr jQuery einfach triggern – der erste Versuch lautete somit, das Event bei einer Änderung des Farbe einfach manuell anzustoßen. Denn der Color Picker kann umfassend konfiguriert werden, die Optionen werden in einem Objekt einfach beim Aufruf übergeben. Beispielsweise könnte eine derartige Konfiguration so aussehen:

var colorPickerOptions = {
            // a callback to fire whenever the color changes to a valid color
            change: function(event, ui){
                var element = event.target;
                $(element).trigger('change');

            },
            // a callback to fire when the input is emptied or an invalid color
            clear: function(event) {
                var element = event.target;
                // so something senseful here...
            
            }
        };

Die Initialisierung des WordPress Color Picker Elements in der adminJavaScript()-Methode der Widget-Klasse ändert sich damit wie folgt:

 $('.azbalac-mein-plugin-color-picker').wpColorPicker(colorPickerOptions);

Das funktionierte dann auch – fast. Zwar wurde der Speichern-Button aktiviert und im Customizer wurden ebenfalls die Update-Routinen aufgerufen. Das Verhalten vom Color Picker selbst hatte sich jedoch geändert, und zwar dahingehend, dass keine Farbauswahl erfolgte. Das klingt ein wenig widersprüchlich, aber es war so, dass die ausgewählte Farbe neben “Farbe auswählen” sich während der Auswahl nicht änderte, dasselbe gilt für den RGB-Werte der Farbe.

Der folgende Screenshot verdeutlicht das Verhalten: Der Nutzer hat eine neue Farbe gewählt, der Button ändert sich, genau wie er es sollte, auf “Speichern“. Der Wert des Input-Feldes – und somit die Farbe – hat sich jedoch nicht geändert.

Integration des WordPress Color Pickers in die Widget-Admin-UI 8

Hier hatte die Änderung des Event-Handlings also dazu geführt, dass sich letztlich gar keine Farbe mehr einstellen ließ, da der alte Wert des Input-Feldes nicht überschrieben wurde. Ich hätte es mir vielleicht noch näher angesehen, aber ich hatte wiederum überraschend wenig über das interne Event-Handling des WordPress Color Pickers gefunden, insbesondere auch nichts darüber, wann welches Feld mit welchem Event getriggert wird.

Insofern habe ich zur Lösung des Problems einen Workaround angewendet, der mir zwar nicht ganz sauber vorkommt, aber dennoch gut funktioniert. Anstatt das eigene Input-Feld mit dem “change“-Event zu bemühen, wird einfach ein anderes Eingabefeld genutzt. Im Code:

 var colorPickerOptions = {
            // a callback to fire whenever the color changes to a valid color
            change: function(event, ui){
                var element = event.target;
                var titleFieldClass = '.azbalac_mein_widget_field_title';

                $(element).parents('.widget-content').find(titleFieldClass).trigger('change'); // works, but is not very nice
                

            // a callback to fire when the input is emptied or an invalid color
            clear: function(event) {
                var element = event.target;
                var titleFieldClass = '.azbalac_mein_widget_field_title';
                $(element).parents('.widget-content').find(titleFieldClass).trigger('change'); // works, but is not very nice
 
            }
        };

Das Input-Feld für den Titel wurde um die CSS-Klasse “azbalac_mein_widget_field_title” ergänzt. Somit wird bei einer Änderung, d.h. wenn das change-Event des Color Pickers stattfindet, oder aber beim Löschen der Eingaben das change-Event auf dem Titel-Feld im Formular getriggert. Die meisten Widgets dürften ein Titel-Feld besitzen, im Zweifel kann natürlich auch ein anderes Input-Feld genutzt werden. Ob sich dessen Inhalt tatsächlich geändert hat oder nicht, ist für die hier beschriebene Anforderung irrelevant, entscheidend ist nur, dass das change-Event aufgerufen wird.

Ich wiederhole es nochmal – diese Lösung funktioniert zwar, erscheint mir aber nicht besonders sauber. Lieber hätte ich das change-Event des Eingabefeldes getriggert, auf dem sich der Color Picker befand. Leider zeigte sich dabei das beschriebene, fehlerhafte Verhalten. Ein Nachteil der Lösung sind die Abhängigkeiten, d.h. die DOM-Struktur der Widget-Admin-UI bzw. der Widget-Einstellungen darf nicht geändert werden. Die Lösung setzt darauf, dass in den oberhalb liegenden Elementen die CSS-Klasse “widget-content” zu finden ist. Falls in einer nächsten WordPress-Version etwa der Name der CSS-Klasse geändert wird oder die Struktur plötzlich eine völlig andere sein sollte, wird das hier beschriebene Verfahren nach dem Motto “wandere alle Eltern-Elemente hinauf bis zu einer bestimmten Klasse, suche dann nach der CSS-Klasse des Titel-Feldes” möglicherweise scheitern.

Dennoch stellt sich genau so die aktuelle Lösung dar, da ich weiteres Debugging des Color Picker Moduls bzw. des Iris Color Picker von Automattic, der das WordPress Color Picker Modul darstellt, vermeiden wollte.

Fazit

Mit derartigen Problemen hätte ich zugegebenermaßen nicht gerechnet. Immerhin handelt es sich beim WordPress Color Picker um ein vielfach verwendetes Standard-Element. War der Ansatz, den Color Picker in Widget-Einstellungen zu verwenden, vielleicht doch nicht empfehlenswert? Oder habe ich eine Lösung übersehen? Wie immer nehme ich gerne Tipps und Hinweise entgegen! Vielen Dank für die Aufmerksamkeit!

 

 

Schreibe einen Kommentar

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

Tags: