Liste der Anhänge anzeigen (Anzahl: 1)
Schritt 8: Parameter fürs Modul
Die Ausgabe im Frontendmodul möchte ich gerne in der Modulverwaltung parametrisieren können. Einerseits soll man auswählen können, ob das Modul nur aktive oder nur inaktive, oder alle Paare auflisten soll. Andererseits soll der Sortiermodus zwischen "Nachnamen alphabetisch" und "Nach Altersgruppe aufsteigend" wählbar sein.
Dafür ist der etablierte Weg eine Erweiterung der Tabelle tl_module um die benötigten Felder. Man soll bestehende Fehler wenn möglich recyclen, aber man recycelt dann auch die Beschreibungstexte usw mit, das war für mich nicht passend.
Also habe ich neue Felder angelegt. In der config/database.php meiner Extension:
Code:
--
-- Extend table 'tl_module'
--
CREATE TABLE `tl_module` (
`gw_tp_showonlyactive` char(1) NOT NULL default 'B',
`gw_tp_couplesorting` char(1) NOT NULL default 'A',
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
Hier füge ich also zwei "CHAR"-Felder hinzu, die Buchstabencodes enthalten (Beim Filter: B = Alle Paare [Both], A = Nur aktive Paare, I = Nur inaktive Paare; Beim Sortieren: A = Alphabetisch, C = Nach Altersklasse [Class]), die ich dann später im Frontend-Ausgabemodul auslesen und interpretieren muss. WICHTIG: Obwohl ich nur eine schon bestehende Tabelle ERWEITERN will, muss ich hier "CREATE TABLE" verwenden. Das Installtool macht das Richtige daraus...
Danach muss ich das Installtool aufrufen, um die neuen Felder anlegen zu lassen.
Dann muss der DCA-Record für tl_module so erweitert werden, dass meine Felder angezeigt werden, wenn in der Dropdown-Liste der zur Verfügung stehenden Module die Turnierpaarliste ausgewählt wird.
Dafür nehmen wir uns system/modules/gw_turnierpaare/dca/tl_module.php vor:
PHP-Code:
$GLOBALS['TL_DCA']['tl_module']['palettes']['gw_turnierpaarliste'] = '{title_legend},name,headline,type;{sort_legend},gw_tp_showonlyactive,gw_tp_couplesorting;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID,space';
In der Subpalette für mein Modul füge ich einen neuen Abschnitt mit Überschrift "sort_legend" ein. Darin werden meine beiden neuen Datenbankfelder angezeigt.
Die müssen noch im DCA definiert werden:
PHP-Code:
$GLOBALS['TL_DCA']['tl_module']['fields']['gw_tp_showonlyactive'] = array
(
'label' => &$GLOBALS['TL_LANG']['tl_module']['gw_tp_showonlyactive'],
'default' => 'B',
'exclude' => true,
'inputType' => 'select',
'options' => array('B','A','I'),
'reference' => &$GLOBALS['TL_LANG']['tl_module']['gw_tp_filteroptions'],
'eval' => array('mandatory'=>true, 'tl_class' => 'w50')
);
$GLOBALS['TL_DCA']['tl_module']['fields']['gw_tp_couplesorting'] = array
(
'label' => &$GLOBALS['TL_LANG']['tl_module']['gw_tp_couplesorting'],
'default' => 'A',
'exclude' => true,
'inputType' => 'select',
'options' => array('A','C'),
'reference' => &$GLOBALS['TL_LANG']['tl_module']['gw_tp_sortoptions'],
'eval' => array('mandatory'=>true)
);
Der Typ ist "select", also eine Dropdownbox. Die "options" sind die Werte, die in die Datenbank geschrieben werden sollen. Die "reference" sind im Gegensatz dazu die Werte, die in der Dropdown-Box angezeigt werden sollen. Hier war mir anfangs unklar, wie genau die Übersetzungen definiert werden müssen. Dazu gleich mehr. Hier setze ich erstmal die Referenz auf ein Array, was in den Sprachfiles definiert wird. Da die Werte in der Modulverwaltung angezeigt werden, habe ich mich dazu entschlossen, sie unter ['tl_module'] einzusortieren, aber mit meinem Präfix "gw_tp", um Namenskollisionen unwahrscheinlich zu machen.
Nun noch die Definition der Strings in der deutschen Sprachdatei (englisch geht genauso). Da die String zur Modulverwaltung gehören, habe ich mich dazu entschlossen, sie in die Datei system/modules/gw_turnierpaare/languages/de/modules.php zu schreiben. Man hätte auch eine neue Datei tl_module.php anlegen können. Ich weiss nicht, was da "best practice" ist.
Wir ergänzen also die modules.php um:
PHP-Code:
/**
* Back end fields for tl_module
*/
$GLOBALS['TL_LANG']['tl_module']['sort_legend'] = 'Filter und Sortierung';
$GLOBALS['TL_LANG']['tl_module']['gw_tp_showonlyactive'] = array('Aktive/Inaktive Paare auflisten?', 'Bitte wählen Sie aus, ob nur aktive, inaktive oder alle Paare gelistet werden sollen');
$GLOBALS['TL_LANG']['tl_module']['gw_tp_filteroptions'] = array('B' => 'Alle Paare', 'A' => 'Nur aktive Paare', 'I' => 'Nur inaktive Paare');
$GLOBALS['TL_LANG']['tl_module']['gw_tp_couplesorting'] = array('Sortiermodus', 'Bitte wählen Sie aus, wie die Liste der Turnierpaare sortiert sein soll');
$GLOBALS['TL_LANG']['tl_module']['gw_tp_sortoptions'] = array('A' => 'Alphabetisch', 'C' => 'Nach Altersklasse aufsteigend');
Hier kommt nun der Knackpunkt der "reference"-Arrays, der mir nicht klar war, und erst durch Abgucken bei anderen Extensions geklärt wurde:
Das reference-Array darf nicht einfach ein aufsteigendes Array der Texte zu den Optionen sein wie das "options"-Array (also "array('text1','text2','text3');"), sondern es muss ein assoziatives Array mit der Beziehung 'option1' => 'Text1' sein, sonst funktioniert es nicht. Die DCA-Referenz bleibt da leider sehr schwammig.
Auch die Abschnittsüberschrift "sort_legend" definieren wir hier.
Damit haben wir dieses Ergebnis:
https://community.contao.org/de/atta...1&d=1268390837
Leider schaffe ich es in diesem Schritt nicht mehr, die neuen Felder in der Frontendausgabe auch noch auszuwerten (Der Kampf mit dem reference-Array hat doch etwas Zeit gekostet), das muss bis zum nächsten Mal (übernächste Woche) warten, sorry.
Liste der Anhänge anzeigen (Anzahl: 1)
Schritt 8b: Parameter für das Modul, die zweite
Nun geht es daran, die Modulparameter aus Schritt 7 im Frontendmodul auch wirklich zu nutzen.
Dafür muss ich sie mir zuerst beschaffen. Das geschieht zu Beginn der compile()-Funktion in /system/modules/gw_turnierpaare/gwTurnierpaarliste.php:
PHP-Code:
$moduleParams = $this->Database->prepare("SELECT * FROM tl_module WHERE id=?")
->limit(1)
->execute($this->id);
$this->id enthält die Modul-ID, und über einen Blick in die Datenbank bekommen wir alle Felder der entsprechenden Zeile und damit auch unsere Parameter.
Die muss ich nun auswerten uns entsprechend reagieren. Zunächst das Flag, das mir nur die aktiven Paare, nur die inaktiven oder beide liefert:
PHP-Code:
$whereClause = '';
if($moduleParams->gw_tp_showonlyactive == 'A')
{
$whereClause = "WHERE aktiv='1'";
}
else
{
if($moduleParams->gw_tp_showonlyactive == 'I')
{
$whereClause = "WHERE aktiv=''";
}
}
Die Variable $whereClause baue ich später in meinen SELECT der Turnierpaare ein.
Für die Sortierung muss ich die alphabetische Sortierung nach Nachname Herr und Nachname Dame einerseits und die Sortierung nach Startgruppe (und dann Startklasse und erst dann Nachnamen) unterscheiden.
Der erste Fall ist einfach ("ORDER BY partnernachname, partnerinnachname"), der andere aber komplizierter, da die Sortierrreihenfolge nicht alphabetisch ist, sondern sich nach den Altersklassen richten soll, die Jüngsten zuerst. Aber ein wenig Googeln hilft, das Problem mit einem SQL-Query zu lösen. Die FIELD()-Funktion liefert mir den Index des Inhalts eines Datenbankfeldes innerhalb einer Liste von Werten. Das kann ich benutzen, um in beliebiger Reihenfolge zu sortieren. Will ich das Feld "field" in der Reihenfolge "BCA" sortieren, leistet das "ORDER BY FIELD(field,'B','C','A')".
PHP-Code:
$orderClause = 'partnernachname, partnerinnachname';
if($moduleParams->gw_tp_couplesorting == 'C')
{
// Nach Altersgruppen sortieren
$fieldstartgruppe="''";
foreach(array('KIN I','KIN II', 'JUN I', 'JUN II', 'JUG', 'HGR', 'HGR II', 'SEN I', 'SEN II', 'SEN III', 'SEN IV') as $gruppe)
{
$fieldstartgruppe .= ",'".$gruppe."'";
}
$fieldstartklasse="''";
foreach(array('-', 'E','D', 'C', 'B', 'A', 'S', 'PRO', 'LL', 'OL', 'RL', '2. BL', '1. BL') as $klasse)
{
$fieldstartklasse .= ",'".$klasse."'";
}
$orderClause = "FIELD(startgruppe,".$fieldstartgruppe."), FIELD(startklassestandard,".$fieldstartklasse."), FIELD(startklasselatein,".$fieldstartklasse."), partnernachname, partnerinnachname";
}
Defaultmäßig wird nach den Nachnamen alphabetisch sortiert. Wenn aber das gw_tp_couplesorting-Feld 'C' ist, dann wird aus den möglichen Werten der Altersgruppe und der Startklasse jeweils ein String zusammengebaut, der dann in $orderClause zum "ORDER BY"-Teil zusammengebaut wird.
So wird zuerst nach der Altersgruppe (in vorgegebener Reihenfolge), dann nach den Startklassen Standard und Latein (in vorgegebener Reihenfolge) und schließlich nach den Nachnamen sortiert. Natürlich hätte ich die Liste der Gruppen und Klassen direkt als String definieren können, statt ein Array anzulegen, mit einer Variable drüberzulaufen, um daraus wieder einen String zu machen. Aber der Gedanke war, das 'options"-Feld der DCA-Definition der entsprechenden Felder im DCA-Record zu nutzen. Eine Änderung der möglichen Optionen dort würde dann auch gleich an dieser Stelle genutzt werden. Leider scheint die DCA-Definition des Backend-Moduls an dieser Stelle (Frontend-Modul!) leider nicht geladen zu sein, $GLOBALS['TL_DCA'] ist zumindest nicht vorhanden. Schade! Aber vielleicht lagere ich die Arrays noch in eine gemeinsame, geteilte Include-Datei aus, die ich in DCA-Definition und im Frontendmodul nutzen kann. Darum bleibt es erstmal bei der Schleife über den Arrayelementen.
Schließlich muss das ursprüngliche SELECT-Statement noch um die neuen Variablen $whereClause und $orderClause erweitert werden:
PHP-Code:
$objPaare = $this->Database->execute("SELECT * FROM tl_gw_turnierpaare " . $whereClause . "ORDER BY " . $orderClause);
Ich definiere nun zwei Module, einmal die Liste der aktiven Turnierpaare, die eben nur die aktiven Paare anzeigt, sortiert nach Startgruppen und Klassen, und eine Liste der inaktiven Paare, alphabetisch sortiert. Beide Module füge in in eine Seite als Inhaltselemente ein.
Das Ergebnis:
https://community.contao.org/de/atta...1&d=1269375406
Oben das Modul, das die aktiven Turnierpaare sortiert nach Altersgruppe anzeigt, unten das Modul, das die inaktiven Paare alphabetisch sortiert anzeigt. Die graue Farbe bei den Inaktiven wird auch noch geändert, und im nächsten Schritt wird es an den "Detail"-Link gehen.
Liste der Anhänge anzeigen (Anzahl: 3)
Schritt 9: Details, Details, Details!
In der Listenübersicht der Turnierpaare ist als Link bereits vorgesehen, dass man beim Klick auf den "Detail"-Link eines Paares zu einer Detail-Seite kommen kann. Ich möchte das mit demselben Modul regeln, also ohne Weiterleitungsseite auf eine (versteckte) Seite mit einem "DetailViewer"-Modul.
Ich habe mir das schamlos beim EFG abgeguckt. Meine Turnierpaarliste liegt unter turnierpaarliste.html. Wenn man als URL z.B. turnierpaarliste/info/8.html verwendet, wird trotzdem die Seite "turnierpaarliste" aufgerufen, aber jetzt hat der GET-Parameter "info" den Wert 8. Sehr praktisch.
Prüft man im Modulcode auf diesen Parameter, kann man dann gegebenenfalls auf das Detail-Template wechseln und anderen Code ausführen.
Nochmal der entsprechende Abschnitt aus dem Template der /system/modules/gw_turnierpaare/templates/gw_turnierpaarliste.tpl:
PHP-Code:
</td>
<td><?php echo '<a href="/turnierpaarliste/info/'.$paar['id'].'.html">Detail</a>'; ?>
</td>
Hier wird aus der Paar-ID der Detail-Link gebaut. Mich stört noch, dass mein Seitenname "turnierpaarliste" hardkodiert ist. Das werde ich noch ändern müssen, habe gerade aber keine Idee, wie ich an diese Information komme. Außerdem wäre es schöner, statt mit der numerischen ID mit einem alphanumerischen Alias zu arbeiten, so wie bei Seiten oder Artikeln. Das werde ich noch in einem zukünftigen Schritt implementieren.
Das Template für die Detailseite /system/modules/gw_turnierpaare/templates/gw_turnierpaarliste_detail.tpl sieht so aus:
PHP-Code:
<div class="<?php echo $this->class; ?> block paarvisitenkarte"<?php echo $this->cssID; ?>
<?php if ($this->style): ?> style="<?php echo $this->style; ?>"<?php endif; ?>>
<h3><?php echo $this->paar['partnernachname']; ?>
<?php if($this->paar['partnervorname']) { echo ", ".$this->paar['partnervorname']; }; ?>
<?php if($this->paar['partnerinnachname']) { echo " und ".$this->paar['partnerinnachname']; }; ?>
<?php if($this->paar['partnerinvorname']) { echo ", ".$this->paar['partnerinvorname']; }; ?>
</h3>
<?php if ($this->paar['bild']): ?>
<div class="left">
<?php if($this->paar['bildfullsize']): ?>
<a href="<?php echo $this->paar['bildfullsize']; ?>" rel="lightbox[bild]">
<?php endif; ?>
<img src="<?php echo $this->paar['bild']; ?>">
<?php if($this->paar['bildfullsize']): ?>
</a>
<?php endif; ?>
</div>
<?php endif; ?>
<div class="right">
<ul>
<?php if ($this->paar['startklassestandard'] != '-'): ?>
<li>
<?php echo $this->paar['startgruppe']." ".$this->paar['startklassestandard']." Standard"; ?>
</li>
<?php endif; ?>
<?php if ($this->paar['startklasselatein'] != '-'): ?>
<li>
<?php echo $this->paar['startgruppe']." ".$this->paar['startklasselatein']." Latein"; ?>
</li>
<?php endif; ?>
<li>
Aktiv: <?php echo $this->paar['aktivseit']; ?> - <?php if($this->paar['aktivbis']) { echo $this->paar['aktivbis']; } else { echo "heute"; }; ?>
</li>
<?php if ($this->paar['anschrift']): ?>
<li>
<?php echo nl2br($this->paar['anschrift']); ?>
</li>
<?php endif; ?>
<?php if ($this->paar['telefon']): ?>
<li>
Tel.: <?php echo $this->paar['telefon']; ?>
</li>
<?php endif; ?>
<?php if ($this->paar['fax']): ?>
<li>
Fax: <?php echo $this->paar['fax']; ?>
</li>
<?php endif; ?>
<?php if ($this->paar['mobil']): ?>
<li>
Mob.: <?php echo $this->paar['mobil']; ?>
</li>
<?php endif; ?>
<?php if ($this->paar['email']): ?>
<li>
EMail: {{email::<?php echo $this->paar['email']; ?>}}
</li>
<?php endif; ?>
<?php if ($this->paar['homepage']): ?>
<li>
Homepage: <a href="<?php echo $this->paar['homepage']; ?>"><?php echo $this->paar['homepage']; ?></a>
</li>
<?php endif; ?>
</ul>
</div>
<div class="twocol">
<?php echo nl2br($this->paar['beschreibung']); ?>
</div>
</div>
Im Prinzip nichts besonderes, ich bastele aus den Namen eine schöne Überschrift, Bild und die anderen Texte werden in DIVs verpackt, die ich per CSS dann "hübsch" anordne. Beim Bild wird unser Modulcode in $paar['bild'] eine verkleinerte Version (180px breit max.) zurückliefern, in $paar['bildfullsize'] befindet sich das Originalbild, das hier im Template dann über eine Lightbox großklickbar ist. Das Array $paar im Template entspricht ansonsten den Datenbankfeldern in tl_gw_turnierpaare.
Die Hauptarbeit passiert nun in der Frontend-Modul-Klasse /system/modules/gw_turnierpaare/gwTurnierpaarliste.php:
PHP-Code:
class gwTurnierpaarliste extends Module
{
/**
* Template
* @var string
*/
protected $strTemplate = 'gw_turnierpaarliste';
protected $strDetailKey = 'info';
Zunächst definiere ich wie üblich den "Default"-Templatenamen, das ist der für die gesamte Liste. Zusätzlich definiere ich hier das URL-Fragment, was meinen "Detail-Link" ausmachen soll.
PHP-Code:
function obj2Arr($objPaar)
{
$newArr = array
(
'partnernachname' => trim($objPaar->partnernachname),
'partnervorname' => trim($objPaar->partnervorname),
'partnerinnachname' => trim($objPaar->partnerinnachname),
'partnerinvorname' => trim($objPaar->partnerinvorname),
'startgruppe' => $objPaar->startgruppe,
'startklasselatein' => $objPaar->startklasselatein,
'startklassestandard' => $objPaar->startklassestandard,
'aktiv' => $objPaar->aktiv,
'aktivseit' => $objPaar->aktivseit,
'aktivbis' => $objPaar->aktivbis,
'id' => $objPaar->id,
'beschreibung' => $objPaar->beschreibung,
);
if($objPaar->zeigeanschrift == '1')
{
$newArr['anschrift'] = $objPaar->anschrift;
}
if($objPaar->zeigetelefon == '1')
{
$newArr['telefon'] = $objPaar->telefon;
}
if($objPaar->zeigefax == '1')
{
$newArr['fax'] = $objPaar->fax;
}
if($objPaar->zeigemobil == '1')
{
$newArr['mobil'] = $objPaar->mobil;
}
if($objPaar->zeigeemail == '1')
{
$newArr['email'] = $objPaar->email;
}
if($objPaar->zeigehomepage == '1')
{
$newArr['homepage'] = $objPaar->homepage;
}
return $newArr;
}
Diese Utility-Funktion füllt mir eine Zeile des Resultsets der Datenbank in ein Array. Ich brauche das an zwei Stellen, darum habe ich das hierhin ausgelagert. Hier werden auch schon die "zeige"-Flags berücksichtigt: Sind die nicht angehakt, landet auch nichts im Array. Damit spare ich mir die Abfragen im Template.
PHP-Code:
protected function compile()
{
if ( strlen($this->Input->get($this->strDetailKey)) )
{
// A "detail"-URL is given -> Output turnierpaarliste_detail template
$this->compileDetailTemplate();
}
else
{
// Output the turnierpaarliste template
$this->compileListTemplate();
}
}
Die alte "Compile"-Funktion wurde angenehm kurz. Falls der GET-Parameter mit dem Namen "info" gesetzt ist (über unsere URL turnierpaarliste/info/8.html), dann wird eine Funktion zur Ausgabe des Detail-Templates aufgerufen, ansonsten eine für das Listentemplate.
PHP-Code:
// Compiles the turnierpaarliste template
protected function compileListTemplate()
{
$moduleParams = $this->Database->prepare("SELECT * FROM tl_module WHERE id=?")
->limit(1)
->execute($this->id);
$whereClause = '';
if($moduleParams->gw_tp_showonlyactive == 'A')
{
$whereClause = "WHERE aktiv='1'";
}
else
{
if($moduleParams->gw_tp_showonlyactive == 'I')
{
$whereClause = "WHERE aktiv=''";
}
}
$orderClause = 'partnernachname, partnerinnachname';
if($moduleParams->gw_tp_couplesorting == 'C')
{
// Nach Altersgruppen sortieren
$fieldstartgruppe="''";
foreach(array('KIN I','KIN II', 'JUN I', 'JUN II', 'JUG', 'HGR', 'HGR II', 'SEN I', 'SEN II', 'SEN III', 'SEN IV') as $gruppe)
{
$fieldstartgruppe .= ",'".$gruppe."'";
}
$fieldstartklasse="''";
foreach(array('-', 'E','D', 'C', 'B', 'A', 'S', 'PRO', 'LL', 'OL', 'RL', '2. BL', '1. BL') as $klasse)
{
$fieldstartklasse .= ",'".$klasse."'";
}
$orderClause = "FIELD(startgruppe,".$fieldstartgruppe."), FIELD(startklassestandard,".$fieldstartklasse."), FIELD(startklasselatein,".$fieldstartklasse."), partnernachname, partnerinnachname";
}
$arrPaare = array();
$objPaare = $this->Database->execute("SELECT * FROM tl_gw_turnierpaare " . $whereClause . "ORDER BY " . $orderClause);
while ($objPaare->next())
{
$newArr = $this->obj2Arr($objPaare);
if(strlen($objPaare->bild) == 0)
{
$newArr['bild'] = '/system/modules/gw_turnierpaare/icons/default.png';
}
else
{
$newArr['bild'] = $this->getImage($objPaare->bild, '', '48');
}
$arrPaare[] = $newArr;
}
$this->Template->paare = $arrPaare;
}
Bei der Ausgabe des Listentemplates hat sich nicht viel gegenüber dem letzten Schritt getan, nur die Utility-Funktion obj2Arr wird jetzt benutzt, um den Großteil der Felder aus der Datenbank ins Array zu packen. Als Paarbild wird ein Thumbnail von max. 48 Pixeln Höhe ausgegeben.
PHP-Code:
// Compiles the data for the turnierparliste_detail template
protected function compileDetailTemplate()
{
$coupleRow = $this->Database->prepare("SELECT * FROM tl_gw_turnierpaare WHERE id=?")
->limit(1)
->execute($this->Input->get($this->strDetailKey));
$this->strTemplate = 'gw_turnierpaarliste_detail';
$this->Template = new FrontendTemplate($this->strTemplate);
$newArr = $this->obj2Arr($coupleRow);
if(strlen($coupleRow->bild) == 0)
{
$newArr['bild'] = '/system/modules/gw_turnierpaare/icons/default.png';
}
else
{
$newArr['bild'] = $this->getImage($coupleRow->bild, '180', '');
$newArr['bildfullsize'] = $coupleRow->bild;
}
$this->Template->paar = $newArr;
}
Hier die Ausgabe des Detail-Templates: Zunächst holen wir uns das Paar aus der Datenbank, dessen Detailseite ausgegeben werden soll. Hier wäre wohl zukünftig noch eine Fehlerabfrage ("ID existiert nicht") notwendig. *Hüstel*
Dann müssen wir das andere Template wählen. Dafür ersetze ich den Namen durch den Neuen. Das alleine reichte aber nicht: Es erschien immer noch das alte Template, weil es schon initialisiert war, als compile() aufgerufen wurde.
Eine Änderung von $strTemplate bleibt dann unbemerkt. Darum muss jetzt in der nächsten Zeile ein neues Frontendtemplate instanziert werden. Ich hätte den Namen auch direkt als Parameter bei der Instanzierung angeben können. Dann fülle ich mit obj2Arr() mein Paar-Array, und gebe (falls vorhanden) das Paarbild-Thumbnail mit maximal 180 Pixel Breite aus, und zusätzlich das Originalbild.
Das Ergebnis ist das Folgende - Turnierpaarliste:
https://community.contao.org/de/atta...1&d=1269594717
Ich habe das Default-Bild noch ausgetauscht, das angezeigt wird, wenn kein Paarbild hinterlegt ist (Ich hänge es auch hier nochmal an). Das "richtige" Paarbild ist übrigens ein freies aus Wikimedia Commons.
Nach Klick auf einen Detaillink:
https://community.contao.org/de/atta...1&d=1269594718
Liste der Anhänge anzeigen (Anzahl: 6)
Schritt 11: Die zweite Tabelle
Ich habe mich lange davor gedrückt, aber jetzt muss es an meine zweite Tabelle gehen: tl_gw_meldungen.
Meldungen sind Teilnahmen von Turnierpaaren an Turnieren. Sie bestehen aus dem Datum der Turnierteilnahme, dem Ort, der Startgruppe (Altersklasse) und der Startklasse (aus Regeltechnischen Gründen müssen die NICHT zwingend mit den entsprechenden Werten, die beim Turnierpaar eingetragen sind übereinstimmen), der Tanzart (Standard/Lateinamerikanisch), der Turnierart (Hier unterscheidet man "offene Turniere", "Einladungsturniere", "Landesmeisterschaften", "Deutsche Meisterschaften" usw), der Anzahl der gestarteten Paare, dem Platz (Da es geteilte Plätze wie 5.-7. geben kann gibt es "platz_von" und "platz_bis"), und einem Freitext als Bemerkung.
Soviel zur Domain specific knowledge.
Schauen wir uns nochmal die SQL-Definition in config/database.sql an:
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` date NOT NULL default '2000-01-01',
`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;
Die ersten 4 Felder sind ja so "default". Hier macht pid (Parent-ID) aber auch Sinn. Die Meldungen sind "Childs" der Turnierpaare. Im Backend wollte ich auch so vorgehen, also bei den Turnierpaaren auf einen Tree-View umstellen. Im DCA-Record für tl_gw_turnierpaare habe ich also unter "sort" den "mode" auf 5 gestellt, und im DCA-Record für tl_gw_meldungen auf 6.
Dann straft mich TYPOlight aber mit der SQL-Fehlermeldung, dass es in der Tabelle tl_gw_turnierpaare kein Feld "pid" gäbe. Richtig, gibt es auch nicht. Da das die Parent-Tabelle ist, braucht die auch kein "pid". Nun gut, offensichtlich erfordert die Logik das so, also erfülle ich den Wunsch und spendiere auch tl_gw_turnierpaare ein Feld "pid".
Beim Betrachten im Backend fällt mir aber auf, dass das unpraktisch ist: Der Sportwart, der das im Backend pflegt, den interessiert nur die nach dem Turnierdatum sortierte Liste, neueste ganz oben. Der will gar nicht erst das Turnierpaar suchen, um dort eine Meldung einzugeben.
Ich entscheide mich, Turnierpaare und Meldungen im Backend getrennt zu verwalten, aber bei den Meldungen dann eine foreign-key-Beziehung über die pid zur Turnierpaartabelle zu haben. Wenn der Sportwart eine neue Meldung anlegt, soll er über ein Dropdown das Turnierpaar auswählen, zu dem diese Meldung gehört, die Liste selbst soll aber nach dem Datum absteigend sortiert sein.
Ich brauche also eine weitere Seite im Backend, und modifiziere /system/modules/gw_turnierpaare/config/config.php auf:
PHP-Code:
// Back end module
$GLOBALS['BE_MOD']['content']['gw_turnierpaare'] = array
(
'tables' => array('tl_gw_turnierpaare'),
'icon' => 'system/modules/gw_turnierpaare/icons/turnierpaare.png'
);
$GLOBALS['BE_MOD']['content']['gw_meldungen'] = array
(
'tables' => array('tl_gw_meldungen'),
'icon' => 'system/modules/gw_turnierpaare/icons/meldeliste.png'
);
Also zwei hier formal getrennte Tabellen mit getrennten Backendseiten und verschiedenen Icons (meldeliste.png hänge ich hier an, das soll ein Siegerpodest darstellen).
Dann gehts an DCA für die Meldungstabelle, in /system/modules/gw_turnierpaare/dca/tl_gw_meldungen.php:
PHP-Code:
/**
* Table tl_gw_meldungen
*/
$GLOBALS['TL_DCA']['tl_gw_meldungen'] = array
(
// Config
'config' => array
(
'dataContainer' => 'Table',
'enableVersioning' => true,
),
Hier keine Besonderheiten, es ist eine Tabelle mit Versionierung...
PHP-Code:
// List
'list' => array
(
'sorting' => array
(
'mode' => 1,
'fields' => array('datum DESC', 'turnierort'),
'panelLayout' => '',
'flag' => 8,
),
Sortierung nach festem Feld, nämlich dem absteigenden Datum, und dann nach dem Turnierort alphabetisch. Das Panel für Sortieren, Filtern usw lasse ich erstmal weg (Das sorgte nämlich für eine kryptische Fehlermeldung - ich kümmere mich später darum). flag 8 ist das absteigende Sortieren nach dem Monat. Zu meinem Problem damit komme ich später.
PHP-Code:
'label' => array
(
'fields' => array('datum', 'turnierort', 'turnierart', 'startgruppe','startklasse','lat_std','pid'),
'format' => '<span style="font-weight: bold;">%s</span>, %s (%s), %s %s %s - %s'
),
Hier bastele ich mir die Ausgabezeile, mit dem Datum in fett, dann dem Ort, der Turnierart, Startgruppe und Klasse und der Info, ob Standard oder Lateinamerikanisch. Dann müsste dort eigentlich noch der Name des Tanzpaares hin, aber der steht ja nicht (direkt) in der Tabelle, sondern nur die pid. Darum nehme ich erstmal die - besser als Nix. Ich vermute, ich muss/kann da mit einem Label-Callback arbeiten und die pid selbst in die Namen auflösen.
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'
)
)
),
Hier bleibt erstmal alles so, wie vom Extension Creator vorgegeben.
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
(
'' => ''
),
Auch hier nichts spannendes: Eine normale Palette für alle Felder.
PHP-Code:
// Fields
'fields' => array
(
'pid' => array
(
'label' => &$GLOBALS['TL_LANG']['tl_gw_meldungen']['pid'],
'inputType' => 'select',
'foreignKey' => 'tl_gw_turnierpaare.partnernachname',
'search' => true,
'sorting' => true,
'eval' => array('mandatory'=>true)
),
pid soll ein Foreign Key in die Turnierpaare-Tabelle sein. mir der foreignKey-Option wird das Dropdown-Feld mit den Partnernachnamen aus der Turnierpaar-Tabelle gefüllt. Zum ersten Testen ist das ganz OK, aber eigentlich stelle ich mir das anders vor: Es sollen dort nur AKTIVE Paare auswählbar sein, und ich hätte dort gerne die kompletten Namen des Turnierpaares stehen, nicht nur den Nachnamen des Herrn. Auch da wird wohl ein Callback hermüssen - später.
PHP-Code:
'datum' => array
(
'label' => &$GLOBALS['TL_LANG']['tl_gw_meldungen']['datum'],
'inputType' => 'text',
'search' => true,
'sorting' => true,
'flag' => 11,
'eval' => array('mandatory'=>true, 'datepicker'=>$this->getDatePickerString(), 'tl_class'=>'w50 wizard', 'minlength' => 1, 'maxlength'=>64, 'rgxp' => 'date')
),
Abgeguckt beim DCA-Record für Artikel: Das Datumsfeld. Insbesondere den getDatePickerString() verstehe ich nicht - muss ich aber auch erstmal nicht. Kommt Zeit, kommt Erleuchtung.
PHP-Code:
'turnierort' => array
(
'label' => &$GLOBALS['TL_LANG']['tl_gw_meldungen']['turnierort'],
'inputType' => 'text',
'search' => true,
'sorting' => true,
'flag' => 1,
'eval' => array('mandatory'=>true, 'minlength' => 1, 'maxlength'=>128, 'tl_class' => 'w50')
),
Nichts Besonderes hier...
PHP-Code:
'turnierart' => array
(
'label' => &$GLOBALS['TL_LANG']['tl_gw_meldungen']['turnierart'],
'inputType' => 'select',
'sorting' => false,
'options' => gwTurnierpaarliste::$TurnierArten,
'eval' => array('mandatory'=>false, 'includeBlankOption' => true, 'tl_class' => 'w50')
),
Ähnlich wie bei den Startgruppen und Klassen lagere ich die Optionen für "Turnierart" in meine Frontendklasse aus. Gefällt mir besser so, und ich kann es "Re-usen".
PHP-Code:
'startgruppe' => array
(
'label' => &$GLOBALS['TL_LANG']['tl_gw_meldungen']['startgruppe'],
'inputType' => 'select',
'sorting' => true,
'flag' => 1,
'options' => gwTurnierpaarliste::$StartGruppen,
'eval' => array('mandatory'=>false, 'includeBlankOption' => true, 'tl_class' => 'w50')
),
'startklasse' => array
(
'label' => &$GLOBALS['TL_LANG']['tl_gw_meldungen']['startklasse'],
'inputType' => 'select',
'sorting' => false,
'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')
),
Wie zuvor, normale Dropdownfelder, deren Optionen ich in der Frontendklasse gwTurnierpaarliste ablege, um sie von verschiedenen Bereichen aus nutzen zu können.
PHP-Code:
'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)
),
)
);
Auch hier eigentlich "Hausmannskost": 3 Felder für Zahlen und ein Textfeld für die Bemerkung.
/system/modules/gw_turnierpaare/gwTurnierpaarliste.php (das Frontend-Modul) erweitere ich noch um die Optionenlisten für Tanzart und Turnierart:
PHP-Code:
public static $TurnierArten = array('-', 'OT','ET', 'LM', 'DM', 'EM', 'WM');
public static $TanzArten = array('-', 'Std','Lat');
Zum ersten Testen füge ich zwei Testdatensätze in die Meldungstabelle ein. Ergebnis:
https://community.contao.org/de/atta...1&d=1271095947
Gut, die Tabellenheader sehen noch nicht so aus wie gewünscht, aber das ist Feinschliff. Die gewünschten Informationen (mit pid 7) werden angezeigt.
Wenn ich einen neuen Eintrag hinzufüge, sieht das so aus:
https://community.contao.org/de/atta...1&d=1271096091
Durch das Fehlen der Language-Einträge natürlich noch sehr unschön, aber alle Felder sind da. Die Foreign-Key-Beziehung in die Turnierpaar-Tabelle klappt (rudimentär) auch.
Mich wundert der "20.12.2000" als Default im Datumsfeld, aber ich habe auch keinen Default vorgegeben. Es wäre wohl praktisch, durch einen Load-Callback das aktuelle Datum dort als Default vorzugeben.
Ich gebe also mal Daten ein:
https://community.contao.org/de/atta...1&d=1271096301
Und drücke auf Speichern- was sehe ich beim Datum:
https://community.contao.org/de/atta...1&d=1271096393
30.11.1999? Das habe ich im Datepicker aber nicht ausgewählt....auch eine manuelle Eingabe sorgt für das selbe 1999er-Ergebnis.
Und in der Übersichtstabelle dann das:
https://community.contao.org/de/atta...1&d=1271163730
0000-00-00...hm.
Das ist alles nicht so ermutigend. Ich habe die Konfiguration für das Datumsfeld bei tl_article.php abgeschaut, da funktioniert ja auch alles.
Erstmal Pause, demnächst geht es weiter.
Stefan
Liste der Anhänge anzeigen (Anzahl: 1)
Ja, ich vermute sehr stark, dass das das Problem ist. Habe es einfach intuitiv (falsch) gemacht. Leider fehlte mir die Zeit, vorher "im Core" abzugucken. Eins habe ich sowieso schon gelernt: Das Einzige, auf was man sich in Sachen "TL-Entwickler-Doku" verlassen kann, ist der Core-Source ;-).
EDIT: habe es schnell geändert und das Feld "datum" auf varchar(10) umgestellt. Jetzt klappt der Datepicker. Aber jetzt wird das Feld mit einem Unix-Timestamp gefüllt. Gebe ich das Feld also in meinen Tabellenzeilen aus oder als Sortierheader, steht dort nur Zahlenwust statt eines Datums. Ich wusste schon, warum ich lieber "date" haben wollte ;-). Ich kriege graue Haare. Da muss ich mir also definitiv noch was einfallen lassen...
https://community.contao.org/de/atta...1&d=1271100757
Stefan
Herzlichen Dank und Hilfe
Zuerst einmal herzichen Dank für dieses hervorragende Tutorial!!! So etwas habe ich mir schon sehr lange gewünscht.
Ich versuche nun, nach Deiner Vorlage eine Verwaltung für unseren Vereinsbekleidungs-Verleih zu programmieren. Ich habe den Extension-Creator, die Datenbank-Einrichtung und die DCA-Programmierung fürs Erste recht gut hinbekommen und alle kleinen Fehler ziemlich schnell lokalisieren und beheben können. Ich arbeite auf einer lokalen Installation und habe die Fehlermeldung eingeschaltet.
Wenn ich mich richtig erinnere, so kommt seitdem ich das language file "modules.php" angefasst habe, folgende Fehlermeldung:
Zitat:
Warning: Cannot modify header information - headers already sent by (output started at mein_pfad\system\modules\to_bekleidungsverleih\languages\de \modules.php:1) in mein_pfad\system\libraries\Template.php on line 174
#0 [internal function]: __error(2, 'Cannot modify h...', 'mein_pfad...', 174, Array)
#1 mein_pfad\system\libraries\Template.php(174): header('Content-Type: t...')
#2 mein_pfad\system\modules\backend\BackendTemplate.php(135): Template->output()
#3 mein_pfad\typolight\main.php(286): BackendTemplate->output()
#4 mein_pfad\typolight\main.php(102): Main->output()
#5 mein_pfad\typolight\main.php(295): Main->run()
#6 {main}
Leider bin ich nun mit meinem Latein am Ende... Weiß jemand Rat?
Liste der Anhänge anzeigen (Anzahl: 1)
Wenn ich die /languages/de/modules wie folgt ändere:
PHP-Code:
/**
* Back end modules
*/
$GLOBALS['TL_LANG']['MOD']['to_bekleidungsverleih'] ='Bekleidungsverleih';
$GLOBALS['TL_LANG']['MOD']['to_magazin'] = array('Magazin', '');
$GLOBALS['TL_LANG']['MOD']['to_verleih'] = array('Verleih', '');
verschwinden zwar die zwei Fehler der Startseite und das Modul wird in der Mittelspalte angezeigt:
Zitat:
Warning: Illegal offset type in mein_pfad\typolight\main.php on line 183
#0 mein_pfad\typolight\main.php(183): __error(2, 'Illegal offset ...', 'mein_pfad...', 183, Array)
#1 mein_pfad\typolight\main.php(93): Main->welcomeScreen()
#2 mein_pfad\typolight\main.php(295): Main->run()
#3 {main}
Der Ursprungsfehler bleibt aber:
Zitat:
Warning: Cannot modify header information - headers already sent by (output started at mein_pfad\system\modules\to_bekleidungsverleih\lan guages\de \modules.php:1) in mein_pfad\system\libraries\Template.php on line 174
#0 [internal function]: __error(2, 'Cannot modify h...', 'mein_pfad...', 174, Array)
#1 mein_pfad\system\libraries\Template.php(174): header('Content-Type: t...')
#2 mein_pfad\system\modules\backend\BackendTemplate.p hp(135): Template->output()
#3 mein_pfad\typolight\main.php(286): BackendTemplate->output()
#4 mein_pfad\typolight\main.php(102): Main->output()
#5 mein_pfad\typolight\main.php(295): Main->run()
#6 {main}
??? Ratlosigkeit - Bin schon gespannt, wie's weitergeht...:rolleyes: