Formulare "manuell" erstellen

Wenn Sie in Ihrer Application Express-Anwendung ein Formular benötigen, nutzen Sie am besten den Formular-Assistenten ( Erstellen - Region - Formular). Nachdem Sie die Tabelle ausgewählt, die Spalten zum Bearbeiten und die Details zum Primärschlüssel bestimmt haben, erledigt Application Express den ganzen Rest für Sie. Allerdings gibt es Fälle, in denen der Assistent an seine Grenzen stösst ...

  • Ihre Tabelle hat mehr als zwei Primärschlüsselspalten
  • Sie möchten mehrere Tabellen mit einem Formular bearbeiten
  • Sie möchten gleichzeitig eine Stored Procedure aufrufen
  • und, und, und ...

In diesen Fällen müssen Sie das Formular entweder nachbearbeiten oder gar komplett manuell erstellen. Für das (manuelle) Erstellen von Formularen empfiehlt sich eine bestimmte Vorgehensweise - diese stellen wir im Folgenden vor. Ausgangspunkt ist die Tabelle ANGESTELLTE, welche Sie im SQL Workshop mit diesem Skript erstellen.

1. Formularelemente erstellen

Erzeugen Sie auf der Anwendungsseite eine neue Region vom Typ HTML. Die Regionsquelle können Sie leer lassen. Erzeugen Sie anschließend neue Elemente, in dem Sie in der Seitenbearbeitung unter Elemente rechts auf Erstellen klicken. Klicken Sie am besten direkt den Link Mehrere Elemente erstellen unten an.


Elemente erstellen

Abbildung 1: Elemente erstellen

Erstellen Sie die Elemente, die Sie benötigen und bearbeiten Sie diese dann ggfs. nach. In der Darstellung in Abbildung 2 ist das Element für den Primärschlüssel (P1_ID) ausgeblendet - dies muss jedoch nicht so sein - Texteingaben, Auswahllisten oder andere Elementtypen sind ebenso möglich.

Elemente erstellen: Mehrere Elemente auf einmal erstellen

Abbildung 2: Mehrere Elemente auf einmal erstellen

2. Schaltflächen erstellen

Erzeugen Sie nun die Schaltflächen, die Sie benötigen. Wie für Elemente gibt es auch für Schaltflächen die Möglichkeit, mehrere auf einmal zu erstellen.

Mehrere Schaltflächen auf einmal erstellen

Abbildung 3: Mehrere Schaltflächen auf einmal erstellen

Anschließend sollte das Formular in etwa wie in Abbildung 4 aussehen.

Zwischenergebnis: Das Formular

Abbildung 4: Zwischenergebnis: Das Formular

Nun ist das Formular scheinbar fertig - wenn Sie allerdings etwas eintragen und eine der Schaltflächen klicken, passiert nichts weiter - Sie werden lediglich eine Fehlermeldung erhalten, die besagt, dass keine Verzweigung zur Verfügung gestellt wurde ...

3. Prozesse und Verzweigungen einrichten

Nun geht's los: Bis hierhin hätte der Application Express-Assistent die Aufgabe ebenso erledigen können. Nun geht es an die Prozesse, welche die Formulareingaben in die Datenbanktabelle speichern bzw. die Formularinhalte aus der Tabelle lesen.

Implementieren Sie zunächst den Prozess zum Lesen der Formularinhalte aus der Tabelle. Erstellen Sie einen Prozess vom Typ PL/SQL, der Beim Laden - vor Regionen ausgeführt wird (Abbildung 5).

onLoad-Prozess vom Typ PL/SQL erstellen

Abbildung 5: onLoad-Prozess vom Typ PL/SQL erstellen

Hinterlegen Sie folgenden PL/SQL-Code als Prozessquelle.

begin
  if :P1_ID is not null then
    begin
      select nachname, vorname, to_char(geburtsdatum,'DD.MM.YYYY'), gehalt
      into :P1_NAME, :P1_VORNAME, :P1_GEBURTSDAT, :P1_GEHALT
      from angestellte
      where id = :P1_ID;
    exception
      when NO_DATA_FOUND then
        :P1_ID := null;
      when TOO_MANY_ROWS then
        :P1_ID := null;
    end;
  end if;
