Browsing "Older Posts"

APEX 4.2.5 Tabular Form - Komischer Bug

Von Tobias Arnhold → 2.27.2015
In einem meiner Projekte habe ich etwas intensiver auf Tabular Forms gesetzt.

In gewissen Konstellationen habe ich immer wieder folgende Meldung nach einem Validierungsfehler erhalten:
Aktuelle Daten für tabulare Forms sind zu alt; die Quelldaten wurden geändert.
Klicken Sie hier, um Ihre Änderungen zu verwerfen und die Daten aus der Datenbank neu zu laden.


Eventuell auch in Englisch:
Current tabular form data is too old; the source data has been modified.
Click here to discard your changes and reload the data from the database.


Direkt kam mir der Gedanke mit der Sprache und der Number-Spalte (., Setzung). Habe auch danach gesucht:
https://community.oracle.com/thread/2458512

Aber mein Fehler hatte nix mit der Sprache zu tun, sondern lag an einem Unique Constraint.
https://community.oracle.com/thread/2143396

Sobald ich diesen gelöscht habe, gab es dieses Phänomen nicht mehr! Habe ich Ihn wieder hinzugefügt, so habe ich den Fehler direkt wieder erhalten. :(




Twitter und der Oracle SQL Developer

Von Tobias Arnhold → 2.20.2015
Der Oracle SQL Developer hat eine kurze und bewegte Geschichte hinter sich. Aus meiner persönlichen Sicht ist die Software inzwischen wirklich gereift, läuft performant und wirkt immer noch nicht überfrachtet. Nichts desto trotz gibt es auch im SQL Developer eine Reihe versteckter sehr nützlicher Features. Um diese den Leuten näher zu bringen, hat das Oracle SQL Developer Team einen sehr eleganten Weg gefunden. Sie nutzen einen Twitter Account OracleSQLDev. Unter diesem werden immer wieder die versteckten TOP Features gepostet. Um Ihnen das Leben leicht zu machen, habe ich mal die aus meiner Sicht bisher Besten zusammengefasst:






Ps.: Neues Feature unter 4.1

APEX Anwendungen importieren und exportieren mit Hilfe des SQL Developers

Von Tobias Arnhold →
Wussten Sie das der Oracle SQL Developer eine sehr gute APEX Integration bietet? Ich möchte dies Anhand der Import und Export Fähigkeit näher demonstrieren.

Im SQL Developer gibt es im Navigationsmenü neben den üblichen Verdächtigen (Tabellen, Funktionen, Triggern, ...) auch einen Punkt Namens: Application Express
Wenn Sie diesen öffnen, dann sehen Sie alle installierten APEX Anwendungen die auf das Schema referenziert sind.

Export
Über die Rechte Maustaste > Schnell-DDL > In Datei schreiben... können Sie die Anwendung exportieren.

Import
Rechte Maustaste auf Application Express, anschließend "Anwendung importieren..." auswählen. 
 Natürlich können Sie auch im SQL Developer alle Installations-Optionen wie in APEX mitgeben.
Und nun kommt etwas Neues!
Wenn die Installation gestartet ist, dann können Sie die komplette Installation im Log nachverfolgen.

Diese Info hat mir schon einmal mehrere Stunden Suche gespart. Denn falls Sie es hin bekommen eine APEX Anwendung zu zerstören, so dass der Import nicht mehr funktioniert. Können Sie mit Hilfe des SQL Developer's die genaue Stelle des Fehlers herausfinden und anschließend die Anwendung korrigieren und erneut exportieren.

Tabular Form, Dynamic Action und Neue Zeile

Von Tobias Arnhold → 2.13.2015
Einige von euch haben bestimmt schon die ein oder andere jQuery Codezeile in einem Tabular Form verwendet.
Das Ganze funktioniert recht zuverlässig solange der Button "Neue Zeile" nicht verwendet wird. In diesem Beitrag geht es darum, auch auf neue Zeilen reagieren zu können.

Angenommen ich habe in meinem Tabular Form eine Spalte mit einem Textfeld und will den Wert nach dem ändern in eine andere Spalten im Tabular Form übernehmen.
Dazu muss zunächst in der Spalte von "Textfeld1" eine fiktive CSS Klasse hinterlegt werden.
Column Attributes > Element CSS Classes > tf_textfeld1

Normalerweise würden Sie nun eine Dynamic Action anlegen, die bei Veränderung (Change) auf die Klasse "tf_textfeld1" reagiert.
Im folgenden JavaScript Code übertragen Sie anschließend den Wert in das neue Feld:
$(this.triggeringElement).parent().parent().find('input[name=f02]').val($(this.triggeringElement).val());
Was passiert in dem Code Schnipsel?
Davon ausgehend das wir vom "Textfeld1" aus starten, wird über die parent() Funktion die nächst höhere Instanz aufgerufen.
Nach zwei Ebenen sind wir auf der TR-Ebene (Zeile). Nun suchen wir in dieser nach dem "Textfeld2" hinterlegt mit dem Namen "f02_xxxx". Der vordere Teil definiert die Spalte und der hintere Teil des Names verkörpert die Zeilennummer. Über die val() Funktion übergeben wir nun noch den Wert des Quell-Elements in das Zielelement.
Wie anfänglich erwähnt funktioniert diese Lösung nur solange Sie keine neuen Zeilen hinzufügen. Auch der Event Scope "Dynamic" löst dieses Problem bei meiner Anwendung NICHT. Obwohl dieser eigentlich genau das machen sollte.

Des Rätsels Lösung ist die Verwendung einer Funktion in Kombination mit einer Dynamic Action:
Gehen Sie dazu in die Page Defintion und hinterlegen Sie eine JS-Funktion.
function tfChangeEvent() {
  $('.tf_textfeld1').on('change', function() {
    $(this).parent().parent().find('input[name=f02]').val($(this).val());
  });
}
Nun noch zwei Schritte bis zum Ziel:
1. Button: Dieser muss so konfiguriert werden, dass er durch eine Dynamic Action startet.

2. Dynamic Action: Diese muss nun die Funktionalität des "Neue Zeile" Button adaptieren und anschließend unsere Funktion aufrufen.
Code 1: apex.widget.tabular.addRow();
Code 2: tfChangeEvent();
Info: Fire On Page Load bei Code 2 nicht vergessen

Das wärs. Jetzt können Sie beliebig komplexe Szenarien entwickeln. Mit und ohne "Neue Zeile"! :)

