TEE-04 Backend DCA

Aus Contao Community Documentation

MsgError.png Unvollständiger Artikel: dieser Artikel ist noch nicht sauber bearbeitet.

Bitte erweitere ihn und entferne erst anschliessend diesen Hinweis.

Tagebuch einer Extension-Entwicklung

betrifft
TYPOlight Version ab TL 2.8
Extensions Extension Creator


Wir wagen uns in das DCA-Land

Jetzt kommt es zu einem (vermutlich) harten Brocken. Mein Backend-Modul wird links in der Navigation des Backends (in der Sidebar) angezeigt, aber man kann noch keine Datensätze anlegen oder verändern. Dafür müssen wir einen passenden DCA-Record anlegen. Ich werde mich wieder vom CD-Collection-Tutorial und der Referenz zu den DCA-Records leiten lassen. Die Referenz ist schon mal erschlagend-beeindruckend.

Mithilfe der DCA-Records erstellt TYPOlight die Masken, mit denen man im Backend die Tabellen füllen, verändern und löschen kann. In /system/modules/gw_turnierpaare/dca/tl_gw_turnierpaare.php hat der Extension-Generator freundlicherweise schon ein Skelett für einen DCA-Record für die Tabelle tl_gw_turnierpaare angelegt.

PHP-Code:

$GLOBALS['TL_DCA']['tl_gw_turnierpaare'] = array 
( 
    // Config 
    'config' => array 
    ( 
        'dataContainer'               => 'Table', 
        'enableVersioning'            => true 
    ), 
...

Der zweite Array-Key in $GLOBALS ist der Name unserer Tabelle. Im darauffolgenden mehrfach verschachtelten Array gibt es zunächst die config-Sektion. Hier wird zunächst festgehalten, dass es sich bei der Datenquelle um eine Tabelle handelt. Laut Referenz sind auch noch File und Folder vorgesehen. Sicherlich sind Tabellen der am häufigsten gebrauchte Datacontainer. enableVersioning erlaubt die Versionierung der Einträge - das ist OK und passt mir ins Konzept. Die Referenz verrät mir, daß ich eine "child Table" angeben kann. Da die Turniermeldungen Childs der Turnierpaare werden soll, ergänze ich also

PHP-Code:

        'ctable'                      => 'tl_gw_meldungen'

Die verbleibenden Optionen in config erscheinen mir nicht weiter von Bedeutung. Weiter geht es mit dem Abschnitt list, und dort mit sorting:

PHP-Code:

    // List 
    'list' => array 
    ( 
        'sorting' => array 
        ( 
            'mode'                    => 1, 
            'fields'                  => array(''), 
            'flag'                    => 1 
        ),

Sortierart und Sortierreihenfolge sind für mich erst mal ok, da ich gerne nach Nachnamen von Herrn und Dame sortieren würde, verändere ich die Zeile mit fields auf:

PHP-Code:

            'fields'                  => array('partnernachname','partnerinnachname'),

Als nächstes kommt ein Block label:

PHP-Code:

        'label' => array 
        ( 
            'fields'                  => array(''), 
            'format'                  => '%s' 
        ),

Hier scheint es wohl darum zu gehen, was in der Liste der schon bestehenden Tabelleneinträge angezeigt wird. Ich verändere die Zeilen auf

PHP-Code:

            'fields'                  => array('partnernachname','partnervorname','partnerinnachname','partnerinvorname','startgruppe','startklassestd','startklasselat'), 
            'format'                  => '%s, %s und %s, %s - %s %s LAT / %s STD'

Etwas "domain-specific knowledge": Startgruppe ist im Prinzip die Altersklasse, Startklasse ist die Leistungsklasse (Die "Liga"), in der das Paar tanzt, und zwar unterschieden nach lateinamerikanischen und Standardtänzen. Diese Infos sind für den Sportwart interessant und sollten in der Übersichtsliste vorhanden sein. Die "%s" im format-String werden in der Reihenfolge mit Feldinhalten befüllt, wie wir sie oben drüber im Array angegeben haben. Der Aufbau des format-Strings sollte PHP- (oder C-)Programmierern bekannt sein. Dann kommt ein Abschnitt global_operations und operations, den ich aber gar nicht verändern will:

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_turnierpaare']['edit'], 
                'href'                => 'act=edit', 
                'icon'                => 'edit.gif' 
            ), 
            'copy' => array 
            ( 
                'label'               => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['copy'], 
                'href'                => 'act=copy', 
                'icon'                => 'copy.gif' 
            ), 
            'delete' => array 
            ( 
                'label'               => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['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_turnierpaare']['show'], 
                'href'                => 'act=show', 
                'icon'                => 'show.gif' 
            ) 
        ) 
    ),

Laut Referenz sollte global_operations ein Unterpunkt von operations sein, im Skelett-File des Extension-Generators stehen sie aber auf gleicher Ebene. Ich bin etwas verwundert, aber wird schon funktionieren. Nächster Abschnitt im vorgegeben File sind palettes und subpalettes. Leider stehen die nicht in der Referenz, und auch die Seite über palettes macht mich nicht so richtig schlauer.

PHP-Code:

    // Palettes 
    'palettes' => array 
    ( 
        '__selector__'                => array(''), 
        'default'                     => '' 
    ), 
    // Subpalettes 
    'subpalettes' => array 
    ( 
        ''                            => '' 
    ),

Ein Blick ins CD-Collection-Tutorial verrät, dass man unter default die Felder angeben kann, die in Paletten sortiert werden sollen: Felder innerhalb der Palette mit Komma getrennt, Beginn einer neuen Palette durch ein Semikolon. Da ich es zeitlich für diesen Post nicht schaffen werde, alle Felder meiner tl_gw_turnierpaare-Tabelle zu definieren, will ich zunächst nur die Namensfelder definieren, und zum Testen 2 Paletten benutzen. Ich editiere den palettes-Eintrag also in

PHP-Code:

    // Palettes 
    'palettes' => array 
    ( 
        '__selector__'                => array(''), 
        'default'                     => 'partnernachname,partnervorname;partnerinnachname,partnerinvorname' 
    ),

Subpalettes lässt mich weiterhin ratlos, also Finger weg davon. Der letzte Teil der Skelett-Datei (und hier wird es richtig spannend!) ist das fields-Array:

PHP-Code:

    // Fields 
    'fields' => array 
    ( 
        '' => array 
        ( 
            'label'                   => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare'][''], 
            'exclude'                 => true, 
            'inputType'               => 'text', 
            'eval'                    => array('mandatory'=>true, 'maxlength'=>255
) 
        ) 
    ) 
);

Ich halte mich erst mal an das Skelett, und füge nur die Feldnamen hinzu, und vervielfältige den Block auf insgesamt 4 Stück:

PHP-Code:

    // Fields 
    'fields' => array 
    ( 
        'partnernachname' => array 
        ( 
            'label'                   => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['partnernachname'], 
            'exclude'                 => false, 
            'inputType'               => 'text', 
            'eval'                    => array('mandatory'=>true, 'maxlength'=>64) 
        ), 
        'partnervorname' => array 
        ( 
            'label'                   => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['partnervorname'], 
            'exclude'                 => false, 
            'inputType'               => 'text', 
            'eval'                    => array('mandatory'=>false, 'maxlength'=>64
) 
        ), 
        'partnerinnachname' => array 
        ( 
            'label'                   => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['partnerinnachname'], 
            'exclude'                 => false, 
            'inputType'               => 'text', 
            'eval'                    => array('mandatory'=>false, 'maxlength'=>64
) 
        ), 
        'partnerinvorname' => array 
        ( 
            'label'                   => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['partnerinvorname'], 
            'exclude'                 => false, 
            'inputType'               => 'text', 
            'eval'                    => array('mandatory'=>false, 'maxlength'=>64
) 
        ) 
    )

Die Labels müssen wir später noch in den Sprachfiles definieren, exclude = true bedeutet, dass nur Admins das Feld sehen können. Da später ein Nicht-Admin die Tabelle pflegen können soll, setze ich es also überall auf false. Ich hoffe mein Gedankengang ist da richtig. Wir setzen für jedes Feld die Maximallänge auf 64 Zeichen, und nur der Partner-Nachname ist verpflichtend. Warum nicht auch Vorname und der Namen der Partnerin? Ich brauche für meine Anwendung EINE klitzekleine Ausnahme, in der ich gerne eine Mannschaft in die Startliste eintragen würde. Deren Name würde dann in partnernachname stehen, die restlichen Felder wären leer.

Für den nächsten Post wird das alles noch verfeinert, weitere Optionen für die Felder hinzugefügt und vor allem alle Felder der Tabelle im DCA-Record definiert. Aber erst mal ein kleines, bescheidenes Zwischenergebnis zur Motivation:

Eingabemaske

Und man kann auch schon was eingeben:

Datensatz

Da Startgruppe und Klasse(n) noch nicht einzugeben sind, bleiben die in der Übersichtsliste noch leer. Aber: Grundlegend funktioniert das schonmal, und auch den Begriff palette habe ich jetzt (anhand des Screenshots) verstanden.

Im nächsten Post wird das alles erweitert und "poliert". Zur Übersicht nochmal mein aktueller Stand der Datei /system/modules/gw_turnierpaare/dca/tl_gw_turnierpaare.php:

PHP-Code:

/** 
 * Table tl_gw_turnierpaare  
 */ 
