Schritt 12: Callback-Magie
Ja, nach langer Pause geht es wirklich weiter. Diesmal wird die Backendpflege der "Meldungen"-Tabelle komplettiert. Gegenüber dem letzten Schritt haben sich nach fruchtbarer Diskussion einige Änderungen ergeben.
Zunächst die Datenbankdefinition der Tabelle tl_gw_meldungen in system/modules/tl_gw_turnierpaare/config/database.sql:
Code:
--
-- Table `tl_gw_meldungen`
--
CREATE TABLE `tl_gw_meldungen` (
`id` int(10) unsigned NOT NULL auto_increment,
`pid` int(10) unsigned NOT NULL default '0',
`sorting` int(10) unsigned NOT NULL default '0',
`tstamp` int(10) unsigned NOT NULL default '0',
`datum` varchar(10) NOT NULL default '',
`startgruppe` varchar(32) NOT NULL default '',
`startklasse` varchar(12) NOT NULL default '',
`lat_std` char(32) NOT NULL default '',
`turnierort` varchar(128) NOT NULL default '',
`turnierart` varchar(64) NULL default NULL,
`anzahlpaare` int(4) NULL default NULL,
`platz_von` int(4) NULL default NULL,
`platz_bis` int(4) NULL default NULL,
`bemerkung` text NULL,
PRIMARY KEY (`id`),
KEY `pid` (`pid`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
Das Feld datum war vorher vom Typ "date", aber das funktioniert so nicht, wenn man es als Datumsfeld im Backend verwenden will. Darum jetzt ein varchar(10). Der Rest blieb unverändert.
Auch das Einfügen der Backendmodule in die Auswahlleiste auf der linken Seite des Backends ändere ich in /system/modules/tl_gw_turnierpaare/config/config.php ab:
PHP-Code:
// Back end module
array_insert($GLOBALS['BE_MOD'], 0, array(
'gw_paarverwaltung' => array(
'gw_turnierpaare' => array
(
'tables' => array('tl_gw_turnierpaare'),
'icon' => 'system/modules/gw_turnierpaare/icons/turnierpaare.png'
),
'gw_meldungen' => array
(
'tables' => array('tl_gw_meldungen'),
'icon' => 'system/modules/gw_turnierpaare/icons/meldeliste.png'
)
)
)
);
Durch das array_insert kann ich mit zweiten Parameter steuern, an welcher Stelle das als letzter Parameter angegebene Array eingefügt werden soll. Bei einer "0" wie hier wandern meine Arrayeinträge ganz nach oben, bei einer "1" an zweite Stelle, usw. Da in der Praxis auf der Webseite häufiger neue Turnierpaarmeldungen eingegeben werden als neue Artikel, mächte ich die Turnierpaarverwaltung gerne ganz oben haben.
Und dadurch, dass ich in dem Array was ich einfüge eine zusätzliche Ebene "gw_paarverwaltung" habe, gruppiere ich die beiden Einträge gw_turnierpaare und gw_meldungen in eine Untergruppe innerhalb des Menüs. Das schafft mehr Übersicht.
Um die Language-Datei für diese Einträge muss ich mich jetzt auch einmal kümmern (/system/modules/tl_gw_turnierpaare/languages/modules.php):
PHP-Code:
/**
* Back end modules
*/
$GLOBALS['TL_LANG']['MOD']['gw_turnierpaare'] = array('Turnierpaare', 'Verwaltung der Turnierpaare.');
$GLOBALS['TL_LANG']['MOD']['gw_meldungen'] = array('Meldungen', 'Verwaltung der Turniermeldungen (Meldeliste).');
$GLOBALS['TL_LANG']['MOD']['gw_paarverwaltung'] = 'Paarverwaltung';
ACHTUNG: Während die Language-Einträge für die Module selbst ein Array bestehend aus dem Label des Menüeintrags und dem Beschreibungstext des Moduls sein müssen, ist der Label für die Überschrift der Gruppe im Menü (gw_paarverwaltung) nur ein einfacher String!
Wie sieht das nun aus?
Wo wir schonmal bei den Language-Dateien sind, machen wir auch noch die Label für die Felder des Backendmoduls (/system/modules/tl_gw_turnierpaare/languages/de/tl_gw_meldungen.php):
PHP-Code:
<?php if (!defined('TL_ROOT')) die('You can not access this file directly!');
/**
* Fields
*/
$GLOBALS['TL_LANG']['tl_gw_meldungen']['pid'] = array('Paar', 'Turnierpaar auswählen');
$GLOBALS['TL_LANG']['tl_gw_meldungen']['datum'] = array('Turnierdatum', 'Turnierdatum eingeben');
$GLOBALS['TL_LANG']['tl_gw_meldungen']['turnierort'] = array('Turnierort', 'Turnierort eingeben');
$GLOBALS['TL_LANG']['tl_gw_meldungen']['turnierart'] = array('Turnierform', 'Turnierform auswählen');
$GLOBALS['TL_LANG']['tl_gw_meldungen']['startgruppe'] = array('Startgruppe', 'Startgruppe auswählen');
$GLOBALS['TL_LANG']['tl_gw_meldungen']['startklasse'] = array('Startklasse', 'Startklasse auswählen');
$GLOBALS['TL_LANG']['tl_gw_meldungen']['lat_std'] = array('Latein/Standard', 'Tanzart auswählen');
$GLOBALS['TL_LANG']['tl_gw_meldungen']['anzahlpaare'] = array('Anzahl gestarteter Paare', 'Die Paaranzahl des Turniers eingeben');
$GLOBALS['TL_LANG']['tl_gw_meldungen']['platz_von'] = array('Platz', 'Erzielter Platz');
$GLOBALS['TL_LANG']['tl_gw_meldungen']['platz_bis'] = array('Platz bis', 'Geteilter Platz: Platz bis... - sonst leer lassen');
$GLOBALS['TL_LANG']['tl_gw_meldungen']['bemerkung'] = array('Bemerkung', 'Bemerkung eingeben');
/**
* Reference
*/
$GLOBALS['TL_LANG']['tl_gw_meldungen']['couple_legend'] = 'Paar';
$GLOBALS['TL_LANG']['tl_gw_meldungen']['tournament_legend'] = 'Turnier';
$GLOBALS['TL_LANG']['tl_gw_meldungen']['result_legend'] = 'Ergebnis';
/**
* Buttons
*/
$GLOBALS['TL_LANG']['tl_gw_meldungen']['new'] = array('Neue Meldung', 'Eine neue Turniermeldung anlegen');
$GLOBALS['TL_LANG']['tl_gw_meldungen']['edit'] = array('Editieren', 'Die Turniermeldung editieren');
$GLOBALS['TL_LANG']['tl_gw_meldungen']['copy'] = array('Meldung kopieren', 'Die Turniermeldung in die Zwischenablage kopieren');
$GLOBALS['TL_LANG']['tl_gw_meldungen']['delete'] = array('Meldung löschen', 'Die Turniermeldung aus der Liste entfernen');
$GLOBALS['TL_LANG']['tl_gw_meldungen']['show'] = array('Details', 'Die Detailansicht der Turniermeldung anzeigen');
?>
Da muss ich wohl nicht mehr viel zu schreiben: Der erste Block sind Label und Beschreibungen für die Eingabefelder, der zweite Block sind die Label für die Palettenüberschriften, und die dritte Gruppe schließlich die Label und Beschreibungen für die "Aktionsbuttons".
Kommen wir zum spannenden Teil, mir dem ich im Schritt 11 ja noch einen Kampf zu fechten hatte: der DCA-Record (/system/modules/tl_gw_turnierpaare/dca/tl_gw_meldungen.php):
PHP-Code:
<?php if (!defined('TL_ROOT')) die('You can not access this file directly!');
/**
* Table tl_gw_meldungen
*/
$GLOBALS['TL_DCA']['tl_gw_meldungen'] = array
(
// Config
'config' => array
(
'dataContainer' => 'Table',
'enableVersioning' => true,
),
Hier so weit nichts Spannendes: Weiterhin wird eine Tabelle bearbeitet, und ich aktiviere Versionierung.
PHP-Code:
// List
'list' => array
(
'sorting' => array
(
'mode' => 2,
'fields' => array('datum DESC', 'turnierort', 'pid'),
'panelLayout' => 'filter;sort,search,limit'
),
mode = 2 bedeutet, dass das Sortierfeld im Header der Tabelle wählbar ist, defaultmäßig sortiere ich nach Turnierdatum (Jüngste ganz oben), dann dem Ort, und schließlich der Paar-ID.
Mit panelLayout aktiviere ich die Paletten oben am Kopf der Tabelle, um das Sortierfeld wählbar zu machen, das Suchfeld zu aktivieren und die Anzahl der Treffer limitieren zu können.
PHP-Code:
'label' => array
(
'fields' => array('datum','turnierort', 'turnierart', 'startgruppe','startklasse','lat_std'),
'format' => '%s - #name# - <span style="font-weight: bold;">%s</span> - <span style="color: #section_colour#;">%s %s %s %s</span>',
'label_callback' => array('tl_gw_meldungen', 'lookup_pid')
),
So, hier beginnt etwas die Magie...ich bastele mir das Format der Ausgabezeilen für die Backendansicht.
Leider habe ich hier nur die Felder der aktuellen Tabelle (tl_gw_meldungen) zur Verfügung, ich möchte aber gerne den Namen des Paares anzeigen, was zur "pid" gehört, die im Datensatz steht (pid ist der Foreign Key in die tl_gw_turnierpaare-Tabelle. Im klassischen SQL würde ich hier also einen JOIN machen).
So direkt geht das leider nicht, darum definiere ich einen "label_callback", der weiter unten in dieser Datei als Funktion "lookup_pid()" in der Klasse "tl_gw_meldungen" definiert wird. Die Felder, die ich jetzt schon habe, definiere ich auch direkt im String. Für den Namen des Paares setze ich zunächst einen Platzhalter "#name#" in den String.
Außerdem gibt es noch einen Platzhalter "#section_colour#". Ich möchte Turnierstarts in der Tanzform Standardtänze in orange darstellen, und solche in der Tanzform Lateinamerikanische Tänze in rot. Das kann ich an dieser Stelle im DCA-Record nicht entscheiden, weil es vom Inhalt des Felds "lat_std" abhängt. Auch das wird im label_callback geregelt.
Die Platzhalter werden im label_callback durch ein str_replace mit den gewünschten Werten ersetzt. Schön ist das nicht, aber funktioniert.
PHP-Code:
'global_operations' => array
(
'all' => array
(
'label' => &$GLOBALS['TL_LANG']['MSC']['all'],
'href' => 'act=select',
'class' => 'header_edit_all',
'attributes' => 'onclick="Backend.getScrollOffset();"'
)
),
'operations' => array
(
'edit' => array
(
'label' => &$GLOBALS['TL_LANG']['tl_gw_meldungen']['edit'],
'href' => 'act=edit',
'icon' => 'edit.gif'
),
'copy' => array
(
'label' => &$GLOBALS['TL_LANG']['tl_gw_meldungen']['copy'],
'href' => 'act=copy',
'icon' => 'copy.gif'
),
'delete' => array
(
'label' => &$GLOBALS['TL_LANG']['tl_gw_meldungen']['delete'],
'href' => 'act=delete',
'icon' => 'delete.gif',
'attributes' => 'onclick="if (!confirm(\'' . $GLOBALS['TL_LANG']['MSC']['deleteConfirm'] . '\')) return false; Backend.getScrollOffset();"'
),
'show' => array
(
'label' => &$GLOBALS['TL_LANG']['tl_gw_meldungen']['show'],
'href' => 'act=show',
'icon' => 'show.gif'
)
)
),
Zu den globalen Operationen gibt es eigentlich nichts zu sagen. Die bleiben so, wie der Extensioncreator sie angelegt hat...
PHP-Code:
// Palettes
'palettes' => array
(
'__selector__' => array(''),
'default' => '{couple_legend},pid;{tournament_legend},datum,turnierort,turnierart,startgruppe,startklasse,lat_std;'
.'{result_legend},anzahlpaare,platz_von,platz_bis,bemerkung;'
),
// Subpalettes
'subpalettes' => array
(
'' => ''
),
Die Palettendefinition hat sich nicht geändert, und Subpaletten gibt es weiterhin nicht.
PHP-Code:
// Fields
'fields' => array
(
'pid' => array
(
'label' => &$GLOBALS['TL_LANG']['tl_gw_meldungen']['pid'],
'inputType' => 'select',
'options_callback' => array('tl_gw_meldungen', 'getActiveCouples'),
'search' => true,
'sorting' => true,
'eval' => array('mandatory'=>true)
),
Das Feld "pid" war bisher ein foreignKey-Feld, aber damit konnte ich z.B. nur mit dem Partner-Nachnamen verknüpfen, nicht mit dem Paarnamen. Außerdem wurden hier immer ALLE Paare angezeigt, hier sollen aber nur AKTIVE Paare auswählbar sein. Um das besser machen zu können, definiere ich die Möglichkeiten in der Dropdown-Box selbst durch den options_callback "getActiveCouples()" in der Klasse tl_gw_meldungen, die gleich noch folgt.
PHP-Code:
'datum' => array
(
'label' => &$GLOBALS['TL_LANG']['tl_gw_meldungen']['datum'],
'inputType' => 'text',
'search' => true,
'sorting' => true,
'flag' => 6,
'eval' => array('mandatory'=>true, 'datepicker'=>$this->getDatePickerString(), 'tl_class'=>'w50 wizard', 'minlength' => 1, 'maxlength'=>10, 'rgxp' => 'date')
),
"flag" = 6 ist hier der große Trick, damit das Timestamp-Format von "datum" richtig als Datum im Format TT.MM.JJJJ angezeigt wird. Abgeguckt habe ich das übrigens im Backend in /system/modules/backend/dca/tl_log.php .
PHP-Code:
'turnierort' => array
(
'label' => &$GLOBALS['TL_LANG']['tl_gw_meldungen']['turnierort'],
'inputType' => 'text',
'search' => true,
'sorting' => true,
'flag' => 11,
'eval' => array('mandatory'=>true, 'minlength' => 1, 'maxlength'=>128, 'tl_class' => 'w50')
),
'turnierart' => array
(
'label' => &$GLOBALS['TL_LANG']['tl_gw_meldungen']['turnierart'],
'inputType' => 'select',
'search' => true,
'sorting' => true,
'flag' => 11,
'options' => gwTurnierpaarliste::$TurnierArten,
'eval' => array('mandatory'=>false, 'tl_class' => 'w50')
),
'startgruppe' => array
(
'label' => &$GLOBALS['TL_LANG']['tl_gw_meldungen']['startgruppe'],
'inputType' => 'select',
'search' => true,
'sorting' => true,
'flag' => 11,
'options' => gwTurnierpaarliste::$StartGruppen,
'eval' => array('mandatory'=>false, 'includeBlankOption' => true, 'tl_class' => 'w50')
),
'startklasse' => array
(
'label' => &$GLOBALS['TL_LANG']['tl_gw_meldungen']['startklasse'],
'inputType' => 'select',
'search' => true,
'sorting' => true,
'flag' => 11,
'options' => gwTurnierpaarliste::$StartKlassen,
'eval' => array('mandatory'=>true, 'tl_class' => 'w50')
),
'lat_std' => array
(
'label' => &$GLOBALS['TL_LANG']['tl_gw_meldungen']['lat_std'],
'inputType' => 'select',
'sorting' => false,
'options' => gwTurnierpaarliste::$TanzArten,
'eval' => array('mandatory'=>true, 'tl_class' => 'w50')
),
'anzahlpaare' => array
(
'label' => &$GLOBALS['TL_LANG']['tl_gw_meldungen']['anzahlpaare'],
'inputType' => 'text',
'eval' => array('mandatory'=>false, 'minlength' => 1, 'maxlength'=>4, 'rgxp' => 'digit')
),
'platz_von' => array
(
'label' => &$GLOBALS['TL_LANG']['tl_gw_meldungen']['platz_von'],
'inputType' => 'text',
'eval' => array('mandatory'=>false, 'minlength' => 1, 'maxlength'=>4, 'rgxp' => 'digit', 'tl_class' => 'w50')
),
'platz_bis' => array
(
'label' => &$GLOBALS['TL_LANG']['tl_gw_meldungen']['platz_bis'],
'inputType' => 'text',
'eval' => array('mandatory'=>false, 'minlength' => 1, 'maxlength'=>4, 'rgxp' => 'digit', 'tl_class' => 'w50')
),
'bemerkung' => array
(
'label' => &$GLOBALS['TL_LANG']['tl_gw_meldungen']['bemerkung'],
'inputType' => 'textarea',
'eval' => array('mandatory'=>false, 'cols' => 80, 'rows' => 20, 'allowHtml' => false)
),
)
);
Hier gibt es keine großen Besonderheiten mehr. Gegenüber Schritt 11 habe ich die durchsuchbaren und sortierbaren Felder etwas erweitert bzw. geändert.
Nun kommt die Backendklasse, in der ich die Callbacks unterbringe:
PHP-Code:
class tl_gw_meldungen extends Backend
{
/**
* Import the back end user object
*/
public function __construct()
{
parent::__construct();
$this->import('BackendUser', 'User');
}
Auftaktgeplänkel...
Zunächst der options_callback, der mir die Liste der aktiven Turnierpaare für die Auswahl in der Dropdown-Box liefert:
PHP-Code:
public function getActiveCouples()
{
$couples = array();
// Get all the active couples
$objCouples = $this->Database->prepare("SELECT id,partnernachname,partnervorname,partnerinnachname,partnerinvorname FROM tl_gw_turnierpaare WHERE aktiv='1' ORDER by partnernachname, partnervorname, partnerinnachname, partnerinvorname")
->execute();
while ($objCouples->next())
{
$k = $objCouples->id;
$v = $objCouples->partnernachname;
if($objCouples->partnervorname)
{
$v .= ', '.$objCouples->partnervorname;
}
if($objCouples->partnerinnachname)
{
$v .= ' und '.$objCouples->partnerinnachname;
if($objCouples->partnerinvorname)
{
$v .= ', '.$objCouples->partnerinvorname;
}
}
$couples[$k] =$v;
}
return $couples;
}
Der größte Teil der Funktion ist eigentlich das "Zusammenbasteln" des Paarnamens aus den Einzelbestandteilen der Namen von Partner und Partnerin. Zunächst werden aus der tl_gw_turnierpaare-Tabelle die Namen der aktiven Paare selektiert und der Ergebnisstring daraus zusammengesetzt. Das Ergebnis wird dann einem Array zugewiesen, wobei die ID des Paares der Array-Key ist, und der Paarname der Value - also z.B. $couples[47] = "Wupp, Willi und Wupp, Sieglinde".
In der Dropdownbox steht der String, und wenn ich den dort auswähle, landet die ID 47 als "pid" in der Datenbanktabelle tl_gw_meldungen. Und, was noch vieeeeel cooler ist: Wenn ich nach dem pid-Feld sortiere, dann werden auch die Sortierheader durch die entsprechenden Strings ersetzt, statt dass dort der numerische pid-Wert steht. Leider erfolgt die Sortierung natürlich nach dem Key, nicht nach dem Value...aber das wäre ja fast zuviel verlangt :-).
Weiter geht es mit label_callback. Als erstes Argument bekommt der die aktuelle Zeile aus der Datenbank, als zweites Argument den Label, wie er weiter oben im DCA-Record definiert wurde, hier also mit dem Platzhaltern #name# und #section_colour#.
PHP-Code:
public function lookup_pid($row, $label)
{
$pid = $row['pid'];
// Datensatz mit ID pid aus Tabelle tl_gw_turnierpaare holen
$prow = $this->Database->prepare("SELECT * FROM tl_gw_turnierpaare WHERE id=?")
->execute($pid);
Mit der pid aus dem aktuellen Datensatz holen wir den Datensatz des Turnierpaares...
PHP-Code:
$name = '<span style="font-weight: bold;">'.$prow->partnernachname.'</span>';
if($prow->partnervorname)
{
$name .= ', '.$prow->partnervorname;
};
if($prow->partnerinnachname)
{
$name .= ' und <span style="font-weight: bold;">'.$prow->partnerinnachname.'</span>';
if($prow->partnerinvorname)
{
$name .= ', '.$prow->partnerinvorname;
}
};
...und bauen aus den Namensbestandteilen den Paarnamen zusammen, wobei die Nachnamen fett erscheinen sollen.
PHP-Code:
$colour = 'black';
switch($row['lat_std'])
{
case 'Std':
$colour = 'orange';
break;
case 'Lat':
$colour = 'red';
break;
}
In Abhängigkeit vom Inhalt des lat_std-Feldes wird eine Farbe zugewiesen. Falls der Inhalt unbekannt ist, bleibt die Schrift schwarz.
PHP-Code:
$label = str_replace('#section_colour#', $colour, $label);
$label = str_replace('#name#', $name, $label);
return $label;
}
};
?>
Wir ersetzen die Platzhalter #name# und #section_colour# im Label-String durch die neu berechneten Werte, und geben den Label zurück.
Was haben wir damit nun erreicht:
Unsere Startmeldungen sind nach dem Datum sortiert, die Nachnamen im Paarnamen sind fett, die Stadt auch, und die "Kategorisierung" des Turniers ist orange oder rot, je nachdem ob es um Standardtänze oder lateinamerikanische Tänze geht.
Sortieren wir nach den Paaren, dann wird die numerische pid in den Sortierheader durch unseren options_callback in die entsprechenden Strings umgesetzt - sehr nett. Leider erfolgt die Sortierung weiterhin numerisch nach der pid, und nicht alphabetisch nach den Strings. Vielleicht hat jemand noch eine Idee, wie man das lösen könnte?
Und bei Neuanlage einer Turniermeldung werden in der Dropdown-Liste nurnoch die aktiven Paare aufgeführt, nicht mehr ALLE.
Im nächsten Schritt geht es ans Frontendmodul für die Meldeliste...