Weiter Zurück [Inhalt] Online Suche im Handbuch

49.2 Suchen und Finden

Eine gefüllte Datenbank liefert Ergebnisse auf Suchabfragen in Tabellenform nach Abbildung 3. Das Namensfeld jedes Eintrags ist mit einem Link unterlegt, klickt man darauf, springt der Browser auf die Editierseite und füllt die Felder dort gleich mit den Daten des ausgewählten Eintrags. Der Speichern-Knopf aktualisiert den Datenbank-Eintrag entsprechend den Formularfeldern, ein Druck auf den Delete-Knopf löscht den Eintrag:

name="http://www.linux-magazin.de/ausgabe.1999.05/DBflat/list.gif>"

Was addr.pl im einzelnen tut, ob es ein Eingabeformular darstellt oder einen neuen Eintrag anlegt oder das Ergebnis einer Suchanfrage anzeigt, bestimmen die CGI-Parameter mit denen es aufgerufen wird. Folgende Szenarien steuert addr.pl:

Das Skript addr.pl im Detail:

Listing addr.pl zeigt die Implementierung des Web-Adreßbuches. Zeile 7 holt das CGI-Modul, die angegebenen Tags lassen es die Standard-HTML- und die Tabellen-Funktionen exportieren. Das CGI::Carp-Modul sorgt dafür, daß der Browser bei auftretenden Fehlern nicht das blöde Internal Server Error anzeigt, sondern einen aufschlußreiche Fehlermeldung. Die Zeilen 11 bis 14 spezifizieren die Parameter für den DBI-Flatfile-Treiber. $DB_DIR gibt das Verzeichnis unterhalb des cgi-bin-Verzeichnisses an, das die Tabellendaten als Datei enthält.

Zeile 20 nimmt die Verbindung mit der "virtuellen" Datenbank auf, die Zeilen 23 und 24 geben den CGI-Header und die Überschrift aus, die in jedem Fall im Dokument steht und färben den Hintergrund der Seite weiß ein. Dann scheiden sich die Wege: Der if-Block ab Zeile 26 wird angesprungen, falls ein Benutzer die Formularfelder für einen neuen Eintrag ausgefüllt und den Speichern-Knopf gedrückt hat. Der map-Befehl in Zeile 28 übergibt der insert_rec-Funktion, die die eigentliche Datenbank-Aktualisierung vornimmt, die Formulardaten, indem er für alle Elemente in @dbfields die param-Funktion des CGI-Moduls aufruft und so die entsprechenden CGI-Parameter entgegennimmt und weiterreicht.

insert_rec selbst steht ab Zeile 158 in addr.pl, nimmt das hereingereichete DB-Handle und die Formularparameter entgegen und setzt den SQL-Insert/Update-Befehl an die Datenbank ab. Ist der CGI-Parameter id gesetzt, handelt es sich um eine Aktualisierung eines bestehenden Records und Zeile 167 definiert einen SQL-Update-Befehl. Hier wie auch an anderen Stellen leistet der qq-Operator, der mehrzeilige Strings mit doppelten Anführungszeichen umschließt, nützliche Dienste. Fehlt andererseits id, handelt es sich um einen neuen Eintrag und Zeile 180 kreiert einen SQL-Insert-Befehl.

Zurück zur Hauptschleife: Die page_header-Funktion, die in Zeile 29 aufgerufen wird und ab Zeile 99 implementiert ist, klatscht das kleine Link-Alphabet, das in den Abbildungen 1 und 3 jeweils oben im Fenster zu sehen ist, dorthin und schreibt auch noch das Such-Feld samt den zwei Buttons auf die Seite. Die url()-Funktion aus dem CGI-Modul liefert hierzu den URL des gegenwärtig laufenden Skripts.

Ab Zeile 31 steht der Code zum Löschen eines Eintrags. Jede Zeile in der Datenbanktabelle enthält neben den Adreßbuchdaten auch noch eine eindeutige ID, die als verstecktes (hidden) Feld auf der Seite steht, die die Formularfelder zum Aktualisieren eines Eintrags darstellt. Drückt der Benutzer auf den Knopf Eintrag löschen, sendet der Browser neben den aktualisierten Feldern auch noch die ID mit und addr.pl kann einen DELETE-Befehl losschicken, der mit seinem Tintenkiller genau über die richtige Zeile der Tabelle fährt. Drückte der Benutzer entweder auf den Knopf Neuer Eintrag oder aber auf einen erleuchteten Namen der der Ergebnisliste, wird der Codeblock ab Zeile 40 angesprungen, da der Parameter edit in diesen Fällen gesetzt ist.