$GLOBALS['TL_DCA']['tl_gw_turnierpaare'] = array 
( 
    // Config 
    'config' => array 
    ( 
        'dataContainer'               => 'Table', 
        'enableVersioning'            => true, 
        'ctable'                      => 'tl_gw_meldungen' 
    ), 
    // List 
    'list' => array 
    ( 
        'sorting' => array 
        ( 
            'mode'                    => 1, 
            'fields'                  => array('partnernachname','partnerinnachname'), 
            'flag'                    => 1 
        ), 
        'label' => array 
        ( 
            'fields'                  => array('partnernachname','partnervorname','partnerinnachname','partnerinvorname','startgruppe','startklassestd','startklasselat'), 
            'format'                  => '%s, %s und %s, %s - %s %s LAT / %s STD' 
        ), 
        '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_turnierpaare']['edit'], 
                'href'                => 'act=edit', 
                'icon'                => 'edit.gif' 
            ), 
            'copy' => array 
            ( 
                'label'               => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['copy'], 
                'href'                => 'act=copy', 
                'icon'                => 'copy.gif' 
            ), 
            'delete' => array 
            ( 
                'label'               => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['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_turnierpaare']['show'], 
                'href'                => 'act=show', 
                'icon'                => 'show.gif' 
            ) 
        ) 
    ), 
    // Palettes 
    'palettes' => array 
    ( 
        '__selector__'                => array(''), 
        'default'                     => 'partnernachname,partnervorname;partnerinnachname,partnerinvorname' 
    ), 
    // Subpalettes 
    'subpalettes' => array 
    ( 
        ''                            => '' 
    ), 
    // Fields 
    'fields' => array 
    ( 
        'partnernachname' => array 
        ( 
            'label'                   => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['partnernachname'], 
            'exclude'                 => false, 
            'inputType'               => 'text', 
            'eval'                    => array('mandatory'=>true, 'maxlength'=>64) 
        ), 
        'partnervorname' => array 
        ( 
            'label'                   => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['partnervorname'], 
            'exclude'                 => false, 
            'inputType'               => 'text', 
            'eval'                    => array('mandatory'=>false, 'maxlength'=>64
) 
        ), 
        'partnerinnachname' => array 
        ( 
            'label'                   => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['partnerinnachname'], 
            'exclude'                 => false, 
            'inputType'               => 'text', 
            'eval'                    => array('mandatory'=>false, 'maxlength'=>64
) 
        ), 
        'partnerinvorname' => array 
        ( 
            'label'                   => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['partnerinvorname'], 
            'exclude'                 => false, 
            'inputType'               => 'text', 
            'eval'                    => array('mandatory'=>false, 'maxlength'=>64
) 
        ) 
    ) 
);

Verwirrung im DCA-Land

Nachdem ich einige Testpaare in meine "Minimalmaske" eingetragen habe, stelle ich fest, dass es nicht ganz so aussieht, wie ich es gerne hätte.

Mehrere Datensätze

Für jeden Herrennachnamen gibt es eine eigene Gruppenüberschrift. Das ist irgendwie suboptimal, und verschwendet Platz. Ich hätte gerne keine Gruppenüberschriften, oder nur "A", "B", "C", usw...Ich vermute, dass das mit dem Eintrag ['list']['sorting']['flag'] zusammenhängt, den ich auf "1" hatte, laut Referenz Sort by initial letter ascending:

PHP-Code:

    // List 
    'list' => array 
    ( 
        'sorting' => array 
        ( 
            'mode'                    => 1, 
            'fields'                  => array('partnernachname', 'partnervorname', 'partnerinnachname', 'partnerinvorname'), 
            'flag'                    => 1 
        ),

Ich spiele also etwas mit flag herum, und stelle fest: Irgendwie beeinflusst das garnix.

  • 2 = Sort by initial letter descending,
  • 3 = Sort by initial two letters ascending,
  • 4 = Sort by initial two letters descending,
  • 11 = Sort ascending oder
  • 12 = Sort descending

machen in meiner Auflistung nirgendwo irgendeinen Unterschied. Immer wird stur ascending in der Reihenfolge meiner Sortierfelder sortiert, und für jeden "Unique" Herrennachnamen gibt es eine Gruppenüberschrift.

In meiner Verzweifelung setze ich ['list']['sorting']['mode'] auf 0, laut Referenz Records are not sorted. Das Ergebnis sieht so aus:

Datensätze unsortiert

Immerhin sind die Gruppenüberschriften weg...und stur ascending nach meinen Sortierfeldern sortiert. Fast schon unnötig zu erwähnen, dass flag auch hier keine Wirkung zu haben scheint.

Um das Sortieren vielleicht "von Hand" steuern zu können, füge ich streng nach Referenz die Zeile

PHP-Code:

            'panelLayout'             => 'search,sort,filter'

hinzu in der Erwartung, dass mir dann in der Übersicht die entsprechenden Optionen angeboten werden. Leider - nichts. Die Übersichtsliste der Turnierpaare verändert sich überhaupt nicht.

Meine ['list']['sorting']-Sektion sieht nun so aus:

PHP-Code:

        'sorting' => array 
        ( 
            'mode'                    => 0, 
            'fields'                  => array('partnernachname', 'partnervorname', 'partnerinnachname', 'partnerinvorname'), 
            'flag'                    => 1, 
            'panelLayout'             => 'search,sort,filter' 
        ),

Jemand eine Ahnung, warum das Verhalten so ist, bzw. warum mich die Referenz für die DCA-Records so im Stich lässt? Danke...

Leichte Entwirrung im DCA-Land

Lösung gefunden! Bei den einzelnen field-Beschreibungen muss noch die Freigabe zum Sortieren, Filtern und Suchen gegeben werden. Das sieht jetzt so aus:

PHP-Code:

    // Fields 
    'fields' => array 
    ( 
        'partnernachname' => array 
        ( 
            'label'                   => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['partnernachname'], 
            'exclude'                 => false, 
            'inputType'               => 'text', 
            'search'                  => true, 
            'sorting'                 => true, 
            'filter'                  => true, 
            'flag'                    => 1, 
            'eval'                    => array('mandatory'=>true, 'maxlength'=>64) 
        ), 
        'partnervorname' => array 
        ( 
            'label'                   => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['partnervorname'], 
            'exclude'                 => false, 
            'inputType'               => 'text', 
            'eval'                    => array('mandatory'=>false, 'maxlength'=>64
) 
        ), 
        'partnerinnachname' => array 
        ( 
            'label'                   => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['partnerinnachname'], 
            'exclude'                 => false, 
            'inputType'               => 'text', 
            'search'                  => true, 
            'sorting'                 => true, 
            'filter'                  => true, 
            'flag'                    => 1, 
            'eval'                    => array('mandatory'=>false, 'maxlength'=>64
) 
        ), 
        'partnerinvorname' => array 
        ( 
            'label'                   => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['partnerinvorname'], 
            'exclude'                 => false, 
            'inputType'               => 'text', 
            'eval'                    => array('mandatory'=>false, 'maxlength'=>64
) 
        ) 
    )

Mit Ergebnis:

Datensätze sortiert

Schon besser, auch wenn die Dropdown-Liste hinter Suchen: noch leer ist. Vielleicht liegt das an den noch fehlenden Feld-Labels in den Sprachdateien. Nur warum man flag bei den einzelnen Fields und nochmal global angeben muss, das will ich noch nicht verstehen...

Ergänzung: Und wenn ich ['sorting']['mode'] auf 2 setze, dann kann ich sogar mein Sortierfeld auswählen....sehr schön...

DCA-Polishing

Nachdem also die leichten Verwirrungen rund um den DCA-Record beseitigt sind, geht es weiter damit, die Backend-"Maske" für die tl_gw_turnierpaare-Tabelle zu definieren und zu "polieren".

Zu jedem Feld lege ich einen Verweis auf den Erklärungs-Text an, der unter dem Eingabefeld angezeigt wird, z.B. für das partnernachname-Feld im Abschnitt ['fields']['partnernachname']:

PHP-Code:

            'explanation'             => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['partnernachname_explanation'],

Der entsprechende Text muss in den Sprachfiles natürlich noch eingetragen werden - später.

Außerdem ergänze ich den eval-Wert meiner bisherigen 4 Eingabefelder um den Wert 'minlength' => 1, um bei den Namen eine Mindestlänge zu erzwingen (beim Wert 1 wahrscheinlich überflüssig, aber egal).

Bei dem Nachnamen des Partners und der Partnerin ergänze ich außerdem 'tl_class' => 'w50'. Das sorgt dafür, dass zwei Felder nebeneinander dargestellt werden. Das Feld mit der w50-Klasse links, das darauffolgende rechts. Dadurch werden Nachname und Vorname jeder Person nebeneinander in einer Zeile dargestellt.

Meine Einstellungen für das partnernachname-Feld sehen jetzt so aus:

PHP-Code:

    // Fields 
    'fields' => array 
    ( 
        'partnernachname' => array 
        ( 
            'label'                   => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['partnernachname'], 
            'explanation'             => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['partnernachname_explanation'], 
            'exclude'                 => false, 
            'inputType'               => 'text', 
            'search'                  => true, 
            'sorting'                 => true, 
            'filter'                  => true, 
            'flag'                    => 1, 
            'eval'                    => array('mandatory'=>true, 'minlength' => 1, 'maxlength'=>64, 'tl_class' => 'w50') 
        ),

Wo ich gerade noch optisch etwas aufräume, baue ich den Eintrag default unter palettes so um:

PHP-Code:

    // Palettes 
    'palettes' => array 
    ( 
        '__selector__'                => array(''), 
        'default'                     => '{name_legend},partnernachname,partnervorname,partnerinnachname,partnerinvorname;' 
    ),

{name_legend} legt die Überschrift für die "Palette" fest (also die Felder bis zum nächsten Semikolon). Der Wert muss später im Sprachenfile definiert werden. Ich habe nun alle 4 Textfelder in einer Palette. Optisches Ergebnis:

Eingabemaske

Wenn man sich "vernünftige" Überschriften aus dem Sprachfile dazu vorstellt, schon mal ganz OK :-).

Nun geht es um die noch fehlenden Tabellenfelder.

Zunächst kommt startgruppe, das bezeichnet die Altersklasse des Paars. Das Feld soll mandatory sein, aber auch eine "leere Option" erlauben. Ich will als Ausnahme auch eine Mannschaft in die Paarliste eingeben können, und Mannschaften haben keine Altersklasse. Mein Code für das field sieht so aus:

PHP-Code:

        'startgruppe' => array 
        ( 
            'label'                   => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['startgruppe'], 
            'explanation'             => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['startgruppe_explanation'],
            'exclude'                 => false, 
            'inputType'               => 'select', 
            'options'                 => array('KIN I','KIN II', 'JUN I', 'JUN II', 'JUG', 'HGR', 'HGR II', 'SEN I', 'SEN II', 'SEN III', 'SEN IV'), 
            'eval'                    => array('mandatory'=>true, 'includeBlankOption' => true) 
        ),

Ich wähle also ein select, also eine Dropdown-Box. In options liste ich die möglichen Altersgruppen auf, im eval-Bereich gebe ich noch an, dass eine leere Option hinzugefügt werden soll.

Dann kommen startklasselatein und startklassestandard. Inhaltlich kann in beiden Feldern dasselbe drinstehen, darum ist es fast nur Copy&Paste für das zweite Feld. Die Definition sieht so aus:

PHP-Code:

        'startklasselatein' => array 
        ( 
            'label'                   => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['startklasselatein'], 
            'explanation'             => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['startklasselatein_explanation'], 
            'exclude'                 => false, 
            'inputType'               => 'select', 
            'options'                 => array('E','D', 'C', 'B', 'A', 'S', 'PRO', 'LL', 'OL', 'RL', '2. BL', '1. BL'), 
            'eval'                    => array('mandatory'=>true, 'includeBlankOption' => true, 'tl_class' => 'w50') 
        ),

Auch hier wieder eine Dropdown-Box mit Optionen und Möglichkeit der "leeren Option". Durch tl_class => w50 wird die Dropdown-Box nach links gerückt, so dass rechts daneben noch die gleichartige Box für startklassestandard passt. Die hat natürlich KEIN tl_class => w50!

Eigentlich müsste ich prüfen, dass entweder in startklasselatein oder startklassestandard ein Wert ausgewählt ist (also nicht in beiden Feldern die leere Option gewählt wurde), aber das bürde ich zunächst mal dem User auf, vielleicht ergänze ich hier später eine Validation durch einen Hook.

Zur Motivation will ich meine drei neuen Felder auch in im backend sehen, dazu muss ich sie zur Liste der Paletten hinzufügen. Ich packe sie in eine eigene Palette mit Überschrift.

PHP-Code:

    // Palettes 
    'palettes' => array 
    ( 
        '__selector__'                => array(''), 
        'default'                     => '{name_legend},partnernachname,partnervorname,partnerinnachname,partnerinvorname;
{classes_legend},startgruppe,startklasselatein,startklassestandard' 
    ),

Ergebnis:

Erweiterte Eingabemaske

Weiter geht's, jetzt folgen die Felder aktiv, aktivseit und aktivbis.

PHP-Code:

        'aktiv' => array 
        ( 
            'label'                   => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['aktiv'], 
            'explanation'             => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['aktiv_explanation'], 
            'exclude'                 => false, 
            'inputType'               => 'checkbox', 
            'eval'                    => array('mandatory'=>true, 'isBoolean' => true) 
        ),

Aktiv wird eine Checkbox. Ich weiß zwar nicht, was es für eine Bedeutung hat, aber da eine CheckBox immer "Boolean" ist, setze ich in eval isBoolean => true.

PHP-Code:

        'aktivseit' => array 
        ( 
            'label'                   => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['aktivseit'], 
            'explanation'             => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['aktivseit_explanation'], 
            'exclude'                 => false, 
            'inputType'               => 'text', 
            'eval'                    => array('mandatory'=>false, 'minlength' => 4, 'maxlength' => 4, 'rgxp' => 'digit', 'tl_class' => 'w50') 
        ), 
        'aktivbis' => array 
        ( 
            'label'                   => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['aktivbis'], 
            'explanation'             => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['aktivbis_explanation'],
            'exclude'                 => false, 
            'inputType'               => 'text', 
            'eval'                    => array('mandatory'=>false, 'minlength' => 4, 'maxlength' => 4, 'rgxp' => 'digit') 
        ),

aktivseit und aktivbis sollen nur eine Jahreszahl enthalten. Darum setze ich Minimal- und Maximallänge auf 4 und lasse durch rgxp nur Zahlen zu. Man hätte auch eine Dropdown-Box mit Jahreszahlen drin nehmen können. Ich denke beides hat Vor- und Nachteile. Sich durch DropDown-Boxen zu scrollen, die bei "1900" anfangen, wenn man nach "2004" will, ist auch kein Vergnügen. Das erste Feld setze ich mit tl_class => w50 nach links, um das zweite Feld daneben darstellen zu können.

Ich ergänze die Paletten-Definition um

PHP-Code:

{aktiv_legend:hide},aktiv,aktivseit,aktivbis;

Da die Felder nicht so oft editiert werden, schließe ich die Palette defaultmäßig.

Nun kommt schon ein kleiner Sonderfall: password. Dies soll das Paar-Passwort sein, was zum Eintragen von Turnierergebnissen oder geänderten persönlichen Daten im Frontend dient. Ich will das nur in eigenen PHP-Skripten nutzen, von daher habe ich hier alle Freiheiten, wie ich das realisiere.

Ich möchte gerne, dass der Sportwart in diesem Feld ein Klartextpasswort eingeben kann. In die Datenbank soll aber nur der MD5-Hash des Passworts gelangen. Momentan plane ich, dass in dem Textfeld einfach der MD5-Hash angezeigt wird, wenn man aber etwas in dieses Feld eingibt, dass es dann aber durch einen Hook in den Hash umgewandelt wird, bevor es in der Datenbank gespeichert wird. Um die Realisation kümmere ich mich später. Erstmal soll es ein ganz normales Text-Feld sein:

PHP-Code:

        'password' => array 
        ( 
            'label'                   => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['password'], 
            'explanation'             => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['password_explanation'], 
            'exclude'                 => false, 
            'inputType'               => 'text', 
            'eval'                    => array('mandatory'=>false, 'minlength' => 1, 'maxlength' => 64) 
        )

Dieses Feld soll (alleine) in einer eigenen Palette stehen, darum ergänze ich die Palettendefinition um:

PHP-Code:

{password_legend:hide},password;

Zwischenstand der Backend-Maske:

Eingabemaske mit Passwort-Feld

Und nochmal die gesamte Paletten-Definition:

PHP-Code:

    // Palettes 
    'palettes' => array 
    ( 
        '__selector__'                => array(''), 
        'default'                     => '{name_legend},partnernachname,partnervorname,partnerinnachname,partnerinvorname;
{classes_legend},startgruppe,startklasselatein,startklassestandard;{aktiv_legend:hide},aktiv,aktivseit,aktivbis;{password_legend:hide},password;' 
    ),

Leider ist damit schon wieder das Ende meiner zur Verfügung stehenden Zeit erreicht (Sorry, wenn es zu langsam voran geht). Bald geht es weiter.

Ein Bug, ein Bug!

Nachdem man jetzt Startklassen eingeben, entdecke ich natürlich gleich einen Bug, und zwar in ['list']['label']['fields'].

VORHER (falsch):

PHP-Code:

            'fields'                  => array('partnernachname','partnervorname','partnerinnachname','partnerinvorname','startgruppe','startklassestd','startklasselat'),

NACHHER (richtig):

PHP-Code:

            'fields'                  => array('partnernachname','partnervorname','partnerinnachname','partnerinvorname','startgruppe','startklassestandard','startklasselatein'),

D.h. die beiden Felder für die Startklasse Standard und Latein waren falsch benannt. Sorry.

Und weiter geht es mit Bugs: Wenn ich bei Feldern, die eine "leere Option" zulassen, trotzdem in eval 'mandatory' => true fordere, kann die leere Option nicht ausgewählt werden. Gut, irgendwie auch logisch. Aus diesem Grund setze ich bei startgruppe, startklasselatein und startklassestandard: 'mandatory' => false, um auch die leere Option zuzulassen.

Und letzter kleiner Fehler: Im format-String für die Zeilen in der Übersicht der Turnierpaare ['list']['label']['format'] ist LAT und STD vertauscht, ich drehe das um. Neu:

PHP-Code:

            'format'                  => '%s, %s und %s, %s - %s %s STD / %s LAT'

Nochmals anders

Tjaja, wie das bei so einem Tagebuch im Gegensatz zum "durchgeplanten und polierten" Tutorial so ist: Ich habe mir nochmal was anders überlegt.

Ich habe mich entschlossen, die Felder startklasselatein und startklassestandard doch mandatory zu machen, aber als Option eine Leer-Option "-" hinzuzufügen. So ist der Benutzer gezwungen, explizit anzugeben, dass ein Paar keine Startklasse in einer der beiden Sektionen hat, und in meiner Paarübersicht sieht es besser aus, wenn z.B. vor "LAT" noch ein Strich steht, statt einfach garnichts.

Bei beiden Feldern sieht der Eintrag in ['fields'] jetzt also so aus:

PHP-Code:

            'options'                 => array('-', 'E','D', 'C', 'B', 'A', 'S', 'PRO', 'LL', 'OL', 'RL', '2. BL', '1. BL'), 
            'eval'                    => array('mandatory'=>true)

Dann habe ich noch entdeckt, dass man eine Checkbox nicht mandatory machen darf, weil dann MUSS sie nämlich angehakt werden. Ist irgendwie suboptimal. Also nochmal den Eintrag ['fields']['aktiv']['eval'] geändert auf:

PHP-Code:

            'eval'                    => array('mandatory'=>false, 'isBoolean' => true)

Und schließlich habe ich den Format-String für die Turnierpaar-Übersicht nochmal überarbeitet. Damit die relevantesten Elemente hervor stechen, gebe ich die Nachnamen der Partner fett aus, ebenso die Startgruppe. Die Startklassen zusätzlich in orange (Standard) und rot (Latein). Die Startpässe der Paare in der jeweiligen Sektion haben die gleichen Farben, so dass dies für den Eingeweihten eine natürliche Assoziation ist.

['list']['label']['format'] lautet jetzt:

PHP-Code:

            'format'                  => '<span style="font-weight: bold;">%s</span>, %s und <span style="font-weight: bold;">%s</span>, %s - <span style="font-weight: bold; margin-left: 5px">%s <span style="color: orange; margin-left: 5px;">%s STD</span> / <span style="color: red;">%s LAT</span></span>'

Und sieht so aus:

Backend Datensätze

Aber im nächsten Post geht es endlich mit den restlichen Feldern der tl_gw_turnierpaare-Tabelle weiter.

DCA - Almost there

Schritt 4 scheint kein Ende zu nehmen. Wie erwartet, erweist sich das Thema "DCA" als harter Brocken.

Zunächst geht es weiter mit den restlichen Feldern der tl_gw_turnierpaare-Tabelle. Für Anschrift, Telefonnummer, Fax, Mobilnummer, Email-Adresse und Homepage gibt es jeweils ein Flag, ob es im öffentlichen Profil angezeigt werden soll. Ist es nicht gesetzt, sind die Daten nur im Backend sichtbar. So kann der Sportwart das Paar evtl. erreichen, falls notwendig

Statt die Anschrift in Straße, PLZ, Ort, usw. aufzusplitten, habe ich hierfür eine textarea vorgesehen. Im DCA sieht das so aus:

PHP-Code:

        'anschrift' => array 
        ( 
            'label'                   => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['anschrift'], 
            'explanation'             => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['anschrift_explanation'], 
            'inputType'               => 'textarea', 
            'eval'                    => array('mandatory'=>false, 'cols' => 40, 'rows' => 5) 
        ),

Alles wie gehabt, zusätzlich geben cols und rows die Spalten und Zeilen des Eingabebereichs an.

Die Definition für das Flag, ob die Anschrift öffentlich angezeigt werden soll, ist so wie beim Feld aktiv:

PHP-Code:

        'zeigeanschrift' => array 
        ( 
            'label'                   => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['zeigeanschrift'], 
            'explanation'             => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['zeigeanschrift_explanation'], 
            'inputType'               => 'checkbox', 
            'eval'                    => array('mandatory'=>false, 'isBoolean' => true) 
        ),

Nicht Besonderes! Die Felder für Telefon, Fax, Mobilnummer, EMail und Homepage sind jeweils Textfelder, denen ich je nach Art die passende Regular Expression zur Überprüfung der Inhalte zuweise. Zusätzlich hat jedes Feld die "Anzeigen"-Checkbox:

PHP-Code:

        'telefon' => array 
        ( 
            'label'                   => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['telefon'], 
            'explanation'             => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['telefon_explanation'], 
            'inputType'               => 'text', 
            'eval'                    => array('mandatory'=>false, 'maxlength' => 32, 'rgxp' => 'phone') 
        ), 
        'zeigetelefon' => array 
        ( 
            'label'                   => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['zeigetelefon'], 
            'explanation'             => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['zeigetelefon_explanation'], 
            'inputType'               => 'checkbox', 
            'eval'                    => array('mandatory'=>false, 'isBoolean' => true, 'tl_class' => 'clr m12 w50') 
        ), 
        'fax' => array 
        ( 
            'label'                   => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['fax'], 
            'explanation'             => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['fax_explanation'], 
            'inputType'               => 'text', 
            'eval'                    => array('mandatory'=>false, 'maxlength' => 32, 'rgxp' => 'phone') 
        ), 
        'zeigefax' => array 
        ( 
            'label'                   => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['zeigefax'], 
            'explanation'             => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['zeigefax_explanation'], 
            'inputType'               => 'checkbox', 
            'eval'                    => array('mandatory'=>false, 'isBoolean' => true, 'tl_class' => 'clr m12 w50') 
        ), 
        'mobil' => array 
        ( 
            'label'                   => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['mobil'], 
            'explanation'             => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['mobil_explanation'], 
            'inputType'               => 'text', 
            'eval'                    => array('mandatory'=>false, 'maxlength' => 32, 'rgxp' => 'phone') 
        ), 
        'zeigemobil' => array 
        ( 
            'label'                   => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['zeigemobil'], 
            'explanation'             => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['zeigemobil_explanation'], 
            'inputType'               => 'checkbox', 
            'eval'                    => array('mandatory'=>false, 'isBoolean' => true, 'tl_class' => 'clr m12 w50') 
        ), 
        'email' => array 
        ( 
            'label'                   => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['email'], 
            'explanation'             => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['email_explanation'], 
            'inputType'               => 'text', 
            'eval'                    => array('mandatory'=>false, 'maxlength' => 32, 'rgxp' => 'email') 
        ), 
        'zeigeemail' => array 
        ( 
            'label'                   => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['zeigeemail'], 
            'explanation'             => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['zeigeemail_explanation'], 
            'inputType'               => 'checkbox', 
            'eval'                    => array('mandatory'=>false, 'isBoolean' => true, 'tl_class' => 'clr m12 w50') 
        ), 
        'homepage' => array 
        ( 
            'label'                   => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['homepage'], 
            'explanation'             => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['homepage_explanation'], 
            'inputType'               => 'text', 
            'eval'                    => array('mandatory'=>false, 'maxlength' => 32, 'rgxp' => 'url') 
        ), 
        'zeigehomepage' => array 
        ( 
            'label'                   => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['zeigehomepage'], 
            'explanation'             => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['zeigehomepage_explanation'], 
            'inputType'               => 'checkbox', 
            'eval'                    => array('mandatory'=>false, 'isBoolean' => true, 'tl_class' => 'clr m12 w50') 
        ),

Die tl_class-Werte sind so gewählt, dass jede "Anzeigen"-Checkbox links in einer Reihe mit dem entsprechenden Textfeld (rechts) in einer Zeile steht. Ich hätte es gerne andersrum gehabt, also Textfeld links, Checkbox rechts, aber trotz viel experimentieren mit den tl_class-Werten ist es mir nicht gelungen, das Layout sah immer "zerschossen" aus.

Den Wert ['palettes']['default'] ergänze ich noch um

PHP-Code:

'{contact_legend:hide},zeigeanschrift,anschrift,zeigetelefon,telefon,zeigefax,fax,zeigemobil,mobil,zeigeemail,email,zeigehomepage,homepage;'

Ergebnis:

Eingabemaske

Beschreibung ist eine Textarea, die in eigener Palette angezeigt werden soll. Einzige Besonderheit ist hier, dass ich HTML im Inhalt zulassen will.

PHP-Code:

        'beschreibung' => array 
        ( 
            'label'                   => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['beschreibung'], 
            'explanation'             => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['beschreibung_explanation'], 
            'inputType'               => 'textarea', 
            'eval'                    => array('mandatory'=>false, 'cols' => 80, 'rows' => 20, 'allowHtml' => true) 
        ),

Schließlich fehlt noch das Bild. Hier wollte ich eine Bilder-Auswahl wie im Content-Element "Bild" haben. Ich habe eine Weile herumexperimentiert, insbesondere mit dem Feldtyp "radioTable" (der aber ganz falsch ist, wie mir jetzt klar ist). Lösung brachte dann ein Blick in das CD-Collection-Tutorial, wo auch so eine Bilderauswahl drin ist. Der richtige DCA-Eintrag lautet:

PHP-Code:

        'bild' => array 
        ( 
            'label'           => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['bild'], 
            'explanation'     => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['bild_explanation'], 
            'inputType'       => 'fileTree', 
            'eval'            => array('mandatory'=>false, 'files'=>true, 'fieldType'=>'radio', 'filesOnly' => true, 'extensions' 		=> 'jpg,jpeg,png,gif', 'path' => 'tl_files/GW/Bilder_Turnierpaare/') 
        )

Typ ist also ein fileTree, in dem man mittels Radiobutton EIN File auswählen kann (fieldType => radio), in dem Dateien mit den Erweiterungen jpeg,jpg,png und gif angezeigt werden (extensions), in dem Unterverzeichnisse UND Dateien angezeigt werden (files => true, sonst werden NUR Verzeichnisse angezeigt), und in dem man mittels des Radiobuttons auch ausschließlich Dateien auswählen kann, KEINE Unterverzeichnisse (filesOnly => true). Zusätzlich gebe ich den "Basis-Pfad" an, aus dem man auswählen kann (path). Wobei der natürlich bei Jedem anders heißen kann...Also eigentlich nicht so toll. Muss nochmal drüber nachdenken.

Noch die Palettendefinition erweitern um

PHP-Code:

'{beschreibung_legend:hide},beschreibung;{bild_legend:hide},bild;'

und wir landen hier:

Bildauswahl

Damit bin ich im Prinzip mit der Maskendefinition für diese Tabelle fertig, abgesehen vom Hook für das MD5-Hashing meines Passworts. Das verschiebe ich erstmal auf später :-).

