WordPress: TinyMCE Plugin mit Dialogbox

In diesem Beitrag zeige ich eine Möglichkeit, wie man den WordPress-standardeditor TinyMCE um einen Button erweitert der eine beliebig komplexes Dialogfenster öffnet mit dem man mit den gewohnten APIs auf die WordPressumgebung zugreifen kann. Sämtliche Codebeispiele sind als Gist direkt zu sehen.

Das Tutorial beschreibt ein erdachtes WordPress-Plugin mit folgender Verzeichnisstruktur:

my-plugin/
	i18n/
		mce_locale.php
	js/
		editor_plugin.js
	my_plugin.php

TinyMCE

Ein TinyMCE-Plugin ist wie Folgt aufgebaut:

Sämtliche Plugin-Dateien sind in einem eigenen Verzeichnis zusammengefasst. In der editor_plugin.js wird das Plugin initialisiert:

// js/editor_plugin.js
/**
 * an example tinyMCE Plugin
 */
tinymce.create(
	'tinymce.plugins.myPlugin',
	{
		/**
		 * @param tinymce.Editor editor
		 * @param string url
		 */
		init : function( editor, url ) {
			/**
			 * register a new button
			 */
			editor.addButton(
				'my_plugin_button',
				{
					cmd : 'my_plugin_button_cmd',
					title : editor.getLang( 'myPlugin.buttonTitle', 'My Button' ),
					image : url + '/img/example.gif'
				}
			);
			/**
		 	* and a new command
		 	*/
			editor.addCommand(
				'my_plugin_cmd',
				function() {
					/**
					* @link http://www.tinymce.com/wiki.php/API3:method.tinymce.WindowManager.open
					* @param Object Popup settings
					* @param Object Arguments to pass to the Popup
					*/
					editor.windowManager.open(
						{
							file : url + '/dialog.htm',
							width : 320,
							height : 120,
							inline : 1
						},
						{
							plugin_url : url,
							some_custom_arg : 'custom value'
						}
					);
				}
			);
		}
	}
);
// register plugin
tinymce.PluginManager.add( 'myPlugin', tinymce.plugins.myPlugin );

(Das ist der Standard von TinyMCE, die Datei kann natürlich auch anders heißen). Dafür wird der Methode tinymce.create() neben dem Namen des Plugin-Objekts an zweiter Stelle ein Objekt übergeben, welches u.A. die Methode init definiert. Diese akzeptiert wiederum zwei Parameter: das Objekt des Editorfensters und die URL zum Pluginverzeichnis. Mit dem Editor-Objekt und dessen Methoden kann man sich in die Funktionalität von TinyMCE einklinken und z.B. einen Button registrieren, ein Kommando definieren, dass beim Klick auf den Button zur Ausführung kommt und so fort. Über die Methode editor.getLang() kommt man an die übersetzten Textbausteine heran. Dazu später mehr. Um nun ein Dialog mit mehreren Eingabemöglichkeiten zu öffnen, geht TinyMCE den Weg über eine externe HTML-Datei die es in einem Inline-Frame öffnet. Das ist in sofern von Nachteil, da in diesem Kontext nur schwerlich an die WordPress API heranzukommen ist. Von der umständlichen Handhabung, das Fenster so zu stylen, dass es im WordPress-Look daher kommt mal ganz abgesehen.

WordPress bietet eine andere Option an: Das Markup (in den meisten Fällen ein HTML-Formular) für das Dialogfenster wird einfach über den Hook wp_footer in die Seite eingesetzt und per CSS ausgeblendet.

# somewhere in my_plugin.php
if ( is_admin() ) {
	/**
	 * hook in only, if current user can
	 * see the editor and want to have
	 * a rich text editor
	 */
	if (
	    (    current_user_can( 'edit_posts' )
          || current_user_can( 'edit_pages' ) 
	    )
	    && 'true' == get_user_option( 'rich_editing' )
	) {
		add_action( 'wp_footer', 'my_plugin_dialog' );
	}
}

function my_plugin_dialog() { 
	?>
	<div style="display:none;">
		<form id="my_plugin_dialog" tabindex="-1" action="">
			<div>
				<input type="text" name="foo" />
			</div>
			<div>
				<input type="submit" class="button-primary" value="Go" />
			</div>
		</form>
	</div>
	<?php
}

Die Funktionalität des Popups stellt das WP-Interne TinyMCE-Plugin »WP-Dialog« zur Verfügung. Der Methode editor.windowManager.open wird anstelle eines Pfads zum Popup einfach die ID des Formulars übergeben. Das ganze an ein Befehl gebunden, der wiederum an einen neuen Button gebunden wird, öffnet das Fenster beim Klick auf selbigen.

