Deutsch English
Blog
Home
Über tdbengine
Newsletter
Download
Helpware
Chat
Dokumentation
Einführungskurs
Grundlagen
Programmierumgebung
CGI Aufbereitung
EASY Programmierung
Standard-Bibliothek
Die Datenbank
HTML-Formulare
Befehlsreferenz
HOWTO - Wie kann ich...?
Projekte
Links
Benchmarks
Bug Reporting
Supportanfrage
 
Home    Überblick    Suche    Impressum    Kontakt    Mitglieder
Lektion 7: Die Bearbeitung von HTML-Formularen

In der letzten Lektion haben wir eine kleine Suchmaschine für eine Adressen-Datenbank gebaut. Dabei haben wir die wichtigsten Funktionen aus der Standardbibliothek zum Öffnen und Schliessen von Tabellen sowie den lesenden Zugriff auf einzelne Zeilen und Spalten kennengelernt.

Diesmal geht es nun darum, Informationen in eine Tabelle zu übertragen.

Zunächst wollen wir neue Datensätze in die Tabelle einfügen. Dabei müssen wir folgende Reihenfolge einhalten:

  1. Öffnen der Tabelle zum Einfügen neuer Datensätze
  2. Bereitstellung eines leeren Satzpuffers
  3. Füllen der Felder im Satzpuffer
  4. Übertragen des Satzpuffers in die Tabelle
Die Rechte beim Öffnen von Tabellen
Die Funktion OpenDB() haben wir bereits kennengelernt. Bislang haben wir Tabellen aber ausschliesslich zum Lesen geöffnet. Bei den schreibenden Zugriffen unterscheiden wir folgende Aktionen:

  • Eine Tabelle wird erweitert, indem ein neuer Datensatz angehängt wird.
  • Ein bestehender Datensatz wird verändert.
  • Ein Datensatz wird aus der Tabelle gelöscht.

Diese Aktionen werden von oben nach unten betrachtet zunehmend "gefährlicher", was den möglichen Verlust von Informationen betrifft: Beim Anhängen einen Datensatzen geht keine Information verloren, bei Verändern eines bestehenden können einige Feldinhalte verloren gehen, beim Löschen schließlich ein ganzer Datensatz.

Entsprechend dieser Hierarchie werden die Tabellenrechte beim Öffnen kodiert:

1 = Anhängen neuer Datensätze ist erlaubt (append, insert)
2 = Veränderung bestehender Datensätze ist erlaubt (edit, modify)
4 = Löschen von Datensätzen ist erlaubt (delete)

Der Wert 4 für das Lösch-Recht mag zunächst verwundern. Er erlaubt uns aber, beliebige Recht-Kombinationen einfach durch Addition der jeweiligen Kennzahlen anzugeben:

3 = Anhängen und Verändern erlaubt
5 = Änhängen und Löschen erlaubt, aber Verändern verboten
6 = Verändern und Löschen erlaubt
7 = Anhängen, Verändern und Löschen erlaubt

Zunächst sollte man ein Tabelle immer nur mit den minimalen Rechten öffnen, also mit denjenigen, die man später unbedingt benötigt. In diesem Fall sorgt dann die tdbengine dafür, dass irgendwelche Programm-Fehler (und solche gibt es immer und immer und immer) nicht unbedingt einen Datenverlust zur Folge haben. Es mag bequem sein, eine Tabelle immer mit allen Rechten zu öffnen, verantwortungsvolles Programmieren ist das aber nicht.

Die Funktion OpenDB() erwartet die Rechte-Kennzahl als letzten Parameter, nach Tabellenpfad, Passwort und Verschlüsselungscode.

OpenDB(DB-Pfad, Passwort : STRING; Code, Rechte : REAL) : REAL

Auf Passwort und Verschlüsselungscode wollen wir in diesem Kurs verzichten. Also geben wir hier einen Leerstring und 0 an. Zum Öffnen der Tabelle "database/adressen.dat" mit dem Recht zum Anhängen neuer Datensätze benötigen wir demnach folgenden Funktionsaufruf:

db:=OpenDB('database/adressen.dat'',''',0,1)

Hinweis: Es gibt noch ein weiteres Recht (8 = Indizieren erlaubt), das wir hier jedoch nicht weiter behandeln.

Satzpuffer leeren
Das Leeren des Satzpuffers erfolgt durch Aufruf der Funktion ReadRec() mit der Satznummer 0.