Folgende "Probleme" habe ich noch: Meine Palettendefinition sieht insgesamt so aus:

PHP-Code:

    // Palettes 
    'palettes' => array 
    ( 
        '__selector__'                => array(''), 
        'default'                     => '{name_legend},partnernachname,partnervorname,partnerinnachname,partnerinvorname;'.'{classes_legend},startgruppe,startklasselatein,startklassestandard;'.'{aktiv_legend:hide},aktiv,aktivseit,aktivbis;password_legend:hide},password;'.'{contact_legend:hide},zeigeanschrift,anschrift,zeigetelefon,telefon,zeigefax,fax,zeigemobil,mobil,zeigeemail,email,zeigehomepage,homepage;'.'{beschreibung_legend:hide},beschreibung;{bild_legend:hide},bild;' 
    ),

Eigentlich erwarte ich, dass nur die oberste Palette geöffnet ist, und alle folgenden geschlossen. Komischerweise sind beim Editieren bestehender Einträge und auch bei der Neuanlage alle geöffnet bis auf password und beschreibung. Ich kann nicht verstehen, wieso. Kann mich jemand schlau machen?

Die Eigenschaft exclude im Abschnitt fields soll steuern, ob das jeweilige Feld in der Usergruppenverwaltung spezifisch für einzelne Gruppen (de)aktivierbar ist. Bei exclude => true soll das Feld in der Usergruppenverwaltung erscheinen, bei false soll es dort nicht erscheinen und immer sichtbar sein (für die Gruppen, die das Backend-Modul überhaupt freigegeben haben).