Diese zwei Fälle unterscheiden sich dahingehend, daß ein angeklickter Eintrag der Ergebnisliste den id-Parameter setzt. In diesem Fall muß addr.pl vor dem Darstellen der Felder die Werte aus der Datenbank übernehmen. Hierzu erzeugt es in Zeile 44 einen SQL-Select-Befehl, der die Daten aus der Datenbank holt. Die Zeilen 51 bis 56 holen das Ergebnis des Queries ab, wegen der eindeutigen ID im SELECT ist das Ergebnis stets eine einzelne Zeile. Die fetch-Methode im while-Kopf liefert eine Referenz auf einen Array zurück, dessen Elemente die Spaltenwerte des Tabelleneintrags beinhalten. Da die Tabellenzeile zusätzlich zu den in @dbfields aufgelisteten Spalten als erstes Element die id-Spalte führt, startet der Index $i in Zeile 52 mit dem Wert 1 statt des sonst üblichen 0 .

Der Aufruf der param-Methode in Zeile 54 manipuliert die CGI-Eingangsparameter und gaukelt den nachfolgenden Abfragen vor, der Benutzer hätte die Adreßdaten des selektierten Eintrags selbst eingetragen - derweil stammen sie aus der Datenbank. Die Zeilen 59 bis 80 geben eine zweispaltige HTML-Tabelle aus, die das Formular zum Anlegen/Editieren eines Adreßeintrags nach Abbildung 2 in den Browser zeichnet.

Für den Fall, daß der Benutzer eine Suchanfrage startete oder einen Buchstaben im Reiter-Alphabet des Seitenkopfes anklickte, ist der CGI-Parameter search gesetzt, entsprechend springt addr.pl den Block ab Zeile 82 an. Für den Buchstabenklick enthält search den entsprechenden Buchstaben, wurde etwas ins Suchfeld eingetragen und der Suche starten-Knopf gedrückt, steht in search der Suchbegriff. Die Funktion keyword_search übernimmt in beiden Fällen die Suche, sie ist ab Zeile 118 definiert. Dort holt eine SQL-Abfrage passende Records aus der Datenbank, indem sie mittels des CLIKE-Konstrukts überprüft, ob Vor- oder Nachname eines Eintrags mit dem gegebenen Suchausdruck beginnen, Groß- und Kleinschreibung werden ignoriert. Für einen leeren Suchstring liefert keyword_search großzügigerweise einfach alle Einträge der Tabelle.

Die while-Schleife ab Zeile 145 gibt die Treffer in einer HTML-Tabelle aus, indem sie Vor- und Nachnamen zu einer Tabellenspalte zusammenfaßt und einen HTML-Link drumherum baut, der die CGI-Parameter edit auf 1 und id auf die in der Datenbank gefundene ID des Eintrags setzt, so daß das Skript bei einem Klick auf den Eintrag sofort den Eintrag in der Datenbank referenzieren kann. Der Block ab Zeile 86 kommt nur bei der Installation des Skripts kurz zum Einsatz und ruft die Initialisierungsfunktion init_db auf, die ab Zeile 190 definiert ist, und das Unterverzeichnis der Pseudo-Datenbank erzeugt. Weiter setzt sie einen SQL-Create-Befehl ab, der die Pseudo-Tabelle anlegt. Ist überhaupt kein CGI-Parameter gesetzt (wie beim ersten Aufruf des Skripts), kommt Zeile 90 zum Einsatz und zeichnet lediglich den Seitenkopf mit dem Suchfeld und dem Reiteralphabet.

#!/usr/bin/perl -w
##################################################
# CGI Address Book
# 1999, mschilli@perlmeister.com
##################################################

use CGI qw/:standard :html3/;
use CGI::Carp qw/fatalsToBrowser/;
use DBI;

my $DB_DIR      = "./addressbook";
my $DB_DSN      = "DBI:CSV:f_dir=$DB_DIR";
my $DB_USER     = "";
my $DB_PASSWD   = "";

my @dbfields = qw/fname lname phone email addr 
                  notes/;
my $dbflist  = join(', ', @dbfields);

my $dbh = DBI->connect($DB_DSN, $DB_USER, 
   $DB_PASSWD) or die "Cannot connect to DB";

print header(), start_html(-BGCOLOR => 'white'),
      h1("Adreßbuch");