ReadRec(Tabelle : REAL; 0)

Nach der Ausführung der Funktion dieser Funktion ist der Satzpuffer komplett geleert, d.h.

  • Stringfelder enthalten Leerstring
  • Zahlenfelder enhalten 0 bzw. sind undefiniert (wenn U-spezifiziert)
  • Datums- und Zeitfelder sind undefiniert
  • Memo- und Blobfelder enthalten keine Referenzen
  • Auto-Increment ist undefiert (wird '0' angezeigt)
Felder füllen
In der letzten Lektion haben wir kurz den Zugriff auf die einzelnen Felder (Spalten) eines Datensatzes (Zeile) angesprochen:

GetField(Tabelle : REAL; Spalte : REAL|STRING) : STRING

Hinzu kommt noch die Möglichkeit, binär auf numerische Feldinhalte zugreifen zu können:

GetRField(Tabelle : REAL; Spalte : REAL|STRING) : REAL

Beispiel:


VAR Vorname, Name : STRING
VAR Gehalt : REAL
...
Vorname:=GetField(db,'Vorname')
Name:=GetField(db,''Name')
Gehalt:=GetRField(db,'Gehalt')

Zu diesen beiden Funktionen gibt es die entsprechenden Gegenstücke für den umgekehrten Informationsfluss:

SetField() setzt eine Tabellenspalte auf einen Wert (allgemeine Version)
SetRField() setzt eine numerische Tabellenspalte auf einen Wert (binäre Version)

SetField(Tabelle : REAL; Spalte : REAL|STRING; Wert : STRING) : STRING

Diese Funktion ist für alle Spaltentypen (mit Ausnahme von Blobs und Memos) einsetzbar. Der Wert der Spalte wird immer als Zeichenkette angegeben.

Beispiel:

SetField(db,'Name','Müller')
SetField(db,'Aufnahmedatum','20.09.2000')
SetField(db,'Aufnahmezeit',TimeStr(Now))
SetField(db,'Gehalt','12000')

Der übergebene Wert wird bei der Ausführung der Funktion in den jeweiligen Spaltentyp konvertiert. Wenn dabei etwas schiefgeht, wird ein Laufzeitfehler ausgelöst (immer noch besser als Datenmüll). Diesen Laufzeitfehler können Sie mit den bekannten Methoden abfangen.

Die aufwändige Konvertierung bei numerischen Feldern kann man durch den Einsatz von SetRField() umgehen:

SetRField(Tabelle : REAL; Spalte : REAL|STRING; Wert : REAL) : REAL

Beispiel:

SetRField(db,'Aufnahmedatum',20.09.2000)
SetRField(db,'Aufnahmezeit',Now)
SetRField(db,'Gehalt',12000)

Bleiben noch längere Texte. Solche werden üblicherweise in Memos gespeichert. Dafür gibt es die Funktion ReadMemo():

ReadMemo(Tabelle : REAL; Spalte : REAL|STRING; Textdatei : STRING) : REAL

Doch die Zuweisung eines Textes an ein Memofeld ist eine wesentlich heiklere Operation. Das liegt daran, dass Memos in einer eigenen Datei abgelegt werden. In der eigentlichen Tabelle wird nur ein Zeiger (=die Position in der Zusatzdatei, an der das zugehörige Memo beginnt) gespeichert. Beim Einlesen ein Textes in ein Memofeld passiert also folgendes:

  • Der Text wird in die Memodatei eingefügt. Dabei wird die Startposition festgestellt.
  • Diese Startpoisition wird in das Memofeld des Datensatzes der Ausgangstabelle eingetragen.

Das ist natürlich noch stark vereinfacht, denn wenn das Feld schon belegt war, muss das zugehörige Memo in der Zusatzdatei zuvor auch noch gelöscht werden.

Das Hauptproblem beim Einlesen eines Textes in ein Memofeld besteht darin, dass die Operation einschliesslich des Schreibens des zugehörigen Datensatzes als eine Einheit durchgeführt werden soll. Andernfalls könnten inkosistente Zustände auftreten: In der Zusatzdatei ist der neue Text bereits eingetragen, aber im Memofeld steht noch der Verweis auf die Position des inzwischen gelöschten Textes...

Aus dem genannten Grund führt die Funktion ReadMemo() eine komplette Transaktion (= Überführung eines Datenbanksystems von einem konsistenten Zustand in einer anderen) durch, der zugehörige Datensatz wird also gespeichert, zusätzlich zum Einlesen des Textes. Damit unterscheidet sich ReadMemo() von den anderen Feld-Zuweisungen SetField() und SetRField(), denn hier wird nur der interne Satzpuffer verändert.

Man wird also normalerweise bei der Anlage eines neuen Datensatzes mit Memos so vorgehen:

  1. Löschen des Satzpuffers (mit READREC(db,0))
  2. Setzen der einfachen Felder (mit SETFIELD und SETRFIELD)
  3. Schreiben des Satzespuffers (mit WRITEREC(db,FILESIZE(db)+1))
  4. Lesen der Memos (mit READMEMO)
Datensatz in Tabelle anfügen
Die Übertragung des Satzpuffers in die Tabelle erfolgt mit der Funktion WriteRec():

WriteRec(Tabelle : REAL; Satznummer : REAL) : REAL

Zulässige Werte für die Satznummer sind 1 bis FileSize(Tabelle)+1. Ist der Wert kleiner als FileSize(Tabelle)+1, so wird ein bestehender Satz überschrieben, andernfalls wird der Satz an die bisherige Tabelle angehängt, sie wächst also um genau eine Zeile.

Informationen aus HTML-Dokumenten
So, jetzt wissen wir in etwa, wie Felder belegt und Datensätze in die Tabelle eingefügt werden. Doch woher kommen die Informationen für die Felder? Selbstverständlich aus HTML-Formularen!

Auch hier müssen wir wieder zwischen normalen"Feldern und Memos unterscheiden. Für die Eingabe von Memos kommt eigentlich nur das HTML-Tag "<textarea>" in Frage. Zwar könnte ein Tag der Form "<input type="text"> auch eine beliebig lange Zeile aufnehmen, aber halt nur eine Zeile.

Für die anderen Felder gibt es ein ganze Reihe von Eingabemöglichkeiten:
Textfelder <input type="text"
Checkboxen <input type="checkbox""
Radiobuttons <input type="radio""
Auswahl <select
Schalter <input type="submit"

Jedes dieser Tags hat ein Option »name«. Obwohl nicht unbedingt notwendig, spricht doch vieles dafür, hier die gleichen Namen wie die entsprechenden Feldbezeichner zu wählen:

<input type="text" name="Name"  ...
<input type="text" name="Vorname" ...

Am meisten werden wird wohl mit Textfeldern arbeiten. Hier gibt es zwei wichtige Optionen:

size=»Breite des Eingabefeldes in Zeichen«
maxlength=»maximale Anzahl der eingegeben Zeichen«

Da die Kapazität von Stringfeldern in einer Tabelle beschränkt ist, sollten Sie die Option »maxlength« immer entsprechend setzen. Andernfalls wird der Anwender häufig zu viele Zeichen eingeben, die dann nicht mehr in der Tabelle gespeichert werden können.

Eine weitere wichtige Option, die alle Eingabe-Tags (mit Ausnahme von <textarea>) beinhalten, ist »value«. Dabei ist aber zwischen dem Textfeld <input type="text"...> und allen anderen Eingabefeldern zu unterscheiden.

  • Beim Textfeld handelt es sich um den Vorgabewert, also diejenige Zeichenkette, die bereits bei der Anzeige des Formulars im Eingabefeld steht.
  • Bei einer Checkbox oder bei Radiobuttons handelt es sich um den Wert, der an das Programm geliefert wird, wenn das entsprechende Feld angekreuzt wurde.
  • In einem select-Tag werden mit value die Werte definiert, die bei der Auswahl der jeweiligen Option geliefert werden.
  • Bei einem submit-Schalter handelt wird durch »value« einerseits der Ausdruck des Schalters festgelegt, andererseits aber auch der Wert, der geliefert wird, wenn der Anwender genau diesen Schalter drückt.

Wie aber wird bei Checkboxen, Radiobuttons und Select-Feldern eine Vorgabe eingestellt? Zu diesem Zweck besitzen diese Tags eine weitere Option. Bei Checkboxen und Radiobottons handelt es sich dabei um das Wort »checked«. Wird dieses innerhalb des Tags angegeben, so wird das entsprechende Feld als ausgewählt gekennzeichnet.

Etwas anders liegt der Fall bei einem select-Tag. Dieser bildet einen Container für einzelne option-Tags. Und innerhalb jedes option-Tags kann des Wort »selected« eingesetzt werden, um genau diese Option als vor-ausgewählt zu kennzeichnen.

Formular-Templates
Für den Datenfluss zwischen Browser und CGI-Programm spielen nur diese Eingabefelder im Zusamenhang mit dem FORM-Tag eine Rolle, alle weiteren Gestaltungsmittel jedoch nicht. Aus diesem Grund ist es immer am Besten, mit einem möglichst schlichten Formular zu beginnen - ohne Tabellen oder sonstigen Schnickschnack.

Ein solches Einfachstformular könnte für unsere Adress-Datenbank beispielsweise so aussehen:

<html>
<head><title>Eingabeformular für Adressen</title></head>
<body bgcolor="white">
<h3>Eingabeformular</h3>
#fehlermeldung#
<form action="#action#" method="post">
<table>
<tr><td>Name</td><td><Input type="text" name="Name" maxlength="40" value="#Name#"></td></tr>
<tr><td>Vorname</td><td><Input type="text" name="Vorname" maxlength="30" value="#Vorname#"></td></tr>
<tr><td>Strasse</td><td><Input type="text" name="Strasse" maxlength="30" value="#Strasse#"></td></tr>
<tr><td>PLZ</td><td><Input type="text" name="PLZ" maxlength="5" value="#PLZ#"></td></tr>
<tr><td>Ort</td><td><Input type="text" name="Ort" maxlength="30" value="#Ort#"></td></tr>
<tr><td>Telefon</td><td><Input type="text" name="Telefon" maxlength="20" value="#Telefon#"></td></tr>
<tr><td>E-Mail</td><td><Input type="text" name="Email" maxlength="30" value="#Email#"></td></tr>
<tr><td colspan="2" align="center"><Input type="submit" name="send" value="absenden"></td></tr>
</table>
</form>
</body>
</html>

Hinweis: Es ist wenig sinnvoll, ein Eingabeformular direkt aus einem CGI-Programm heraus zu gestalten, denn dann müsste bei jedem Änderungswunsch am Design das Programm umgeschrieben werden. Zudem beraubt man sich damit der Möglichkeit, die Gestaltung einem Spezialisten zu übertragen, der ein Template durchaus mit seinen Methoden und Programmen (Dreamweaver, Frontpage etc.) bearbeiten kann.

Sie könnten nun fragen, welchen Zweck die Platzhalter für die einzelnen Value-Optionen erfüllen sollen, denn wir wollen doch neue Datensätze aufnehmen, und dann sollten ja gerade keine Werte an dieser Stelle stehen. Die Antwort darauf ist relativ einfach: Zunächst kann das gleiche Template zur Bearbeitung bestehener Daten verwendet werden (weise Voraussicht). Aber auch bei einer Neueingabe sind Fehleingaben durch den Benutzer möglich. Und in diesem Fall wollen wir diesem das Formular zur Korrektur zurückschicken (was auch den Platzhalter #fehlermeldung# erklären dürfte). Dabei sollen natürlich seine bisherigen Eingaben erhalten bleiben, und deshalb die Platzhalter für die einzelnen Values.

Programmskizze für Neueingabe
Wir können nun unser Programm für die Neueingabe eines Datensatzes so skizzieren:

Hole alle Angaben aus dem Formular.
Wenn alles IN Ordnung,
dann übernimm den Satz IN die Tabelle,
ansonsten lege dem Anwender das Formular mit einem Hinweis erneut vor.

Die Ausführung
Um die Angaben aus dem Formular zwischenzuspeichern, legen wir dafür in unserem Programm globale Variablen an:

VAR Name, Vorname, Strasse, PLZ, Ort, Telefon, EMail : STRING

Die Prozedur zum Abholen der Formular-Angaben sollte uns auch keine Schwierigkeiten mehr bereiten:

PROCEDURE HoleAngaben
  Name:=CGIGetParam('Name')
  Vorname:=CGIGetParam('Vorname')
  Strasse:=CGIGetParam('Strasse')
  PLZ:=CGIGetParam('PLZ')
  Ort:=CGIGetParam('Ort')
  Telefon:=CGIGetParam('Telefon')
  Email:=CGIGetParam('Email')
ENDPROC

Die (Funktions-)Prozedur zur Überprüfung der Eingabe gestalten wir so, dass sie gleich eine entsprechende Fehlermeldung liefert. Wird keine Fehlermeldung geliefert, sind alle Angaben akzeptiert.

PROCEDURE EingabePrüfung : STRING
  IF Name='' THEN RETURN 'Bitte den Namen eingeben'
  ELSIF Vorname='' THEN RETURN 'Vorname fehlt noch'
  ELSIF Strasse='' OR PLZ='' OR Ort=''
    THEN RETURN 'Anschrift bitte komplett!'
  ELSIF Email='' THEN RETURN 'Die E-Mail ist das Wichtigste!'
  ELSIF Scan('@',Email)<>1 OR NOT Email LIKE '?*@?*'
    THEN RETURN 'E-Mail NICHT korrekt'
  ELSE RETURN ''
  END
ENDPROC

Während wir hier die Angaben zu Name, Vorname, Strasse, PLZ und Ort nur auf bloßes Vorhandensein prüfen, unterziehen wir die EMail einer genaueren Untersuchung. Die Standard-Funktion Scan() zählt die Vorkommen eines Substrings in einem String. Da in einer Email-Adresse das Zeichen '@' genau einmal vorkommen muss, können wir im gegenteiligen Fall die Angabe abweisen. Der Ausruck EMail LIKE '?*@?*' sorgt dafür dass vor und nach dem '@'-Zeichen wenigstens ein Zeichen steht. Für Internet-Mail-Adressen könnten wir das Prüfmuster auf '?*@?*.?*' erweitern, denn hier besteht eine Domain immer aus Domain und Top-Level-Domain (wie »tdb-engine.de«). Im Intranet sind aber auch Rechnernamen ohne Punkt möglich.

Als nächsten wollen wir uns um die Prozedur zum Anfügen eines neuen Datensatzes kümmern.

PROCEDURE DatensatzAnfügen
VARDEF db, RecNo : REAL
  SetPara('ec 1')
  IF db:=OpenDB('database/adressen.dat','',0,15)=0
    THEN CGIWriteLn('Datenbank-Fehler'); Halt
  ELSE
    ReadRec(db,0) // Neuen Satz bereitstellen
    SetField(db,'Name',Name)
    SetField(db,'Vorname',Vorname)
    SetField(db,'Strasse',Strasse)
    SetField(db,'PLZ',PLZ)
    SetField(db,'Ort',Ort)
    SetField(db,'Telefon',Telefon)
    SetField(db,'Email',Email)
    RecNo:=WriteRec(db,FileSize(db)+1)
    IF RecNo=0 THEN
      CGIWriteLn('Datensatz konnte NICHT geschrieben werden')
    ELSE
      CGIWriteLn('Datensatz wurde erfolgreich aufgenommen')
    END
    CloseDB(db)
  END
  SetPara('ec 0')
ENDPROC

Bleibt noch die Prozedur zur Wiedervorlage des Formulars, wobei die auszugebende Fehlermeldung als Parameter übergeben wird.

PROCEDURE WiederVorlage(msg : STRING)
  IF LoadTemplate('templates/adressen_eingabe.html')<>0 THEN
    CGIWriteLn('Template NICHT gefunden')
  ELSE
    Subst('#action#',ParamStr(0))
    Subst('#fehlermeldung#',msg,1)
    Subst('#Name#',Name,1)
    Subst('#Vorname#',Vorname,1)
    Subst('#Strasse#',Strasse,1)
    Subst('#PLZ#',PLZ,1)
    Subst('#Ort#',Ort,1)
    Subst('#Telefon#',Telefon,1)
    Subst('#Email#',Email,1)
    CGIWriteTemplate
  END
ENDPROC

Entsprechend unserer Programm-Skizze können wir jetzt die Hauptprozedur Main schreiben:

PROCEDURE Main
VARDEF msg : STRING
CGIWriteLn('content-type: text/html')
CGIWriteLn('')
HoleAngaben
IF msg:=EingabePrüfung = '' // Alles IN Ordnung
THEN DatensatzAnfügen
ELSE WiederVorlage(msg)
END
ENDPROC

Dieses Programm funktioniert schon recht gut. Es hat aber den kleinen Schönheitsfehler, dass doppelte Einträge nicht abgefangen werden. Doch wann ist ein Eintrag als doppelt zu betrachen? Wir wollen dieses Kriterium an der E-Mail-Adresse festmachen.

Das Werkzeug, um zu prüfen, ob ein Eintrag bereits vorhanden ist, haben wir bereits in der letzten Lektion kennengelernt. Es versteckt sich hinter der Funktion FindAndMark(). Um diese Funktion anwenden zu können, muss die Tabelle geöffnet sein. Deshalb wollen wir diese Prüfung in die Prozedur »DatensatzAnfügen« einbauen:

PROCEDURE DatensatzAnfügen
VARDEF db, RecNo : REAL
  SetPara('ec 1')
  IF db:=OpenDB('database/adressen.dat','',0,1)=0
    THEN CGIWriteLn('Datenbank-Fehler')
  ELSIF FindAndMark(db,'$Email LIKE Email')>0 // doppelter Eintrag
    THEN
      CGIWriteLn('Sie sind bereits IN der Datenbank')
      CloseDB(db)
  ELSE
    ReadRec(db,0) // Neuen Satz bereitstellen
    SetField(db,'Name',Name)
    SetField(db,'Vorname',Vorname)
    SetField(db,'Strasse',Strasse)
    SetField(db,'PLZ',PLZ)
    SetField(db,'Ort',Ort)
    SetField(db,'Telefon',Telefon)
    SetField(db,'Email',Email)
    RecNo:=WriteRec(db,FileSize(db)+1)
    IF RecNo=0 THEN
      CGIWriteLn('Datensatz konnte NICHT geschrieben werden')
    ELSE
      CGIWriteLn('Datensatz wurde erfolgreich aufgenommen')
    END
    CloseDB(db)
  END
  SetPara('ec 0')
ENDPROC

Direkte Feldzugriffe
Ein Hinweis zur Syntax der Selektion in FindAndMark():

$Email=Email

(Vergleicht das Feld »Email« aus der Tabelle »adressen« mit dem Inhalt der Variablen »Email«)

Das schaut erst einmal komisch aus. In dieser Selektion verwenden wir einen sogenannten direkten Feldzugriff:

$Email

Das ist eine erlaubte Abkürzung für

$adressen.Email

Die allgemeine Form für einen direkten Feldzugriff lautet:

$Tabellenname.Feldbezeichner

oder

$Tabellenname.Feldnummer

Wenn die Eindeutigkeit sichergestellt ist, kann sowohl das $-Zeichen als auch der Tabellenname (mit dem folgenden Punkt) weggelassen werden. Da wir eine Variable mit dem Namen »Email« haben, ist zumindest das $-Zeichen notwendig.

Der Einsatz von direkten Feldzugriffen setzt voraus, dass die zugehörige Tabelle geöffnet ist. Da die tdbengine (anders als TDB oder VDP) die Tabellen immer erst zur Laufzeit (also wenn das Programm ausgführt wird) öffnet, können im normalen Programtext keine direkten Feldzugriffe erfolgen. Eine Anweisung wie:

CGIWriteLn($adressen.Email)

wird immer zu einer Fehlermeldung des Compilers führen.

Sie können direkte Feldzugriffe ausschließlich in sogeannten dynamischen Argumenten verwenden, also solchen, die ebenfalls erst zur Laufzeit berechnet werden. Die Funktion FindAndMark() akzeptiert in der Selektion ein solches dynamisches Argument.

Aber warum, so werden Sie jetzt sicher fragen, soll man überhaupt einen direkten Feldzugriff verwenden? Die Selektion könnte ja auch so formuliert werden:

'getfield(db,"Email") LIKE Email'

Das ist richtig, und das funktioniert auch.
Der Grund für den Einsatz: Die tdbengine kann direkte Feldzugriffe in Datenbank-Selektionen wesentlich effizienter verarbeiten. Bei kleinen Datenbanken mit wenigen tausend Datensätzen werden Sie keinen großen Unterschied feststellen. Wächst die Datenbank jedoch an, so wird die Suchstrategie entscheidend, und die kann (zumindest derzeit) nur dann automatisch optimiert werden, wenn direkte Feldzugriffe verwendet werden.

Verbesserung des Programms
Und noch ein kleiner Schönheitsfehler könnte uns die Freude an diesem Programm verdrießen: Beim ersten Aufruf über ».../cgi-tdb/adressengabe.prg« kommt gleich die Fehlermeldung: »Bitte Namen eingeben«, und nicht etwa ein Hinweis: »Bitte geben Sie hier Ihre Adresse und Ihre E-Mail ein«.

Doch woher weiss das Programm, dass es über eine einen Link gestartet wurde und nicht über das Formular? Nun - zum Versenden der Formulars muss der »submit«-Schalter gedrückt werden, und dieser kann mit CGIGetParam() abgefragt werden. Wie wir aus unserem Template ersehen können, hat dieser Schalter hier den Namen »send«. Somit lautet der Test, ob der Schalter gedrückt wurde:

IF CGIGetParam('send')
THEN // Aufruf kommt aus Formular
ELSE // Aufruf kommt über Link ODER direkte URL-Eingabe
END

Bauen wir diese Prüfung noch in unsere Hauptprozedur ein:

PROCEDURE Main
VAR msg : STRING
  CGIWriteLn('content-type: text/html')
  CGIWriteLn('')
  IF CGIGetParam('send') // Aufruf aus Formular?
  THEN
    HoleAngaben
    IF msg:=EingabePrüfung = '' // Alles IN Ordnung
    THEN DatensatzAnfügen
    ELSE WiederVorlage(msg)
    END
  ELSE
    WiederVorlage('Bitte geben Sie hier Ihre Adresse UND Ihre E-Mail ein')
  END
ENDPROC

Änderung bestehender Datensätze
Zum Abschluss dieser Lektion wollen wir noch an die Änderung bestehender Datensätze wagen. Es könnte ja sein, dass jemand umzieht oder seine E-Mail ändert.

Wiederum wollem wir an der E-Mail-Adresse die Identität festmachen (das Feld EMail hat in unserer Datenbank demnach die Funktion eines Primärschlüssels).

Wir benötigen diesmal erst nur ein kleines Eingabeformular, in das eine E-Mail-Adresse eingetragen werden kann und das unser Editierprogramm startet. Der submit-Schalter sollte hier natürlich nicht den Namen »send« haben, damit wir ihn einfach vom submit-Schalter unseres Adressen-Formulars unterscheiden können. Wir wählen den Namen »edit«:

<html>
<body bgcolor="white">
<form action="#action#" method="post">
E-Mail: <Input type="text" name="Email"><br>
<Input type="submit" name="edit" value="bearbeiten">
</form>
</body>
</html>

Speichern Sie dieses Formular unter »templates/adressen_edit.html«.

Das Editierprogramm kann dann so skizziert werden:

FALLS Email<>''
  FALLS send gedrückt // Datensatz wurde aktualisert
    Aktualisiere_Datensatz
  SONST
    FALLS Email_in_Datenbank=1
      Wiedervorlage // mit den zugehörigen Daten
    SONST
      Fehlermeldung
    ENDE
  ENDE
SONST
  Eingabe_wiederholen
ENDE

Die erste Abfrage »FALLS Email<>''« sorgt zum Einen dafür, dass wir in unserer Datenbank nicht nach leeren Einträgen suchen müssen, und zum Anderen, dass wir das Programm wiederum per Link oder URL-Eingabe starten können, denn hier kommt dann »Eingabe_wiederholen« zum Tragen.

Zur Speicherung der gefundenen Informationen verwenden wir wieder die gleichen Variablen wie bei der Neuaufnahme:

VAR Name, Vorname, Strasse, PLZ, Ort, Telefon, EMail : STRING

Zusätzlich benötigen wir noch eine Variable für die Satznummer des zu bearbeitenden Datensatzes.

VAR Satznummer : STRING

Da »HoleEmail« nur aus der einzelnen Anweisung »Email:=CGIGetParam('Email')« besteht, wollen wir hier auf eine eigenen Prozedur verzichten und die Anweisung selbst in die Hauptprozedur aufnehmen.

Kommen wir zur Funktionsprozedur »Email_in_Datenbank«. Im positiven Fall, also wenn wir die Email finden, soll diese Prozedur auch gleich die Variablen »Name«, »Vorname« etc. setzen, denn schließlich haben wir in dieser Prozedur die Tabelle geöffnet.

PROCEDURE Email_in_Datenbank : REAL
VAR db, x, hits : INTEGER
  SetPara('ec 1')
  IF db:=OpenDB('database/adressen.dat')=0 THEN
    CGIWriteLn('Datenbank-Fehler'); Halt
  ELSE
    // hier ist die Tabelle geöffnet
    hits:=FindAndMark(db,'$Email=Email')
    IF hits=0 // kein Treffer
    THEN CGIWriteLn('Leider NICHT gefunden')
    ELSIF hits>1 // Holla, NICHT eindeutig!
    THEN CGIWriteLn('Adresse NICHT eindeutig zu bestimmen')
    ELSE // eindeutig gefunden
      ReadRec(db,x:=FirstMark(db))
      Name:=GetField(db,'Name')
      Vorname:=GetField(db,'Vorname')
      Strasse:=GetField(db,'Strasse')
      PLZ:=GetField(db,'Strasse')
      Ort:=GetField(db,'Ort')
      Telefon:=GetField(db,'Telefon')
      Email:=GetField(db,'Email')
      Satznummer:=Str(x)
    END
    CloseDB(db)
  END
  SetPara('ec 0')
  RETURN hits
ENDPROC

Satznummer übergeben
Die Prozedur Wiedervorlage können wir fast einszueins aus dem obigen Programm übernehmen. Wir müssen uns nur eine Möglichkeit überlegen, wie wir die Satznummer des gefundenen Datensatzes übergeben können, so dass die Prozedur »Aktualisere_Datensatz« später darauf zugreifen kann. Eine Möglichkeit bestünde darin, das zugehörige Template um ein entsprechendes Feld zu erweitern. Meist versteckt man innerhalb eines form-Tags solche Angaben in einem hidden-input-Feld (<input type=«hidden«...>). Die andere Möglichkeit besteht darin, die zusätzliche Information in den Programmaufruf (also die action-URL) zu verpacken. Das bewerkstelligen wir, indem wir

Subst('#action#',ParamStr(0))

ersetzen durch

Subst('#action#',ParamStr(0)+'?RecNo='+Satznummer)

Bekanntermaßen kann dann die Prozedur Aktualisere_Datensatz mit der Funktion GetQueryString() auf diesen Parameter zugreifen.

PROCEDURE Aktualisiere_Datensatz
VAR db, RecNo : INTEGER
  SetPara('ec 1')
  IF db:=OpenDB( 'database/adressen.dat','',0,2)=0
  THEN CGIWriteLn('Tabellen-Felder'); Halt
  ELSE
    RecNo:=Val(GetQueryString('recno'))
    IF RecNo=0 OR recno>FileSize(db)
    THEN CGIWriteLn('Illegale Satznummer')
    ELSE
      ReadRec(db,RecNo)
      HoleAngaben
      SetField(db,'Name',Name)
      SetField(db,'Vorname',Vorname)
      SetField(db,'Strasse',Strasse)
      SetField(db,'PLZ',PLZ)
      SetField(db,'Ort',Ort)
      SetField(db,'Telefon',Telefon)
      SetField(db,'Email',Email)
      RecNo:=WriteRec(db,RecNo)
      IF RecNo=0 THEN
        CGIWriteLn('Datensatz konnte NICHT geschrieben werden')
      ELSE
        CGIWriteLn('Datensatz wurde erfolgreich geschrieben')
      END
    END
    CloseDB(db)
  END
  SetPara('ec 0')
ENDPROC

In dieser Prozedur wird auf »HoleEingaben« zurückgegriffen, die aus dem obigen Programm entnommen werden kann.

Bleibt noch die Hauptprozedur:

PROCEDURE Main
  CGIWriteLn('content-type: text/html')
  CGIWriteLn('')
  Email:=CGIGetParam('Email')
  IF Email<>'' THEN
    IF CGIGetParam('send') THEN // Datensatz wurde aktualisert
      Aktualisiere_Datensatz
    ELSE
      IF Email_in_Datenbank=1
        WiederVorlage('Sie können die Angaben ändern')
      ELSE
        // Fehlermeldung
      END
    END
  ELSE
    LoadTemplate('templates/adressen_edit.html')
    Subst('#action#',ParamStr(0))
    CGIWriteTemplate
  END
ENDPROC

In der nächsten (und abschließenden) Lektion dieses Einführungskurses werden wird die einzelnen Programmteile zu einer kompletten Internet-Datenbank zusammenführen.

Aufgabe:
Ändern Sie das Programm zum Ändern bestehender Datensätze so ab, dass auch hier Fehleingaben überprüft werden.



tdbengine Anwendungen im Web:

Open-Source Web CMS


Open-Source Bug-Tracking


Free wiki hosting

Open-Source Wiki-System

Kostenloses Foren-Hosting

Diät mit tdbengine 8-)

tdbengine chat
irc.tdbengine.org
#tdbengine

   Copyright © 2003-2004 tdb Software Service GmbH
   Alle rechte vorbehalten. / All rights reserved
   Letzte Änderung: 05.05.2004


ranking-charts.de

Programmers Heaven - Where programmers go!