Das Verhalten scheint aber ein anderes zu sein: Egal ob ich exclude true oder false zuweise, erscheint das Feld in der Usergruppenverwaltung. Nur wenn ich exclude ganz weglasse, erscheint es dort nicht. Ein Bug? Keine Ahnung. Zumindest ist es in der Referenz anders beschrieben. Da ich diese Steuerung auf Feldebene nicht brauche, sondern das ganze Backendmodul nur einer bestimmten Usergruppe freischalten möchte, habe ich die exclude-Eigenschaft aus allen Felddefinitionen entfernt.

Mein "Traum" wäre, dass man, falls ein Bild schon ausgewählt ist im Filetree man sofort dieses Bild sieht, statt erst den Filetree öffnen zu müssen, um zu sehen ob irgendwo der Radiobutton gesetzt ist. So kann man beim Öffnen eines Datensatzes nicht schnell sehen, ob ein Paar ein Bild zugewiesen hat, oder nicht. Aber ich glaube, das ist so (noch) nicht vorgesehen.

So, das sollte das Ende von Schritt 4 gewesen sein (puh). Um die Maske zu vervollständigen, werde ich mit der Definition der Texte in den Sprachfiles weitermachen.

Ansichten
Meine Werkzeuge

Contao Community Documentation

Programmierer brauchen viel Bit, auch wenn es Beck's ist.

Christian Schiffler
Navigation
Verstehen
Verwenden
Entwickeln
Verschiedenes
Werkzeuge