Ps: Ich selbst habe es nicht mit einem Texfeld sondern mit einem PopUp LOV umgesetzt. Von daher könnte es sein, das der Change Event eher SubOptimal ist und ein LoseFocus besser geeignet wäre. 

Update 14.02.2015
Nach den Kommentaren von Peter, habe ich eine Beispielanwendung unter APEX 5 erstellt und mal nach getestet. Da funktioniert das anfänglich beschriebene Beispiel mit Hilfe von Event Scope "Dynamic" auf Anhieb. Warum es in meiner APEX 4.2.5 Anwendung nicht funktioniert, werde ich später noch einmal testen. Es muss wohl am alten Template bzw. an alten jQuery Erweiterungen liegen.
Den Link zur Anwendung findet ihr hier: https://apexea.oracle.com/pls/apex/f?p=11617

Custom CSV Export in APEX

Von Tobias Arnhold → 2.10.2015

Sometimes the standard export doesn't fit your requirements. For example you do not want the double apostrophe ".



In those cases take a look at these examples:

http://spendolini.blogspot.de/2006/04/custom-export-to-csv.html
http://www.brainre.org/oracle-apex-csv-file-download-with-iso-encoding-not-utf-8/
https://community.oracle.com/thread/2318795

Let us assume that this is our table:
  CREATE TABLE "MY_TABLE" 
   ( "ID" NUMBER, 
 "CAR_NAME" NUMBER, 
 "CAR_VALUE" NUMBER, 
 "CAR_KM" NUMBER
   ) ;
Based on this table I want to create the export.
Next step is to create a view which generates the export as clob.
Why CLOB? The alternative would be a PL/SQL loop which takes much longer to be generated.
CREATE VIEW VW_MY_TABLE AS
SELECT
 DBMS_XMLGEN.CONVERT(XMLAGG(XMLELEMENT(E,COL_VALUE||CHR(13)||CHR(10))).EXTRACT('//text()').GETCLOBVAL(),1) AS CLOB_VAL,
 COUNT(*) AS NUMBER_OF_ROWS
FROM  (
 SELECT 'ID;CAR_NAME;CAR_VALUE;CAR_KM' AS COL_VALUE FROM DUAL
 UNION ALL
 SELECT ID||';'||
   CAR_NAME||';'||
   CAR_VALUE||';'||
   CAR_KM AS COL_VALUE
 FROM MY_TABLE
);
Finally I create a "On Load - Before Header" process to export the data:
DECLARE
    L_BLOB           BLOB;
    L_CLOB           CLOB;
    L_DEST_OFFSET    INTEGER := 1;
    L_SRC_OFFSET     INTEGER := 1;
    L_LANG_CONTEXT   INTEGER := DBMS_LOB.DEFAULT_LANG_CTX;
    L_WARNING        INTEGER;
    L_LENGTH         INTEGER;