end;

Wenn die Seite geladen wird und das Element P1_ID einen Wert enthält (not null), dann wird die SQL-Abfrage ausgeführt und die Inhalte der Tabellenspalten in die Formularelemente geladen. Wird keine Zeile gefunden, passiert nichts. Natürlich können Sie hier auch mehrere Tabellen selektieren, PL/SQL-Funktionen ausführen oder andere Dinge tun. Da der Prozess vom Typ PL/SQL ist, sind Sie völlig frei in Ihrem Tun.

Als nächstes geht es ans Speichern der Änderungen. Erzeugen Sie wiederum einen PL/SQL-Prozess, wählen Sie als Ausführungspunkt jedoch nun Bei Weiterleitung - Nach Berechnungen und Validierungen. Nennen Sie ihn Eingaben verarbeiten.

onSubmit-Prozess vom Typ PL/SQL erstellen

Abbildung 6: onSubmit-Prozess vom Typ PL/SQL erstellen

Sie können nun für jede Schaltfläche einen eigenen Prozeß erstellen. Im weiter hinten folgenden Dialog können Sie dann die Schaltfläche auswählen, für die jener Prozeß gültig sein soll. Hier gehen wie jedoch einen anderen Weg: Um die Bedeutung der Variable REQUEST vorzustellen, wird der gesamte PL/SQL-Code für alle Schaltflächen in einen einzigen Prozeß aufgenommen.

Die Variable REQUEST enthält in einem onSubmit-Prozeß stets den Namen (nicht das Label) der geklickten Schaltfläche - dies ist insbesondere nützlich, wenn Sie ein- und denselben Prozeß mit mehreren Schaltflächen aktivieren möchten - die Standard-Dialoge erlauben nur die Auswahl einer Schaltfläche. Der folgende PL/SQL-Code enthält die Datenbankaktionen für alle drei Schaltflächen.

begin
  if :REQUEST = 'CREATE' then
    insert into angestellte (nachname, vorname, geburtsdatum, gehalt)
    values (:P1_NAME, :P1_VORNAME, to_date(:P1_GEBURTSDAT,'DD.MM.YYYY'), :P1_GEHALT);
  elsif :REQUEST = 'DELETE' then
    delete from angestellte where id = :P1_ID;
  elsif :REQUEST = 'CHANGE' then
    update angestellte set
      nachname = :P1_NAME,
      vorname = :P1_VORNAME,
      geburtsdatum = to_date(:P1_GEBURTSDAT, 'DD.MM.YYYY'),
      gehalt = :P1_GEHALT
    where id = :P1_ID;
  end if;
end; 

Dieser Prozeß soll ausgeführt werden, wenn irgendeine der drei Schaltflächen angeklickt wurde. Strenggenommen bräuche man keine Bedingung. Da jedoch in Zukunft weitere Schaltflächen auf der Seite angelegt werden könnten, versehen wir den Prozeß zur Sicherheit noch mit einer Bedingung.

Bedingung für den Prozeß einrichten

Abbildung 7: Bedingung für den Prozeß einrichten

Nun ist (fast) alles fertig. Es fehlen nur noch einige Kleinigkeiten. Erstellen Sie eine Seitenverzweigung - damit wird festgelegt, auf welche Seite nach Klick auf eine der Schaltflächen navigiert werden soll. In den meisten Fällen ist dies eine andere Seite (u.U. mit einem Bericht auf die Tabelle). Klicken Sie dazu in der Seitenbearbeitung unter Verzweigungen auf Erstellen .

Erstellen Sie die Verzweigung wie in Abbildung 8 dargestellt. In diesem Beispiel wird nach Klick auf die Schaltfläche auf die Seite 10 verzweigt - jede andere (auch die Formularseite selbst) ist möglich. Interessant ist noch die Einstellung Cache Inhalt leeren - die dort eingetragene Seite 1 bewirkt, dass alle Formularelemente dieser Seite bei Ausführung der Verzweigung geleert werden.

