Mehrsprachigkeit mit Oracle APEX

Oracle APEX bietet out-of-the-box die Möglichkeit, eine Anwendung in mehreren Sprachen zu veröffentlichen. Wie das funktioniert, möchte ich Ihnen in diesem Beitrag näher erläutern.

TIPP!

Bitte beachten Sie auch meinen Post zum Thema

Gefahren und Fallen beim Umgang mit APEX Translations

In diesem Beitrag sind noch weitere nützliche Tipps enthalten, die Ihnen viel Zeit sparen werden.

Szenario

Wir möchten eine Anwendung schreiben, die in 2 Sprachen, Englisch und Deutsch, verfügbar ist. Die Sprache soll sich automatisch ändern, wenn die Browsersprache geändert wird.

Als Standardsprache für die Entwicklung legen wir die englische Sprache fest, da wir uns international positionieren möchten.

Als kleine Zusatzanforderung möchten wir, dass beim Wechsel der Sprache das Datumsformat sich automatisch auf das jeweilige  lokale Format anpasst (für deutsch: DD.MM.YYYY, für englisch: DD/MM/YY).

Vorgehensweise

  1. Festlegen der Standardsprache (in welcher Sprache wird entwickelt?)
  2. Festlegen, auf welche Weise die Sprache gewechselt werden kann
  3. Applikationseinstellungen für das Sprachhandling vornehmen
  4. Applikation in eine andere Sprache übersetzen
  5. Weitere notwendige Aktionen
  6. Formatmaske für Datumsfelder angeben
  7. Tipps und Tricks beim Umgang mit Translation Files

Zu 1: Festlegen der Standardsprache (in welcher Sprache wird entwickelt?)

Am Anfang sollte man sich überlegen, in welcher Sprache die Anwendung realisiert wird (Primary Language). APEX nutzt diese als Basis für alle weiteren Übersetzungen.

Wie wir bereits festgelegt haben, soll die Standardsprache für die Entwicklung englisch sein.

TIPP!

Entwickeln Sie in der Sprache, die für Sie am wichtigsten ist und die als erstes veröffentlicht werden soll. Alle weiteren Übersetzungen können später jederzeit noch hinzugefügt werden.

Die Standardsprache wird automatisch immer dann verwendet, wenn eine Sprache aktiv ist, für die es keine Übersetzung gibt!!!

Bitte beachten Sie auch, dass jede Änderung an den Labels oder Texten in der Standardsprache Änderungen in allen Übersetzungen nach sich zieht!

Zu 2: Festlegen, auf welche Weise die Sprache gewechselt werden kann

Legen sie fest, wie die Sprache innerhalb der Anwendung geändert werden kann. Hierbei sollte man sich überlegen, ob die Sprachanpassung automatisch oder durch benutzereingriff erfolgen soll.

APEX bietet ihnen hierzu folgende Möglichkeiten zur Sprachauswahl:

- Übernahme der Browsersprache

- Einstellen der Sprache über eine Umgebungsvariable namens FSP_LANGUAGE_PREFERENCE

- Einstellen der Sprache über eine selbst definitere Umgebungsvariable

In diesem Beispiel soll sich die Sprache analog zu der im Browser eingestellten Sprache verhalten. D.h. sie soll automatisch wechseln, wenn die Browsersprache geändert wird.

Zu 3: Applikationseinstellungen für das Sprachhandling vornehmen

Zuerst machen wir die globalen Einstellungen für das Sprachhandling in der Applikations-Definition.

  • Über „Shared Components“ -> „Definition“ -> „Globalization“ müssen wir 2 Einstellungen machen.
    • „Application Primary Language“ auswählen

