Seite 1 von 4 123 ... LetzteLetzte
Ergebnis 1 bis 40 von 129

Thema: Tagebuch einer Extension-Entwicklung

  1. #1
    Contao-Nutzer
    Registriert seit
    04.12.2009.
    Beiträge
    194

    Standard Tagebuch einer Extension-Entwicklung

    AKTUELLER STAND DES QUELLTEXTES:
    Aktueller Stand des Quelltextes vom 29.5.
    Bitte das ZIP innerhalb des /system/modules/-Ordners entpacken!

    Hallo!

    Ich möchte hier gerne eine Art "Tagebuch" einer Extension-Entwicklung schreiben. Ich besitze das TL-Buch, ich kenne das "Hello, World"-Beispiel, ich kenne das CD-Collection-Beispiel, ich kenne das FlowPlayer-Beispiel. Trotzdem ist mir weiterhin nicht glasklar, welche Schritte man bis zur eigenen Extension gehen muss. Ich bin PHP-erfahren, habe aber noch nie eine TL-Extension geschrieben. Ich möchte mit diesem Tagebuch anderen "Anfängern" die Möglichkeit geben, an einem realen Beispiel zu lernen, und auch meine Entscheidungen und Gedanken entlang des Wegs kennen zulernen. In vielen Tutorials wird nur vorgegeben, was in welche Datei geschrieben wird, und was Zeile X oder Zeile Y dort tut. Der Prozess hin zu Zeile X oder Y bleibt leider zu oft im Dunkeln, und erschwert mir die Übertragung der vorgestellten Tutorial-Inhalte auf meine eigenen Probleme. Ich möchte hier auch Irrwege dokumentieren, wie sie für Extension-Anfänger wahrscheinlich typisch sind, und nicht nur ein Tutorial zum Endprodukt abliefern. Ich hoffe, die Foren-Admin akzeptieren so etwas als "Tutorial", falls nicht, dann bitte ich um Verschiebung in die "Fragen"-Sektion. Wobei ich eher berichten als fragen möchte. Natürlich freue ich mich auch über Hinweise, wenn ich vielleicht ganz in die falsche Richtung denke, oder ich im Rahmen meines "Tutorials" selbst nicht mehr weiter weiß.
    Geändert von dl1ely (29.05.2010 um 18:09 Uhr)

  2. #2
    Contao-Nutzer
    Registriert seit
    04.12.2009.
    Beiträge
    194

    Standard

    Vorneweg:
    TL = TYPOlight
    FE = Frontend (Seite, die jeder Surfer sieht)
    BE = Backend (Seite, die nur eingeloggte "Redakteure" sehen unter der /typolight URL)

    Alles soll sich unter TL 2.8.0 abspielen, und ich werden den Extension Creator verwenden, um mir das "Skelett" der Extension zu erstellen.

    Die Extension wird sehr speziell auf die Bedürfnisse der von mir betreuten Seite zugeschnitten sein. Ich glaube nicht, dass mit dem engen Scope jemand anders sie nutzen kann. Darum werde ich sie wohl auch nicht im Extension-Repository veröffentlichen. Ich kann und werde den Quelltext aber sicherlich zur Verfügung stellen, sei es zur Benutzung oder zum "Studium".

    Schritt 1: Anforderungen & Randbedingungen

    Zunächst mal zum Hintergrund:
    Ich bin Webmaster der Seite http://www.gruen-weiss-aachen.de , der Webpräsenz des momentan größten Tanzsportvereins in NRW, und einem der größten in Deutschland. Wie man unschwer erkennen kann, bedarf die Seite dringend eines Relaunchs, der auf Basis von TYPOlight erfolgen soll. Dazu erfolgt auf einer Subdomain parallel zum weiteren Betrieb der bisherigen Webseite der Aufbau einer TL-basierten Seite. Das Design steht noch nicht, erstmal soll die technische Funktionalität geschaffen werden. 95% der bisherigen Seiteninhalte lassen sich mit "TL-Bordmitteln" oder schon verfügbaren Extensions wie efg erschlagen. Allerdings gibt es auf der alten Seite einige "handgestrickte" PHP-Skripte, deren Funktionalität mehr oder weniger auf der neuen Seite erhalten bleiben soll.

    Eine Funktionalität, die sich mit verfügbaren Extensions meiner Meinung nach nicht nachbilden lässt, und für die ich deshalb eine eigene Extension entwickeln möchte/muss, ist folgende:
    Der Verein besitzt eine Menge an Tanzturnierpaaren. Zu diesen Turnierpaaren gehören Daten wie Name(n), Start- und Alterklassen, und ein Flag, ob es noch aktiv ist. Diese Daten werden von einer berechtigten Person (Sportwart) im BE gepflegt. Zusätzlich gehört zu einem Turnierpaar eine Art "Visitenkarte" mit Bild, Freitext, Email-Adresse, Telefonnummer...Diese Daten sollen auch im Backend pflegbar sein, zusätzlich sollen diese Daten aber auch von dem Turnierpaar selbst unter Kenntnis eines individuellen Passworts veränderbar sein. Dazu möchte ich aber KEINE Frontend-User-Verwaltung nutzen.

    Für jedes Turnierpaar gibt es N "Meldungen", dies sind Datensätze, die enthalten, zu welchem Tanzturnier ein Paar fahren wird/gefahren ist. Turnierpaare informieren den Sportwart darüber, bei welchen Turnieren sie starten wollen. Der Sportwart führt die Anmeldung beim Turnierveranstalter durch (So ist das Procedere im Tanzsport), und trägt danach die "Meldung" in der Datenbank ein. Auf der sog. "Meldeliste" kann das Tanzpaar dann selbst sehen, ob es zu einem Turnier gemeldet worden ist. Die Liste der Meldungen (1:N-Beziehung zu den Turnierpaaren) wird vom Sportwart im BE gepflegt. Auch hier gibt es die Möglichkeit, dass ein Turnierpaar unter Kenntnis seines Passworts seinen erzielten Platz auf einem Turnier und einen freien Kommentar zu einer Meldung hinzufügen kann.

    Sollte das Tanzpaar das Turnier seiner Leistungsklasse gewonnen haben, darf es direkt im Anschluss das Turnier der nächsthöheren Klasse mittanzen (Ohne Meldung durch den Sportwart). Hier muss es die Möglichkeit geben, dass das Paar selbst einen Meldungs-Eintrag in der Datenbank vornehmen kann (Das sog. Folgeturnier).

    Die Meldeliste dient also mehreren Zwecken:
    - Rückmeldung vom Sportwart an die Paare, dass die Meldung beim Veranstalter erfolgt ist
    - Außenwirkung für den Verein: Wie viele Paare haben wir, und wo starten sie
    - Außenwirkung für die Tanzpaare selbst: Wie gut haben wir abgeschnitten

    Übertragen auf die Tabellenstruktur handelt es sich also um die Tabelle der Turnierpaare, klassisch im BE durch ein BE-Modul gepflegt. Die Meldungen sind "Childs" der Turnierpaare, und sollten so auch im Backend angezeigt werden (Wie Artikel - Inhaltselemente). Zugriff auf das BE-Modul hat nur der Sportwart.

    Zusätzlich soll es einen Mechanismus geben, unter Angabe eines Tanzpaar-spezifischen Passworts bestimmte Felder in der Tanzpaar-Tabelle und in der Melde-Tabelle durch ein Frontend-Formular zu verändern. Außerdem soll es die Möglichkeit geben, einen neuen Satz in der Melde-Tabelle anzulegen.

    Die Ausgabe der Daten im Frontend soll auf verschiedene Weisen geschehen:

    • Modul "Aktive Turnierpaare"
    • Modul "Ehemalige Turnierpaare"
    • Für beide Module jeweils eine Detail-Ansicht des gewählten Turnierpaares mit Bild, Freitext, freigegebenen Kontaktdaten wie Telefon oder Email, und einer Liste aller Meldungen dieses Paares
    • Modul "Meldeliste" - Eine chronologisch absteigend sortierte Liste aller Melde-Einträge, Neueste also zuerst.
    • Module/Formulare für Änderung der Paardaten im Frontend und zum Hinzufügen von Platzierung/Kommentar bei Meldungen


    Da das Ergänzen/Ändern der einzelnen Tabellenfelder mithilfe des Paar-Passworts ohne die Einführung von Frontend-Usern doch ziemlich an der TL-Philosophie vorbeigeht, tendiere ich momentan dazu, dafür Formulare zu verwenden, deren Weiterleitungsziel eine Seite ist, die per insert-Tag ein stand-alone-PHP-File einbindet, was nach Prüfung des Passworts die entsprechenden Datenbankfelder updatet.

    Gibt es hier eigentlich Erfahrungen mit zeitgleichem Zugriff auf Tabellen bei zwei eingeloggten BE-Usern, oder wie hier in meinem Fall, wenn ein Skript auf FE-Seite potentiell Daten in der Datenbank ändert, die vielleicht gerade ein BE-User betrachtet? Aber damit werde ich leben, die Kollisionswahrscheinlichkeit ist sehr gering.

    Coming up next: Extension-"Skelett" anlegen mit dem Extension-Creator.

    P.S.: Da ich das nur in meiner Freizeit mache, wird es mit meinem "Projekt" eher langsam vorangehen.
    Geändert von dl1ely (19.02.2010 um 10:02 Uhr) Grund: Lesbarer formatiert

  3. #3
    Contao-Fan Avatar von deerwood
    Registriert seit
    24.11.2009.
    Ort
    Hamburg
    Beiträge
    344

    Standard

    Hallo dl1ely,

    kannst Du bitte begründen, warum Du die Frontend-Benutzer/Member Funktionalität nicht nutzen willst? Ich sähe nämlich (aus der Hüfte) in etwa dies Datenmodell:
    Code:
    +-------------+ 1    N +--------+        +-----------+        +-----------+
    |             |-------<|        |        |           |        |           |
    |             |  Mann  |        | 1    N |           | N    1 |           |
    |  tl_member  |        |  Paar  |-------<|  Meldung  |>-------|  Turnier  |
    |             |  Frau  |        |        |           |        |           |
    |             |-------<|        |        |           |        |           |
    +-------------+ 1    N +--------+        +-----------+        +-----------+
    Dann könnte man z.B. leicht prüfen, ob ein angemeldeter FE Nutzer Felder eines Paar-Datensatzes bearbeiten darf usw.: seine ID muss == Mann/Frau ID sein. Direkte Personendaten wären in tl_member, nur echte Paardaten wären in Paar. Turnierdaten (Termin, Ort usw.) wären sauber/normalisiert in Turnier, Meldung enthielte nur noch wenig, wie etwa Anmeldetermin, Anmelder (Sportwart oder Paar-Partner), erreichter Platz. In Tabelle Turnier könnte man auch eine Selbstreferenz auf das Folgeturnier haben und so verhindern, dass Paar-Partner sich selbst für beliebige Turniere anmelden. Man könnte auch herausbekommen, wenn Partner im Laufe der Zeit in verschiedenen Paarungen tanzen.

    Eventuell könnte man diese Erweiterung auch generalisieren: statt "Paar" etwa "Mannschaft/Gruppe" und nicht 2 Verweise auf tl_member, sondern ein serialisiertes Array mit Mitgliedern und ihrer Rolle in der Mannschaft (Mann/Frau, Torwart, Schlagzeuger, Vorschoter) und z.B. dem Start/Ende-Datum der Mitgliedschaft in der Gruppe ... oder klassisch sogar eine N:M Tabelle zwischen tl_member und "Mannschaft/Gruppe". Besonderheiten wie "Anmeldung normalerweise nur durch den Sportwart" könnte man eventuell in eine Erweiterung zum Modul auslagern (Hook vorsehen bei der Rechteprüfung z.B.), dann hätte man ein Basismodul, das auch andere verwenden können und eben eine Tanzturnier-Speziallösung. Ich will nicht unnötig verkomplizieren, aber ich denke man sollte im Vorfeld schon versuchen, das Modul wiederverwendbar auszulegen und möglichst auf das TL Framework setzen.

    Ich bin auch kein erfahrener Modul-Programmierer und werde diesen Thread mit Interesse verfolgen. LG, Georg
    Geändert von deerwood (19.02.2010 um 06:14 Uhr)

  4. #4
    Contao-Fan Avatar von deerwood
    Registriert seit
    24.11.2009.
    Ort
    Hamburg
    Beiträge
    344

    Standard

    Moin alle,

    nochmals etwas weiter gedacht:

    Mitglieder-Gruppen (tl_member_group) gibt es ja schon in TL mit allem Drum und Dran / Verwaltung. Vielleicht muss man für Mannschaften/Paare nur noch einige Felder und Logik ergänzen?

    Und was sind denn Turniere/Veranstaltungen? Das sind doch "Events" (tl_calendar_events), für die es ein Basis-Modul in TL gibt, und, soweit ich sehen kann, auch schon diverse Erweiterungen, die zusätzliche Felder bieten.

    Was fehlt also noch? Eigentlich nur die "Mitte" (Meldung) zwischen member_group und calendar_event.

    Das ist halt eine zusätzliche/besondere Beziehung/Relation zwischen member_group und calendar_event, die eine zusätzliche Logik erfordert, sobald jemand die Beziehung herstellen/bearbeiten möchte, implementiert als Modul (und wohl mit einer zusätzlichen N:M Tabelle "Meldung/Teilnahme").

    Bin ich abgeflogen, oder versteht jemand diesen Ansatz oder bin ich einfach dumm und alle machen das bereits so?

    LG, Georg
    Geändert von deerwood (19.02.2010 um 06:01 Uhr) Grund: Weiter gedacht

  5. #5
    Contao-Nutzer
    Registriert seit
    04.12.2009.
    Beiträge
    194

    Standard

    Hallo Georg,

    vielen Dank für deinen Input. Habe ihn gestern Abend noch gelesen, aber keine Zeit/Ruhe zum Antworten gehabt. Prinzipiell hast du mit deiner Idee sicherlich recht, auch wenn ich aufgrund von (Noch-)Unkenntnis der schon implementierten Tabellenstruktur nicht sagen kann, ob da irgendwo noch ein Haken ist. Für ein abstraktes "Lehrbuchbeispiel" einer Extension wäre das wohl genau der richtige Weg.

    Folgende Gründe (Die alle nicht "zwingend" sind, aber die Summe macht es) machen es aber für mich unattraktiv:
    • Eine Normalisierung in Meldung und Turnier ist in 90% der Fälle Overkill. Häufig gibt es zu einem Turnier genau eine Meldung. Häufig wird es sich also um eine 1:1-Beziehung handeln.


    • Handling der "Personen" als Front-Member: Werde ich mir nochmal anschauen, aber ein großes Erbe in meinem konkreten Fall ist leider die Notwendigkeit, schon bestehende Daten zu migrieren, insbesondere die Liste der Turnierpaare, die bis in die 90er Jahre zurückreicht. Man kann sich vorstellen, dass da bei aktuell 45 Turnierpaaren (=90 Personen AKTIV) über die Jahre geschätzt 300-400 beteiligte Personen angesammelt haben, die ich alle in die tl_member-Tabelle migrieren müsste, obwohl davon eben nur 90 überhaupt noch potentiell einloggen.

      Die "Alten" nutzen die Seite eh nicht mehr, sollen sowieso nicht mehr ihre alten Einträge editieren dürfen, und sind (ehrlich gesagt) inzwischen teilweise verstorben. Für einen Großteil der Personen, die (inaktive) Paare bilden, müsste ich aus dem alten "Paar"-Datensatz zwei Datensätze für die tl_member-Tabelle erstellen, und mir dabei größtenteils die Inhalte der Datenfelder (email, passwort, whatever) ausdenken.

      Natürlich sehe ich die von dir geschilderten Vorteile der Abstraktion/Normalisierung, der Verfolgbarkeit des Wechsels von Paarzusammenstellungen, usw...Aber ich erkaufe es mir auch mit dem oben geschilderten Overhead.


    • Im Tanzpaar übernimmt meistens einer das Eintragen der Ergebnisse (meist der Herr :-). Wenn ich die Damen auch als Member anlegen würde, würden 45% der Member-Einträge selbst der aktiven Paare wahrscheinlich nie genutzt werden.


    • Vorschlag der Generalisierung: Sicherlich erstrebenswert, aber ich sehe die Gefahr, mich vor lauter Generalisierung "zu verzetteln", und 90% meiner Arbeit in "what-ifs" zu stecken, die in meinem Fall gar nicht benötigt werden.

      Wenn ich mir den Source der efg-Extension angucke, die ja eine eierlegende Wollmilchsau ist, dann wird mir schwindelig, obwohl ich von mir behaupte, ganz fit in PHP zu sein. Ich verstehe nur einen Bruchteil der Dinge, die da abgehen. Auch wenn es für den Launch der neuen Webseite keinen fixen Termin gibt, möchte ich eigentlich schnell zu Ergebnissen kommen, und kein "never-ending Project" draus machen, an dem ich mich verhebe. Schließlich ist das meine allererste Extension.

      Vielleicht wird eine v2 daraus, die dann Anspruch hat, eine allgemeine "Sport-Mannschaften-Turnierwesen"-Verwaltungs-Extension zu sein. Erstmal möchte ich aber mein Problem lösen. So clean wie möglich, aber auch so dirty wie nötig.

      Im Endeffekt will ich in meinem Tutorial zeigen, wie man Informationen mit Vater-Kind-Beziehungen ablegt, im BE editierbar macht, und in verschiedenen "Views" im Frontend anzeigt. Meine "Spezialsauerei" der Editierbarkeit eines Teils der Daten aus dem Frontend heraus darf jemand, der das hier nachvollziehen möchte, gerne gedanklich filtern. Da ist mir klar, dass es der reinen TL-Lehre etwas widerspricht. Ich werde diese Funktionalität auch als Letztes implementieren, und vielleicht sogar aus diesem "Bericht" her ganz rauslassen. Dann wird es nur eine Art "extended CD-Collection"-Beispiel (http://https://contao.org/projects/t...rialsExtension), was schon fast in meine Richtung geht, was mir nur _zu_ Hoppla-Hopp durch alle Schritte durchgeht.


    • Ich muss bedenken, dass alle Anwender (Der Sportwart im Backend, aber vor Allem die Turnierpaare) nun jahrelang einen gewissen Workflow bezüglich Meldeliste und Eintragen der Turnierergebnisse gewöhnt sind. Ein großer Teil unserer Turnierpaare befindet sich im mittleren bis hohen Seniorenalter, und schafft es trotzdem seine Turnierergebnisse online einzutragen. Ich kann hier keine großen Revolutionen veranstalten und möchte den technischen Ablauf deshalb möglichst nachbilden.

      Da es bisher möglich war, durch Angabe eines Paar-Passworts seine Turnierergebnisse in einem Formular einzutragen, würde ich das gerne beibehalten. Ein formelles "Einloggen" in die Seite wäre für manche Anwender wahrscheinlich schon zuviel der Veränderung :-). Außerdem schürt so etwas bei Nicht-Turnierpaaren das Gefühl, dass es für eingeloggte Personen "Geheimseiten" gibt, die ihnen nicht zur Verfügung stehen. In diesem Fall wäre das ja auch so, wäre aber nicht transparent.

      Wenn jeder Benutzer die Formularseite "Turnierergebnis für Paar XY eintragen, aber nur wenn man das Paarpasswort kennt" sieht, wird das viel weniger Fragen aufwerfen, als wenn es auf einmal "Login-Möglichkeiten" im FE geben wird, die es bisher nicht gab. Das hat immer den Geruch von Privilegisierung, die bei den "Nicht-Privilegierten" auf Ablehnung stößt.

      Ich weiß, das klingt übertrieben, ist aber ein Erfahrungswert. Mache ich ein Frontend-Login auf die Seite, wird es sofort 10-20 Anfragen geben, ob man (als nicht-Turnierpaar) nicht auch einen Login bekommen könnte, weil man sonst ja bestimmt was verpasst...Ich will da keine Begehrlichkeiten wecken.


    • Das Problem der Datenmigration habe ich schon mal angesprochen. Sowohl in der Turnierpaarliste als auch in der Meldeliste gibt es Einträge bis in die 90er Jahre zurück, die ich natürlich ins neue System übernehmen muss.

      Jede Normalisierung über die Beziehung "Turnierpaar<- 1:N ->Meldung" hinaus, so sinnvoll sie akademisch auch sein mag, macht mir sehr viel Arbeit beim Schreiben eines Migrationsskriptes, weil ich da nämlich dann Normalisierung algorithmisch durchführen muss, die in den alten Daten nicht vorhanden ist. Von daher würde ich gerne auch unter Missachtung von "clean-Design"-Grundsätzen bei der Konzeption der Struktur der Datenbanktabellen möglichst nah an der etablierten Form bleiben.

      Die Nachteile erscheinen mir erträglich, die Vorteile beim Arbeitsaufwand immens. Ich habe auch schon gesehen, wie mit sklavischer Normalisierungswut sich selbst in den Fuß schießt, weil man vor lauter Tabellen, die N:M-Beziehungen abbilden, kein vernünftiges SELECT mehr schreiben kann...

      Man darf da gerne anderer Meinung sein, für mein Beispiel würde ich gerne bei zwei Tabellen, eine für Turnierpaare, eine für Meldungen, bleiben. Für die Turnierpaare schaue ich mir nochmal die tl_member-Tabelle an, ob ich die Personen dort nicht ablege, aber aus den o.g. psychologischen Gründen würde ich ungerne ein Frontend-Login einführen (müssen).


    • Auf PHP-Seite möchte ich natürlich so weit wie möglich (und so weit es meine bisherigen Kenntnisse des Frameworks es zulassen) auf das TL-Framework setzen. Ist doch klar!


    Auf jeden Fall vielen, vielen Dank für das Feedback so weit, genau so hatte ich mir den Thread/die Diskussion eigentlich vorgestellt. Mit Vorschlägen von Alternativen, Anbringen von Zweifeln, usw...

    Demnächst geht es weiter :-).

    Stefan
    Geändert von dl1ely (19.02.2010 um 10:12 Uhr) Grund: Lesbarer formatiert

  6. #6
    Contao-Urgestein Avatar von Thomas
    Registriert seit
    16.08.2009.
    Ort
    Visselhövede
    Beiträge
    1.947
    User beschenken
    Wunschliste

    Standard

    Könntest Du die Texte mit Absätzen formatieren?
    Das liest sich sehr anstrengend vom Monitor aus.

    Nach 3-5 Zeilen verliert man die Lust zu lesen.
    Gruß Thomas
    "Zuerst ignorieren sie dich, dann lachen sie über dich, dann bekämpfen sie dich und dann gewinnst du." Mahatma Gandhi

  7. #7
    Contao-Nutzer
    Registriert seit
    04.12.2009.
    Beiträge
    194

    Standard

    Sehr gerne, danke für den Tipp. Ich werde die Posts, wo es in der Extension auch wirklich vorwärts geht zusätzlich mit roten großen Überschriften versehen, damit man später beim Lesen des ganzen Threads die "Nebendiskussion" leichter überspringen kann.

    Stefan

  8. #8
    Contao-Urgestein Avatar von FloB
    Registriert seit
    19.06.2009.
    Ort
    Sonnensystem
    Beiträge
    1.618

    Standard

    Zitat Zitat von dl1ely Beitrag anzeigen
    Handling der "Personen" als Front-Member: Werde ich mir nochmal anschauen, aber ein großes Erbe in meinem konkreten Fall ist leider die Notwendigkeit, schon bestehende Daten zu migrieren, insbesondere die Liste der Turnierpaare, die bis in die 90er Jahre zurückreicht. […]

    Die "Alten" nutzen die Seite eh nicht mehr, sollen sowieso nicht mehr ihre alten Einträge editieren dürfen, und sind (ehrlich gesagt) inzwischen teilweise verstorben. Für einen Großteil der Personen, die (inaktive) Paare bilden, müsste ich aus dem alten "Paar"-Datensatz zwei Datensätze für die tl_member-Tabelle erstellen, und mir dabei größtenteils die Inhalte der Datenfelder (email, passwort, whatever) ausdenken.
    Nun, aus was bestehen denn die bestehenden Daten? Name, Geburtsdatum ist klar, dann noch Tanzpartner (am besten UserID – oder "Gruppen-ID", sprich zwei Partner bekommen eine neue Nummer? Damit wäre auch der Grundstein gelegt, ganze Tanzgruppen ohne Mehraufwand anzulegen), Turnierklasse, aktive Jahre. Diese Felder kann man ja problemlos zu tl_member hinzufügen.

    Wenn du diese Daten in einem bestimmten Format hast (CSV, XML o. ä.) kannst du sie per SQL importieren. Die Daten, die du von manchen Personen nicht hast (E-Mail etc.) musst du da nicht angeben – nur wenn man versucht diese Einträge über das Backend / Frontend zu ändern, meckert TL. Frontendanzeige sollte problemlos funktionieren. Somit hast du die saubere Integration von Nutzer und Paaren.

    OK, ich verstehe dein Argument, dass das möglicherweise ein wenig Overkill für diese Art von Daten ist. Dann wäre es sinnvoll für Paare eine zugehörige "Verwalter-ID" zu geben, die auf einen Member-Account verweist. Dieser Member kann dann alle Einträge ändern, auf dessen die Paare / Personen verweisen. Ein zusätzliches Feld für die Trainer-ID (hinter dem auch ein Member steckt) in der Paarliste ermöglicht dann dem Trainer zusätzliche Einstellungen an der Paarung vorzunehmen (z. B. Turnier bestätigen).


    P. S.: Großartige Idee mit diesem Tagebuch!
    So long,
    FloB since Nov. 2007 +706P +115P and counting

  9. #9
    Contao-Nutzer
    Registriert seit
    04.12.2009.
    Beiträge
    194

    Standard

    Hehe, ihr blast das Ding schon viel zu weit auf...Trainer-ID o.ä. ist nicht notwendig, Trainer spielen in diesem "Prozess" keine Rolle. Ich denke es wird klarer (vielleicht auch enttäuschender), wenn ich die SQL-Files für meine Daten zeige. Es ist eigentlich recht billig, gerade für einen "TL-Profi". Aber das bin ich noch nicht ;-).

    Die Daten liegen noch in einer (anderen) MySQL-Datenbank. Der technische Vorgang der Migration ist kein Problem, aber ich werde per Skript manche Felder der bisherigen Datenbankstruktur verändern müssen, um es meiner geplanten neuen Struktur anzupassen.

    Leider komme ich momentan nicht dazu, diesen Thread täglich weiterzuführen, es ist nur Hobby. Aber ich mache weiter, keine Sorge :-).

    Stefan

  10. #10
    Contao-Nutzer
    Registriert seit
    04.12.2009.
    Beiträge
    194

    Standard

    Schritt 2: Extension-Creator

    Also, los geht es mit dem Skelett für die geplante Extension. Ich verwende den Extension-Creator aus dem Extension-Repository.

    • Titel: Es geht um Turnierpaar-"Verwaltung", und um Namenskonflikte zu vermeiden möchte ich gerne einen spezifischen Präfix nutzen: gw angelehnt an den Vereinsnamen "Grün-Weiß". Also: Titel = gw_turnierpaare. An dieser Stelle bin ich mir noch nicht so sicher, wo dieser "Titel" überall erscheinen wird, und ob es deshalb ein beliebiger Text sein soll, oder eher ein "identifier", also z.b. ohne Leerzeichen u.ä. Sicherheitshalber gehe ich den Identifier-Weg. Besser hässlich als nicht-funktionierend.


    • Ordnername: Ebenfalls gw_turnierpaare.


    • Autor, Copyright und Lizenz: Selbsterklärend


    • Paket: Ein Paketname ist gefragt. Der Hilfetext unter dem Eingabefeld schlägt hilfreich "z.B. meinEigenesModul" vor. Ist das Modul jetzt das Paket? Was ist ein Paket? Sowas wie ein Namensraum? Da ich noch nicht weiß, ob ich für die Vereinsseite noch andere Extensions pogrammieren werde, nehme ich die Vereinsabkürzung als "Paketname", also "GW". Weitere vereinsspezifische Extensions würde ich dann in dasselbe Paket stecken.


    • Ein Backend-Modul hinzufügen: Ja klar, schließlich sollen Daten im Backend bearbeitet werden. Also dort ein Häkchen.


    • Backend-Klassen: Wenig hilfreicher Erklärungstext: "Hier können Sie eine kommagetrennte Liste der zu erstellenden Backend-Klassen eingeben." - Was sind Backend-Klassen? Wofür brauche ich die? Eigentlich müsste doch alles, was ich im Backend vorhabe, durch Einträge im DCA-File realisierbar sein, schließlich geht es nur um Pflege von zwei abhängigen Datenbanktabellen. Also mal mutig leer gelassen, falls das falsch ist kann man es später noch hinzufügen.


    • Backend-Tabellen: Das sind wohl meine Datentabellen, ich will eine für Turnierpaare, eine für Meldungen, also: "tl_gw_turnierpaare,tl_gw_meldungen".


    • Backend-Templates: Auch hier wieder wenig erhellender Hilfetext. Auch das TL-Buch beschränkt sich da leider fast auf das Abschreiben der Hilfetexte unter den Eingabefeldern. Ich kenne Templates nur für das Frontend, also beschließe ich mutig, dass ich das wohl nicht brauche. Sollte sich das später als Irrtum herausstellen, wird es sich hoffentlich noch korrigieren lassen.


    • Ein Frontend-Modul hinzufügen: Aber klar, die Daten sollen schließlich im Frontend visualisiert werden. Also Haken dran.


    • Frontend-Klassen: So weit wie ich es bisher verstanden habe, braucht jedes Modul eine eigene Klasse. Nach meiner bisherigen Planung brauche ich ein Modul "Turnierpaarliste" inklusive Detail-Ansicht der einzelnen Paar-Einträge. Die Unterscheidung aktiv/nicht aktiv würde ich gerne über einen Parameter im Modul lösen, so dass dasselbe Modul die Liste der aktiven und der ehemaligen Paare anzeigen kann. Außerdem benötige ich ein Modul "Meldeliste" mit der chronologisch sortierten Übersicht der Meldungen. Also: "gwTurnierpaarliste,gwMeldeliste" als Klassennnamen.


    • Frontend-Tabellen: Ratlosigkeit. Was unterscheidet Frontend-Tabellen von Backend-Tabellen? Da ich meine Tabellen schon in den Backend-Tabellen abgehandelt habe, lasse ich das Feld leer.


    • Frontend-Templates: Natürlich! "gw_turnierpaarliste,gw_turnierpaarliste_detail,gw _meldeliste" fallen mir sofort ein, vielleicht genügt das schon. Falls nicht, kann ich später noch welche hinzufügen.


    • Sprachpakete erstellen: Natürlich. Auch wenn es wahrscheinlich niemand in Englisch benutzen wird, tut es mir aber auch nicht weh, also Sprachen = "en,de".


    Dann "speichern und schließen", und auf den grünen Haken am Ende der neuen Zeile "gw_turnierpaare" im Extension-Creator geklickt. Die Warnung bestätigt, und der Extension-Creator hat mir erstmal den Grundstock an Files in /system/modules/gw_turnierpaare/ erzeugt.

    Und zwar:
    • gwMeldeliste.php,gwTurnierpaarliste.php: meine Frontendklassen
    • config/config.php und config/database.sql (Letzteres für meine SQL-Tabellenstruktur)
    • dca/tl_gw_turnierpaare.php und dca/tl_gw_meldungen.php: Die DCA-Definitionen für meine Tabellen zur Bearbeitung im Backend
    • languages/...: Die Sprachfiles in en und de-Variante
    • templates/...: Die drei Frontend-Templates, die ich angegeben hatte


    Im nächsten Schritt orientieren wir uns etwas und beginnen, die vorgegebenen Files zu modifizieren.

    P.S.: Wenn die erfahrenen TL-Programmierer jetzt schon Gänsehaut haben: Sorry. Ich mache das zum ersten Mal, und stelle mich nicht künstlich dumm an. Ich versuche so zu schildern, wie ich als Einsteiger die Sachen sehe, was mich verwirrt usw...Bin für Verbesserungsvorschläge z.B. zu Namens-Schemata usw immer zu haben. Genauso freue ich mich über Aufklärung zu Dingen, die ich selbst nicht verstehe...Backendklassen, Backend-Templates, Frontend-Tabellen, Paket...
    Geändert von dl1ely (19.02.2010 um 21:51 Uhr)

  11. #11
    Contao-Nutzer
    Registriert seit
    22.02.2010.
    Beiträge
    24

    Standard

    also erstmal Hallo.. ich bin der neue

    Und dann direkt mal ein großes Dankeschön für dieses Tutorial. Ich habe die gleichen Gedankengänge und Probleme und bin gespannt, wie es weiter geht.

    Ich pfusche mich durch diverse vorhandenen Extensions und versuche, mir eine Logik zusammenzustricken, da sämtliche Tutorials leider keinerlei Grundlagen vermitteln, sondern nur direkte Lösungswege.

    Ich hatte mir auch schon überlegt, mal so ein Tagebuch zu schreiben, weil man selber auch versteht, was man da tut und welche Fehler man macht.

    Also Daumen hoch für die Idee.

    Bin gespannt, wie es weitergeht.

    Gruß, Angel

  12. #12
    Contao-Nutzer
    Registriert seit
    04.12.2009.
    Beiträge
    194

    Standard

    Schritt 3: Backend-Modul registrieren und SQL-Tabellenstruktur anlegen

    Jetzt geht es also an die Files, die uns der Extension-Generator im letzten Schritt erzeugt hat. Ich vewende WinSCP unter Windows 7 und PSPad als Editor.

    Zunächst will ich mein Backend-Modul in TYPOlight bekannt machen. Dafür öffne ich /system/modules/gw_turnierpaare/config/config.php .

    Im Skelett dieser Datei sind schon Abschnitte für die Eintragung von Backend-Modulen, Front-Modulen, Content Elementen, Hooks und noch viel mehr vorgesehen. Ich orientiere mich am cd-collection-Tutorial (http://https://contao.org/projects/t...rialsExtension) und trage im Abschnitt für Backend-Module folgendes ein:
    PHP-Code:
    $GLOBALS['BE_MOD']['content']['gw_turnierpaare'] = array
    (
        
    'tables' => array('tl_gw_turnierpaare','tl_gw_meldungen'),
        
    'icon'   => 'system/modules/gw_turnierpaare/icons/turnierpaare.png'
    ); 
    Hiermit registriere ich ein Modul mit dem Bezeichner "gw_turnierpaare", das sich auf die Datenbanktabellen "tl_gw_turnierpaare" und "tl_gw_meldungen" stützt (wie im Extension-Generator angegeben). In der Liste der Backend-Module (linke Spalte im Backend) soll ein Icon vor dem Bezeichner angezeigt werden, dessen Pfad ich unter 'icon' angegeben habe. Das Icon, was ich mir ausgesucht habe, ist an diesen Post angehängt. Ich habe mich für ein eigenes Unterverzeichnis für mögliche weitere Icons entschieden, und deshalb manuell das Unterverzeichnis "icons" angelegt und mein Icon dort hochgeladen. Im Backend-Modul soll der Berechtigte (also der Sportwart) die Daten Anlegen/Löschen/Ändern dürfen.

    Ich speichere die Datei zunächst, und lade meine Backend-Ansicht (als Administrator!) neu. Nun sehe ich in der linken Spalte unter "Inhalte" den neuen Eintrag "gw_turnierpaare" mit meinem Icon. Ein Klick darauf führt leider noch zu einer Fehlermeldung, da die SQL-Tabellen tl_gw_turnierpaare und tl_gw_meldungen noch nicht angelegt sind.


    Also, die Tabellen anlegen: Ich öffne /system/modules/gw_turnierpaare/config/database.sql , in der mir der Extension-Generator schon ein Skelett für meine beiden Tabellen vorgegeben hat. In beiden Tabellen sind id, pid, sorting und tstamp vorgegeben, sowie der primary key id und der key pid. Ich vermute, dass pid die "Parent ID" ist. Meine Turnierpaare haben keinen parent, darum lösche ich die Definition von "pid" und die Festlegung von "pid" als Key.

    Ich füge meine restlichen Felder hinzu, ohne so recht zu wissen, mit welcher Syntax genau. Ich habe mal was von SQL92-Syntax gelesen, aber die kenne ich nicht. Gibt es BOOLEAN-Datentypen? Char vs. Varchar? Sicherheitshalber halte ich mich erstmal an die MySQL-Syntax. Im Endeffekt sieht der Abschnitt für tl_gw_turnierpaare in database.sql so aus:
    Code:
    CREATE TABLE `tl_gw_turnierpaare` (
      `id` int(10) unsigned NOT NULL auto_increment,
      `sorting` int(10) unsigned NOT NULL default '0',
      `tstamp` int(10) unsigned NOT NULL default '0',
      `partnernachname` varchar(64) NOT NULL default '_',
      `partnervorname` varchar(64) NULL,
      `partnerinnachname` varchar(64) NULL,
      `partnerinvorname` varchar(64) NULL,
      `startgruppe` varchar(32) NOT NULL default '_',
      `startklasselatein` varchar(12) NULL,
      `startklassestandard` varchar(12) NULL,
      `aktiv` int(1) NOT NULL default '0',
      `aktivseit` int(4) NULL,
      `aktivbis` int(4) NULL,
      `password` varchar(32) NULL,
      `bild` varchar(255) NULL,
      `anschrift` text NULL,
      `zeigeanschrift` int(1) NOT NULL default '0',
      `telefon` varchar(32) NULL,
      `zeigetelefon` int(1) NOT NULL default '0',
      `fax` varchar(32) NULL,
      `zeigefax` int(1) NOT NULL default '0',
      `mobil` varchar(32) NULL,
      `zeigemobil` int(1) NOT NULL default '0',
      `email` varchar(128) NULL,
      `zeigeemail` int(1) NOT NULL default '0',
      `homepage` varchar(128) NULL,
      `zeigehomepage` int(1) NOT NULL default '0',
      `beschreibung` text NULL,
      PRIMARY KEY  (`id`),
    ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
    Man kann sich hier jetzt beliebige Gedanken über die Datenstruktur, Abstraktion, Normalisierung usw machen. Ich möchte es jetzt _so_ lösen .

    Meine Meldungen in tl_gw_meldungen sollen parents haben (nämlich die Turnierpaare), darum lasse ich das Skelett so, und erweitere um meine eigenen Felder. Für meinen Fall kommt das hier heraus:
    Code:
    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 '1900-01-01',
      `startgruppe` varchar(32) NOT NULL default '_',
      `startklasse` varchar(12) NOT NULL default '_',
      `lat_std` char(1) NOT NULL default '_',
      `turnierort` varchar(128) NOT NULL default '_',
      `turnierart` varchar(64) NULL,
      `anzahlpaare` int(4) NULL,
      `platz_von` int(4) NULL,
      `platz_bis` int(4) NULL,
      `bemerkung` text NULL,
      PRIMARY KEY  (`id`),
      KEY `pid` (`pid`)
    ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
    Ich speichere database.sql und rufe /typolight/install.php in meiner TYPOlight-Installation auf. TYPOlight bemerkt, daß die Datenbankstruktur nicht mehr aktuell ist, und schlägt mir vor, meine Tabellen anzulegen. Ich bestätige das.


    Was dann kommt, erstaunt mich dann aber doch etwas: Die Datenbankstruktur soll weiterhin nicht aktuell sein:


    Auch wenn ich diese Vorschläge bestätige, ändert sich nichts. Ein Blick in die Datenbank zeigt aber, dass die Tabellen wie von mir gewünscht angelegt wurden. Irgendwie kommt TYPOlight dort ins Schleudern. Ein Klick auf gw_turnierpaare im Backend verläuft jetzt aber ohne Fehlermeldung, auch wenn in diesem Backend-Modul noch nichts "passiert".

    Meine Frage an dieser Stelle also an die erfahrenen Entwickler: Was ist an meiner SQL-Definition "falsch", dass die Tabellen zwar richtig angelegt werden, das TYPOlight-Install-Tool aber damit nicht zurecht kommt?
    Angehängte Grafiken Angehängte Grafiken
    Geändert von dl1ely (11.03.2010 um 09:30 Uhr)

  13. #13
    Contao-Nutzer Avatar von Seitengestalter
    Registriert seit
    30.12.2009.
    Ort
    Geretsried
    Beiträge
    79

    Standard

    Hallo, jeweils am Ende der Zeile das >NULL< durch >''< (das sind zwei einfache Striche, nicht der Doppelte - und ohne die spitzen Klammern) ersetzen.

    Gruß, Roland
    Wenn Null besonders groß ist, ist es fast so groß wie ein bisschen Eins.

  14. #14
    Contao-Nutzer
    Registriert seit
    04.12.2009.
    Beiträge
    194

    Standard

    Hallo Roland,

    leider Nein...
    Das Install-Tool schlägt dann ein Anpassen der Datenbankstruktur z.B. wie folgt vor:
    Code:
    ALTER TABLE `tl_gw_turnierpaare` CHANGE `partnervorname` `partnervorname` varchar(64) '';
    Wenn man das bestätigt, erntet man nur eine SQL-Fehlermeldung (von MySQL).
    Code:
    Fatal error: Uncaught exception Exception  with message Query error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '''' at line 1 (ALTER TABLE `tl_gw_turnierpaare` CHANGE `partnervorname` `partnervorname` varchar(64) '';)
    Noch jemand eine Idee?
    Im o.g. CD-Collection-Tutorial wird es in einer Zeile der database.sql bei einem NULL-Feld auch so gemacht (also mit "NULL" am Ende der Zeile). Lasse ich "NULL" komplett weg, will mir das Installationstool weiterhin die Datenbankstruktur anpassen, obwohl sie eigentlich schon korrekt ist.

    Danke,
    Stefan

  15. #15
    Contao-Nutzer Avatar von Seitengestalter
    Registriert seit
    30.12.2009.
    Ort
    Geretsried
    Beiträge
    79

    Standard

    Sorry, mein Fehler. Muss heißen >default ''<.
    Meiner Erfahrung nach mag Tl kein varchar mit dem Default NULL
    Geändert von Seitengestalter (23.02.2010 um 13:07 Uhr)
    Wenn Null besonders groß ist, ist es fast so groß wie ein bisschen Eins.

  16. #16
    Wandelndes Contao-Lexikon Avatar von BugBuster
    Registriert seit
    15.06.2009.
    Ort
    Berlin
    Beiträge
    10.513
    User beschenken
    Wunschliste

    Standard

    Siehe hier zum Thema wie erstelle ich die sql richtig:
    https://community.contao.org/de/showthread.php?t=41

    Vereinfacht gesagt, vollständige MySQL Syntax.
    Grüße, BugBuster
    "view source" is your guide.
    Danke an alle Amazon Wunschlisten Erfüller

  17. #17
    Contao-Nutzer
    Registriert seit
    04.12.2009.
    Beiträge
    194

    Standard

    OK, wir kommen der Sache näher, aber für mich bleibt es mysteriös...

    • NOT NULL Felder sind kein Problem - Man gibt den Default an, und alles ist OK.
    • varchar-Felder mit NULL werden geschluckt (also funktionieren), wenn man "default ''" anhängt, also z.b.
      Code:
      `name` varchar(32) NULL default ''
    • int- oder text-Felder mit NULL werden vom Install-Tool nicht geschluckt, wenn "default '0'" (für int) oder "default ''" (für text) anhängt, also z.B.
      Code:
      `flag` int(1) default '0'
      Das Install-Tool meint weiterhin, die Datenbankstruktur würde nicht stimmen, und lässt sich auch nicht davon abhalten.


    Ich habe mal in das database.sql der Extension "twitterreader" geschaut, dort steht ein NON-NULL text-Feld als
    Code:
    `feld` text NULL,
    eingetragen, und dort funktioniert das offensichtlich auch. Warum bei mir nicht? Help!

    P.S.: Meine verbleibenden Problem-Felder sind:
    Code:
    ALTER TABLE `tl_gw_turnierpaare` CHANGE `aktivseit` `aktivseit` int(4) NULL default '0000';
    ALTER TABLE `tl_gw_turnierpaare` CHANGE `aktivbis` `aktivbis` int(4) NULL default '0000';
    ALTER TABLE `tl_gw_turnierpaare` CHANGE `anschrift` `anschrift` text NULL default '';
    ALTER TABLE `tl_gw_turnierpaare` CHANGE `beschreibung` `beschreibung` text NULL default '';
    ALTER TABLE `tl_gw_meldungen` CHANGE `bemerkung` `bemerkung` text NULL default '';
    Geändert von dl1ely (23.02.2010 um 13:27 Uhr)

  18. #18
    Wandelndes Contao-Lexikon Avatar von BugBuster
    Registriert seit
    15.06.2009.
    Ort
    Berlin
    Beiträge
    10.513
    User beschenken
    Wunschliste

    Standard

    Zitat Zitat von Seitengestalter Beitrag anzeigen
    Meiner Erfahrung nach mag Tl kein varchar mit dem Default NULL
    Doch, sieht komisch aus, aber so gehts:
    Code:
     `varchar_null_demo` varchar(32) NULL default NULL,
    Siehe Link von mir weiter oben.
    Grüße, BugBuster
    "view source" is your guide.
    Danke an alle Amazon Wunschlisten Erfüller

  19. #19
    Wandelndes Contao-Lexikon Avatar von BugBuster
    Registriert seit
    15.06.2009.
    Ort
    Berlin
    Beiträge
    10.513
    User beschenken
    Wunschliste

    Standard

    Mal einfach gefragt, wenn ein Feld NULL sein darf, wozu dann ein Default?
    Wenn ein Default eingetragen werden soll, wenn nichts übergeben wurde, dann muss NOT NULL definiert werden.
    Textfelder dürfen kein Default haben, bei INT und NULL sieht nächsten Eintrag.
    Geändert von BugBuster (23.02.2010 um 13:33 Uhr)
    Grüße, BugBuster
    "view source" is your guide.
    Danke an alle Amazon Wunschlisten Erfüller

  20. #20
    Wandelndes Contao-Lexikon Avatar von BugBuster
    Registriert seit
    15.06.2009.
    Ort
    Berlin
    Beiträge
    10.513
    User beschenken
    Wunschliste

    Standard

    Code:
    ALTER TABLE `tl_gw_turnierpaare` CHANGE `aktivseit` `aktivseit` int(4) NULL default NULL;
    ALTER TABLE `tl_gw_turnierpaare` CHANGE `aktivbis` `aktivbis` int(4) NULL default NULL;
    ALTER TABLE `tl_gw_turnierpaare` CHANGE `anschrift` `anschrift` text NULL;
    ALTER TABLE `tl_gw_turnierpaare` CHANGE `beschreibung` `beschreibung` text NULL;
    ALTER TABLE `tl_gw_meldungen` CHANGE `bemerkung` `bemerkung` text NULL;
    Grüße, BugBuster
    "view source" is your guide.
    Danke an alle Amazon Wunschlisten Erfüller

  21. #21
    Contao-Nutzer
    Registriert seit
    04.12.2009.
    Beiträge
    194

    Standard

    Halleluja, es ist gelöst!

    Leider gabs etwas zeitliche Überschneidung mit meinen Posts und denen von Glen. Wenn man genau nach https://community.contao.org/de/showthread.php?t=41 arbeitet, dann geht es:
    • NOT NULL-Felder mit default
    • NULL-Felder mit "NULL default NULL"
    • NULL-text-Felder ohne default!

  22. #22
    Contao-Nutzer
    Registriert seit
    04.12.2009.
    Beiträge
    194

    Standard

    Zitat Zitat von BugBuster Beitrag anzeigen
    Mal einfach gefragt, wenn ein Feld NULL sein darf, wozu dann ein Default?
    Einfache Antwort: Weil Roland mich drauf brachte und es (komischerweise) funktioniert. Ich hätte mir sowas nicht ausgedacht ;-). "NULL default NULL" ist aber auch eine gewisse Tautologie. Anyway, es funktioniert...und "NULL default NULL" gefällt mir auch besser, darum habe ich es jetzt darauf geändert.

  23. #23
    Contao-Nutzer
    Registriert seit
    04.12.2009.
    Beiträge
    194

    Standard

    Schritt 3b: SQL reloaded

    Nach kleinem Kampf mit dem Install-Tool sieht das SQL für meine beiden Tabellen in database.sql nun so aus:
    Code:
    CREATE TABLE `tl_gw_turnierpaare` (
      `id` int(10) unsigned NOT NULL auto_increment,
      `sorting` int(10) unsigned NOT NULL default '0',
      `tstamp` int(10) unsigned NOT NULL default '0',
      `partnernachname` varchar(64) NOT NULL default '',
      `partnervorname` varchar(64) NULL default NULL,
      `partnerinnachname` varchar(64) NULL default NULL,
      `partnerinvorname` varchar(64) NULL default NULL,
      `startgruppe` varchar(32) NOT NULL default '',
      `startklasselatein` varchar(12) NULL default NULL,
      `startklassestandard` varchar(12) NULL default NULL,
      `aktiv` int(1) NOT NULL default '0',
      `aktivseit` int(4) NULL default NULL,
      `aktivbis` int(4) NULL default NULL,
      `password` varchar(32) NULL default NULL,
      `bild` varchar(255) NULL default NULL,
      `anschrift` text NULL,
      `zeigeanschrift` int(1) NOT NULL default '0',
      `telefon` varchar(32) NULL default NULL,
      `zeigetelefon` int(1) NOT NULL default '0',
      `fax` varchar(32) NULL default NULL,
      `zeigefax` int(1) NOT NULL default '0',
      `mobil` varchar(32) NULL default NULL,
      `zeigemobil` int(1) NOT NULL default '0',
      `email` varchar(128) NULL default NULL,
      `zeigeemail` int(1) NOT NULL default '0',
      `homepage` varchar(128) NULL default NULL,
      `zeigehomepage` int(1) NOT NULL default '0',
      `beschreibung` text NULL,
      PRIMARY KEY  (`id`),
    ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
    
    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 '1900-01-01',
      `startgruppe` varchar(32) NOT NULL default '',
      `startklasse` varchar(12) NOT NULL default '',
      `lat_std` char(1) 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;
    und wir merken uns:
    • NOT NULL-Felder mit default (logisch)
    • NULL-"text"-Felder OHNE default
    • Sonstige NULL-Felder mit "default NULL"
    • default _KLEIN_ schreiben
    • Und falls man da was verändert: _2_ Leerzeichen zwischen PRIMARY KEY und (`id`)


    Und hier nochmal der Link zum Thread, der das erklärt: https://community.contao.org/de/showthread.php?t=41.

    So, das Install-Tool ist zufrieden, die Datenbank-Tabellen angelegt - es kann weiter gehen!
    Geändert von dl1ely (23.02.2010 um 13:53 Uhr)

  24. #24
    Contao-Nutzer
    Registriert seit
    04.12.2009.
    Beiträge
    194

    Standard

    Schritt 4: 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 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 erstmal 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 eingezeigt 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 obendrü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. 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 erstmal 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 Tebelle 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 erstmal ein kleines, bescheidenes Zwischenergebnis zur Motivation:


    Und man kann auch schon was eingeben:


    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".
    Angehängte Grafiken Angehängte Grafiken
    Geändert von dl1ely (11.03.2010 um 09:33 Uhr)

  25. #25
    Contao-Nutzer
    Registriert seit
    04.12.2009.
    Beiträge
    194

    Standard

    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)
            )
        )
    ); 
    Geändert von dl1ely (11.03.2010 um 09:33 Uhr)

  26. #26
    Contao-Nutzer
    Registriert seit
    04.12.2009.
    Beiträge
    194

    Standard

    Schritt 4b: 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.



    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:



    Immerhin 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...
    Angehängte Grafiken Angehängte Grafiken
    Geändert von dl1ely (11.03.2010 um 09:34 Uhr)

  27. #27
    Contao-Nutzer
    Registriert seit
    04.12.2009.
    Beiträge
    194

    Standard

    Schritt 4c: 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:



    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...
    Angehängte Grafiken Angehängte Grafiken
    Geändert von dl1ely (11.03.2010 um 09:35 Uhr)

  28. #28
    Contao-Nutzer
    Registriert seit
    04.12.2009.
    Beiträge
    194

    Standard

    Schritt 4d: 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:



    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 Drop-Down-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 Drop-Down-Box mit Optionen und Möglichkeit der "leeren Option". Durch tl_class => w50 wird die Drop-Down-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:


    Weiter geht, 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:



    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.
    Angehängte Grafiken Angehängte Grafiken
    Geändert von dl1ely (11.03.2010 um 09:37 Uhr)

  29. #29
    Contao-Nutzer
    Registriert seit
    04.12.2009.
    Beiträge
    194

    Standard

    Schritt 4e: 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' 
    Geändert von dl1ely (11.03.2010 um 09:38 Uhr)

  30. #30
    Contao-Nutzer
    Registriert seit
    04.12.2009.
    Beiträge
    194

    Standard

    Schritt 4f: Nochmal 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 hervorstechen 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:


    Aber im nächsten Post geht es endlich mit den restlichen Feldern der tl_gw_turnierpaare-Tabelle weiter.
    Angehängte Grafiken Angehängte Grafiken
    Geändert von dl1ely (11.03.2010 um 09:38 Uhr)

  31. #31
    Contao-Nutzer
    Registriert seit
    04.12.2009.
    Beiträge
    194

    Standard

    Schritt 4g: 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:


    "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:


    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 (puh),. Um die Maske zu vervollständigen werde ich mit der Definition der Texte in den Sprachfiles weitermachen.
    Angehängte Grafiken Angehängte Grafiken
    Geändert von dl1ely (11.03.2010 um 09:41 Uhr)

  32. #32
    Contao-Nutzer
    Registriert seit
    04.12.2009.
    Beiträge
    194

    Standard

    Schritt 5: Language-Files

    Nun geht es an die Sprachfiles, um das Backend-Modul "hübsch" zu machen.
    Ich demonstriere es für die deutschen Sprachfiles im /system/modules/gw_turnierpaare/languages/de/-Verzeichnis. Englisch geht genau analog :-).

    Zunächst definieren wir die Namen der Back- und Frontendmodule, und einen kurzen Erklärungstext dazu. Das wird in modules.php gemacht:
    PHP-Code:
    /**
     * Back end modules
     */
    $GLOBALS['TL_LANG']['MOD']['gw_turnierpaare'] = array('Turnierpaare''Verwaltung der Turnierpaare und der Meldeliste.');


    /**
     * Front end modules
     */
    $GLOBALS['TL_LANG']['FMD']['gw_turnierpaarliste'] = array('Turnierpaarliste''Dieses Modul zeigt die Turnierpaarliste an');
    $GLOBALS['TL_LANG']['FMD']['gw_meldeliste'] = array('Meldeliste''Dieses Modul zeigt die Meldeliste an'); 
    Die Bezeichner hinter 'MOD' und 'FMD' müssen die sein, dir wir im config/config.php der Extension definiert haben. Die entsprechenden Texte für die beiden geplanten Frontendmodule habe ich hier auch schon mal eingetragen, auch wenn es die Module noch nicht gibt...

    Die Texte für die Backendfelder sind in tl_gw_turnierpaare.php definiert, entsprechend dem Namen der Datenbanktabelle. Die in der DCA-Record-Definition deklarierten Felder müssen wir mit Text füllen. Das ist ziemlich straight-forward:
    PHP-Code:
    /**
     * Fields
     */
    $GLOBALS['TL_LANG']['tl_gw_turnierpaare']['partnernachname']    = array('Nachname des Partners''Bitte den Nachnamen des (m&auml;nnlichen) Partners eingeben');
    $GLOBALS['TL_LANG']['tl_gw_turnierpaare']['partnervorname']     = array('Vorname des Partners''Bitte den Vornamen des (m&auml;nnlichen) Partners eingeben');
    $GLOBALS['TL_LANG']['tl_gw_turnierpaare']['partnerinnachname']  = array('Nachname der Partnerin''Bitte den Nachnamen des (weiblichen) Partners eingeben');
    $GLOBALS['TL_LANG']['tl_gw_turnierpaare']['partnerinvorname']   = array('Vorname der Partnerin''Bitte den Vornamen des (weiblichen) Partners eingeben');

    $GLOBALS['TL_LANG']['tl_gw_turnierpaare']['startgruppe']        = array('Startgruppe''Bitte die Startgruppe (JUG, HGR, SEN, ...) des Paares eingeben');
    $GLOBALS['TL_LANG']['tl_gw_turnierpaare']['startklasselatein']  = array('Startklasse Latein''Bitte die Startklasse (Latein) des Paares eingeben. Kein Lateinstartbuch = "-"');
    $GLOBALS['TL_LANG']['tl_gw_turnierpaare']['startklassestandard'] = array('Startklasse Standard''Bitte die Startklasse (Standard) des Paares eingeben. Kein Standardstartbuch = "-"');

    $GLOBALS['TL_LANG']['tl_gw_turnierpaare']['aktiv']              = array('Aktiv''Bitte angeben, ob das Paar noch aktiv ist');
    $GLOBALS['TL_LANG']['tl_gw_turnierpaare']['aktivseit']          = array('Aktiv seit''Bitte Jahreszahl des ersten Starts angeben (z.B. 2005)');
    $GLOBALS['TL_LANG']['tl_gw_turnierpaare']['aktivbis']           = array('Aktiv bis''Bitte Jahreszahl des letzten Starts angeben, wenn das Paar nicht mehr aktiv ist (z.B. 2008)');

    $GLOBALS['TL_LANG']['tl_gw_turnierpaare']['password']           = array('Passwort''Bitte ein Passwort f&uuml;r das Paar anlegen');

    $GLOBALS['TL_LANG']['tl_gw_turnierpaare']['zeigeanschrift']     = array('Anschrift anzeigen''Anschrift in der Visitenkarte sichtbar?');
    $GLOBALS['TL_LANG']['tl_gw_turnierpaare']['anschrift']          = array('Anschrift''Bitte Anschrift des Paares eingeben');
    $GLOBALS['TL_LANG']['tl_gw_turnierpaare']['zeigetelefon']       = array('Telefonnummer anzeigen''Telefonnummer in der Visitenkarte sichtbar?');
    $GLOBALS['TL_LANG']['tl_gw_turnierpaare']['telefon']            = array('Telefonnummer''Bitte Telefonnummer des Paares eingeben');
    $GLOBALS['TL_LANG']['tl_gw_turnierpaare']['zeigefax']           = array('Faxnummer anzeigen''Faxnummer in der Visitenkarte sichtbar?');
    $GLOBALS['TL_LANG']['tl_gw_turnierpaare']['fax']                = array('Faxnummer''Bitte Faxnummer des Paares eingeben');
    $GLOBALS['TL_LANG']['tl_gw_turnierpaare']['zeigemobil']         = array('Mobilnummer anzeigen''Mobilnummer in der Visitenkarte sichtbar?');
    $GLOBALS['TL_LANG']['tl_gw_turnierpaare']['mobil']              = array('Mobilnummer''Bitte Mobilnummer des Paares eingeben');
    $GLOBALS['TL_LANG']['tl_gw_turnierpaare']['zeigeemail']         = array('Email-Adresse anzeigen''Email-Adresse in der Visitenkarte sichtbar?');
    $GLOBALS['TL_LANG']['tl_gw_turnierpaare']['email']              = array('Email-Adresse''Bitte Emailadresse des Paares eingeben');
    $GLOBALS['TL_LANG']['tl_gw_turnierpaare']['zeigehomepage']      = array('Homepage-Adresse anzeigen''Homepage-Adresse in der Visitenkarte sichtbar?');
    $GLOBALS['TL_LANG']['tl_gw_turnierpaare']['homepage']           = array('Homepage-Adresse''Bitte Homepage-Adresse des Paares eingeben');

    $GLOBALS['TL_LANG']['tl_gw_turnierpaare']['beschreibung']       = array('Beschreibungstext''Bitte Beschreibungstext des Paares eingeben');

    $GLOBALS['TL_LANG']['tl_gw_turnierpaare']['bild']               = array('Paarbild''Bitte ein Paarbild ausw&auml;hlen');

    /**
     * Reference
     */
    $GLOBALS['TL_LANG']['tl_gw_turnierpaare']['name_legend']          = 'Namen';
    $GLOBALS['TL_LANG']['tl_gw_turnierpaare']['classes_legend']       = 'Startdaten';
    $GLOBALS['TL_LANG']['tl_gw_turnierpaare']['aktiv_legend']         = 'Aktiv';
    $GLOBALS['TL_LANG']['tl_gw_turnierpaare']['password_legend']      = 'Passwort';
    $GLOBALS['TL_LANG']['tl_gw_turnierpaare']['contact_legend']       = 'Kontakt';
    $GLOBALS['TL_LANG']['tl_gw_turnierpaare']['beschreibung_legend']  = 'Beschreibung';
    $GLOBALS['TL_LANG']['tl_gw_turnierpaare']['bild_legend']          = 'Bild';

    /**
     * Buttons
     */
    $GLOBALS['TL_LANG']['tl_gw_turnierpaare']['new']    = array('Neues Paar''Ein neues Turnierpaar anlegen');
    $GLOBALS['TL_LANG']['tl_gw_turnierpaare']['edit']   = array('Editieren''Das Turnierpaar editieren');
    $GLOBALS['TL_LANG']['tl_gw_turnierpaare']['copy']   = array('Paar kopieren''Das Turnierpaar in die Zwischenablage kopieren');
    $GLOBALS['TL_LANG']['tl_gw_turnierpaare']['delete'] = array('Paar löschen''Das Turnierpaar aus der Liste entfernen');
    $GLOBALS['TL_LANG']['tl_gw_turnierpaare']['show']   = array('Details''Die Detailansicht des Turnierpaars anzeigen'); 
    Unterschieden werden die Texte für die Eingabefelder, die aus Überschrift/Bezeichner für das Feld und dem darunter angezeigten Beschreibungstext bestehen, den Texten für Palettenüberschriften (mittlerer Teil) und den Texten für die Buttons in den Masken (letzter Teil). Umlaute müssen HTML-üblich durch ihre Ersatzcodes (ä = &auml; usw.) dargestellt werden. Die Beschreibungstexte sollten auch nicht zu lang werden.

    Ich habe in der DCA-Definition auch Referenzen auf Sprach-Strings für die Eigenschaft "explanation" angegeben, z.B. so:
    PHP-Code:
                'explanation'             => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['partnernachname_explanation'], 
    allerdings habe ich nicht erkennen können, wo das benutzt wird. Vielleicht nur in bestimmten Situatuionen/Konfigurationen. Ich habe all diese Referenzen bei jedem Feld aus meiner DCA-Konfiguration also wieder entfernt.

    Die Backend-Maske sieht jetzt so aus:
    Angehängte Grafiken Angehängte Grafiken
    Geändert von dl1ely (11.03.2010 um 09:42 Uhr)

  33. #33
    Contao-Fan Avatar von deerwood
    Registriert seit
    24.11.2009.
    Ort
    Hamburg
    Beiträge
    344

    Standard

    Moin,
    Umlaute müssen HTML-üblich durch ihre Ersatzcodes (ä = &auml; usw.) dargestellt werden
    Nein. UTF8 Zeichen/Editoren sind heutzutage angesagt . Z.B. Notepad++ oder Eclipse oder ... oder ...

    Macht Spass, dies Tutorial einfach zu verfolgen, ich habe auch noch mehr Kommentare, mache aber bitte erst mal ungestört weiter. Es ist lehrreich, Deine Arbeit zu verfolgen. Ab und zu wäre es schön, wenn Du Deinen Stand des Codes als ZIP (etwa immer im ersten Beitrag und mit einer Versions-Nummer versehen) anbieten würdest, dann könnte man das als interessierter Mitleser ohne all zu viel Tipp-Arbeit in eine Test-Installation übernehmen und selbst ein wenig herumprobieren.

    LG, Georg

  34. #34
    Contao-Nutzer
    Registriert seit
    04.12.2009.
    Beiträge
    194

    Standard

    Hallo Georg,

    vielen Dank für den Hinweis. Ich nutze PSPad, den ich eigentlich als "vernünftigen" Editor ansehe, aber trotzdem stand der Zeichensatz auf "ANSI". Mit UTF8 geht es natürlich auch mit Umlauten ;-). Da muss ich in Zukunft mehr drauf achten.

    Ich bitte sehr um Kommentare, genau deshalb mache ich das hier auch. Wenn keine kommen, mache ich "naiv" weiter...Also, bitte keine Zurückhaltung.

    Zum Download: In der Tat wird es langsam sehr viel, und es ist schwer nachzuvollziehen. Das hatte ich mir auch anders vorgestellt. Ich denke ich werde also zukünftig den aktuellen Stand des Codes in der Tat zum Download bereitstellen. Danke für die Anregung.

    Stefan

  35. #35
    Contao-Nutzer
    Registriert seit
    04.12.2009.
    Beiträge
    194

    Standard

    Schritt 6: Von Callbacks und Subpaletten

    Nachdem auch das englische Sprachfile fertig ist, geht es nun an die letzte fehlende Funktionalität der Backend-Maske. Zunächst gibt es aber noch einige Detailkorrekturen.

    Da MySQL keinen "Boolean"-Datentyp bietet, hatte ich die Felder, die nur true/false sein können, als int(1) angelegt, mit den möglichen Werten 0/1. Das klappt auch prinzipiell, die Stati der Checkboxen werden gespeichert und wieder aus der Datenbank ausgelesen, aber ein Problem zeigt sich, wenn man nach so einem Feld filtern will: Ich will nach dem "aktiv"-Feld filtern können. Häufig sind die die "aktiven" Turnierpaare von Interesse, die nicht mehr aktiven verstopfen aber die Liste.

    Wählt man dieses Feld nun als Filter-Feld aus, werden in der DropDown-Liste als Filtermöglichkeiten aber nur "Ja" und nochmals "Ja" angezeigt. Das Filtern klappt damit auch, bei dem einen "Ja" werden nur die aktiven Paare angezeigt, beim anderen "Ja" die inaktiven. Aber das ist natürlich nicht so gewollt.

    Durch Abschauen bei anderen Extensions bin ich darauf gekommen, die Checkbox-Felder durch "char(1)" statt "int(1)" abzubilden. Das klappt genau so gut, und auch das Filtern funktioniert mit "Ja" und "Nein". Alle int(1)-Felder wurden entsprechend in char(1) verändert. Nach dem Anpassen der database.sql muss natürlich das Install-Tool ausgeführt werden, um die Änderungen in der Datenbank durchzuführen.

    Die database.sql sieht jetzt so aus:
    Code:
    CREATE TABLE `tl_gw_turnierpaare` (
      `id` int(10) unsigned NOT NULL auto_increment,
      `sorting` int(10) unsigned NOT NULL default '0',
      `tstamp` int(10) unsigned NOT NULL default '0',
      `partnernachname` varchar(64) NOT NULL default '',
      `partnervorname` varchar(64) NULL default NULL,
      `partnerinnachname` varchar(64) NULL default NULL,
      `partnerinvorname` varchar(64) NULL default NULL,
      `startgruppe` varchar(32) NOT NULL default '',
      `startklasselatein` varchar(12) NULL default NULL,
      `startklassestandard` varchar(12) NULL default NULL,
      `aktiv` char(1) NOT NULL default '',
      `aktivseit` int(4) NULL default NULL,
      `aktivbis` int(4) NULL default NULL,
      `resetpassword` char(1) NULL default '',
      `password` varchar(64) NULL default NULL,
      `bild` varchar(255) NULL default NULL,
      `anschrift` text NULL,
      `zeigeanschrift` char(1) NOT NULL default '',
      `telefon` varchar(32) NULL default NULL,
      `zeigetelefon` char(1) NOT NULL default '',
      `fax` varchar(32) NULL default NULL,
      `zeigefax` char(1) NOT NULL default '',
      `mobil` varchar(32) NULL default NULL,
      `zeigemobil` char(1) NOT NULL default '',
      `email` varchar(128) NULL default NULL,
      `zeigeemail` char(1) NOT NULL default '',
      `homepage` varchar(128) NULL default NULL,
      `zeigehomepage` char(1) NOT NULL default '',
      `beschreibung` text NULL,
      PRIMARY KEY  (`id`),
    ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
    Wer aufgepasst hat, dem ist auch noch ein neues Feld aufgefallen:
    Code:
      `resetpassword` char(1) NULL default '',
    Das werde ich gleich für das Aktivieren einer Subpalette benötigen. Der Inhalt des Feldes in der Datenbank wird später nicht gebraucht, aber leider muss das Feld vorhanden sein, um es so nutzen zu können, wie ich es vorhabe.

    Für das Passwortfeld habe ich ich vor, dass ein dort eingegebenes Passwort SHA1-gehasht in der Datenbank abgelegt wird, nicht im Klartext. Dabei wird ein eventuell schon vorhandes Passwort natürlich überschrieben. Um Fehleingaben zu verhindern, möchte ich eine Checkbox anzeigen, die defaultmäßig "aus" ist. Erst wenn die Checkbox aktiviert ist, soll per AJAX das Passwortfeld angezeigt werden.

    Zunächst fügen die wir Definition für das Checkbox-Feld in die "field"-Sektion des DCA-Records ein:
    PHP-Code:
            'resetpassword' => array
            (
                
    'label'                   => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['resetpassword'],
                
    'inputType'               => 'checkbox',
                
    'default'                 => '',
                
    'eval'                    => array('mandatory'=>false'isBoolean' => true'submitOnChange' => true),
            ), 
    submitOnChange bewirkt, dass das Formular neu geladen wird, wenn das Feld angeklickt wird. nur dann wird das Passwortfeld nachgeladen.

    Dafür benötigen wir eine sogenannte "Subpalette". Die Checkbox (bei mir "resetpassword") muss als "__selector__" angegeben werden im DCA-Record:
    PHP-Code:
        // Palettes
        
    'palettes' => array
        (
            
    '__selector__'                => array('resetpassword'),
    ... 
    In der "default"-Sektion sieht die Palettendefinition so aus:
    PHP-Code:
                                      .'{aktiv_legend:hide},aktiv,aktivseit,aktivbis;{password_legend:hide},resetpassword;' 
    Hier steht also der Name der Subpalette. Der Name des Passwortfeldes steht hier nicht mehr. Das wird in der "subpalettes"-Sektion angegeben:
    PHP-Code:
        // Subpalettes
        
    'subpalettes' => array
        (
            
    'resetpassword'               => 'password'
        
    ), 
    Dies bedeutet, dass das Feld "password" in die Subpalette "resetpassword" eingeblendet wird, wenn "resetpassword" aktiviert ist. Wird es deaktiviert, verschwindet die Subpalette wieder.

    ---


    Die Texte hierzu wurden in den Sprachfiles entsprechend erweitert und angepasst.

    Für beide Felder, resetpassword und password, benötige ich besondere Funktionalitäten. resetpassword soll bei Öffnen der Backendmaske IMMER deaktiviert, das Passwort-Feld also versteckt sein - egal was in der Datenbank für das Feld steht. Dafür setze ich zunächst
    PHP-Code:
                'default'                 => ''
    was dafür sorgt, dass beim Anlegen eines neuen Datensatzes die Checkbox deaktiviert ist. Und dann gibt es noch die Option "load_callback", in der eine Funktion angegeben werden kann, die beim Laden des Feldes aufgerufen wird (Zu den Details von Callbacks gleich mehr).

    Hier habe ich versucht, durch "return '';" immer den Defaultwert zurückzugeben. Leider klappt das nicht richtig, wenn der Datensatz mit "speichern" gespeichert wird, aber geöffnet bleibt. Obwohl die Checkbox dann deaktiviert dargestellt wird, bleibt das Passwort-Feld trotzdem angezeigt und wird nicht versteckt. Erst durch manuelles Aktivieren und erneutes Deaktivieren verschwindet das Passwortfeld wieder. Ich weiß nicht, ob das ein Bug oder gewollt ist, zumindest gefiel es mir nicht.

    Ein weiterer Versuch scheiterte mit dem save_callback: Eine Funktion, die aufgerufen wird, bevor das Feld in die Datenbank gespeichert wird. Hier versuchte ich ebenfalls durch ein "return '';" zu erzwingen, dass immer ein deaktiviertes Feld gespeichert wird, und damit auch beim erneuten Anzeigen des Formulars deaktiviert bleibt. Das funktioniert optisch auch sehr gut. Leider werden dann aber keine Eingaben im Passwort-Feld gespeichert.

    Der Grund wird sehr wahrscheinlich sein, dass der save_callback auf dem resetpassword-Feld ausgeführt wird, bevor der Input im password-Feld verarbeitet wird. Mein Save-Callback deaktiviert die Checkbox, und damit auch die Subpalette, und das Feld in der Subpalette wird gar nicht mehr ausgewertet oder gespeichert. Auch die Reihenfolge der Felddefinitionen hat darauf keinen Einfluss, es kommt wohl auf die Reihenfolge in der Palettendefinition an, und die kann ich nicht umdrehen.

    An der Stelle war tiefe Frustration angesagt, aber ich habe das Problem anders lösen können. Beim resetpassword-Feld werden jetzt keine Callbacks verwendet.

    Dafür aber beim password-Feld, was jetzt in der Definition so aussieht:
    PHP-Code:
            'password' => array
            (
                
    'label'                   => &$GLOBALS['TL_LANG']['tl_gw_turnierpaare']['password'],
                
    'inputType'               => 'text',
                
    'eval'                    => array('mandatory'=>false'minlength' => 1'maxlength' => 64),
                
    'load_callback'           => array(array('tl_gw_turnierpaare','password_load_callback')),
                
    'save_callback'           => array(array('tl_gw_turnierpaare','password_save_callback'))
            ), 
    Durch diese Definition wird beim Laden des Feldes die Funktion "password_load_callback" und beim Speichern "password_save_callback" aufgerufen, die sich in der Klasse "tl_gw_turnierpaare" befinden. Diese Klasse lege ich in der DCA-Definitions-Datei /modules/gw_turnierpaare/dca/tl_gw_turnierpaare.php an.

    PHP-Code:
    class tl_gw_turnierpaare extends Backend
    {

        
    /**
         * Import the back end user object
         */
        
    public function __construct()
        {
            
    parent::__construct();
            
    $this->import('BackendUser''User');
        }

      
      public function 
    password_load_callback()
      {
     ...
     }
      
      public function 
    password_save_callback($var$dc)
      {
    ...
      }


    Das Gerüst habe ich mir bei anderen Extensions abgeschaut, es scheint zumindest klug zu sein, von "Backend" zu erben, und den Konstruktur zu überschreiben. Vielleicht ist es auch nicht nötig, ich habe es nicht probiert

    Ob der load_callback Parameter übergeben bekommt weiss ich nicht, aber ich benötige keinen Parameter.

    Der save_callback erhält den Wert des Feldes, das gespeichert werden soll ($var), und den DataContainer ($dc), der zu dem Formular gehört. Meist (wie auch hier) ist es DC_Table, der DataContainer für Datenbanktabellen.

    Mein password-Feld in der Datenbank wird den SHA1-Hash, also einen langen String unverständlicher hexadezimaler Zahlen enthalten. Es nützt nichts, wenn ich den im Backend im Passwort-Feld anzeige. Dort will der Admin Klartext-Passwörter eingeben. Im Load-Callback setze ich den Wert des password-Felds also auf einen leeren String, egal was in der Datenbank steht:
    PHP-Code:
      public function password_load_callback()
      {
        
    // Passwort-Feld immer leer anzeigen (Damit der User SHA1-Hash nicht sieht)
        
    return '';
      } 
    Damit wird der User schon mal nicht vom "Müll" aus der Datenbank belästigt. Umgekehrt müssen wir aber nicht das Klartext-Passwort, sondern den Hash in die Datenbank schreiben. Das macht der save_callback:
    PHP-Code:
      public function password_save_callback($var$dc)
      {
        
    // Kein neues PW angegeben: Feld nicht ändern
        
    if(strlen($var) < 1) return '';

        
    // Aktuellen Datensatz aus DB holen
        
    $row $this->Database->prepare("SELECT * FROM tl_gw_turnierpaare WHERE id=?")
                                  ->
    execute($dc->id);

        
    // PW in Passwort und Salt aufspalten
        
    list($strPassword$strSalt) = explode(':'$row->password);

        
    // Falls kein Salt vorhanden, dann erzeugen
            
    if (!strlen($strSalt))
            {
                
    $strSalt substr(md5(uniqid(''true)), 023);
            }

        
    // SHA1-Hash aus Salt+neuem Passwort berechnen, Salt anhängen
        
    $pwd sha1($strSalt $var) . ':' $strSalt;

        
    // Das resetpassword-Feld löschen
        
    $this->Database->prepare("UPDATE tl_gw_turnierpaare SET resetpassword='' WHERE id=?")
                                  ->
    executeUncached($dc->id);

        return 
    $pwd;
      } 
    Falls kein Passwort angegeben wurde, macht der save_callback garnichts, und gibt einen leeren String zurück. Das Feld "Id" des DataContainers enthält die id des aktuellen Datensatzes in der Datenbank. Um den Hash berechnen zu können, benötige ich das "alte" Passwort in der Datenbank. Darum hole ich mir erstmal den gesamten Datensatz mit der ID ab.

    Die Hash-Erzeugung habe ich mir beim File /system/libraries/User.php abgeschaut und funktioniert genauso wie in der Userverwaltung von TYPOlight: Der Hash wird zusammen mit einem "Salt" erzeugt, der zusammen mit dem Hash (durch Doppelpunkt getrennt) im Passwort-Feld abgespeichert wird. Existiert noch kein Salt, wird er erzeugt. Existiert der Salt schon, wird er weiterverwendet (und dafür muss ich das alte Passwort aus der Datenbank auslesen - um an den evtl. schon vorhandenen Salt zu kommen). Die Hash-Erzeugung läuft dann ziemlich straight-forward ab.

    Und fast ganz am Ende nochmal der Knackpunkt: Hier setze ich das resetpassword-Feld in der Datenbank auf ''. Das (und leider nur das) sorgt in allen Fällen dafür, dass beim Öffnen von Datensätzen die Subpalette für das Passwort-Feld geschlossen ist.

    Abschließend gebe ich den berechneten Hash zurück. Er wird dann in die Datenbank eingetragen.

    Wichtiger Hinweis noch: load_callback und save_callback müssen doppelt geschachtelte Arrays sein, weil es mehrere Callbacks geben kann, die nacheinander aufgerufen werden, der Feldwert wird jeweils durch alle durchgeschleust. Falls man aber z.B. einen onSubmitCallback für das ganze Formular vorgeben möchte, ist das nur ein einfaches Array mit Klassennamen und Methodenname, weil es hier nur einen Callback gibt. Das ist so leider in der Referenz der möglichen Callbacks nicht dokumentiert, und hat mir kurz graue Haare beschert.

    Der aktuelle Codestand wird gleich oben im ersten Post aktualisiert.

    Damit ist das Backend-Modul für die Turnierpaar-Tabelle erstmal fertig.
    Weiter wird es dann (endlich) mit dem Frontendmodul für die Turnierpaarliste gehen.
    Angehängte Grafiken Angehängte Grafiken
    Geändert von dl1ely (23.03.2010 um 20:31 Uhr)

  36. #36
    Contao-Fan Avatar von okapi
    Registriert seit
    03.09.2009.
    Ort
    Wien
    Beiträge
    251

    Standard

    Hallo Stefan,

    nur ein kleines Feedback...
    Dieses Tagebuch ist eine ganz tolle Idee! Sehr spannend und ermutigend, sich selbst an die Erstellung eines Moduls zu wagen!
    Danke für die doppelte Arbeit, die du für diese Dokumentation bei der Modulerstellung aufwendest.

    In üblichen Tutorials finden ja fast nie die möglichen Fehler und die unvermeidlichen Situationen der Ratlosigkeit Erwähnung. Ich finde gerade diese Art des Lernens mittels "Blick über die Schulter" besonders eindringlich und lebendig! Bitte mach weiter so!

    Ich freue mich schon - auch aus anlassbezogen eigenem Interesse - auf den Frontend-Teil, auf die Listen und Abfragen...

    Gruß
    Michael
    Geändert von okapi (10.03.2010 um 10:03 Uhr)

  37. #37
    Contao-Nutzer
    Registriert seit
    04.12.2009.
    Beiträge
    194

    Standard

    Hallo!

    Vielen Dank. Die erste Version des Frontend-Teils ist schon fertig, kommt gleich online. Es wird da aber mehrere Verfeinerungsstufen von geben. Erst "was anzeigen", danach dann tunen. Leider habe ich für meine Entwicklung (da reines Hobby) nur wenig Zeit, darum geht es so langsam voran. Jeder erfahrene Fulltime-Entwickler hätte die bisherigen Dinge innerhalb von 15 Minuten fertig gehabt ;-).

    Ich habe auch noch Verbesserungspotential in meinen Callback-Funktionen entdeckt, von daher werde ich dorthin nochmal zurückspringen. So wie es in der Praxis halt ist ;-).

    Vielen Dank fürs positive Feedback.

    Stefan
    Geändert von dl1ely (10.03.2010 um 13:12 Uhr)

  38. #38
    Contao-Nutzer
    Registriert seit
    04.12.2009.
    Beiträge
    194

    Standard

    Schritt 7: Endlich Frontend!

    nach soviel Kampf hinter den Kulissen wird jetzt endlich was im Frontend angezeigt und - um das vorweg zu nehmen - das klappt ziemlich reibungslos.

    Zunächst mache ich eine "Simpel-Version" des Frontends, die erstmal was anzeigt. Verfeinerungsschritte kommen dann nach und nach dazu. Erstmal muss das schnelle Erfolgserlebnis her.

    Zunächst mal müssen die Frontendmodule in system/modules/gw_turnierpaare/config/config.php "registriert" werden:
    PHP-Code:
    // Front end module
    array_insert($GLOBALS['FE_MOD']['turnierpaare'], 0, array
    (
        
    'gw_turnierpaarliste' => 'gwTurnierpaarliste',
        
    'gw_meldeliste' => 'gwMeldeliste'
    )); 
    'turnierpaare' scheint die Zwischenüberschrift zu sein, die man in der Dropdownliste der zur Verfügung stehenden Module zu sehen kriegt. Die Keys in dem Array sind einfach nur die Bezeichner, die durch die Languagefiles auch übersetzt werden. Die Values dahinter sind die Namen der Frontendklassen. Zu jeder Frontendklasse existiert /system/modules/gw_turnierpaare/*.php mit der entsprechenden Klassendefinition.

    Damit in der Modul-Dropdown-Liste nicht das hässliche 'turnierpaare' steht, muss die Languagedatei erweiter werden, z.b. system/modules/gw_turnierpaare/languages/de/modules.php um:
    PHP-Code:
    $GLOBALS['TL_LANG']['FMD']['turnierpaare'] = array('Turnierpaare'); 
    Nun kommt das eine große Puzzle-Teil für die Frontendausgabe: Die PHP-Klasse, die unsere Inhalte aus der Datenbank holt, aufbereitet und an das Template weiterreicht:
    PHP-Code:
    class gwTurnierpaarliste extends Module
    {

        
    /**
         * Template
         * @var string
         */
        
    protected $strTemplate 'gw_turnierpaarliste';

        
    /**
         * Generate module
         */
        
    protected function compile()
        {
        
    $arrPaare = array();
        
    $objPaare $this->Database->execute("SELECT * FROM tl_gw_turnierpaare ORDER BY partnernachname, partnerinnachname");

        while (
    $objPaare->next())
        {
          
    $newArr = array
          (
            
    'partnernachname' => trim($objPaare->partnernachname),
            
    'partnervorname' => trim($objPaare->partnervorname),
            
    'partnerinnachname' => trim($objPaare->partnerinnachname),
            
    'partnerinvorname' => trim($objPaare->partnerinvorname),
            
    'startgruppe' => $objPaare->startgruppe,
            
    'startklasselatein' => $objPaare->startklasselatein,
            
    'startklassestandard' => $objPaare->startklassestandard,
            
    'aktiv' => $objPaare->aktiv,
            
    'aktivseit' => $objPaare->aktivseit,
            
    'aktivbis' => $objPaare->aktivbis,
          );
          
          if(
    strlen($objPaare->bild) == 0)
          {
            
    $newArr['bild'] = '/system/modules/gw_turnierpaare/icons/default.png';
          }
          else
          {
            
    $newArr['bild'] = $this->getImage($objPaare->bild'30''30');
          }
          
          
    $arrPaare[] = $newArr;
        }

        
    $this->Template->paare $arrPaare;
        }

    Wir leiten von Module ab, und $strTemplate enthält den Namen des Templates, was wir füllen wollen. Die Funktion, die die Werte für das Template liefert, muss compile() heißen.

    Wir holen uns alle Turnierpaardatensätze aus der Datenbank, sortiert nach den Nachnamen (Andere Sortierungen und Filter werden später hinzugefügt!), durchlaufen diese in einer Schleife und füllen pro Eintrag ein Array $newArr mit den Werten der Datenbankfelder.

    Das 'bild'-Feld wird anders behandelt: Ist es leer (also kein Bild ausgewählt), wird als Bild-URL ein Default-Bild übergeben, was ich von Hand zum icons-Unterverzeichnis hinzufüge (Das benutzte Bild hänge ich hier an). Falls ein Bild angegeben wurde, hole ich mir über die in TL eingebaute getImage()-Funktion ein Thumbnail des Bildes, was maximal 30 mal 30 Pixel groß ist.

    Dann wird das das Array, was die Daten eines Paares enthält zum Array mit allen Paardaten hinzugefügt, und ganz am Ende die Template-Variable 'paare' mit unserem Ergebnis (Also den Daten aller Paare in der Liste) gefüllt.

    Nun das Ausgabetemplate system/modules/gw_turnierpaare/templates/gw_turnierpaarliste.tpl:
    Code:
    <div class="<?php echo $this->class; ?>"<?php echo $this->cssID; ?><?php if ($this->style): ?> style="<?php echo $this->style; ?>"<?php endif; ?>>
    <?php if ($this->headline): ?>
    <<?php echo $this->hl; ?>><?php echo $this->headline; ?></<?php echo $this->hl; ?>>
    <?php endif; ?>
    
    <table cellpadding="4" cellspacing="0" summary="Turnierpaarliste">
      <thead>
        <tr>
          <th>&nbsp;</th>
          <th>Name</th>
          <th>Startgruppe</th>
          <th>Std</th>
          <th>Lat</th>
          <th>Aktiv seit</th>
          <th> Aktiv bis</th>
        </tr>
      </thead>
      <tbody>
    <?php foreach ($this->paare as $paar): ?>
    
      <tr<?php if($paar['aktiv'] != '1') { echo ' style="color: #888;"'; } ?>>
      <td>
      <img src="<?php echo $paar['bild']; ?>">
      </td>
      <td><?php echo $paar['partnernachname']; ?>
    <?php if($paar['partnervorname']): ?>
    <?php   echo ', '.$paar['partnervorname']; ?>
    <?php endif; ?>
    <?php if($paar['partnerinnachname']): ?>
    <?php   echo ' und '.$paar['partnerinnachname']; ?>
    <?php   if($paar['partnerinvorname']): ?>
    <?php     echo ', '.$paar['partnerinvorname']; ?>
    <?php   endif; ?>
    <?php endif; ?>
      </td>
      <td><?php echo $paar['startgruppe']; ?>
      </td>
      <td><?php echo $paar['startklassestandard']; ?>
      </td>
      <td><?php echo $paar['startklasselatein']; ?>
      </td>
      <td><?php echo $paar['aktivseit']; ?>
      </td>
      <td><?php echo $paar['aktivbis']; ?>
      </td>
      </tr><?php endforeach; ?>
      </tbody>
    </table>
    
    </div>
    Den Teil bis zum Beginn der Table habe ich aus einem anderen Template übernommen, und enthält den "Standardheader" mit der Möglichkeit, spezielle Klassen, IDs und Styles anzugeben. Auch eine Überschrift wird zugelassen, falls sie gesetzt ist.

    In der Tabelle selbst wird ein Kopfbereich definiert mit den Spaltenüberschriften.
    Dann werden alle Paare in der Paarliste (Variable $this->paare) durchlaufen. Falls das Paar NICHT aktiv ist, setze ich einen style für die Tabellenzeile (graue Schrift). Das ist so nicht "sauber" und wird auch so nicht bleiben, sondern durch richtiges CSS-Markup ersetzt. Erstmal dient es zur Visualisierung.

    Aus den Namen von Herr und Dame wird ein "schöner" Namensstring zusammengebaut und ausgegeben. Die rechtlichen Felder werden ziemlich straight-forward ausgegeben.

    Nun wären wir eigentlich "fast" fertig. Aber um unser neues Frondendmodul nutzen zu können, muss es im Backend unter "Module" angelegt werden. Das übernimmt die Backend-Klasse tl_module, und die weiss noch nichts detailliertes über unser neues Frontendmodul.

    Wir müssen die DCA-Konfiguration für die Backend-Klasse tl_module so erweitern, dass die richtige Palette angezeigt wird, wenn bei Modul-Typ unser neues Frontendmodul ausgewählt wird. Dafür legen wir in system/modules/gw_turnierpaare/dca/ eine neue Datei tl_module.php an. Dort können wir die Definition erweitern. Das tun wir NICHT im tl_module-Backendmodul selbst. Da die DCA-Definitionen aller Extensions nacheinander eingelesen werden (und auf jeden Fall nach Frontend und Backend) können wir den fehlenden Eintrag für tl_module bei uns im Modul nachholen.

    In die tl_module.php kommt:
    PHP-Code:
    <?php
    // Add a palette to tl_module

    $GLOBALS['TL_DCA']['tl_module']['palettes']['gw_turnierpaarliste'] = 'name,type,headline;align,space,cssID';
    ?>
    Wenn in der Modulverwaltung in der Drop-Down-Liste der zur Verfügung stehenden Module unseres ('gw_turnierpaare') ausgewählt wird, wird diese Palette aktiviert, und als Modul-Optionen Name, Typ und Überschrift, Ausrichtung, Abstände und css-Optionen angezeigt. Später werden wir hier noch weitere Optionen einfügen.

    Der letzte Key der Definition ['gw_turnierpaarliste'] muss die ID unseres Moduls sein, so wie in config/config.php definiert. Hier hatte ich zuerst 'tl_gw_turnierpaare' benutzt - funktioniert nicht!

    Jetzt können wir das neue Modul in der Modulverwaltung anlegen:

    ---


    Hier sehen wir unsere Frontendmodule (das zweite kommt noch) in der Auswahlliste und bei Selektion des oberen Moduls die geänderte Palette (durch die Erweiterung des DCAs von tl_module). "align" scheint aber irgendwie nicht zu funktionieren. Ich ignoriere das erstmal.

    Jetzt können wir das Modul einem Artikel hinzufügen, und bei mir sieht es dann so aus:


    Gut, das Icon ist auf meinem Hintergrund noch nicht "hübsch", aber die zugrunde liegende Funktionalität (Default-Bild wenn keins gesetzt), usw... ist erkennbar. Auch die Thumbnailerstellung eines Paarbildes funktioniert, und nicht-aktive Paare werden grau dargestellt.

    Damit genug für heute, ein erstes Ergebnis ist erreicht, aber das Frontendmodul wird noch deutlich aufgebohrt werden. In den nächsten Folgen ;-).
    Angehängte Grafiken Angehängte Grafiken
    Geändert von dl1ely (23.03.2010 um 20:32 Uhr)

  39. #39
    Contao-Fan Avatar von stefan.sl
    Registriert seit
    19.06.2009.
    Ort
    Iserlohn
    Beiträge
    352
    Partner-ID
    1371

    Daumen hoch

    Diese Entwicklungstagebuch ist wirklich eine super Idee! Hab's vorhin entdeckt
    und musste es komplett durchlesen.

    Viele Deiner Gedankengänge hatte auch ich bei meiner ersten Modulerstellung,
    andersrum hast Du mir aber auch ein paar Aha-Erlebnisse beschert.

    Ich hätte auch mittendrin ein paar Anmerkungen gehabt, jedoch haben die sich
    Deinerseits in späteren Folgen erübrigt. Mal schauen, wie's weitergeht.

    Edit: Und putz Dich nicht immer so runter. Ich bin mir sicher, dass außnahmslos
    jeder Entwickler hier einen ähnlichen Start hatte. Erfreulicherweise kommt man
    dank des Frameworks sehr schnell zu einem Ergebnis und es macht einfach Spass
    mit TL zu entwickeln.
    Geändert von stefan.sl (10.03.2010 um 12:53 Uhr)

  40. #40
    Contao-Nutzer
    Registriert seit
    04.12.2009.
    Beiträge
    194

    Standard

    Habe gerade zufällig entdeckt, dass die angehängten Bilder nicht inline angezeigt werden, wenn man nicht im Forum angemeldet ist. Das könnte den davon betroffenen Lesern vielleicht etwas den roten Faden kosten. Hat jemand eine Idee, wie ich die Bilder besser einbinden kann?

    Bisher lade ich sie als Anhang hoch, und benutzt den "Grafik Einfügen" Knopf zusammen mit der URL des hochgeladenen Bildes.

    Danke,
    Stefan

Aktive Benutzer

Aktive Benutzer

Aktive Benutzer in diesem Thema: 1 (Registrierte Benutzer: 0, Gäste: 1)

Ähnliche Themen

  1. [tl_gallery] Skizzierung einer neuen Galerie Extension
    Von cbeier im Forum Entwickler-Fragen
    Antworten: 6
    Letzter Beitrag: 23.03.2012, 22:01
  2. Update einer Extension
    Von Lengen1971 im Forum Installation / Update
    Antworten: 0
    Letzter Beitrag: 16.02.2011, 15:27
  3. Allgeimene Frage bezüglich Extension Entwicklung
    Von endlezZ im Forum Entwickler-Fragen
    Antworten: 37
    Letzter Beitrag: 02.11.2010, 10:11
  4. SQL einer individuellen Extension aktualisiert sich nicht
    Von andreasisaak im Forum Entwickler-Fragen
    Antworten: 3
    Letzter Beitrag: 16.03.2010, 11:04
  5. Verwendung einer externen PHP Klasse in einer Extension klappt nicht
    Von Schlauchbeutelmaschine im Forum Entwickler-Fragen
    Antworten: 2
    Letzter Beitrag: 03.12.2009, 11:03

Lesezeichen

Lesezeichen

Berechtigungen

  • Neue Themen erstellen: Nein
  • Themen beantworten: Nein
  • Anhänge hochladen: Nein
  • Beiträge bearbeiten: Nein
  •