BEGIN

    -- create new temporary BLOB
    DBMS_LOB.CREATETEMPORARY(L_BLOB, FALSE);
    
    --Select CLOB
    SELECT CLOB_VAL INTO L_CLOB FROM VW_MY_TABLE;
    
    -- tranform the input CLOB into a BLOB of the desired charset
    DBMS_LOB.CONVERTTOBLOB( DEST_LOB     => L_BLOB,
                            SRC_CLOB     => L_CLOB,
                            AMOUNT       => DBMS_LOB.LOBMAXSIZE,
                            DEST_OFFSET  => L_DEST_OFFSET,
                            SRC_OFFSET   => L_SRC_OFFSET,
                            BLOB_CSID    => NLS_CHARSET_ID('WE8MSWIN1252'),
                            LANG_CONTEXT => L_LANG_CONTEXT,
                            WARNING      => L_WARNING
                          );

    -- determine length for header
    L_LENGTH := DBMS_LOB.GETLENGTH(L_BLOB);  

    -- create response header
    OWA_UTIL.MIME_HEADER( 'text/csv', FALSE);
    HTP.P('Content-length: ' || L_LENGTH);
    HTP.P('Content-Disposition: attachment; filename="export_my_table.csv"');

    OWA_UTIL.HTTP_HEADER_CLOSE;

    -- download the BLOB
    WPG_DOCLOAD.DOWNLOAD_FILE( L_BLOB );

    -- release BLOB from memory
    DBMS_LOB.FREETEMPORARY(L_BLOB);

    -- stop APEX
    APEX_APPLICATION.STOP_APEX_ENGINE;
  EXCEPTION
    WHEN OTHERS THEN
      DBMS_LOB.FREETEMPORARY(L_BLOB);
      RAISE;
END;
Update 14.09.2015
After some problems with Chrome saving the file inline. I updated the script like this:
DECLARE
    L_BLOB           BLOB;
    L_CLOB           CLOB;
    L_DEST_OFFSET    INTEGER := 1;
    L_SRC_OFFSET     INTEGER := 1;
    L_LANG_CONTEXT   INTEGER := DBMS_LOB.DEFAULT_LANG_CTX;
    L_WARNING        INTEGER;
    L_LENGTH         INTEGER;
BEGIN

    -- create new temporary BLOB
    DBMS_LOB.CREATETEMPORARY(L_BLOB, FALSE);
    
    --Select CLOB
    SELECT CLOB_VAL INTO L_CLOB FROM VW_MY_TABLE;
    
    -- tranform the input CLOB into a BLOB of the desired charset
    DBMS_LOB.CONVERTTOBLOB( DEST_LOB     => L_BLOB,
                            SRC_CLOB     => L_CLOB,
                            AMOUNT       => DBMS_LOB.LOBMAXSIZE,
                            DEST_OFFSET  => L_DEST_OFFSET,
                            SRC_OFFSET   => L_SRC_OFFSET,
                            BLOB_CSID    => NLS_CHARSET_ID('WE8MSWIN1252'),
                            LANG_CONTEXT => L_LANG_CONTEXT,
                            WARNING      => L_WARNING
                          );

    -- determine length for header
    L_LENGTH := DBMS_LOB.GETLENGTH(L_BLOB);  

    -- first clear the header
    HTP.FLUSH;
    HTP.INIT;

    -- create response header
    OWA_UTIL.MIME_HEADER( 'text/csv', FALSE);

    HTP.P('Content-length: ' || L_LENGTH);
    HTP.P('Content-Disposition: attachment; filename="export_my_table.csv"');
    HTP.P('Set-Cookie: fileDownload=true; path=/');

    OWA_UTIL.HTTP_HEADER_CLOSE;

    -- download the BLOB
    WPG_DOCLOAD.DOWNLOAD_FILE( L_BLOB );

    -- stop APEX
    APEX_APPLICATION.STOP_APEX_ENGINE;
  EXCEPTION
    WHEN OTHERS THEN
      DBMS_LOB.FREETEMPORARY(L_BLOB);
      RAISE;
END;

Tabular Form - Regular Expression Validation

Von Tobias Arnhold → 2.05.2015
A simple example how to check a tabular form column to be numeric with a max length of 2.

Use a regular expression validation:
Another example in checking only the length (length max 20 character):