Wählen Sie hier die Standardsprache, die wir zuvor als Primary Language festgelegt haben, aus (in unserem Beispiel „English (en)“.

    • “Application Language Derived From” auswählen.

Wählen Sie hier „Browser (use browser language preference)“ aus.

    • Optional geben wir hier schon mal das Standard-Datumsformat für die englische Sprache an (DD/MM/RRRR)
Define global language

Define global language

Zu 4: Applikation in eine andere Sprache übersetzen

Nun wollen wir die eigentliche Übersetzungsarbeit durchführen. Dazu gehen Sie bitte, wie folgt, vor.

  • Über „Shared Components“ -> „Translate Application“ (in der Region „Globalization“) gelangen Sie in das Translation Menü. Dieses Menü muss nun, mehr oder weniger, von oben nach unten durchgearbeitet werden.
  • Klicken Sie auf “1. Map your primary language application to a translated application” und dann auf “Create”.

Hier wird nun zuerst einmal die Sprache erzeugt, die veröffentlicht werden soll. Wir machen nun die notwendigen Eingaben und setzen folgende Werte:

    • Translation Application: 1501
    • Language Code: German (Germany) (de)
    • Comments: <Irgendein Kommentar>

TIPP!

In dem Feld “Translation Application” wird eine eindeutige ID erwartet, die man jedoch selbst vergeben muss. Wenn man viele Applications auf einer Instanz erzeugt hat, wird das etwas schwierig mit der Eindeutigkeit. Deshalb meine Empfehlung: Überlegen Sie sich eine Regel, wie Sie IDs für Übersetzungen verwenden. Sie können z.B., wie in unserem Beispiel, die ID der Primary Language (in unserem Falle 150) nehmen und eine Stelle anhängen und diese dann einfach für jede Übersetzung um 1 erhöhen (1501 deutsch, 1502 italienisch, 1503 spanisch, usw.)

  • Über die Breadcrumb Region gehen wir zurück zum Translation Menü und erzeugen das Translation-File.
    • Klicken auf „Seed and export the translation text of your application into a translation file.”
    • Sprache auswählen und Texte erzeugen

Hier werden nur die von uns angelegten Sprachen angezeigt. In unserem Beispiel existiert nur die „1501 de“. Diese wählen wir aus und klicken auf „Seed Translatable Text“.

Wir gelangen nun in den XLIFF Export. Hier können wir entscheiden, ob wir die Übersetzungen für die gesamte Anwendung oder nur für bestimmte Seiten erzeugen wollen. Zudem kann ausgewählt werden, ob alle Übersetzungstexte oder nur die, die eine Übersetzung benötigen, exportiert werden sollen.

Wir wollen nun die gesamte Anwendung übersetzen und auch nur die Texte, die eine Übersetzung benötigen.

Wir wählen hier nun die Application aus, markieren die Option „Only those elements requiring translation“ und klicken auf „Export XLF File for Application“, um das Übersetzungsfile zu erzeugen.

Es wurde nun eine Datei mit allen notwendigen Übersetzungen erzeugt (f150_1501_en_de.xlf). Dies ist nun die Basis, auf der wir die Texte übersetzen müssen. In unserem kleinen Beispiel können wir die Texte nun direkt in dem erzeugten File ändern (Bitte nur jeweils die „Target“-Texte ändern!!!). Bei einer richtigen Anwendung kann das allerdings ziemlich viel Aufwand werden.

Hinweis!

Unter Punkt 7 finden Sie noch einige Tipps und Tricks für das Übersetzen der Texte und den Umgang mit den Translation-Files.

  • Haben wir die Texte im Translation-File übersetzt, müssen wir die Übersetzung noch veröffentlichen.
    • In der Breadcrumb Region klicken wir auf „Translate Application“ und dann auf „4. Apply your translation file and publish“.
    • Auf der Seite „XLIFF Translation Files“ klicken wir nun auf „Upload XLIFF“ um das Translation File hochzuladen. Wir machen folgende Eingaben:

Title: 1501_de

Description: <irgendeine Beschreibung für das Import File>

XLIFF File: <auswählen der Datei mit unseren Übersetzungen, in unserem Fall f150_1501_en_de.xlf>

    • Nachdem wir auf „Upload…“ geklickt haben, wird uns die importierte Datei angezeigt. Klicken Sie nun auf den Title der Datei und wählen im folgenden Dialog die Sprache aus, für die die Übersetzung bestimmt ist (“apply to” – in unserem Fall „1501 de“).
    • Klicken Sie nun auf „Apply XLIFF Translation File“, prüfen Sie im folgenden Dialog, dass bei „Create Application“ die richtige Applikation angezeigt wird (ggf. korrigieren) und dann oben rechts auf „Publish Application“, um die Sprache schließlich zu veröffentlichen.

Hinweis!

APEX erzeugt für jede veröffentlichte Sprache eine eigene Applikation. Im Hinblick auf die Performance ist das sicherlich eine sehr gute Lösung. Jedoch werden Sie früher oder später sehen, dass dies auch einige Nachteile hat. Wenn wir nämlich nun PLSQL-Code verändern, der nichts mit irgendwelchen Übersetzungs-relevanten Texten zu tun hat, müssen wir trotzdem alle Übersetzungen erneut veröffentlichen, da APEX teilweise auch funktionalen Code dupliziert und für jede Übersetzungs-Applikation explizit vorhält. Mehr dazu finden Sie unter „Gefahren und Fallstricke beim Umgang mit APEX Translations“.

Zu 5: Weitere notwendige Aktionen

Übersetzen von internen APEX Messages

Wenn Sie die Anwendung nun aufrufen und Ihren Browser auf z.B. „German (de)“ umstellen, werden Sie feststellen, dass einige Labels noch in englischer Sprache dargestellt werden. Z.B. werden in einer Reportmaske mit Seitennavigation die „Next“/“Previous“ Labels noch in Englisch dargestellt.

Grund hierfür sind die APEX internen Messages, die nicht out-of-the-box in das Translation-File exportiert werden. Eine Aufstellung aller internen Messages finden Sie unter folgendem Link in Kapitel „Table 16-3 Internal Messages Requiring Translation“.

http://download.oracle.com/docs/cd/E10513_01/doc/appdev.310/e10499/global.htm#BABFHCJA

http://apex.oracle.com/i/doc/global_mess_reports.htm

Um diese internen Messages zu übersetzen, gehen Sie bitte wie folgt vor.

  • „Shared Components“ -> unter „Globalization“ auf „Text Messages“ und dann auf „Create“ klicken.
  • Geben Sie bei Name den gewünschten Substitution String aus der Tabelle „Internal Messages Requiring Translation“ (zuvor erklärt) ein.
  • Wählen Sie die Sprache aus, die als Hauptsprache bei der Application angegeben wurde (bei uns „Englisch“).
  • Geben Sie den Übersetzungstext ein (in englisch).

Hier können auch Platzhalter eingegeben werden. Um z.B. den Info-Text in der Navigations-Popupliste (X_Y_of_Z: row(s) 1 – 10 of 12) zu ändern, geben Sie bei Name „WWV_RENDER_REPORT3.X_Y_OF_Z“ und bei Text z.B. „Zeile(n) %0 – %1 von insgesamt %2“ ein.

Beachte!

Diesen Vorgang müssen Sie für alle benötigten internen Messages durchführen.

  • Nun, wie unter Punkt 4 beschrieben, die übersetzten Applikationen neu erzeugen (der Punkt “Map your Primary Language Application to…” kann weggelassen werden, da die Anwendung bereits gemapt ist). Wenn die gerade angelegten Texte in der Hauptsprache erzeugt wurde, werden diese Texte ebenfalls mit in die jeweiligen XLF-Files eingetragen. Nach dem Übersetzen und „publishen“ müssten auch hier nun die Übersetzungen angezeigt werden.

TIPP!

Sollten diese internen Messages nun nicht im Translation-File auftauchen, lesen Sie bitte den Beitrag Interne Messages und dynamische Übersetzungen werden nicht in das XLF-File exportiert.

HINWEIS!

Alle Übersetzungstexte für interne Messages können auch hier komplett erstellt werden. Jedoch ist der Weg über die Translation-Files der bessere, wenn die Übersetzungsarbeit von einem Übersetzer durchgeführt wird.

Übersetzen von statischen Wertelisten

Wenn Sie statische Wertelisten verwenden die nicht über eine LOV implementiert, sondern mit „STATIC:…“ einfach bei dem entsprechenden Item eingegeben wurden, so werden diese Inhalte nicht übersetzt.

Hierfür sollten Sie auf jeden Fall eine statische LOV erzeugen und ausschließlich diese verwenden (auch im Sinne von Wiederverwendbarkeit sinnvoll!). Statische LOV-Werte werden beim erzeugen der Translation-Files (siehe oben) mit exportiert und können dann zusammen mit den anderen Texten übersetzt werden.

Übersetzen von dynamischen Wertelisten

Ein weiteres Problem besteht darin, LOV-Werte, die dynamisch aus der Datenbank ermittelt werden, zur Laufzeit zu übersetzen. APEX stellt dafür eine PLSQL-API zur Verfügung (APEX_LANG).

Wie das funktioniert, möchte ich Ihnen hier kurz zeigen.

Gegeben sei eine Wertelist, die verfügbare Sprachen darstellt und auswählbar macht. Diese Sprachen sind in einer Datenbanktabelle mit dem Namen „LANGUAGE“ eingetragen (In unserem Beispiel sind nur 2 Einträge in der Tabelle für „German“ und „English“).

Gehen Sie bitte wie folgt vor.

  • Übersetzungstexte für alle Werte der Tabelle LANGUAGE erzeugen.
    • Über „Shared Components“ -> „Translate Application“ (Globalization) -> “Optionally identify any data that needs to be dynamically translated …” -> “Create” einen neuen Text in der gewünschten Übersetzungssprache erzeugen.

Language: “German (Germany) (de)” auswählen

Translate From Text: “German” (das ist der Key bzw. der Text, der in der Tabelle vorhanden ist und übersetzt werden muss).

Translate To Text: „German“ (das ist der Text, der angezeigt werden soll).

Bild zu dynamic translation messages

    • “Create” klicken, um den Text anzulegen.
    • Das Gleiche machen wir nun mit allen Sprachen, die in der Tabelle vorhanden sind und übersetzt werden müssen.

Hinweis!

Ich habe hier als Übersetzungstexte die englischen Texte eingegeben, um das Szenario aufzuzeigen. Wenn Sie hier direkt die richtigen Übersetzungstexte eingeben, sparen Sie sich die spätere Übersetzung.

Beachte!

Wenn Sie diese dynamischen Texte in der Hauptsprache (in unserem Fall “Englisch”) eingeben, werden diese Texte, im Gegensatz zu den anderen Text-Messages, nicht in das XLIFF-File exportiert! Deshalb sollten Sie darauf achten, direkt die richtige Sprache auszuwählen.

  • Erzeugen einer LOV zur Selektion dieser Sprachen.

Beim Erzeugen der LOV müssen wir die Apex Language API (APEX_LANG) benutzen, um auf die Übersetzungstexte zuzugreifen. Das Statement für die LOV sieht dementsprechend wie folgt aus.

  • Nun müssen wir die LOV dem Item zuweisen, welches diese Werteliste beinhalten soll.
  • Nun, wie unter Punkt 4 beschrieben, die übersetzten Applikationen neu erzeugen. Wenn die gerade angelegten dynamischen Werte in der Hauptsprache erzeugt wurden, werden diese ebenfalls mit in die jeweiligen XLF-Files eingetragen. Nach dem Übersetzen und „publishen“ müssten auch hier nun die Übersetzungen richtig angezeigt werden.

HINWEIS!

Alle Übersetzungstexte für interne Messages können auch hier komplett erstellt werden. Jedoch ist der Weg über die Translation-Files der bessere, wenn die Übersetzungsarbeit von einem Übersetzer durchgeführt wird.

  • Weitere Informationen zum Umgang mit der APEX_LANG-API hierzu finden Sie unter

http://download.oracle.com/docs/cd/E10513_01/doc/appdev.310/e10499/global.htm#insertedID0

Übersetzen sonstiger dynamischer Inhalte

Nun kann sich auch noch die Notwendigkeit ergeben, Werte aus anderen Tabellen, die in Reports oder Formularen benötigt werden, zu übersetzen. Hierfür stellt APEX noch eine andere Funktion der APEX_LANG-API zur Verfügung.

APEX_LANG.MESSAGE

Das Handling hierfür ist fast identisch zu dem vorherigen Punkt („Übersetzen von dynamischen Wertelisten“). Der Unterschied besteht hauptsächlich darin, dass der Übersetzungstext in APEX an einer anderen Stelle eingetragen wird.

Zum Erstellen des Übersetzungstextes klicken Sie bitte unter „Shared Components“ -> „Text Messages“ (Globalization) -> und klicken auf „Create“. Bei Namen bitte den Text bzw. Key eintragen (der Text aus der Tabelle, der übersetzt werden soll), wählen die Sprache aus und tragen den Übersetzungstext ein. Fertig!

Nun können Sie innerhalb Ihres Codings (im Prinzip überall, wo Sie PLSQL-Code verwenden), mit der APEX_LANG-API auf diesen Text zugreifen.

Beispiel: (Dieses Statement würde die Städtenamen aus der Tabelle PERSONEN übersetzen, wenn zuvor die Städte als Übersetzungstexte angegeben worden sind)

SELECT APEX_LANG.MESSAGE(city) FROM PERSONEN;

Weitere Informationen zur APEX_LANG-API  finden Sie unter

http://download.oracle.com/docs/cd/E10513_01/doc/appdev.310/e10499/global.htm#insertedID0

ACHTUNG BUG in Version 3.2.1!!!

Wenn man zuvor eine Translation zu einer Sprache mit einer anderen ID erstellt und dann gelöscht hatte (z.B. zuvor die „151 de“), dann bleiben Fragmente in der Tabelle WWV_FLOW_TRANSLATABLE_TEXT$ die verhindern, dass eine deutsche Übersetzung mit einer anderen ID (jetzt 1501) erstellt werden kann. Als workaround geht folgendes:

  1. Die Translation, die Probleme macht, löschen.
  2. Gesamte Applikation exportieren
  3. Gesamte Applikation löschen
  4. Applikation importieren.

Nun kann die Translation wieder normal angelegt und damit gearbeitet werden.

Zu 6: Formatmaske für Datumsfelder definieren

Wie Sie Formatmasken in APEX definieren können, finden Sie hier.

Datumsformate applikationsweit defnieren

Einen Beitrag zum Thema “Localization” für Datums- und Zeitformate kommt bald. Sollten Sie Informationen dazu benötigen, können Sie mich gerne kontaktieren.

Zu 7: Tipps und Tricks zum Umgang mit Translation Files

In diesem Beitrag wurde nun erklärt, wie man mit APEX eine Anwendung mehrsprachig auslegen kann. Nun stellen sich allerdings noch folgende Fragen.

  1. Wer übersetzt denn nun eigentlich die ganzen Texte?
  2. In welcher Form stelle ich dem Übersetzer die Texte zur Verfügung?

Naja, die Antwort auf die 1. Frage ist recht einfach – die Übersetzer!

Im Unternehmen haben wir vielleicht Leute die gut genug englisch sprechen, um die englische Übersetzung zu machen. Aber was ist mit den anderen Sprachen? Hierfür benötige ich natürlich einen oder vielleicht sogar mehrere Übersetzer.

Und schon sind wir bei der 2. Frage. In welcher Form kann ich den Übersetzern meine Texte zur Verfügung stellen?

Das Problem

Die Translation Files (XLF-Files) sind so unübersichtlich, dass ein Übersetzer mir dieses wahrscheinlich nach spätestens 5 Minuten um die Ohren schlagen würde.

Also was tun?

  • Nicht benötigte Labels und Texte von den Translations ausschließen

APEX bietet eine Möglichkeit, ein paar unnötige Übersetzungen zu unterdrücken. Das macht auch durchaus Sinn, da eh schon genügend Texte als Übersetzungen erkannt und vorgeschlagen werden, die gar keine Übersetzung benötigen.

    • Unnötige Region Labels unterdrücken

Bei den Region-Attributes gibt es die Möglichkeit, die Option „exclude title from translation“ anzuklicken. In diesem Fall wird der Region-Title nicht als Übersetzung eingetragen. Diese Option macht Sinn für Hilfs-Regions, die nicht angezeigt werden.

    • Templates von den Translations ausschließen

Bei den Templates gibt es die Möglichkeit, ein Template komplett auszuschließen. Sie sollten also prüfen, ob statische Texte in Ihren Templates enthalten sind. Wenn nicht, markieren Sie die entsprechenden Templates als „non-translatable“ (wie folgende Abbildung zeigt).

Bild zu Define option Template Translatable

Define option Template Translatable

  • Nur die Texte in das Translation File exportieren, die eine Übersetzung benötigen

Sie können beim erzeugen des XLF-Files die Option „Only those elements requiring translation“ auswählen. Dadurch werden schon mal eine nicht benötigte Texte ausgeschlossen.

  • Übersetzungstexte über APEX Dialog bearbeiten

APEX bietet einen Dialog, um Übersetzungstexte direkt im APEX Builder zu bearbeiten. Hinweis!

Beim späteren Erzeugen des Translation Files (XLF) werden die Änderungen erkannt und dort eingetragen.

Bild zu Manually edit translations

Manually edit translations

  • XLF-File in eigene Tabelle laden und Maske bauen

In meinem letzten Projekt haben wir ein JAVA-Programm geschrieben,  um das komplette XLF-File in eine eigene Datenbanktabelle zu schreiben. Für diese Tabelle haben wir einen einfachen änderbaren Report entwickelt, mit dem wir die Übersetzungstexte ganz einfach bearbeiten konnten. Ein zweites kleines JAVA-Programm hat die übersetzten Texte aus der Tabelle gelesen und ein neues APEX-konformes XLF-File erzeugt. Dieses konnten wir dann importieren, um damit die benötigen übersetzten Applications zu erzeugen.

Bei Interesse stellen wir Ihnen diese JAVA Programme gerne zur Verfügung (natürlich kostenlos – auch gerne mit Source-Code, damit Sie diese Programme für sich anpassen können).

  • Übersetzungstexte aus Repository auslesen

Zu diesem Punkt habe ich einen eigenen Post geschrieben.

Übersetzungstexte aus dem APEX Repository auslesen

Eine Popup-Tree-Auswahl mit Oracle Apex erstellen

Anforderung

  • Eine platzsparende Tree-Auswahl in einem Oracle Apex-Report oder einer Oracle Apex Form. Die Tree-Auswahl soll erst bei Bedarf geöffnet werden.
  • Dabei soll die gesamte Funktionalität von dem Standard Apex-Tree erhalten bleiben. Insbesondere soll es möglich durch den Tree zu navigieren.
  • Der ausgewählte Wert soll immer eingeblendet werden.

Die Recherche nach einem Apex-Plugin für Trees brachte einige Anregungen aber nicht das gewünschte Ergebnis. Hier stelle ich ein Beispiel vor, wie man die Anforderungen größtenteils mit den Bordmitteln von Oracle Apex implementieren kann

  • über eine Tree-Region
  • die als Modal-Fenster angezeigt wird
  • Ein Display-Item zeigt, den aktuell ausgewählten Tree-Anzeigewert an und triggert das Modal-Fenster
  • Ein Hidden-Item enthält den aktuell ausgewählten internen Tree-Wert
  • Eine Dynamic Action für Clicks auf die Tree-Elemente (durch einen JQuery selector), setzt den internen und den angezeigten Tree-Wert neu und schließt das Modal-Fenster.

 

Demo

https://apex.oracle.com/pls/apex/f?p=DEMO_JENS_MARRE:TREE_POPUP

(Anmelden mit Username „demo“ / Passwort „demo“)

 

Hier sind die Schritte für den Apex Builder im Detail:

  • Eine Apex HTML-Region anlegen: „Choose from tree“
  • Ein Text Item „Display Only“ für den ausgewählten Tree-Anzeigewert anlegen
    • Name: P1_SELECTED_EMP_NAME
    • Type: Display Only
    • Default: „(Choose)“
  • Ein Hidden-Item anlegen, um den ausgewählten internen Tree-Wert zu hinterlegen
    • Name: P1_SELECTED_EMP_ID
    • not protected
  • Eine Apex Computation anlegen, um den angezeigten aus dem internen Tree-Wert eingangs zu ermitteln.
    • For item: P1_SELECTED_EMP_NAME
    • Type: SQL Query

SELECT ENAME

FROM EMP

WHERE EMPNO = to_number(:P1_SELECTED_EMP_ID);

  • Eine Apex Tree-Subregion von „Choose from tree“ anlegen
    • Type: „Tree“
    • Name: „Employees“
    • Region Template: „Modal Region“
    • Parent Region: „Choose from tree“
    • Static ID: „treeRegion“ (diese wird benötigt, um das Apex Modal-Fenster zu öffnen und zu schließen)
    • Tree Template: (what you like)
    • Table: EMP
    • ID EMPNO
    • *Parent ID : MGR
    • *Node Text : ENAME
    • *Start With MGR
    • *Start Tree: Value is Null
    • Order Sibling By: ENAME
    • Selected Node Page Item: P1_SELECTED_EMP_ID
  • Einen Triggerlink auf P1_SELECTED_EMP_NAME mit einer Apex Dynamic Action mittels JQuery wrappen. Eine Apex Dynamic Action „on Page load“ anlegen
    • Name: WRAP_OPEN_MODAL_TRIGGER
    • True Action: Execute Java Script Code

    $(“#P1_SELECTED_EMP_NAME”).css({“color”: “#3467a9″,”text-decoration”: “underline”}).wrap(“<a id=’modalTrigger’></a>”);

    $(“#modalTrigger”).attr(“href”,”#”).click( function() {

    openModal(“treeRegion”);

    });

  • Eine Apex Dynamic Action anlegen, als JQuery-Selector für Clicks auf Tree-Elemente
    • Name: SELECT_FROM_TREE
    • Event: Click
    • Selection Type: jQuery Selector
    • jQuery Selector: div.tree li > a
    • True – Action:
    • Action: Execute JavaScript Code
    • Fire on Page Load: no
    • Code:

    var selectedId = $(this.triggeringElement).parents(“li:first”).attr(“id”);

    var selectedName = $(this.triggeringElement).text();

    $(“#P1_SELECTED_EMP_NAME”).text(selectedName);

    closeModal(“treeModal”);

    $s(“P1_SELECTED_EMP_ID”, selectedId);

 

Ich verwende hier die „$s“-Funktion, anstatt der JQuery-Funktion „$“, damit der vollständige Funktionsumfang des Oracle Apex JS-Frameworks erhalten bleibt: z.B. um onchange Dynamic Actions auf  P1_SELECTED_EMP_ID zu triggern.

 

 

Kategorien:Allgemein

Multiple Select-List mit APEX 4 PlugIn

Wie man ein APEX PlugIn mit APEX 4.x erstellt, ist in allen möglichen Sprachen auf diversen anderen Blogs beschrieben. Deshalb verweise ich an dieser Stelle einfach auf einen sehr gut beschriebenen Blog und konzentriere mich auf das Wesentliche.

Der folgende Blog beschreibt anhand eines einfachen Beispiels, wie man APEX PlugIns erstellt und das Ganze sogar in deutscher Sprache.

Code modularisieren – APEX erweitern: mit APEX 4.0 PlugIns

Eine multi-selectlist mit Checkboxen

Bei einem unserer Kunden hatte ich vor kurzem die Anforderung, eine Auswahlbox zu realisieren, mit der man über Checkboxes einzelne Werte als Filterkriterium hinzufügen bzw. wegnehmen kann. Es sollte eine Möglichkeit geben, alle mit einem Klick zu markieren oder auch zu demarkieren und das lästige Markieren einzelner Werte mit der Steuerungs- bzw. Shift-Taste sollte auch vereinfacht werden.

Die herkömmlichen HTML-Select Boxen geben das leider nicht her. Also musste eine neue Lösung her. Die Idee war nun, ein APEX Plugin zu realisieren, mit dem diese Anforderungen gelöst werden konnten.

Demo-Anwendung: Multi-Select Checkbox-List PlugIn Demo

Bild zur Demo-Page

New Features V1.0.3

Die Version 1.0.3 ist da! Hier eine kurze Aufstellung der Neuerungen.

  • Cascading LOV Feature
  • Pre-Search Option

Mit der “Pre-Search Option” besteht die Möglichkeit ein zusätzliches Eingabefeld anzuzeigen, mit dem man die Select-Liste noch mal einschränken kann bzw. die Liste vorfiltern kann.

           

Lösung

Bei dieser Lösung wird ein Fieldset gerendert, welches ein Hidden-Item zur Aufnahme der Rückgabewerte als Liste und ein div-Bereich mit allen Werten – dargestellt als Checkboxes – beinhaltet. Die Liste mit Rückgabewerten sieht dann z.B. so aus: ANALYST:CLERK:MANAGER oder bei numerischen Rückgabewerten: 10:12:18:22.

Die Liste mit den Rückgabewerden ist durch ein Sonderzeichen getrennt. Default derzeit “:”, kann für jedes Page-Item explizit angepasst werden

Limits

Die zusammengebaute Liste mit Rückgabewerten darf nicht mehr als 4000 Zeichen beinhalten. Grund hierfür ist Oracle APEX! Ein Page-Item kann nur normale VARCHAR2-Werte empfangen (max. 4000 Zeichen).

Verwendung

  • Object-Type erzeugen: Um die Rückgabewerteliste später in SQL verwenden zu können, realisieren wir ein Object-typen, Eine PL/SQL-Table und eine Pipelined Function, die diese PL/SQL-Tabelle liefert.

CREATE TYPE value_list_to AS OBJECT (retval VARCHAR2(4000));

  • Table-Type erzeugen

CREATE TYPE value_list_tt AS TABLE OF value_list_to;

  • PL/SQL Funktion zum konvertieren in PL/SQL-Table erstellen (Pipelined Function)

CREATE OR REPLACE FUNCTION parse_ms_valuelist_to_sql(p_delimited_str_list IN VARCHAR2
,p_value_delimiter IN VARCHAR2 DEFAULT ‘:’)
RETURN value_list_tt pipelined
IS
l_ret_tab apex_application_global.vc_arr2;
BEGIN
l_ret_tab := apex_util.string_to_table(p_delimited_str_list, p_value_delimiter);
for i in 1..l_ret_tab.COUNT loop
PIPE ROW(value_list_to(l_ret_tab(i)));
end loop;
return;
END parse_ms_valuelist_to_sql;

  • Page-Item erstellen auf Basis des Item-Plugins. Einfach ein Page-Item erzeugen, als Typ “PlugIn” auswählen und dann weiter wie man es von APEX kennt. Es gibt noch einige spezielle Eigenschaften für diesen Typ. Diese finden Sie in der Region “Settings”.
    • Value Required: das kennt jeder! Mussfeld-Angabe.
    • Enable “All”-Selector as default: Wenn hier “Yes” ausgewählt wird, ist standardmäßig alles markiert.
    • Additional Label: zusätzliches Label, was zwischen der “All”-Checkbox und der Liste gerendert wird.
    • Select-List Value Delimiter: Hier kann ein anderes Werte-Trennzeichen eingegeben werden, was für die Rückgabewerteliste benutzt wird. ACHTUNG! Wen diese geändert wird, muss darauf geachtet werden, dass beim Aufruf der Split-Funktion der entsprechende Werte-Trenner ebenfalls angegeben wird!
    • Style for Fieldset: mit CSS-Styles das Fieldset anpassen.
    • Style for “All”-Checkbox Field: mit CSS-Styles die “All”-Checkbox anpassen deprecated.
    • Style for “All”-Checkbox Label: mit CSS-Styles das Label der “All”-Checkbox anpassen.
    • Style for div-Object: mit CSS-Styles den div-Bereich anpassen.
    • Style for Checkbox (List): mit CSS-Styles die Checkboxes in der Liste anpassen deprecated.
    • Style for Checkbox-Label (List): mit CSS-Styles die Labels in der Checkbox-Liste anpassen.
    • Style additional Label: mit CSS-Styles das zusätzliche Label anpassen.

Beispiel

Um nun z.B. ein mit diesem Plugin erstellte Filterbox mit Werten in einem Report zu berücksichtigen, kann folgendes Statement als Basis verwendet werden. Das Statement zeigt, wie die Rückgabewerteliste (Jobs: MANAGER:ANALYST:PRESIDENT) in einem Report eingebunden wird.

select * from emp e
where EXISTS
(select 1
from table(parse_ms_valuelist_to_sql(:P8_DEPARTMENT_FILTER))
where retval = e.deptno)
and EXISTS
(select 1
from table(parse_ms_valuelist_to_sql(:P8_JOB_FILTER))
where retval = e.job)

Schauen Sie sich bitte auch das Demo an und testen etwas herum. Es lohnt sich.

Demo-Anwendung: Multi-Select Checkbox-List PlugIn Demo

Dieses APEX-PlugIn kann kostenlos runtergeladen und verwendet werden.

Download

V1.0.3 (new): Multi-Select Checkbox-List PlugIn V1.0.3

V1.0 (old): Multi-Select Checkbox-List PlugIn V1.0

Bekannte Probleme

Wenn Sie relativ lange Labels innerhalb der Checkbox-Liste anzeigen, kann es sein, dass die Labels über die Begrenzung rechts heraus ragen und evtl. rechts davon dargestellte Objekte überschreiben. Hier ein Beispiel, wie das aussehen kann.

 

Ursache

Die Ursache liegt darin, dass der innere DIV-Bereich relativ zum äußeren Fiedset zu groß dargestellt wird. Die Labels drücken das DIV nach außen.

Lösung

Einfach beim DIV-Property die gleiche Weite wie beim Fieldset angeben und ggf. noch etwas korrigieren.

   

Cascading LOV in Tabular Form

November 2, 2010 4 Kommentare

Da Oracle leider noch keine out-of-the-box Lösung zu diesem Thema bereitstellt, möchte ich in diesem Beitrag ein Beispiel aufzeigen, wie man eine cascading LOV in einer Tabular Form realisieren kann.
Es gibt sicherlich noch andere Ansätze und Lösungen, aber diese erscheint mir derzeit die einfachste funktionierende zu sein.

Diese kleine Beispiel-Anwendung basiert auf dem EMP-Schema und der EMP-Tabelle. Per Definition sollen bei der Auswahl eines Departments nur die Personen als Manager angezeigt werden, die dem entspr. Department zugeordnet sind.
D.h. dass die Spalte MGR als cascading LOV realisiert werden soll. Durch Änderung des Departments soll sich die MGR-Liste enstp. des zuvor ausgewählten Departments automatisch aktualisieren.
Das Beispiel dazu finden Sie hier: http://apex.oracle.com/pls/apex/f?p=39514

Die nachfolgende Beschreibung basiert darauf, dass bereits eine Tabular Form existiert und in etwa so aussieht, wie in folgender Abbildung dargestellt. Die LOVs auf den Spalten DEPTNO und MGR sind simple LOVs, die alle Departments bzw. alle Personen selektieren.

Aufbau Tabular Form EMP

Welche Schritte sind nun zusätzlich notwendig, um eine cascading LOV in einer Tabular Form zu realisieren?
1. Hilfs-Item erzeugen
2. On-Demand Prozess erzeugen
3. Javascript Funktion mit AJAX-Call implementieren
4. Javascript Funktionsaufrufe integrieren

Lösung

 

1. Zuerst erstellen wir ein Hilfs-Item, mit dem wir die Parent-ID (in unserem Falle die Dept-No) austauschen können 

  • Create Page Item
  • Item Type: HIDDEN
  • Name: P<x>_DEPTNO_REFVAL (“Px_” bitte austauschen durch die entsprechende Page-Nummer)
  • Protected: NO
  • default belassen und “Create…”

2. On-Demand Prozess erzeugen

Dieser Prozess soll entspr. der ausgewählten Department-ID alle Employees lesen und als JSON-String aufbereiten.
Hinweis: Dieser Prozess wird später als Ajax-Call aufgerufen!

  • Shared components -> Application Processes -> Create
  • Name: GET_CASCADING_LOV_JSON (ACHTUNG! hier bitte unbedingt auf Groß-/ Kleinschreibung achten!)
        Sequence: <egal>
        Process Point: On Demand
  • Process Text:

BEGIN
  — hier bitte unbedingt beim item-namen das “x” durch ihre page-nr ersetzen
  APEX_UTIL.JSON_FROM_SQL(‘select ename, empno from emp where deptno = ‘||:P<x>_DEPTNO_REFVAL||’ order by 1′);
END;

  • KEINE Conditions angeben und “Create Process” klicken.

3. Javascript Funktion mit AJAX-Call implementieren

Der AJAX-Call wird vollständig in Javascript erstellt. Für dieses Beispiel werde ich den JS-Code einfach in die Form übernehmen.
Bitte gestatten Sie mir die Anmerkung, dass ich in Bezug auf die Widerverwendbarkeit und Lesbarkeit den JS-Codes lieber in einem separatesn File ablege und dieses JS-File über das script-Tag importiere.
Jedoch zur Vereinfachung wollen wir die JS-Funktion einfach in die Form reinkopieren.

JS-Code anpassen
    Damit unsere AJAX-Call später richtig funktioniert, müssen wir zuerst einige Anpassungen machen.
    Bitte tauschen Sie mit Suchen und Ersetzen …

  • “f05″ durch die item-nr des DEPTNO-items in ihrem report
  • “f06″ durch die item-nr des MGR-items in ihrem report
  • “P<x>_DEPTNO_REFVAL” => das <x> ersetzen durch ihre page-nr.
  • “…APPLICATION_PROCESS=GET_CASCADING_LOV_JSON…” => tauschen gegen den Namen des On-Demand Processes.
  • “myLovObjects.row[i].EMPNO” und “myLovObjects.row[i].ENAME” tauschen gegen die Spalten-Namen, die Sie im On-Demand Prozess verwendet haben.

     Bevor ich die einzelnen Codestellen erkläre, hier erstmal der gesamte JS-Code.

function refresh_cascading_lov(pRowItemObj){

  // separate given html-object, name and ID.
  var tmpRowObj = $(‘#’+pRowItemObj.id);
  var tmpObjName = tmpRowObj.attr(‘name’);
  var tmpObjID   = tmpRowObj.attr(‘id’);
 
  // compute begin-position of row-number.
  var pos = tmpObjID.lastIndexOf(‘_’);
  if(pos <= 0)
    return;
 
  var row = tmpObjID.substring(pos+1, tmpObjID.length);
  var rowNum = null;
  try {
    rowNum = parseInt(row);
  } catch (E) {
    rowNum = 1;
  }
 
  var selectTag = $(‘#f06_’+row);
  var selectTagVal = selectTag.val();
 
  // if curr-item is NOT the parent-id item, check if list would be refreshed for this row before.
  // to check this we have to create a help-item per row and call it “hMgrRefreshFlag_<rownum>”
  var mgrRefreshFlag = $(‘#hMgrRefreshFlag_’+row);
  var mgrRefreshFlagVal = mgrRefreshFlag.text();
  // if refresh-flag exists …
  if(mgrRefreshFlagVal!=undefined && mgrRefreshFlagVal!=null && mgrRefreshFlagVal!=””) {
    // EXISTS Path: check if the list had been refreshed before.
    if(mgrRefreshFlagVal==’true’) {
      if(tmpObjName == “f05″) {
        null;
      } else {
        $(‘#f06_’+row).val(selectTagVal);
        return;
      }
    } else {
      // EXISTS Path: if it not had been refreshed before, set value to “true” -> refreshed for this row!
      $(‘#hMgrRefreshFlag_’+row).text(‘true’);
    }
  } else {
    // NOT-EXISTS Path: if not exists, create this help-item and set the value to “true”.
    tmpRowObj.after(‘<span id=”hMgrRefreshFlag_’+row+'” name=”hMgrRefreshFlag” style=”display:none;”>true</span/>’);
  }
 
  /*
  set parent-id (deptno) to ref-item.
  !!! NOTE !!!
  In this case parent-id (deptno) is column f05 in row. If you try this, please check column deptno what column-id it is in your case.
  The easiest way to do that is to view the generated html-code and look for an select-tag with the value of our departments.
  */
  var deptObj  = $x(‘f05_’+row);
  var deptObjVal  = deptObj.value;
  var lovRefID = deptObjVal;
  $(‘#P6_DEPTNO_REFVAL’).val(lovRefID);
 
  /*
  define the Ajax call. The only variable of note in this example is the application_process of type “On Demand” created a view moments before.
  */
  var get = new htmldb_Get(null, $v(‘pFlowId’), ‘APPLICATION_PROCESS=GET_CASCADING_LOV_JSON’,$v(‘pFlowStepId’));
 
  /*
  add the value in f05_xxxx into the session value for P6_DEPTNO_REFVAL. this is important as without this step the APEX server would not know
  what value the user had entered
  */
  get.add(‘P6_DEPTNO_REFVAL’,lovRefID);
 
  /*
  call the ondemand process and accept the returning values
  */
  var gReturn = get.get();
  var myLovObjects = $u_eval(‘(‘ + gReturn + ‘)’); 
 
  /*
  remove all options from select-list
  */
  selectTag.empty();
  /*
  add null-option for select-list
  */
  selectTag.append(‘<option value=””>&nbsp;</option>’);

  /*
  iterate above all returned list-objects and add as options to select-tag.
  */
  var empnoFoundInList=false;
  for(i=0; i<myLovObjects.row.length;i++) {
    var tmpEmpno = myLovObjects.row[i].EMPNO;
    /*
    if current empno in listis equal to mgr-no selected before, set this as default selected value.
    */
    if(tmpEmpno == selectTagVal) {
      selectTag.append(‘<option selected=”selected” value=”‘+tmpEmpno+'”>’+myLovObjects.row[i].ENAME+'</option>’);
      empnoFoundInList = true;
    } else {
      selectTag.append(‘<option value=”‘+tmpEmpno+'”>’+myLovObjects.row[i].ENAME+'</option>’);
    }
  }
 
  // if empno not found in list, add empno as additional option.
  if(!empnoFoundInList) {
    selectTag.append(‘<option selected=”selected” value=”‘+selectTagVal+'”>’+selectTagVal+'</option>’);
  }
 
  /*
  at least mark default value as selected.
  */
  var tmpF06=$(‘#f06_’+row);
  tmpF06.val(selectTagVal);
  var tmp2=$(‘#f06_’+row).val();
}

 

Nun ein paar wichtige Erklärungen zum JS-Code.

Der funktion “refresh_cascading_lov” wird als Argument pRowItemObj übergeben. Hier steht später die Select-Liste als Objekt drin, auf das der Anwender geklickt hat (also entweder DEPTNO oder MGR).
Als erstes ermitteln wir nun den Namen und die ID des übergebenen Objekt und ziehen uns daraus die jeweilige Zeilennummer. Dann ermitteln wir noch die Ziel-Select-Liste und speichern den Ursprungswert.

  var tmpRowObj = $(‘#’+pRowItemObj.id);
  var tmpObjName = tmpRowObj.attr(‘name’);
  var tmpObjID   = tmpRowObj.attr(‘id’);
 
  // compute begin-position of row-number.
  var pos = tmpObjID.lastIndexOf(‘_’);
  if(pos <= 0)
    return;
 
  var row = tmpObjID.substring(pos+1, tmpObjID.length);
  var rowNum = null;
  try {
    rowNum = parseInt(row);
  } catch (E) {
    rowNum = 1;
  }

  var selectTag = $(‘#f06_’+row);
  var selectTagVal = selectTag.val();

Refresh-Flag einführen

Der folgende Code ist vielleicht auf den ersten Blick etwas verwirrend. Zum besseren Verständnis erstmal die Problematik, die wir damit lösen wollen.
Solange der Anwender später immer zuerst das Department und dann den Manager auswählt, haben wir kein Problem. Nun kann es aber sein, dass der Anwender OHNE zuvor das Department zu wechseln, direkt auf die Manager-Liste klickt. Beim ersten Mal stehen hier noch alle Werte drin, da diese noch nicht aktualisiert wurde. Später implementieren wir auf der MGR-Liste das Event “onFocus”, was diese Liste beim ersten Klick refreshed. Der folgende Code dient nun dazu zu verhindern, dass jedes Mal, wenn man auf die MGR-Liste klickt, der Server-Call ausgeführt wird. Beim ersten Mal reicht völlig aus.
Am besten wäre es, wenn man die Aktualisierung direkt beim Laden der Page durchführt. Nur leider sehe ich derzeit keine einfache Lösung, um dies auch so umzusetzen, dass es funktioniert. Vielleicht stellt Oracle irgendwann einmal Mechanismen zur Verfügung, mit denen man so etwas machen kann (ähnlich dem POST-QUERY-TRIGGER in Oracle Forms).

  var mgrRefreshFlag = $(‘#hMgrRefreshFlag_’+row);
  var mgrRefreshFlagVal = mgrRefreshFlag.text();
  // if refresh-flag exists …
  if(mgrRefreshFlagVal!=undefined && mgrRefreshFlagVal!=null && mgrRefreshFlagVal!=””) {
    // EXISTS Path: check if the list had been refreshed before.
    if(mgrRefreshFlagVal==’true’) {
      if(tmpObjName == “f05″) {
        null;
      } else {
        $(‘#f06_’+row).val(selectTagVal);
        return;
      }
    } else {
      // EXISTS Path: if it not had been refreshed before, set value to “true” -> refreshed for this row!
      $(‘#hMgrRefreshFlag_’+row).text(‘true’);
    }
  } else {
    // NOT-EXISTS Path: if not exists, create this help-item and set the value to “true”.
    tmpRowObj.after(‘<span id=”hMgrRefreshFlag_’+row+'” name=”hMgrRefreshFlag” style=”display:none;”>true</span/>’);
  }

Hier wird nun der Referenzwert, also die DEPTNO, aus der entspr. Zeile ermittelt und in das Hilfs-Item geschrieben. 
BEACHTE! Das Hilfs-Item haben wir im On-Demand Prozess angegeben.

  var deptObj  = $x(‘f05_’+row);
  var deptObjVal  = deptObj.value;
  var lovRefID = deptObjVal;
  $(‘#P6_DEPTNO_REFVAL’).val(lovRefID);

Nun machen wir den AJAX-Aufruf, um die Aktualisierte Liste zu erhalten und speichern die Response, also unseren JSON-String, in “gReturn”.

  var get = new htmldb_Get(null, $v(‘pFlowId’), ‘APPLICATION_PROCESS=GET_CASCADING_LOV_JSON’,$v(‘pFlowStepId’));
 
  get.add(‘P6_DEPTNO_REFVAL’,lovRefID);
 
  var gReturn = get.get();

Der folgende Aufruf splitet nun den Return-String auf und erstellt ein neues JS-Objekt mit einem JS-Array. Darüber können wir dann auf die neuen LOV-Werte zugreifen.

  var myLovObjects = $u_eval(‘(‘ + gReturn + ‘)’); 

Nun leeren wir die MGR-Liste (also das SELECT-Tag, indem wir alle OPTIONS entfernen) und erstellen Sie komplett neu. Dazu iterieren wir über alle LOV-Werte aus “myLovObjects” und erstellen zu jedem ein neues OPTION-Tag.
Zusätzlich prüfen wir noch, ob die zuvor enthaltene Manager-ID im Array vorhanden ist und, wenn ja, setzen wir diese als “selected-option”. Wenn die Manager-ID nicht enthalten war, fügen wir nach der Iteration noch eine zusätzliche Option ein, die nur die nicht-enthaltene ID darstellt. Das ist jedoch Geschmacksache und kann verändert werden.

  selectTag.empty();
  selectTag.append(‘<option value=””>&nbsp;</option>’);

  var empnoFoundInList=false;
  for(i=0; i<myLovObjects.row.length;i++) {
    var tmpEmpno = myLovObjects.row[i].EMPNO;
    if(tmpEmpno == selectTagVal) {
      selectTag.append(‘<option selected=”selected” value=”‘+tmpEmpno+'”>’+myLovObjects.row[i].ENAME+'</option>’);
      empnoFoundInList = true;
    } else {
      selectTag.append(‘<option value=”‘+tmpEmpno+'”>’+myLovObjects.row[i].ENAME+'</option>’);
    }
  }
 
  if(!empnoFoundInList) {
    selectTag.append(‘<option selected=”selected” value=”‘+selectTagVal+'”>’+selectTagVal+'</option>’);
  }

Als letztes wollen wir noch, dass der Ursprungswert, wenn er in der neuen Liste enthalten ist, auch wieder selektiert wird. Hier könnte man
aber auch einfach die Liste auf den NULL-Wert setzen -> das ist auch Geschmacksache.

  var tmpF06=$(‘#f06_’+row);
  tmpF06.val(selectTagVal);
  var tmp2=$(‘#f06_’+row).val();

Nachdem wir nun den JS-Code angepasst haben, kopieren wir diesen nun einfach in die Page
    => Page Attributes aufrufen über Edit Page Attributes und nach unten scrollen bis “Function and Global Variable Declaration”
    => hier kopieren wir nun den zuvor angepassten JS-Code rein und klicken auf “Apply Changes”.

4: Javascript Funktionsaufrufe integrieren
Im Report müssen wir nun bei den spalten DEPTNO und MGR jeweils eine JS-Aufruf machen. Dazu die Report-Attributes aufrufen und

  • bei Spalte DEPTNO unter “Column Attributes” -> “Element Attributes” folgende onChange Funktion einbauen:
    • onchange=”javascript:refresh_cascading_lov(this);”
  • bei Spalte MGR unter “Column Attributes” -> “Element Attributes” folgende onChange Funktion einbauen:
    • onfocus=”javascript:refresh_cascading_lov(this);”

Erklärung:

  1. Der Aufruf bei DEPTNO sorgt dafür, dass die LOV auf der Spalte MGR aktualisiert wird, wenn sich die DEPTNO ändert.
  2. Der Aufruf bei MGR sorgt dafür, dass wenn man auf die select-list bei MGR klickt, ohne dass zuvor die DEPTNO geändert wurde, die aktuelle LOV entspr. der angezeigten DEPTNO aufgerufen wird.

HINWEIS: Dieser Aufruf sollte eigentlich beim onload-Event für jede Zeile stattfinden. Leider kenne ich derzeit keine Möglichkeit, wie ich APEX dazu bewegen kann, ohne die Tabular Form komplett von Hand zu rendern.

Viel Glück!

301 Redirect mit Oracle Apex?

Warum der 301-Redirect?

Wird eine Web-Anwendung im Internet veröffentlicht tauchen ganz neue Aspekte auf, die im Intranet keine Rolle spielen. Ein zentraler Aspekt ist der Umgang der Anwendung mit Google & Co.

Ist zum Beispiel eine veraltete URL nach einer Zeit nicht mehr in der Anwendung verfügbar, muss man davon ausgehen, dass Suchmaschinen die Seite trotzdem noch indiziert haben. Dies muss noch nicht einmal eine komplette Page sein, sondern kann auch nur ein Produkt sein, das nicht mehr im Stock des Onlineshops vorhanden ist. Dabei hilft es auch nicht die sitemap.xml zu aktualisieren. Das Sperren der URL über die robots.txt ist zwar im Prinzip möglich, allerdings auch sehr schwerfällig.

In solchen Fällen rät Google & Co dem Webmaster, einen 301-Redirect für die URL (moved permenantly) einzurichten. Dies geschieht zum Beispiel über eine mod_rewrite-Regel im Apache. Im Falle einer Oracle Apex-Anwendung ist dies allerdings auch zu unflexibel. Besser ist ein 301 Redirect aus der Anwendung heraus.

Kein 301 Redirect mit owa_util

Im Prinzip sollte ein 301 Redirect über owa_util möglich sein, dies scheitert aber an der aktuellen Implementierung:

Verschiedene Quellen in Web schlagen folgendes Coding vor

owa_util.status_line(301, null,FALSE);
owa_util.redirect_url('http://apextipps.wordpress.com', TRUE);

Erzeugt man einen entsprechenden Page-Prozess als “before header”-Prozess, zeigt sich beim Aufruf der Seite , dass trotzdem ein 302 Redirect (Moved Temporarily) zum Client gesendet wird. Ein schönes Tool, um das Redirect-Verhalten zu kontrollieren ist z.B. das Firfox-Plugin Tampa Data

Dieses Verhalten zeigt sich sowohl in der Kombination Oracle XE + Apex 3.2.1 als auch mit Oracle 11g + Apex 4

Die Alternative: 410er Get mit Meta-Redirect

Als Alternative zum 301-Redirect gibt es folgenden Workaround, der in beiden Oracle/Apex – Kombinationen funktioniert hat und den gleichen Effekt für Google & Co hat:

  1. Die URL, die aus dem Google-Index gelöscht werden soll, schickt den Status-Code 410 (Gone) zurück. Eine 410er-Seite muss zum Glück keinen bestimmten Aufbau haben. Das erleichtert die Programmierung in Apex
  2. Um trotzdem einen Redirect zum Client zu senden, falls die URL noch nicht aus dem Index gelöscht wurde wird ein Meta-Redirect verwendet.

Folgendes Coding kann für den Page-Prozess (wieder Before-Header) dafür verwendet werden:

-- 410 instead of 200 indicates that the page should be dropped
-- from any crawler index:
owa_util.status_line(410, 'Gone',FALSE);
-- send a meta-redirect in case a client still ends up on the page:
htp.p('Refresh: 0; url=http://apextipps.wordpress.com');
owa_util.http_header_close;


Kleiner Schönheitsfehler: Der Meta-Redirect hat immer eine Latenzzeit von 1 Sek. Der Client bekommt also kurz die 410er Seite angezeigt.

Kategorien:APEX 3.x, APEX 4.0, Oracle 11g, Oracle XE Schlagworte:

Probleme beim Anlegen einer Websheet application

August 12, 2010 5 Kommentare

Wenn Sie versuchen, im Application Builder über “Create” -> “Websheet” eine neue Application vom Typ “Websheet” anzulegen, kann es zu folgender Fehlermeldung kommen:

The database objects required to create Websheet applications are either invalid or do not exist. Please contact your Workspace Administrator.

Ursache

Um ein Websheet erzeugen zu können, werden einige spezielle neue APEX Datenbank-Objekte benötigt.

Ob diese installiert sind, können Sie prüfen, indem Sie wenn o.g. Meldung angezeigt wird, auf “Manage Websheet Database Objects” klicken und dann den Link “Validate Websheet Database Objects” benutzen. Hier werden Ihnen nun die benötigten Websheet Objekte angezeigt. Die Spalte “Exists” ist hierbei wichtig! Wird hier nun bei einigen Objekten “No” angezeigt, fehlen wichtige Websheet Objekte und es kann kein Websheet erzeugt werden.

Leider hab ich bisher noch keine Möglichkeit gefunden, diese Objekte nachinstallieren zu können.

Lösung

APEX generiert die benötigten Objekte in das zugehörige DB-Schema, wenn ein neues Workspace angelegt wird!

VORSICHT!

Wenn Sie ein neues Workspace erzeugen und ein existierendes DB-Schema wiederverwenden (re-use=YES), dann bekommen Sie einen Fehler.

ORA-20001: table error:APEX$_WS_ROWS ORA-01031: insufficient privileges

Erzeugen Sie also ein neues Workspace und lassen Sie von APEX ein neues DB-Schema anlegen (re-use existing=NO). In diesem Falle werden alle benötigten Websheet Database Objekte automatisch im jeweiligen DB-Schema erzeugt.

Nun sollte das Anlegen einer Websheet Application einwandfrei funktionieren.

Prüfen Datenbank Objekte

Bevor Sie nun ein Websheet anlegen, können Sie noch prüfen, ob alle benötigten Datenbank Objekte vorhanden und validierbar sind.

  1. Melden Sie sich in Ihrem neuen Workspace an und klicken auf “Administration”.
  2. Klicken Sie nun in der rechten Sidebar “Tasks” auf “Websheet Objects” und dann auf “Validate Websheet Database Objects”.

In dieser tabellarischen Anzeige sollten nun alle Objekte als existierend und valide angezeigt werden.

Unterschiede von APEX 4.0 und 3.x bei Mehrsprachigkeit

In diesem Beitrag möchte ich die Unterschiede zwischen APEX 4.0 und 3.x im Umgang mit der Mehrsprachigkeit von Oracle Application Express darstellen.

Den Gesamtbeitragm zum Thema „Mehrsprachigkeit mit Oracle APEX“ finden Sie hier.


Übersetzungen durchführen und veröffentlichen

Beim Durchführen und Veröffentlichen von Übersetzungen gibt es lediglich ein paar kleine Änderungen zu APEX 3.2.1. Generell ist alles gleich geblieben und mein alter Beitrag zu diesem Thema behält somit für Version 4.0 seine Gültigkeit.

- Dynamic Translation Messages nun über „Shared Components“ -> “Globalization” -> “Translate Application” -> “Translation Utilities” zu erreichen.

- Download XLIFF Funktion nun als eigenständigen Menüpunkt verfügbar

Die Download-Funktion für das XLIFF-File war bisher nur aus dem Formular „Seed translatable text to translation repository“ verfügbar. Diese Funktion ist nun im Translation-Menü als eigenständiger Punkt verfügbar. Dies hat den Vorteil, dass die Translation-Text Vorbereitung („Seed translatable text to translation repository“) nicht unbedingt vorher durchgeführt werden muss. Man kann nun bei Bedarf einfach das exportieren wiederholen.

Alle anderen Translation-Funktionen sind im Wesentlichen identisch zu denen in V3.2.1.


Localisation (L10n)

Bei der Lokalisierung (Oracle nennt es „Globalization“) hat Oracle ein paar Features nachgelegt. Es gibt nun die Möglichkeit, lokale Zeitformatierungen zentral zu definieren (die folgenden Einstellungen finden Sie über „Edit Application Properties“ -> „Globalization“ (Tab)):

- Application Timestamp Format

Hier kann ein Format für Timestamp Felder definiert werden. Intern wird beim Erzeugen einer neuen APEX Session der Parameter NLS_TIMESTAMP_FORMAT umgesetzt.

Diese Einstellung wird von APEX automatisch gezogen, sobald man ein Feld definiert, das über eine „TO_TIMESTAMP“ Funktion oder eine Datenbankspalte vom Typ „TIMESTAMP“ befüllt wird.

Um diese Einstellung dynamisch zu ändern, kann man ein Application Item (z.B. MY_TIMESTAMP FORMAT) definieren und diesen Wert zur Laufzeit (z.B. wenn die Browsersprache und das Territorium geändert wird) umswitchen. Auf diese Weise kann man z.B. wenn sich ein User mit einer anderen Sprache anmeldet, das Timestamp-Format dynamisch ändern.

Einstellung unter Globalization

Neues Application Item mit Computation

- Application Timestamp Time Zone Format

Hier kann ein Format für Timestamp Felder  mit Time Zone definiert werden. Intern wird beim Erzeugen einer neuen APEX Session der Parameter NLS_TIMESTAMP_TZ_FORMAT.

Zum allgemeinen Umgang mit Timestamp Funktionen gibt es auch einen sehr guten Artikel von Carsten Czarski.

http://sql-plsql-de.blogspot.com/2009/11/date-timestamp-und-zeitzonen-in-sql-und.html

Weiter Informationen zu Änderungen und new Features der Version APEX 4.0 finden Sie unter

http://www.oracle.com/technology/products/database/application_express/html/4.0_new_features.html

Übersetzungstexte aus dem APEX Repository auslesen

Um die Übersetzungstexte aus dem APEX Repository auszulesen, benötigen wir 3 APEX Tabellen.

HINWEIS!

Mit einem normalen APEX-Entwicklungs-User kommen wir an diese Tabellen in der Regel nicht dran. Deshalb zum Ausführen der folgenden Statements am besten mit dem SYSTEM-User oder dem APEX-Schema Owner (z.B. APEX_030200, wenn nicht gesperrt) anmelden.

WWV_FLOW_TRANSLATABLE_TEXT$

Über diese Tabelle können Sie alle normalen Übersetzungstexte.

WWV_FLOW_DYNAMIC_TRANSLATIONS$

Über diese Tabelle können Sie die dynamischen Übersetzungstexte ermitteln.

WWV_FLOW_MESSAGES$

Über diese Tabelle können Sie die internen Messages.

Das folgende SQL-Statement ist ein Beispiel, wie man die Texte aus dem APEX Repository auslesen kann.

HINWEIS!

Bevor Sie diese Statements absetzen, sollten Sie die Translations erzeugt haben und „Seed Translatable Text“ ausgeführt haben, da bei diesem Vorgang erst die Einträge in die Tabelle WWV_FLOW_TRANSLATABLE_TEXT$ erzeugt werden.


set heading off

set pagesize 5000

set define on

 

SELECT '"TEXT";"' || TRANSLATE_FROM_TEXT || '";"' || TRANSLATE_TO_TEXT || '"'

FROM apex_030200.WWV_FLOW_TRANSLATABLE_TEXT$

WHERE flow_id = &application_id

AND TRANSLATE_TO_LANG_CODE = 'de'

AND TRANSLATE_FROM_TEXT IS NOT NULL

/

 

SELECT '"INTERNAL_MESSAGE";"' || NAME || '";"' || MESSAGE_TEXT || '"'

FROM apex_030200.WWV_FLOW_MESSAGES$

WHERE flow_id = &application_id

AND MESSAGE_LANGUAGE = 'en'

AND NAME IS NOT NULL

/

 

SELECT '"DYNAMIC";"' || TRANSLATE_FROM_TEXT || '";"' || TRANSLATE_TO_TEXT || '"'

FROM apex_030200.WWV_FLOW_DYNAMIC_TRANSLATIONS$

WHERE flow_id = &application_id

AND TRANSLATE_TO_LANG_CODE = 'en'

AND TRANSLATE_FROM_TEXT IS NOT NULL

/

Folgen

Erhalte jeden neuen Beitrag in deinen Posteingang.