if(param('insert')) {
    # Insert/Update record according to form fields
    insert_rec($dbh, map { $dbh->quote(param($_)) } @dbfields);
    page_header();

} elsif(param('delete')) {
    # Delete a record according to ID field
    my $id = $dbh->quote(param('id'));
    $dbh->do(<<EOT) or die "Cannot delete data";
        DELETE FROM addressbook
        WHERE id = $id 
EOT
    page_header();

} elsif(param('edit')) {
    # Display fields for inserting/updating a rec
    if(param('id')) {
        $id = $dbh->quote(param('id'));
        # ID exists - Get record and preset fields
        my $sql = qq[SELECT id, $dbflist
                     FROM addressbook
                     WHERE id = $id];
        my $cursor = $dbh->prepare($sql) or 
                     die "Cannot select ($sql)";
        $cursor->execute() or die "SQL failed";

        while(defined($row = $cursor->fetch)) {
            my $i = 1;
            foreach $field (@dbfields) {
                param($field, $row->[$i++]);
            }
        }
    }

    print start_form(), 
      hidden(-name => 'id'),
      table({"border" => 1},
      TR(td("Vorname:"), 
         td(textfield(-name => 'fname'))),
      TR(td("Nachname:"), 
         td(textfield(-name => 'lname'))),
      TR(td("Telefon:"), 
         td(textfield(-name => 'phone'))),
      TR(td("Email:"), 
         td(textfield(-name => 'email'))),
      TR(td("Adresse:"), 
         td(textarea(-name => 'addr', -rows => 3))),
      TR(td("Notizen:"), 
         td(textarea(-name => 'notes', -rows => 3))),
      );

    print submit(-name  => 'insert', 
                 -value => 'Speichern'), 
          submit(-name  => 'delete', 
                 -value => 'Eintrag löschen'), 
          end_form();

} elsif(defined param('search')) {
    page_header();
    keyword_search($dbh, param('search'));

} elsif(param('init')) {
    page_header();
    init_db($dbh);

} else {
    page_header();
}

print end_html();

$dbh->disconnect();  # Datenbankverbindung lösen.

##################################################
sub page_header {
##################################################
    print start_form();
    foreach $letter ('A'..'Z') {
        print a({href => url() . 
          "?search=$letter"}, "$letter ");
    }
    print a({href => url() .  "?search="}, 
            " Alle Einträge"),
          p("Suchbegriff:", 
            textfield(-name => 'search'), 
            submit(-name => 'Search', 
                   -value => 'Suche starten'),
            submit(-name => 'edit', 
                   -value => 'Neuer Eintrag'));
    print end_form();
}

##################################################
sub keyword_search {
##################################################
    my ($dbh, $keyword) = @_;
    my $cursor; 
    my $where_clause = "";

    if($keyword ne "") {
        $keyword = $dbh->quote("$keyword%");
        $where_clause = qq[
            WHERE fname CLIKE $keyword OR
                  lname CLIKE $keyword];
    }

    my $sql = qq[ SELECT id, $dbflist
                  FROM addressbook
                  $where_clause
                  ORDER BY lname];

    $cursor = $dbh->prepare($sql) or 
              die "Select failed: $sql";

    $cursor->execute() or 
        die "Can't execute ($sql): ", $cursor->errstr;

    print "<TABLE BORDER=1>\n";
    print TR(map { th($_) } 
      qw/Name Telefon Email Adresse Notizen/);

    while(defined(my $row = $cursor->fetch)) {
        print TR(td(
        a({href => url() . "?id=$row->[0]=1"}, 
          "$row->[2], $row->[1]"), 
        td("$row->[3]"),
        td("$row->[4]"), td("$row->[5]"),
        td("$row->[6]"),
        )), "\n";
    }
    print "\n";
}

##################################################
sub insert_rec {
##################################################
    my($dbh, $fname, $lname, $phone,
       $email, $addr, $notes) = @_;

    if(param('id')) {
        # ID there, it's an update!
        my $id = $dbh->quote(param('id'));

        my $sql = qq[ 
          UPDATE addressbook
          SET id=$id, fname=$fname, 
              lname=$lname, phone=$phone, 
              email=$email, notes=$notes
          WHERE id = $id];

        $dbh->do($sql) or die "Update failed ($sql)";

    } else {
        # ID not there, it's a new record!
        my $id = time . $$;  # Generate ID
        $id = $dbh->quote($id);

        my $sql = qq[
          INSERT INTO addressbook
          (id, $dbflist)
          VALUES
                  $email, $addr, $notes)];
        $dbh->do($sql) or die "Insert failed ($sql)";
    }
}

##################################################
sub init_db {
##################################################
    my $dbh = shift;

    if(! -d $DB_DIR) {
        mkdir($DB_DIR, 0755) || 
            die "Cannot create dir $DB_DIR";
    }

    $dbh->do(<<'EOT') or die "Cannot create table";
        CREATE TABLE addressbook (
            id     char(20),
            fname  char(40),  lname char(40), 
            phone  char(20),  email char(40), 
            addr   char(100), notes char(100)
        )
EOT
}


Weiter Zurück [Inhalt] Online Suche im Handbuch