Heute, im zweiten Teil, geht's um Formulare und ein simples
Online-Order-System. Ein CGI-Skript kann mit CGI.pm
dem Client
nicht nur den notwendigen HTML-Code senden, sondern
sogar die eingegebenen Parameter in Empfang nehmen und wiederum
HTML zurückschicken.
Abbildung 1 zeigt, was mit reinem HTML heutzutage alles an Tipp- und Klick-Schnickschnack möglich ist: Popup-Menüs, die auf Knopfdruck hervorschnellen und eine Auswahl zulassen, drückbare Radio- und Checkbuttons, ein- oder mehrzeilige Textfelder, scrollbare Listen und schließlich Knöpfe zum Senden bzw. Zurücksetzen der Formularinformation.
Das CGI-Skript aus Listing form.pl
zeichnet für diese Ausgabe
verantwortlich. Die Tags :standard
und :html3
exportieren
aus CGI.pm
reguläre HTML- und Tabellen-Funktionen.
Zeile 7 legt den HTML-Code für ein Popup-Menü in der Variablen
$popup_menu
ab. Das Teil trägt den Namen farbe1
. Und genauso
wird auch die Variable heißen, die der Browser nach dem Absenden
des Formulars zurück an den Server sendet, gesetzt auf den
vom Benutzer gewählten Wert. 'r'
, 'g'
und 'b'
stehen
intern zur Auswahl, der Benutzer freilich bekommt nur die über den
Hash %labels
gemappten Wörter Rot
, Grün
und Blau
zu sehen.
'r'
, also 'Rot'
selektiert der Browser vor.
Eine radio_group
wie die in Zeile 13 besteht aus einer Gruppe von
Radio-Buttons, von denen genau einer selektiert ist und so den Wert der
Ausgangsvariablen bestimmt.
Die textfield
- bzw. textarea
-Elemente aus den Zeilen
19 bzw. 23 unterscheiden sich nur durch die Anzahl der Zeilen
des Eingabefensters - eines für textfield
, beliebig viele für die
textarea
.
scrolling_list
aus Zeile 29 funktioniert ähnlich wie das
popup_menü
weiter oben, nur daß die Option -size
die Anzahl der
sichtbaren Einträge bestimmt (der Rest ist über einen Scrollbar erreichbar)
und mehrere Einträge selektiert werden können, wenn -multiple
auf
'true'
steht.
Die checkbox_group
aus Zeile 37 ähnelt der radio_group
aus Zeile 13, nur
daß sie auch mehrere Optionen gleichzeitig zur Auswahl zuläßt.
Gibt's statt freier Auswahl nur Ja oder Nein, tut's auch die
Einzel-Checkbox aus Zeile 44.
Der Submit-Button aus Zeile 50 dient zum Absenden des Formulars. Die
-value
-Option bestimmt seine Beschriftung. Der Browser
übermittelt diesen Wert in der Variable, die über den -name
-Eintrag
definiert ist. So kann man serverseitig feststellen, welcher von eventuell
mehreren Submit-Buttons gedrückt wurde.
Der Reset-Button kommt ohne Parameter aus, da er lediglich die Formularparameter auf die ursprünglich gesetzten Werte zurücksetzt, nachdem der Benutzer daran herumgespielt hat.
Ab Zeile 55 macht sich form.pl
daran, den ganzen Sermon auszugeben,
angefangen vom Header und der start_html
-Sequenz. Die start_form
-Routine
beginnt die HTML-Formular-Defininition und setzt die Übertragungsmethode
auf GET
(Standard ist POST
) und die -action
, das aufzurufende
CGI-Skript auf /cgi-bin/dump.pl
- unser letztens vorgestelltes
CGI-Debug-Skript.
Ab Zeile 62 verpackt form.pl
die Formular-Bestandteile in eine
zweispaltige Tabelle mit Rahmen und setzt end_form
bzw. end_html
dahinter,
um Formular und HTML-Code sauber abzuschließen.
Ins cgi-bin
-Verzeichnis des Web-Servers verfrachtet, liefert form.pl
einem mit http://server/cgi-bin/form.pl
darauf deutenden
Browser das in Abbildung 1 dargestellte Bild.
Drückt der Benutzer den Submit-Knopf
mit der Aufschrift Absenden
, kontaktiert der Browser das in der
start_form
-Routine gesetzte Skript cgi-bin/dump.pl
nach der GET
-Methode. Dieses gibt vor lauter Schreck
die Werte nach Abbildung 3 aus.
01 #!/usr/bin/perl -w 02 ###################################################################### 03 # Michael Schilli, 1998 (mschilli@perlmeister.com) 04 ###################################################################### 05 06 use CGI qw/:standard :html3/; 07 08 %labels = ('r' => 'Rot', 'b' => 'Blau', 'g' => 'Grün'); 09 10 $popup_menu = popup_menu( ### Popup-Menü 11 '-name' => 'farbe1', # Name des Feldes 12 '-values' => ['r', 'g', 'b'], # Einzelwerte 13 '-default' => 'r', # Voreingestellt 14 '-labels' => \%labels); # Wert -> angezeigter Name 15 16 $radio_group = radio_group( ### Gruppe von Radio 17 '-name' => 'farbe2', # Name des Feldes 18 '-values' => ['r', 'g', 'b'], # Einzelwerte 19 '-default' => 'r', # Vorausgewählt 20 '-labels' => \%labels); # Name -> angezeigter Name 21 22 $textfield = textfield( ### Einzeiliger Text 23 '-name' => 'farbe3', # Name des Feldes 24 '-default' => ''); # Ist anfangs leer 25 26 $textarea = textarea( ### Mehrzeiliger Text 27 '-name' => 'farbe4', # Name des Feldes 28 '-default' => '', # Ist anfangs leer 29 '-rows' => 2, # Zwei Zeilen 30 '-columns' => 20); # 20 Zeichen breit 31 32 $scrolling_list = scrolling_list( ### Scrollbare Liste 33 '-name' => 'farbe5', # Name des Feldes 34 '-values' => ['r', 'g', 'b'], # Wählbare Werte 35 '-default' => ['r', 'g'], # Vorselektiert 36 '-size' => 3, # Höhe der Box 37 '-multiple' => 'true', # Multiple Auswahl OK 38 '-labels' => \%labels); # Name -> angezeigter Name 39 40 $checkbox_group = checkbox_group( ### Gruppe von Schaltern 41 '-name' => 'farbe6', # Name des Feldes 42 '-values' => ['r', 'g', 'b'], # Einzelwerte der Schalter 43 '-default' => 'r', # 1. Schalter gedrückt 44 '-linebreak' => 'true', # Untereinander aufreihen 45 '-labels' => \%labels); # Name -> angezeigter Name 46 47 $checkbox = checkbox( ### Einzelknopf 48 '-name' => 'farbe7', # Name des Feldes 49 '-checked' => 'checked', # Vorgewählt 50 '-value' => 'ja', # Wert falls gedrückt 51 '-label' => 'Ja?'); # Dargestellter Text 52 53 $submit = submit( ### Sende-Knopf 54 '-name' => 'submit_knopf', # Name des Feldes 55 '-value' => 'Absenden'); # Beschriftungstext und gelie- 56 # ferter Wert falls ausgelöst 57 58 $reset = reset( ### Reset-Knopf 59 '-value' => 'Zurücksetzen'); # Beschriftungstext 60 61 print header, # Alles als HTML ausgeben 62 start_html('-title' => 'Form Example', 63 '-BGCOLOR' => '#e0e0e6'), 64 65 start_form('-method' => 'GET', # Formular-Anfang und Aktions-URL 66 '-action' => '/cgi-bin/dump.pl'), 67 68 table({'border' => 1}, # Tabelle mit Formularelementen 69 TR(td(tt("popup_menu")), td($popup_menu)), 70 TR(td(tt("radio_group")), td($radio_group)), 71 TR(td(tt("textfield")), td($textfield)), 72 TR(td(tt("textarea")), td($textarea)), 73 TR(td(tt("scrolling_list")), td($scrolling_list)), 74 TR(td(tt("checkbox_group")), td($checkbox_group)), 75 TR(td(tt("checkbox")), td($checkbox)), 76 TR(td(tt("submit")), td($submit)), 77 TR(td(tt("reset")), td($reset)), 78 ), 79 end_form, # Formularende 80 end_html; # HTML-Ende
Abb.1: Ausgabe von form.pl | Abb.2: ... nach dem Absenden |
Ungeachtet ob ein Parameter varname
via die GET
oder die
POST
-Methode zum CGI-Skript gelangt, liest
$val = param('varname')
den übermittelten Wert aus. Dabei erledigt CGI.pm
automatisch
die Dekodierung maskierter Spezialzeichen. Im Falle von Listen mit
multipler Auswahl liefert
@list = param('multvarname');
eine Liste gesetzter Werte.
Das sagte immer der Showmaster von ``Hopp oder Top!'', eine einst auf Tele 5 ausgestrahlte Quizsendung, in der ich eines Tages die Ehre hatte, mitzuspielen. Leider verlor ich unglücklich gegen eine Hausfrau aus dem Münchner Umland und zog grummelnd mit dem Trostpreis, einem halben Dutzend mit Simpsons-Zeichentrick-Motiven bedruckter Socken ab. Egal!
Mangels Showkarriere stelle ich heute also das vereinfachte Order-System
aus Listing shop.pl
vor,
dessen erste Seite zur Eingabe einer Kundennummer auffordert (siehe Abb. 3).
Ein Klick auf den Auf geht's-Knopf übermittelt diese an den Server,
der wiederum eine Seite mit einer Auswahl von drei Produkten zurückliefert
(siehe Abb. 4). Entscheidet sich der Benutzer für eines,
indem er den entsprechenden
Radio-Button selektiert und den Bestellen!-Knopf drückt, schreibt
shop.pl
Kundennummer: 12345 Bestellung: Prodigy, The Fat of the Land, DM 19,90
in die Datei orders.txt
im cgi-bin
-Verzeichnis und zeigt
dem Besteller die dritte Seite mit ein paar Dankesworten an (siehe Abb. 5).
Woher weiß shop.pl
die Nummer des Kunden, der die
Bestellen!-Taste auf der zweiten Seite drückte?
Die erste Seite ist lange weg, und auch der
Server weiß von nichts. Die Lösung: Kaum nimmt das Skript die Nummer
aus dem ersten
Formular entgegen, schmuggelt Zeile 31 sie mittels eines
Hidden Fields in das Bestellformular, sodaß der Kunde seine
Nummer unbewußt mitschickt, wenn er den Bestellen!-Knopf betätigt.
Das Hidden Field schleppt also die Kundennummer unsichtbar von der ersten Seite zur dritten.
Der flock
-Befehl aus Zeile 35 sichert sich das Exklusiv-Recht auf die
Datei orders.txt
. Kein zweiter Prozeß oder Thread darf das zur
gleichen Zeit. Da CGI-Skripts oft parallel ablaufen, ist diese
Sicherung notwendig, gleichzeitige Schreiber könnten sonst das Dateiformat
zerstören.
Hier muß ich noch erzählen, daß CGI.pm
tatsächlich so etwas
wie Status-Informationen behält: Muß es ein HTML-Formular malen,
und stellt fest, daß der Name eines Feldes bereits im Eingabe-Bereich
vorliegt, setzt es den voreingestellten Wert des Feldes auf den
empfangenen Wert. Im Fall des HIDDEN
-Felds kundennummer
hat dies zur
Folge, daß
print hidden(-name => 'kundennummer');
auch wie
print hidden(-name => 'kundennummer', -value => param('kundennummer');
reagiert und automatisch die Kundennummer weiterreicht.
Damit der Besteller bei eventuell auftretenden Fehlern nicht das unschöne
Internal Server Error zu sehen bekommt, steht der Hauptteil des
Skripts ab Zeile 11 in einem eval
-Konstrukt, Abstürze dazwischen
landen in Zeile 52, die eine Vertröstungs-Meldung ausgibt und die
wahre Fehlerursache in /tmp/errorlog
samt Datum festhält. So kommt
der Systemadministrator (bei komplexeren Systemen als dem vorgestellten)
sporadisch auftretenden Fehlern auf die Schliche.
Schluß für heute! Nächstes Mal kommen Cookies und Server-seitiges Status-Speichern dran. See ya!
Abb.3: Startseite des Order-Systems |
Abb.4: Bestellen ... |
Abb.5: ... fertig! |
shop.pl
01 #!/usr/bin/perl -w 02 ###################################################################### 03 # Michael Schilli, 1998 (mschilli@perlmeister.com) 04 ###################################################################### 05 06 use CGI qw/:standard/; # Cgi-Funktionen 07 use Fcntl qw/:flock/; # LOCK_EX 08 09 print header(); # Header ausgeben 10 11 %products = (1 => 'Perl für Frauen, O\'Reilly, DM 59.90', # Produkte 12 2 => 'Perl in 3 Tagen, SamsNet, DM 18.90', 13 3 => 'Go To Perl5, AWL, DM 59.00'); 14 15 eval { # Fehler abfangen 16 17 if(!defined param('kundennummer')) { # Keine Kundennummer? 18 print start_html('-title', 'Willkommen'), # -> Startseite 19 h1('Willkommen im Perl-Buchladen!'), 20 start_form(), "Ihre Kundennummer:", 21 textfield(-name => 'kundennummer'), 22 submit(-value => "Einkaufen geh'n!"), 23 end_form(), end_html(); 24 25 } elsif(!defined param('bestellung')) { # Keine Bestellung? 26 print start_html('-title', 'Bestellung'), # -> Bestellseite 27 h1("Unser Sortiment:"), start_form(), 28 checkbox_group( 29 '-name' => 'bestellung', 30 '-values' => [keys %products], 31 '-linebreak' => 'true', # Untereinander 32 '-labels' => \%products), # Produkte 33 p(), submit(-value => 'Bestellen'), # Bestellknopf 34 hidden(-name => 'kundennummer'), # Weiterreichen 35 end_form(), end_html(); 36 37 } else { # Bestellung speichern 38 open(ORDER, ">>orders.txt") || die "Cannot open orders.txt"; 39 flock(ORDER, LOCK_EX); # Lock setzen 40 print ORDER "Kundennummer: ", param('kundennummer'), 41 " Bestellung: ", $products{param('bestellung')}, "\n"; 42 close(ORDER); 43 44 print start_html('-title', 'Danke!'), # Danke!-Seite 45 p(), "Vielen Dank. ", 46 i($products{param('bestellung')}), 47 " geht Ihnen in den nächsten Tagen zu.", p(), 48 p(), "Der fällige Betrag wird mit Kundennummer ", 49 param('kundennummer'), " verrechnet.", 50 p(), "Viel Spaß damit!", 51 start_form(), submit(-value => "Zurück zum Eingang"), 52 end_form(), end_html(); 53 } 54 }; 55 56 if ($@) { # Fehler? 57 print "Unser System kann Ihre Order leider momentan nicht " . 58 "entgegennehmen. Bitte versuchen Sie es später nochmal.\n"; 59 open(ERRORLOG, ">>/tmp/errorlog"); 60 print ERRORLOG scalar localtime, "> $@"; 61 close(ERRORLOG); 62 }
Michael Schilliarbeitet als Software-Engineer bei Yahoo! in Sunnyvale, Kalifornien. Er hat "Goto Perl 5" (deutsch) und "Perl Power" (englisch) für Addison-Wesley geschrieben und ist unter mschilli@perlmeister.com zu erreichen. Seine Homepage: http://perlmeister.com. |