Freitag, 2. Juni 2006
Serendipity Drag and Drop Plugin-Manager
Diese Woche hat mich die Lust einmal gepackt, etwas "Eye Candy" für Serendipity einzupflanzen. Das ganze Web 2.0 strotzt über an "Drehen Sie hier", "Ziehen sie dort" und "taggen sie das". Das Serendipity solche Goodies aufgrund des technischen Plugin-Konzepts und übersichtlichem Code auch unterstützt, wollte ich mir natürlich auch mal wieder unter Beweis stellen. Nachdem ja für Serendipity 1.1 schon weitere Goodies angelaufen sind (neue Mediendatenbank, Template-Konfigurator), sollte so ein Feature doch ein netter Anreiz für Beta-Tester sein.
Herausgekommen dabei ist, dass der Plugin-Manager nun die Anordnung der Plugins per Drag and Drop ermöglicht. Einfach die einzelnen Elemente hin- und herschieben, verstecken/deaktivieren - und das alles in einem Arbeitsgang. Keine Klickorgien der Umsortierung mehr, und dann auch noch mit netten "Transparenz"-Effekten.
Als Quell der Inspiration diente dazu das Script von Cyberdummy. Die ganze JS-Bibliothek stammt dorther und kann extrem leicht für Serendipity portiert werden. Damit es hier im Blog mal was technik-lastiger wird, beschreibe ich dies nun einmal detailiert (mit Video am Ende! Bonus-Content! ).
Herausgekommen dabei ist, dass der Plugin-Manager nun die Anordnung der Plugins per Drag and Drop ermöglicht. Einfach die einzelnen Elemente hin- und herschieben, verstecken/deaktivieren - und das alles in einem Arbeitsgang. Keine Klickorgien der Umsortierung mehr, und dann auch noch mit netten "Transparenz"-Effekten.
Als Quell der Inspiration diente dazu das Script von Cyberdummy. Die ganze JS-Bibliothek stammt dorther und kann extrem leicht für Serendipity portiert werden. Damit es hier im Blog mal was technik-lastiger wird, beschreibe ich dies nun einmal detailiert (mit Video am Ende! Bonus-Content! ).
Der Code zur Darstellung des Plugin-Managers befindet sich wie alle anderen Admin-Punkte im Verzeichnis /include/admin/, in unserem Fall also /include/admin/plugins.inc.php. Hilfs-Funktionen zur Darstellung sind in include/functions_plugins_admin.inc.php hinterlegt. Die plugins.inc.php enthält hauptsächlich den Code-Teil, der angeforderte Aktionen durchführt. Weiterhin enthält er nicht nur den Code für die Plugin-Übersicht, sondern auch für die Konfiguration einzelner Plugins. Diesen Bereich konnte man für mein Vorhaben erstmal komplett ausblenden, so dass es auf diesen Code-Ausschnitt ankam:
An diesem Code sieht man zwei Dinge: Erstens, es ist ein HTML/PHP-Mischmasch. Das hat aber durchaus Gründe. Erster Grund ist, man ist mit einem solchen Mischmasch etwas flexibler zur individuellen Ausgabe und hat den Code und die "Logik" an einer Stelle. Das ist aber nicht der Hauptgrund, denn der Vorteil von Templates zur Abstraktion und Trennung des Codes würde hier durchaus stärker wiegen. Vielmehr ist der Grund, im Admin-Teil für wichtige Dinge auf die Smarty-Engine zu verzichten um so potentielle Probleme zu vermeiden. Meine Erfahrung von fast 3 Jahren Serendipity-Support zeigte, dass es immer wieder durch Fehlkonfiguration (fehlende Schreibrechte auf das Smarty-templates_c Verzeichnis!) vorkommen kann, dass ein Template nicht parsebar ist oder gar ein Plugin fehlerhaft läuft. Würden wir im Pluginmanager auf Smarty setzen, könnte es bedeuten, dass bei Fehlkonfiguration dieser Teil nicht mehr aufgerufen werden könnte, und das wäre Fatal zur Fehlerbehebung.
Zum zweiten sieht man an diesem Code-Ausschnitt aber auch, dass sowohl Seitenleisten- als auch Event-Plugins durch die Hilfsfunktion show_plugins() ausgegeben werden, die sich in der Datei include/functions_plugins_admin.inc.php befindet. Zur Einbindung des Drag-and-Drop Moduls müssen wir also hauptsächlich diese Funktion anfassen; für den oberen Teil reicht folgender kleiner Schnippsel zur Einbindung:
Dies prüft, ob die Serendipity Konfigurationsoption "eyecandy" aktiviert ist. Diese Option wurde von mir erst mit diesem Feature eingefügt (mittels der Datei include/tpl/config_personal.inc.php, die im wesentlichen ein einfaches Konfig-Array ist). Die IF-Abfrage prüft ob diese Variable entweder noch nicht gesetzt wurde (dann geht s9y davon aus, dass wir die Eye-Candy haben wollen), oder ob die Variable aktiviert ist. Wenn ja wird das Javascript eingebunden.
Der JavaScript Code ist im wesentlichen der von Cyberdummy. Ich habe dort alle JS-Dateien kombiniert, so dass der code wie Folgender aussieht: dragdrop.js. Auf die angepassten Funktionen werde ich später eingehen.
Zurück also zum Hilfs-Code show_plugins(). Dieser sah vorher so aus:
Irrelevanten Code habe ich mit "SNIP" auskommentiert. Anhand des Codes sieht man, dass bis dato eine Tabelle benutzt wurde, die alle Plugins iterativ durchwanderte, und dann mittels weiterer Hilfsfunktionen wie placement_box() ein Dropdown ausgegben haben, dass ihre Position links oder rechts angab.
Damit der Drag-and-Drop-Code funktioniert, musste vor allem die Tabelle auf mehrere Listen-Elemente aufgeteilt werden. Um später per CSS-Code die Einzelteile hübsch formatieren zu können wurden einige spans/divs mit ausgegeben und der Code konnte auch um einige inline-styles bereinigt werden:
Eine zentrale Tabelle gibt es nach wie vor, die die jeweiligen ol-Listenelemente hält. Für die Seitenleistenplugins gibt es nun eine drei-spaltige Tabelle, die für jeweils links, rechts und "versteckt" die jeweiligen Plugins anzeigt. Jede ol-Gruppe hat eine eindeutige ID, die später für das JavaScript von hoher Wichtigkeit sein wird.
Weiterhin ist es wichtig, dass das Formular am Anfang der Funktion einen onSubmit-Handler enthält (sofern eye-candy aktiviert). Auch muss die JS-Funktion zum initialisieren des JavaScripts als LoadEvent eingefügt werden.
Nun also zum Ausschnitt des angepassten JavaScripts von oben:
Die Init-Funktion erstellt Drag-and-Drop Container für die ol-Listenelemente (left_col, hide_col, right_col). Alle werden innerhalb der Gruppe "g1" zusammengefügt, so dass das JS weiß, das die Plugins untereinander in jede der Gruppen gesetzt werden kann. Die onDragOver/onDragOut Funktionen sind lediglich für den optischen Effekt, dass der Container beim MouseOver einen hervorgehobenen Rahmen enthält. So weiß der Benutzer, über welchem Container er sich gerade befindet.
Die pluginMovergetSort() Funktion ist die, die beim onSubmit des Formulars aufgerufen wird. Sie speichert die aktuelle Anordnung der Plugins in einem lesbaren Format, die dann wiederrum in ein hidden-Feld des Formulares übertragen wird.
Genau jenes Hidden-Field ist, was uns bei speichern der Plugins die Reihenfolge angibt. Bis dato war dafür die Variable serendipity[placement] zuständig, die den jeweiligen Positionsindex (anhand der Reihenfolge des SUBMITs) und auch die dargestellte Seite enthielt. Ab sofort wird die Seite jedoch anhand des Eltern-Containers bestimmt, und der Positionsindex anhand der Reihenfolge der Plugins darin.
Um den Code möglichst nahtlos in Serendipity zu integrieren ist es am ratsamsten, das neue Übermittlugnsformat in das alte zu überführen. Dies geschicht in der include/admin/plugins.inc.php durch folgenden neuen Code:
Das Format der JS-Übermittlung sieht aus wie folgender String: left_col(plugin1,plugin2,plugin3):hide_col():right_col(plugin4). Demzufolge müssen wir erst die einzelnen Teile in ihre Gruppen left_col / hide_col / right_col aufteilen. Das würde mit regulären Ausdrücken gehen, aber für einfache Fälle bevorzuge ich persönlich "explode".
Somit habe ich nach dem ersten
ein Array, was die Einzelteile "left_col(plugin1,plugin2,plugin3)", "hide_col()" und "right_col(plugin4)" enthält. Man geht also nun jede der einzelnen Gruppen durch, prüft ob die Eingabe dem gewünschten Format entspricht (der preg_match() Befehl) und trennt den String anhand des "," Begrenzers erneut auf. Im ersten Durchlauf erhält man dann also als Variablen:
$matches[1]: left_col
$matches[2]: plugin1,plugin2,plugin3
$pluginsidelist: array('plugin1', 'plugin2', 'plugin3')
Anhand des Zuordnungs-Hilfsarrays $col_assoc kann nun sowas wie "left_col" auf den benötigten Plugin_Schlüssel "left/right/event/eventh/hidden" geschlossen werden. Nun haben wir also alle drei Informationen die wir benötigen um das alte Array serendipity[placement] wieder herzustellen: Den Namen des Plugins ('plugin1'), die Gruppe ('left_col') und den Datenbankschlüssel ('left'). Anhand dieser Werte kann der alte Serendipity-Code später die Datenbankoperationen ausführen um die neue Seiten-Reihenfolge zu ändern.
An dieser Stelle sollte uns auffallen, dass durch den noscript-Teil von früher die alten Buttons zum hoch/runter-schieben noch vorhanden sind. Da der alte Code zum Verschieben ebenfalls noch vorhanden ist, wird also die alte Funktionalität nach wie vor ohne Probleme durchgeführt.
Die letzte foreach-Schleife des obigen Code-Schnippsels ist dafür zuständig, die komplette Liste an Plugins durchzugehen und so die neue Sortierungsreihenfolge einzutragen. Dies ist nötig, da nämlich der alte Serendipity-Code nur ausgeführt wird, wenn eben der Sortierungs-Button "geklickt" wurde. Dies passiert ja nun nicht mehr, und daher muss ein eigenständiges Stück Code diese Neu-Sortierung pauschal ausführen. Früher wurde immer nur die Position von 2 Plugins getauscht (weil man ja immer nur ein Plugin pro Klick verschieben konnte), nun wird die Reihenfolge "auf einmal" geändert.
Nachdem der CSS-Code nun noch etwas mit den neuen Selektoren gepimpt wurde, sieht das ganze aus wie folgendes Video:
Gebraucht habe ich für die Entwicklung des ganzen übrigens gut 70 Minuten. Dabei fielen 50 Minuten auf CSS-Verhübschung, 10 Minuten für den JS-Kram und 10 Minuten PHP. Das ist vertretbar, denke ich. Für das erstellen dieses Blog-Eintrags habe ich jedenfalls beinahe länger gebraucht.
Wer nun neugierig geworden ist, kann sich gerne einen aktuellen Snapshot (1.1-alpha) von Serendipity runterladen und installieren.
CODE:
<?php echo BELOW_IS_A_LIST_OF_INSTALLED_PLUGINS ?>
<br />
<br />
<h3><?php echo SIDEBAR_PLUGINS ?></h3>
<a href="?serendipity[adminModule]=plugins&serendipity[adminAction]=addnew" class="serendipityIconLink"><img src="<?php echo serendipity_getTemplateFile('admin/img/install.png') ?>" style="border: 0px none ; vertical-align: middle; display: inline;" alt=""><?php echo sprintf(CLICK_HERE_TO_INSTALL_PLUGIN, SIDEBAR_PLUGIN) ?></a>
<?php serendipity_plugin_api::hook_event('backend_plugins_sidebar_header', $serendipity); ?>
<?php show_plugins(false); ?>
<br />
<br />
<h3><?php echo EVENT_PLUGINS ?></h3>
<a href="?serendipity[adminModule]=plugins&serendipity[adminAction]=addnew&serendipity[type]=event" class="serendipityIconLink"><img src="<?php echo serendipity_getTemplateFile('admin/img/install.png') ?>" style="border: 0px none ; vertical-align: middle; display: inline;" alt=""><?php echo sprintf(CLICK_HERE_TO_INSTALL_PLUGIN, EVENT_PLUGIN) ?></a>
<?php serendipity_plugin_api::hook_event('backend_plugins_event_header', $serendipity); ?>
<?php show_plugins(true); ?>
<br />
<br />
<h3><?php echo SIDEBAR_PLUGINS ?></h3>
<a href="?serendipity[adminModule]=plugins&serendipity[adminAction]=addnew" class="serendipityIconLink"><img src="<?php echo serendipity_getTemplateFile('admin/img/install.png') ?>" style="border: 0px none ; vertical-align: middle; display: inline;" alt=""><?php echo sprintf(CLICK_HERE_TO_INSTALL_PLUGIN, SIDEBAR_PLUGIN) ?></a>
<?php serendipity_plugin_api::hook_event('backend_plugins_sidebar_header', $serendipity); ?>
<?php show_plugins(false); ?>
<br />
<br />
<h3><?php echo EVENT_PLUGINS ?></h3>
<a href="?serendipity[adminModule]=plugins&serendipity[adminAction]=addnew&serendipity[type]=event" class="serendipityIconLink"><img src="<?php echo serendipity_getTemplateFile('admin/img/install.png') ?>" style="border: 0px none ; vertical-align: middle; display: inline;" alt=""><?php echo sprintf(CLICK_HERE_TO_INSTALL_PLUGIN, EVENT_PLUGIN) ?></a>
<?php serendipity_plugin_api::hook_event('backend_plugins_event_header', $serendipity); ?>
<?php show_plugins(true); ?>
An diesem Code sieht man zwei Dinge: Erstens, es ist ein HTML/PHP-Mischmasch. Das hat aber durchaus Gründe. Erster Grund ist, man ist mit einem solchen Mischmasch etwas flexibler zur individuellen Ausgabe und hat den Code und die "Logik" an einer Stelle. Das ist aber nicht der Hauptgrund, denn der Vorteil von Templates zur Abstraktion und Trennung des Codes würde hier durchaus stärker wiegen. Vielmehr ist der Grund, im Admin-Teil für wichtige Dinge auf die Smarty-Engine zu verzichten um so potentielle Probleme zu vermeiden. Meine Erfahrung von fast 3 Jahren Serendipity-Support zeigte, dass es immer wieder durch Fehlkonfiguration (fehlende Schreibrechte auf das Smarty-templates_c Verzeichnis!) vorkommen kann, dass ein Template nicht parsebar ist oder gar ein Plugin fehlerhaft läuft. Würden wir im Pluginmanager auf Smarty setzen, könnte es bedeuten, dass bei Fehlkonfiguration dieser Teil nicht mehr aufgerufen werden könnte, und das wäre Fatal zur Fehlerbehebung.
Zum zweiten sieht man an diesem Code-Ausschnitt aber auch, dass sowohl Seitenleisten- als auch Event-Plugins durch die Hilfsfunktion show_plugins() ausgegeben werden, die sich in der Datei include/functions_plugins_admin.inc.php befindet. Zur Einbindung des Drag-and-Drop Moduls müssen wir also hauptsächlich diese Funktion anfassen; für den oberen Teil reicht folgender kleiner Schnippsel zur Einbindung:
CODE:
<?php
if (!isset($serendipity['eyecandy']) || serendipity_db_bool($serendipity['eyecandy'])) {
echo '<script src="bundled-libs/dragdrop.js" type="text/javascript"></script>';
echo '<div class="warning js_warning"><em>' . PREFERENCE_USE_JS_WARNING . '</em></div>';
}
?>
if (!isset($serendipity['eyecandy']) || serendipity_db_bool($serendipity['eyecandy'])) {
echo '<script src="bundled-libs/dragdrop.js" type="text/javascript"></script>';
echo '<div class="warning js_warning"><em>' . PREFERENCE_USE_JS_WARNING . '</em></div>';
}
?>
Dies prüft, ob die Serendipity Konfigurationsoption "eyecandy" aktiviert ist. Diese Option wurde von mir erst mit diesem Feature eingefügt (mittels der Datei include/tpl/config_personal.inc.php, die im wesentlichen ein einfaches Konfig-Array ist). Die IF-Abfrage prüft ob diese Variable entweder noch nicht gesetzt wurde (dann geht s9y davon aus, dass wir die Eye-Candy haben wollen), oder ob die Variable aktiviert ist. Wenn ja wird das Javascript eingebunden.
Der JavaScript Code ist im wesentlichen der von Cyberdummy. Ich habe dort alle JS-Dateien kombiniert, so dass der code wie Folgender aussieht: dragdrop.js. Auf die angepassten Funktionen werde ich später eingehen.
Zurück also zum Hilfs-Code show_plugins(). Dieser sah vorher so aus:
CODE:
/**
* Show the list of plugins
*
* Shows a HTML list of all installed plugins, complete with config/delete/sort order options
*
* @access public
* @param boolean Indicates if event plugins (TRUE) or sidebar plugins (FALSE) shall be shown
* @return null
*/
function show_plugins($event_only = false)
{
global $serendipity;
?>
<form action="?serendipity[adminModule]=plugins" method="post">
<?php echo serendipity_setFormToken(); ?>
<table border="0" cellpadding="5" cellspacing="0" width="100%">
<tr>
<td colspan="2"> </td>
<td><strong><?php echo TITLE; ?></strong></td>
<td><strong><?php echo PERMISSIONS; ?></strong></td>
<?php
if (!$event_only) {
?>
<td colspan="3" align="center"><strong><?php echo PLACEMENT; ?></strong></td>
<?php
} else {
?>
<td colspan="2"> </dd>
<?php } ?>
</tr>
<?php
$errors = array();
/* Block display the plugins per placement location. */
if ($event_only) {
$plugin_placements = array('event');
} else {
$plugin_placements = array('left', 'right', 'hide');
}
foreach ($plugin_placements as $plugin_placement) {
$plugins = serendipity_plugin_api::enum_plugins($plugin_placement);
if (!is_array($plugins)) {
continue;
}
$sort_idx = 0;
foreach ($plugins as $plugin_data) {
/* ... SNIP ... */
if ($event_only) {
$place = '<input type="hidden" name="serendipity[placement][' . $plugin_data['name'] . ']" value="event" />';
$event_only_uri = '&serendipity[event_plugin]=true';
} else {
$place = placement_box('serendipity[placement][' . $plugin_data['name'] . ']', $plugin_data['placement'], $is_plugin_editable);
$event_only_uri = '';
}
?>
<tr>
<td style="border-bottom: 1px solid #000000">
<div>
<?php if ($is_plugin_editable) { ?>
<input type="checkbox" name="serendipity[plugin_to_remove][]" value="<?php echo $plugin_data['name']; ?>" />
<?php } else { ?>
 
<?php } ?>
</div>
</td>
<td style="border-bottom: 1px solid #000000" width="16">
<?php if ( $can_configure ) { ?>
<a href="?serendipity[adminModule]=plugins&serendipity[plugin_to_conf]=<?php echo $key ?>"><img src="<?php echo serendipity_getTemplateFile('admin/img/configure.png') ?>" style="border: 0; vertical-align: bottom;"></a>
<?php } else { ?>
 
<?php } ?>
</td>
<td style="border-bottom: 1px solid #000000"><strong>
<?php if ( $can_configure ) { ?>
<a href="?serendipity[adminModule]=plugins&serendipity[plugin_to_conf]=<?php echo $key ?>"><?php echo $title; ?></a>
<?php } else { ?>
<?php echo $title; ?>
<?php } ?></strong><br />
<div style="font-size: 8pt"><?php echo $desc; ?></div>
</td>
<td style="border-bottom: 1px solid #000000" nowrap="nowrap"><?php ownership($plugin_data['authorid'], $plugin_data['name'], $is_plugin_owner); ?></td>
<?php if ( !$event_only ) { ?>
<td style="border-bottom: 1px solid #000000" nowrap="nowrap"><?php echo $place ?></td>
<?php } ?>
<td style="border-bottom: 1px solid #000000"><?php echo $moveup ?></td>
<td style="border-bottom: 1px solid #000000"><?php echo $movedown ?></td>
</tr>
<?php
$sort_idx++;
}
}
?>
</table>
<br />
<div>
<input type="submit" name="REMOVE" title="<?php echo REMOVE_TICKED_PLUGINS; ?>" value="<?php echo DELETE; ?>" class="serendipityPrettyButton" />
<input type="submit" name="SAVE" title="<?php echo SAVE_CHANGES_TO_LAYOUT; ?>" value="<?php echo SAVE; ?>" class="serendipityPrettyButton" />
</div>
</form>
<?php
}
* Show the list of plugins
*
* Shows a HTML list of all installed plugins, complete with config/delete/sort order options
*
* @access public
* @param boolean Indicates if event plugins (TRUE) or sidebar plugins (FALSE) shall be shown
* @return null
*/
function show_plugins($event_only = false)
{
global $serendipity;
?>
<form action="?serendipity[adminModule]=plugins" method="post">
<?php echo serendipity_setFormToken(); ?>
<table border="0" cellpadding="5" cellspacing="0" width="100%">
<tr>
<td colspan="2"> </td>
<td><strong><?php echo TITLE; ?></strong></td>
<td><strong><?php echo PERMISSIONS; ?></strong></td>
<?php
if (!$event_only) {
?>
<td colspan="3" align="center"><strong><?php echo PLACEMENT; ?></strong></td>
<?php
} else {
?>
<td colspan="2"> </dd>
<?php } ?>
</tr>
<?php
$errors = array();
/* Block display the plugins per placement location. */
if ($event_only) {
$plugin_placements = array('event');
} else {
$plugin_placements = array('left', 'right', 'hide');
}
foreach ($plugin_placements as $plugin_placement) {
$plugins = serendipity_plugin_api::enum_plugins($plugin_placement);
if (!is_array($plugins)) {
continue;
}
$sort_idx = 0;
foreach ($plugins as $plugin_data) {
/* ... SNIP ... */
if ($event_only) {
$place = '<input type="hidden" name="serendipity[placement][' . $plugin_data['name'] . ']" value="event" />';
$event_only_uri = '&serendipity[event_plugin]=true';
} else {
$place = placement_box('serendipity[placement][' . $plugin_data['name'] . ']', $plugin_data['placement'], $is_plugin_editable);
$event_only_uri = '';
}
?>
<tr>
<td style="border-bottom: 1px solid #000000">
<div>
<?php if ($is_plugin_editable) { ?>
<input type="checkbox" name="serendipity[plugin_to_remove][]" value="<?php echo $plugin_data['name']; ?>" />
<?php } else { ?>
 
<?php } ?>
</div>
</td>
<td style="border-bottom: 1px solid #000000" width="16">
<?php if ( $can_configure ) { ?>
<a href="?serendipity[adminModule]=plugins&serendipity[plugin_to_conf]=<?php echo $key ?>"><img src="<?php echo serendipity_getTemplateFile('admin/img/configure.png') ?>" style="border: 0; vertical-align: bottom;"></a>
<?php } else { ?>
 
<?php } ?>
</td>
<td style="border-bottom: 1px solid #000000"><strong>
<?php if ( $can_configure ) { ?>
<a href="?serendipity[adminModule]=plugins&serendipity[plugin_to_conf]=<?php echo $key ?>"><?php echo $title; ?></a>
<?php } else { ?>
<?php echo $title; ?>
<?php } ?></strong><br />
<div style="font-size: 8pt"><?php echo $desc; ?></div>
</td>
<td style="border-bottom: 1px solid #000000" nowrap="nowrap"><?php ownership($plugin_data['authorid'], $plugin_data['name'], $is_plugin_owner); ?></td>
<?php if ( !$event_only ) { ?>
<td style="border-bottom: 1px solid #000000" nowrap="nowrap"><?php echo $place ?></td>
<?php } ?>
<td style="border-bottom: 1px solid #000000"><?php echo $moveup ?></td>
<td style="border-bottom: 1px solid #000000"><?php echo $movedown ?></td>
</tr>
<?php
$sort_idx++;
}
}
?>
</table>
<br />
<div>
<input type="submit" name="REMOVE" title="<?php echo REMOVE_TICKED_PLUGINS; ?>" value="<?php echo DELETE; ?>" class="serendipityPrettyButton" />
<input type="submit" name="SAVE" title="<?php echo SAVE_CHANGES_TO_LAYOUT; ?>" value="<?php echo SAVE; ?>" class="serendipityPrettyButton" />
</div>
</form>
<?php
}
Irrelevanten Code habe ich mit "SNIP" auskommentiert. Anhand des Codes sieht man, dass bis dato eine Tabelle benutzt wurde, die alle Plugins iterativ durchwanderte, und dann mittels weiterer Hilfsfunktionen wie placement_box() ein Dropdown ausgegben haben, dass ihre Position links oder rechts angab.
Damit der Drag-and-Drop-Code funktioniert, musste vor allem die Tabelle auf mehrere Listen-Elemente aufgeteilt werden. Um später per CSS-Code die Einzelteile hübsch formatieren zu können wurden einige spans/divs mit ausgegeben und der Code konnte auch um einige inline-styles bereinigt werden:
CODE:
function show_plugins($event_only = false)
{
static $opts = array(
'left' => LEFT,
'right' => RIGHT,
'hide' => HIDDEN,
'event' => PLUGIN_ACTIVE,
'eventh' => PLUGIN_INACTIVE
);
global $serendipity;
$eyecandy = !isset($serendipity['eyecandy']) || serendipity_db_bool($serendipity['eyecandy']);
if (!$eyecandy) {
echo ' <form action="?serendipity[adminModule]=plugins" method="post">';
} elseif (!$event_only) {
echo '<script type="text/javascript">addLoadEvent(pluginMoverInit);</script>';
echo ' <form action="?serendipity[adminModule]=plugins" method="post" onsubmit="pluginMovergetSort(); return true">';
echo ' <input type="hidden" name="serendipity[pluginorder]" id="order" value="" />';
} else {
echo '<script type="text/javascript">addLoadEvent(pluginMoverInitEvent);</script>';
echo ' <form action="?serendipity[adminModule]=plugins" method="post" onsubmit="pluginMovergetSortEvent(); return true">';
echo ' <input type="hidden" name="serendipity[pluginorder]" id="eventorder" value="" />';
}
echo serendipity_setFormToken();
?>
<table class="pluginmanager" border="0" cellpadding="5" cellspacing="3" width="100%">
<?php
$errors = array();
/* Block display the plugins per placement location. */
if ($event_only) {
$plugin_placements = array('event', 'eventh');
} else {
$plugin_placements = array('left', 'hide', 'right');
}
$total = 0;
foreach ($plugin_placements as $plugin_placement) {
echo '<td class="pluginmanager_side">';
echo '<div class="heading">' . $opts[$plugin_placement] . '</div>';
echo '<ol id="' . $plugin_placement . '_col" class="pluginmanager_container">';
$plugins = serendipity_plugin_api::enum_plugins($plugin_placement);
if (!is_array($plugins)) {
continue;
}
$sort_idx = 0;
foreach ($plugins as $plugin_data) {
/* SNIP */
if ($event_only) {
$place = placement_box('serendipity[placement][' . $plugin_data['name'] . ']', $plugin_data['placement'], $is_plugin_editable, true);
$event_only_uri = '&serendipity[event_plugin]=true';
} else {
$place = placement_box('serendipity[placement][' . $plugin_data['name'] . ']', $plugin_data['placement'], $is_plugin_editable);
$event_only_uri = '';
}
/* Only display UP/DOWN links if there's somewhere for the plugin to go */
if ($sort_idx == 0) {
$moveup = ' ';
} else {
$moveup = '<a href="?' . serendipity_setFormToken('url') . '&serendipity[adminModule]=plugins&submit=move+up&serendipity[plugin_to_move]=' . $key . $event_only_uri . '" style="border: 0"><img src="' . serendipity_getTemplateFile('admin/img/uparrow.png') .'" height="16" width="16" border="0" alt="' . UP . '" /></a>';
}
if ($sort_idx == (count($plugins)-1)) {
$movedown = ' ';
} else {
$movedown = ($moveup != '' ? ' ' : '') . '<a href="?' . serendipity_setFormToken('url') . '&serendipity[adminModule]=plugins&submit=move+down&serendipity[plugin_to_move]=' . $key . $event_only_uri . '" style="border: 0"><img src="' . serendipity_getTemplateFile('admin/img/downarrow.png') . '" height="16" width="16" alt="'. DOWN .'" border="0" /></a>';
}
?>
<li class="pluginmanager_item_<?php echo ($sort_idx % 2 ? 'even' : 'uneven'); ?>" id="<?php echo $key; ?>">
<a href="#grab<?php echo $key; ?>" id="grab<?php echo $key; ?>" class="pluginmanager_grablet"></a>
<?php if ($is_plugin_editable) { ?>
<input type="checkbox" name="serendipity[plugin_to_remove][]" value="<?php echo $plugin_data['name']; ?>" />
<?php } ?>
<?php if ( $can_configure ) { ?>
<a class="pluginmanager_configure" href="?serendipity[adminModule]=plugins&serendipity[plugin_to_conf]=<?php echo $key ?>"><img src="<?php echo serendipity_getTemplateFile('admin/img/configure.png') ?>" style="border: 0; vertical-align: bottom;"></a>
<?php } ?>
<span class="pluginmanager_title">
<?php if ( $can_configure ) { ?>
<a href="?serendipity[adminModule]=plugins&serendipity[plugin_to_conf]=<?php echo $key ?>"><?php echo $title; ?></a>
<?php } else { ?>
<?php echo $title; ?>
<?php } ?></span><br />
<div class="pluginmanager_description" style="font-size: 8pt"><?php echo $desc; ?></div>
<div class="pluginmanager_ownership"><?php ownership($plugin_data['authorid'], $plugin_data['name'], $is_plugin_owner); ?></div>
<?php echo ($eyecandy ? '<noscript>' : ''); ?>
<div class="pluginmanager_place"><?php echo $place; ?></div>
<div class="pluginmanager_move"><?php echo $moveup ?> <?php echo $movedown ?></div>
<?php echo ($eyecandy ? '</noscript>' : ''); ?>
</li>
<?php
$sort_idx++;
}
echo '</ol></td>';
}
?>
<tr>
<td colspan="3" align="right"><?php printf(PLUGIN_AVAILABLE_COUNT, $total); ?></td>
</tr>
</table>
<br />
<div>
<input type="submit" name="REMOVE" title="<?php echo DELETE; ?>" value="<?php echo REMOVE_TICKED_PLUGINS; ?>" class="serendipityPrettyButton" />
<input type="submit" name="SAVE" title="<?php echo SAVE_CHANGES_TO_LAYOUT; ?>" value="<?php echo SAVE; ?>" class="serendipityPrettyButton" />
</div>
</form>
<?php
}
{
static $opts = array(
'left' => LEFT,
'right' => RIGHT,
'hide' => HIDDEN,
'event' => PLUGIN_ACTIVE,
'eventh' => PLUGIN_INACTIVE
);
global $serendipity;
$eyecandy = !isset($serendipity['eyecandy']) || serendipity_db_bool($serendipity['eyecandy']);
if (!$eyecandy) {
echo ' <form action="?serendipity[adminModule]=plugins" method="post">';
} elseif (!$event_only) {
echo '<script type="text/javascript">addLoadEvent(pluginMoverInit);</script>';
echo ' <form action="?serendipity[adminModule]=plugins" method="post" onsubmit="pluginMovergetSort(); return true">';
echo ' <input type="hidden" name="serendipity[pluginorder]" id="order" value="" />';
} else {
echo '<script type="text/javascript">addLoadEvent(pluginMoverInitEvent);</script>';
echo ' <form action="?serendipity[adminModule]=plugins" method="post" onsubmit="pluginMovergetSortEvent(); return true">';
echo ' <input type="hidden" name="serendipity[pluginorder]" id="eventorder" value="" />';
}
echo serendipity_setFormToken();
?>
<table class="pluginmanager" border="0" cellpadding="5" cellspacing="3" width="100%">
<?php
$errors = array();
/* Block display the plugins per placement location. */
if ($event_only) {
$plugin_placements = array('event', 'eventh');
} else {
$plugin_placements = array('left', 'hide', 'right');
}
$total = 0;
foreach ($plugin_placements as $plugin_placement) {
echo '<td class="pluginmanager_side">';
echo '<div class="heading">' . $opts[$plugin_placement] . '</div>';
echo '<ol id="' . $plugin_placement . '_col" class="pluginmanager_container">';
$plugins = serendipity_plugin_api::enum_plugins($plugin_placement);
if (!is_array($plugins)) {
continue;
}
$sort_idx = 0;
foreach ($plugins as $plugin_data) {
/* SNIP */
if ($event_only) {
$place = placement_box('serendipity[placement][' . $plugin_data['name'] . ']', $plugin_data['placement'], $is_plugin_editable, true);
$event_only_uri = '&serendipity[event_plugin]=true';
} else {
$place = placement_box('serendipity[placement][' . $plugin_data['name'] . ']', $plugin_data['placement'], $is_plugin_editable);
$event_only_uri = '';
}
/* Only display UP/DOWN links if there's somewhere for the plugin to go */
if ($sort_idx == 0) {
$moveup = ' ';
} else {
$moveup = '<a href="?' . serendipity_setFormToken('url') . '&serendipity[adminModule]=plugins&submit=move+up&serendipity[plugin_to_move]=' . $key . $event_only_uri . '" style="border: 0"><img src="' . serendipity_getTemplateFile('admin/img/uparrow.png') .'" height="16" width="16" border="0" alt="' . UP . '" /></a>';
}
if ($sort_idx == (count($plugins)-1)) {
$movedown = ' ';
} else {
$movedown = ($moveup != '' ? ' ' : '') . '<a href="?' . serendipity_setFormToken('url') . '&serendipity[adminModule]=plugins&submit=move+down&serendipity[plugin_to_move]=' . $key . $event_only_uri . '" style="border: 0"><img src="' . serendipity_getTemplateFile('admin/img/downarrow.png') . '" height="16" width="16" alt="'. DOWN .'" border="0" /></a>';
}
?>
<li class="pluginmanager_item_<?php echo ($sort_idx % 2 ? 'even' : 'uneven'); ?>" id="<?php echo $key; ?>">
<a href="#grab<?php echo $key; ?>" id="grab<?php echo $key; ?>" class="pluginmanager_grablet"></a>
<?php if ($is_plugin_editable) { ?>
<input type="checkbox" name="serendipity[plugin_to_remove][]" value="<?php echo $plugin_data['name']; ?>" />
<?php } ?>
<?php if ( $can_configure ) { ?>
<a class="pluginmanager_configure" href="?serendipity[adminModule]=plugins&serendipity[plugin_to_conf]=<?php echo $key ?>"><img src="<?php echo serendipity_getTemplateFile('admin/img/configure.png') ?>" style="border: 0; vertical-align: bottom;"></a>
<?php } ?>
<span class="pluginmanager_title">
<?php if ( $can_configure ) { ?>
<a href="?serendipity[adminModule]=plugins&serendipity[plugin_to_conf]=<?php echo $key ?>"><?php echo $title; ?></a>
<?php } else { ?>
<?php echo $title; ?>
<?php } ?></span><br />
<div class="pluginmanager_description" style="font-size: 8pt"><?php echo $desc; ?></div>
<div class="pluginmanager_ownership"><?php ownership($plugin_data['authorid'], $plugin_data['name'], $is_plugin_owner); ?></div>
<?php echo ($eyecandy ? '<noscript>' : ''); ?>
<div class="pluginmanager_place"><?php echo $place; ?></div>
<div class="pluginmanager_move"><?php echo $moveup ?> <?php echo $movedown ?></div>
<?php echo ($eyecandy ? '</noscript>' : ''); ?>
</li>
<?php
$sort_idx++;
}
echo '</ol></td>';
}
?>
<tr>
<td colspan="3" align="right"><?php printf(PLUGIN_AVAILABLE_COUNT, $total); ?></td>
</tr>
</table>
<br />
<div>
<input type="submit" name="REMOVE" title="<?php echo DELETE; ?>" value="<?php echo REMOVE_TICKED_PLUGINS; ?>" class="serendipityPrettyButton" />
<input type="submit" name="SAVE" title="<?php echo SAVE_CHANGES_TO_LAYOUT; ?>" value="<?php echo SAVE; ?>" class="serendipityPrettyButton" />
</div>
</form>
<?php
}
Eine zentrale Tabelle gibt es nach wie vor, die die jeweiligen ol-Listenelemente hält. Für die Seitenleistenplugins gibt es nun eine drei-spaltige Tabelle, die für jeweils links, rechts und "versteckt" die jeweiligen Plugins anzeigt. Jede ol-Gruppe hat eine eindeutige ID, die später für das JavaScript von hoher Wichtigkeit sein wird.
Weiterhin ist es wichtig, dass das Formular am Anfang der Funktion einen onSubmit-Handler enthält (sofern eye-candy aktiviert). Auch muss die JS-Funktion zum initialisieren des JavaScripts als LoadEvent eingefügt werden.
Nun also zum Ausschnitt des angepassten JavaScripts von oben:
CODE:
function pluginMoverInit() {
var list = document.getElementById("left_col");
DragDrop.makeListContainer(list, 'g1');
list.onDragOver = function() { this.style["border"] = "1px solid #4d759b"; };
list.onDragOut = function() { this.style["border"] = "none"; };
list = document.getElementById("hide_col");
DragDrop.makeListContainer(list, 'g1');
list.onDragOver = function() { this.style["border"] = "1px solid #4d759b"; };
list.onDragOut = function() {this.style["border"] = "none"; };
list = document.getElementById("right_col");
DragDrop.makeListContainer(list, 'g1');
list.onDragOver = function() { this.style["border"] = "1px solid #4d759b"; };
list.onDragOut = function() {this.style["border"] = "none"; };
}
function pluginMovergetSort() {
order = document.getElementById("order");
order.value = DragDrop.serData('g1', null);
return order.value;
}
var list = document.getElementById("left_col");
DragDrop.makeListContainer(list, 'g1');
list.onDragOver = function() { this.style["border"] = "1px solid #4d759b"; };
list.onDragOut = function() { this.style["border"] = "none"; };
list = document.getElementById("hide_col");
DragDrop.makeListContainer(list, 'g1');
list.onDragOver = function() { this.style["border"] = "1px solid #4d759b"; };
list.onDragOut = function() {this.style["border"] = "none"; };
list = document.getElementById("right_col");
DragDrop.makeListContainer(list, 'g1');
list.onDragOver = function() { this.style["border"] = "1px solid #4d759b"; };
list.onDragOut = function() {this.style["border"] = "none"; };
}
function pluginMovergetSort() {
order = document.getElementById("order");
order.value = DragDrop.serData('g1', null);
return order.value;
}
Die Init-Funktion erstellt Drag-and-Drop Container für die ol-Listenelemente (left_col, hide_col, right_col). Alle werden innerhalb der Gruppe "g1" zusammengefügt, so dass das JS weiß, das die Plugins untereinander in jede der Gruppen gesetzt werden kann. Die onDragOver/onDragOut Funktionen sind lediglich für den optischen Effekt, dass der Container beim MouseOver einen hervorgehobenen Rahmen enthält. So weiß der Benutzer, über welchem Container er sich gerade befindet.
Die pluginMovergetSort() Funktion ist die, die beim onSubmit des Formulars aufgerufen wird. Sie speichert die aktuelle Anordnung der Plugins in einem lesbaren Format, die dann wiederrum in ein hidden-Feld des Formulares übertragen wird.
Genau jenes Hidden-Field ist, was uns bei speichern der Plugins die Reihenfolge angibt. Bis dato war dafür die Variable serendipity[placement] zuständig, die den jeweiligen Positionsindex (anhand der Reihenfolge des SUBMITs) und auch die dargestellte Seite enthielt. Ab sofort wird die Seite jedoch anhand des Eltern-Containers bestimmt, und der Positionsindex anhand der Reihenfolge der Plugins darin.
Um den Code möglichst nahtlos in Serendipity zu integrieren ist es am ratsamsten, das neue Übermittlugnsformat in das alte zu überführen. Dies geschicht in der include/admin/plugins.inc.php durch folgenden neuen Code:
CODE:
/* preparse Javascript-generated input */
if (isset($_POST['SAVE']) && !empty($_POST['serendipity']['pluginorder'])) {
$parts = explode(':', $_POST['serendipity']['pluginorder']);
$col_assoc = array(
'left_col' => 'left',
'right_col' => 'right',
'hide_col' => 'hide',
'event_col' => 'event',
'eventh_col' => 'eventh'
);
foreach($parts AS $sidepart) {
preg_match('@^(.+)\((.*)\)$@imsU', $sidepart, $matches);
if (!isset($col_assoc[$matches[1]])) {
continue;
}
$pluginsidelist = explode(',', $matches[2]);
foreach($pluginsidelist AS $pluginname) {
$pluginname = trim(urldecode($pluginname));
if (empty($pluginname)) {
continue;
}
$serendipity['POST']['placement'][$pluginname] = $col_assoc[$matches[1]];
$new_order[] = $pluginname;
}
}
if (is_array($new_order)) {
foreach($new_order AS $new_order_pos => $order_plugin) {
serendipity_db_query("UPDATE {$serendipity['dbPrefix']}plugins SET sort_order = ". (int)$new_order_pos . " WHERE name='" . serendipity_db_escape_string($order_plugin) . "'");
}
}
}
if (isset($_POST['SAVE']) && !empty($_POST['serendipity']['pluginorder'])) {
$parts = explode(':', $_POST['serendipity']['pluginorder']);
$col_assoc = array(
'left_col' => 'left',
'right_col' => 'right',
'hide_col' => 'hide',
'event_col' => 'event',
'eventh_col' => 'eventh'
);
foreach($parts AS $sidepart) {
preg_match('@^(.+)\((.*)\)$@imsU', $sidepart, $matches);
if (!isset($col_assoc[$matches[1]])) {
continue;
}
$pluginsidelist = explode(',', $matches[2]);
foreach($pluginsidelist AS $pluginname) {
$pluginname = trim(urldecode($pluginname));
if (empty($pluginname)) {
continue;
}
$serendipity['POST']['placement'][$pluginname] = $col_assoc[$matches[1]];
$new_order[] = $pluginname;
}
}
if (is_array($new_order)) {
foreach($new_order AS $new_order_pos => $order_plugin) {
serendipity_db_query("UPDATE {$serendipity['dbPrefix']}plugins SET sort_order = ". (int)$new_order_pos . " WHERE name='" . serendipity_db_escape_string($order_plugin) . "'");
}
}
}
Das Format der JS-Übermittlung sieht aus wie folgender String: left_col(plugin1,plugin2,plugin3):hide_col():right_col(plugin4). Demzufolge müssen wir erst die einzelnen Teile in ihre Gruppen left_col / hide_col / right_col aufteilen. Das würde mit regulären Ausdrücken gehen, aber für einfache Fälle bevorzuge ich persönlich "explode".
Somit habe ich nach dem ersten
CODE:
$parts = explode(':', $_POST['serendipity']['pluginorder']);
ein Array, was die Einzelteile "left_col(plugin1,plugin2,plugin3)", "hide_col()" und "right_col(plugin4)" enthält. Man geht also nun jede der einzelnen Gruppen durch, prüft ob die Eingabe dem gewünschten Format entspricht (der preg_match() Befehl) und trennt den String anhand des "," Begrenzers erneut auf. Im ersten Durchlauf erhält man dann also als Variablen:
$matches[1]: left_col
$matches[2]: plugin1,plugin2,plugin3
$pluginsidelist: array('plugin1', 'plugin2', 'plugin3')
Anhand des Zuordnungs-Hilfsarrays $col_assoc kann nun sowas wie "left_col" auf den benötigten Plugin_Schlüssel "left/right/event/eventh/hidden" geschlossen werden. Nun haben wir also alle drei Informationen die wir benötigen um das alte Array serendipity[placement] wieder herzustellen: Den Namen des Plugins ('plugin1'), die Gruppe ('left_col') und den Datenbankschlüssel ('left'). Anhand dieser Werte kann der alte Serendipity-Code später die Datenbankoperationen ausführen um die neue Seiten-Reihenfolge zu ändern.
An dieser Stelle sollte uns auffallen, dass durch den noscript-Teil von früher die alten Buttons zum hoch/runter-schieben noch vorhanden sind. Da der alte Code zum Verschieben ebenfalls noch vorhanden ist, wird also die alte Funktionalität nach wie vor ohne Probleme durchgeführt.
Die letzte foreach-Schleife des obigen Code-Schnippsels ist dafür zuständig, die komplette Liste an Plugins durchzugehen und so die neue Sortierungsreihenfolge einzutragen. Dies ist nötig, da nämlich der alte Serendipity-Code nur ausgeführt wird, wenn eben der Sortierungs-Button "geklickt" wurde. Dies passiert ja nun nicht mehr, und daher muss ein eigenständiges Stück Code diese Neu-Sortierung pauschal ausführen. Früher wurde immer nur die Position von 2 Plugins getauscht (weil man ja immer nur ein Plugin pro Klick verschieben konnte), nun wird die Reihenfolge "auf einmal" geändert.
Nachdem der CSS-Code nun noch etwas mit den neuen Selektoren gepimpt wurde, sieht das ganze aus wie folgendes Video:
Gebraucht habe ich für die Entwicklung des ganzen übrigens gut 70 Minuten. Dabei fielen 50 Minuten auf CSS-Verhübschung, 10 Minuten für den JS-Kram und 10 Minuten PHP. Das ist vertretbar, denke ich. Für das erstellen dieses Blog-Eintrags habe ich jedenfalls beinahe länger gebraucht.
Wer nun neugierig geworden ist, kann sich gerne einen aktuellen Snapshot (1.1-alpha) von Serendipity runterladen und installieren.
Kommentare
Ansicht der Kommentare:
(Linear | Verschachtelt)
Wie sagt der Volksmund heute...? FETT FETT FETT!
- Gee, Garvin, what do you want to do tonight?
- Gee, Garvin, what do you want to do tonight?
... womit auch klar wäre, warum die 1.0 final nicht fertig wird )
Bis zum Video habe ich mich wirklich gefragt, ob man das wirklich braucht. Erst als ich das gesehen habe, habe ich gedacht: Ja, kann man nutzen. Nun bleibt noch ein letzter Test: Funktioniert das auch mit dem Konqueror . Erst dann bin ich zu 100% begeistert.
Ich bin auch völlig hin und weg, das ist eine sowasvon starke Verbesserung und ich freu mir ein Loch in den Bauch! Meinen allerherzlichsten Dank
Naja, das liegt hauptsächlich an den anderen Entwicklern, die sich nicht auf ein Logo einigen konnten. Ich hatte meinen Favoriten, wurde aber umgestimmt und dann fing das alles nochmal von vorne an. Wie auch immer, dafür wird jetzt alles schöner und besser. Da lohnt sich das warten.
HI garvin,
was ich aber besser finde ist die sajax implementierung. Oder alterneativ könntet Ihr den Senden button auch mehr ins Blickfeld rücken, sonst werden sich sicher einige über die nicht geänderte Ordnung wundern.
was ich aber besser finde ist die sajax implementierung. Oder alterneativ könntet Ihr den Senden button auch mehr ins Blickfeld rücken, sonst werden sich sicher einige über die nicht geänderte Ordnung wundern.
Veni, Vidi, VISA am : Serendipity Drag and Drop Plugin-Manager
Vorschau anzeigen