// js/editor_plugin.js
/**
 * somewhere in the init-method
 * of the parameter object at
 * tinymce.create()...
 *
 * this requires the mce plugin WPDialog
 */
editor.addCommand(
	'my_plugin_button_cmd',
	function() {
		/**
		 * @param Object Popup settings
		 * @param Object Arguments to pass to the Popup
		 */
		editor.windowManager.open(
			{
				 // this is the ID of the popups parent element
				 id : 'my_plugin_dialog',
				 width : 480,
				 title : editor.getLang( 'myPlugin.popupTitle', 'Default Title' )
				 height : 'auto',
				 wpDialog : true,
			},
			{
				plugin_url : url
			}
    	);
	}
);

WordPress

WordPress kümmert sich dankenswerter Weise um die Integration des Plugins in TinyMCE. Dazu sind die Filter mce_external_plugins für das Plugin-Script und mce_buttons für den Button bereit gestellt. Beide Filter werden auf je ein Array angewendet, dass erweitert zurück gegeben wird. Um etwas Abstand zwischen die Buttons zu bekommen, setzt man als ein Array-Element einen senkrechten Stricht '|'.

# somewhere in my_plugin.php
add_filter( 'mce_external_plugins', 'register_my_tinymce_plugin' );
add_filter( 'mce_buttons', 'register_my_tinymce_button' );

/**
 * register_my_tinymce_plugin
 *
 * @param array $mce_plugins
 * @return array $mce_plugins
 */
function register_my_tinymce_plugin( $mce_plugins ) {
	$mce_plugins[ 'myPlugin' ] = plugins_url() . '/my-plugin/js/editor_plugin.js';

	return $mce_plugins;
}

/**
 * register_my_tinymce_button
 *
 * @param array $buttons
 * @return array $buttons
 */
function register_my_tinymce_button( $buttons ) {
	array_push( $buttons, '|', 'my_plugin_button' );

	return $buttons;
}

Dynamik in den Dialog bekommt man wie gewohnt über ein JavaScript, dass mit der Funktion wp_enqueue_script() geladen wird. Innerhalb dieses Scripts kommt man über das globale JavaScript-Objekt tinyMCEPopup an den Editor bzw. mit edCanvas an die Textarea im HTML-Modus heran. Zu beachten ist, dass tinyMCEPopup zum Zeitpunkt des jQuery-eigenen Events ready nocht nicht verfügbar ist.

i18n

Um innerhalb des TinyMCE Plugins lokalisierbare Strings zu nutzen, bedarf es einer weiteren PHP-Datei, die über den Filter mce_external_languages geladen wird. Genauer gesagt loopt WordPress über mehrere solche Dateien und erweitert dabei einen String, der die entsprechenden Textausteine enthält, die in bekannter Weise übersetzt werden können. Der String selbst ist JavaScript-Code, der die Methode tinymce.addI18n() aufruft. Über den hier definierten Namensraum sind die Textbausteine dann verfügbar.

<?php
# i18n/mce_locale.php
/**
 * @var string $strings a JavaScript snippet to add another language pack to TinyMCE
 * @var string $mce_locale an ISO 639-1 formated string of the current language e.g. en, de...
 * @deprecated wp_tiny_mce() at wp-admin/includes/post.php (for versions prior WP 3.3)
 * @see _WP_Editors::editor_settings in wp-includes/class-wp-editor.php
 */
$strings =
	'tinyMCE.addI18n(
		"' . $mce_locale . '.myPlugin",
		{
			buttonTitle : "' . esc_js( __( 'My Button Title:', 'my_textdomain' ) ) . '",
			popupTitle  : "' . esc_js( __( 'My Popup Title', 'my_textdomain' ) ) . '",
		}
	);';

Das Laden übernimmt wieder WordPress:

<?php
# somewhere in my_plugin.php
# remember to load the plugin textdomain!
add_filter( 'mce_external_languages', 'my_mce_localisation' );

/**
 * my_mce_localisation
 *
 * @see wp-admin/includes/post.php line 1474
 * @param array $mce_external_languages
 * @return array $mce_external_languages
 */
function my_mce_localisation( $mce_external_languages ) {
	$mce_external_languages[ 'myPlugin' ] = plugin_dir_path( __FILE__ ) . 'i18n/mce_locale.php';

	return $mce_external_languages;
}

Kommentare

Es wurden noch keine Kommentarte zu diesem Artikel geschrieben.

Fragen, Ideen oder Kritik? – Hier ist Platz dafür!

Dein Kommentar

Um ein Kommentar abzugeben, reicht der Text im Kommentarfeld. Die Angabe eines Namens wäre nett, ist aber nicht erforderlich.

Du darfst folgenden HTML-Code verwenden, musst aber nicht:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="">