Verzweigung einrichten

Abbildung 8: Verzweigung einrichten

Zum Abschluß des Formulars macht es noch Sinn, Bedingungen an die einzelnen Schaltflächen zu knüpfen. So sollte die Schaltfläche Erstellen nur angezeigt werden, wenn das Formular leer ist - ist es bereits mit Werten gefüllt (wenn ein existierender Datensatz bearbeitet wird), sollten nur die Schaltflächen Ändern und Löschen angezeigt werden.

Dies ist einfach zu bewerkstelligen - Navigieren Sie in der Seitenbearbeitung zu den Eigenschaften der einzelnen Schaltflächen und dort zu den Bedingungen. Für die Schaltfläche Erstellen (CREATE) hinterlegen Sie die Bedingung vom Typ Element ist NULL - als Element wählen Sie P1_ID (Abbildung 9). Die Bedingung der anderen beiden Schaltflächen Ändern (CHANGE) und Löschen (DELETE) richten Sie analog ein - nehmen Sie hier auch P1_ID, als Bedingstyp wählen Sie allerdings Element ist nicht NULL aus.

Schaltflächenbedingungen einrichten

Abbildung 9: Schaltflächenbedingungen einrichten

Damit ist das Formular fertig - Sie können es nun wie ein vom Assistenten erstelltes verwenden und mit anderen Seiten (Berichten) verknüpfen. Doch was passiert im Mehrbenutzerumfeld?

  • t0: Anwender 1 lädt den Datensatz X in das Formular
  • t1: Anwender 1 geht in die Pause
  • t2: Anwender 2 lädt den gleichen Datensatz X auf seinem PC
  • t3: Anwender 2 macht Änderungen und speichert diese sofort ab
  • t4: Anwender 1 kommt zurück, macht in seinem Formular Änderungen und speichert ab

Mit dem bis hierhin erstellten Formular würden die Änderungen von Anwender 2 verlorengehen. Das liegt am Transaktionsverhalten der Application Express-Prozesse.

Für Fortgeschrittene: Transaktionsverhalten und Sperrmechanismen

Für dieses Problem gibt es grundsätzlich zwei Strategien: Optimistisches und Pessimistisches Sperrverhalten. Pessimistisches Sperren bedeutet allgemein, dass beim Laden des Formulars statt des einfachen SELECT ein SELECT ... FOR UPDATE verwendet wird - in diesem Fall könnte Anwender 2 den Datensatz nicht mehr in sein Formular laden; er müsste warten oder er würde eine Fehlermeldung erhalten.

In Application Express ist dieses pessimistische Sperren gar nicht möglich - der Grund dafür ist, dass nach Abschluß jedes Prozesses ein COMMIT gesendet und die Transaktion damit abgeschlossen wird - eine atomare Datenbanktransaktion über mehrere Prozesse ist also nicht möglich. Wenn Sie also SQL-Anweisungen haben, die entweder ganz oder gar nicht ausgeführt werden sollen, achten Sie darauf, diese in einen einzigen Prozeß aufzunehmen.

In Application Express wird also das optimistische Sperren verwendet - ein Datensatz kann grundsätzlich also geladen werden - wenn beim Speichern jedoch festgestellt wird, dass ein Konflikt vorliegt, so wird ein Fehler ausgelöst. Übersetzt auf obigen Fall bedeutet dies also, dass Anwender 1 eine Fehlermeldung bekommt, sobald er speichern möchte. Diese Meldung teilt ihm mit, dass ein anderer Anwender den Datensatz bereits geändert hat. Die vom Application Express-Assistenten automatisch erstellten Formulare tun genau dies.

4. Konflikterkennung mit MD5-Prüfsumme hinzufügen

Als nächstes geht es also daran, vor dem Speichern festzustellen, ob ein anderer Nutzer zwischenzeitlich Änderungen an diesem Datensatz vorgenommen hat. Dies lässt sich am einfachsten über eine Prüfsumme realisieren. Und dazu stellt Application Express Ihnen die PL/SQL-Prozedur APEX_ITEM.MD5_CHECKSUM bereit. Ändern Sie den vorhin erzeugten onLoad-Prozeß Formular laden wie folgt um:

begin
  if :P1_ID is not null then
    begin
      select nachname, vorname, to_char(geburtsdatum,'DD.MM.YYYY'), gehalt
      into :P1_NAME, :P1_VORNAME, :P1_GEBURTSDAT, :P1_GEHALT
      from angestellte
      where id = :P1_ID;
      htp.p(apex_item.md5_checksum(:P1_NAME, :P1_VORNAME, :P1_GEBURTSDAT, :P1_GEHALT));
    exception
      when NO_DATA_FOUND then
        :P1_ID := null;
      when TOO_MANY_ROWS then
        :P1_ID := null;
    end;
  end if;
end; 

Die zusätzliche Zeile htp.p(apex_item.md5_checksum(...)) bewirkt, dass eine MD5-Prüfsumme über die Formularelemente berechnet und in die Seite mit aufgenommen wird. Nun muss beim Speichern nur noch verglichen werden - am einfachsten erreicht man dies mit einer Validierung - nehmen Sie also eine neue Validierung in Ihre Seite auf. Navigieren Sie dazu in der Seitenbearbeitung zu den Validierungen und dort auf Erstellen . Wählen Sie eine Validierung auf Seitenebene vom Typ Funktion, die booleschen Wert zurückgibt.

Seitenvalidierung zur Prüfung MD5-Summe erstellen

Abbildung 10: Seitenvalidierung zur Prüfung MD5-Summe erstellen

Hinterlegen Sie folgenden PL/SQL-Code. Wichtig ist, dass der Aufruf der Funktion APEX_ITEM.MD5_CHECKSUM mit exakt den gleichen Parametern wie im onLoad-Prozeß Formular laden erfolgt. Wenn also (wie hier) das Geburtsdatum im onLoad-Prozeß in die Schreibweise DD.MM.YYYY umgewandelt wird, so muss dies hier ebenso erfolgen.

declare
  v_result boolean;
  v_checksum varchar2(100);
begin
  select
    xmltype(
      apex_item.md5_checksum(
        nachname,
        vorname,
        to_char(geburtsdatum,'DD.MM.YYYY'),
        gehalt
      )
    ).extract('/input/@value').getstringval() into v_checksum
  from angestellte 
  where id =:P1_ID FOR UPDATE NOWAIT;
  if apex_application.g_fcs(1) = v_checksum then
    v_result := true;
  else
    v_result := false;
  end if;
  return v_result;
end; 

Da diese Summenprüfung nur Sinn macht, wenn ein bestehender Datensatz geändert oder gelöscht wird, sollten Sie auch eine entsprechende Bedingung festlegen. Wählen Sie bei den Bedingungen der Validierung den Typ PL/SQL-Ausdruck aus und hinterlegen Sie :REQUEST in ('DELETE','CHANGE'). Es sind natürlich auch andere Bedingungen denkbar - so wäre es möglich, (für Superuser) dem Formular eine Checkbox hinzuzufügen. Klickt der Anwender die Checkbox an, so werden die Eingaben auf jeden Fall gespeichert, egal ob zwischenzeitlich Änderungen stattfanden ...

Wenn Sie nun einen Datensatz in das Formular laden, diesen zwischenzeitlich mit SQL*Plus oder im SQL Workshop ändern und anschließend eine Änderung über das Formular absetzen, dann sollte das Ergebnis wie in Abbildung 11 aussehen.

Das Ergebnis: Fehlermeldung bei zwischenzeitlich geänderten Daten

Abbildung 11: Das Ergebnis: Fehlermeldung bei zwischenzeitlich geänderten Daten

Damit können Ihre Formulare beliebige Aktionen durchführen - ob es dabei ...

  • um Tabellen mit mehr als zwei Primärschlüsselspalten,
  • um Änderungen an mehreren Tabellen,
  • um Änderungen Tabellen und den Aufruf von Stored Procedures
  • oder andere Aktionen

... geht - Alles ist möglich.

Zurück zur Community-Seite