Archiv

Archive for the ‘APEX 4’ Category

Automatisch refreshen nach Auswahl in Popup-LOV

Problembeschreibung

Ich möchte in APEX eine Popup-LOV benutzen, um eine Auswahlliste zu implementieren. Nach der Auswahl in der Popup-LOV soll abhängig von der getroffenen Auswahl eine Region automatisch refreshed werden.

Eigentlich klingt das super einfach! Schon tausend mal gemacht.

Man nimmt eine dynamic Action, hängt sie an ein Change-Event und macht einen Partial-Page-Refresh.

Tja, schade nur, dass das Change-Event nicht zündet.

Bis APEX 4.2 klappt das so nicht. ACHTUNG! Mit APEX 5 haben sie es endlich in den Griff bekommen.

Das Problem hierbei ist, dass das zugrunde liegende Item deaktiviert ist und somit keine Event zünden. Ich bin mir auch nicht sicher, woran es liegt. Eigentlich dachte ich, dass die HTML Spezifikation Schuld daran wäre, aber seitdem ich gesehen habe, dass es mit APEX 5 klappt, denke ich, liegt es wohl doch eher an APEX.

Anyway…

Jedenfall benötigen wir deshalb bis APEX 4.2 einen Workaround. Natürlich gibt es mehrere Wege, wie man einen Workaround hierfür aufsetzen kann. Eine Variante, die mir am einfachsten erscheint, habe ich implementiert und hier im folgenden beschrieben.

Konzept

Die Idee hinter dieser Lösung ist, dass man sich in das on-click Event der Popup-LOV einklingt, die existierende Funktionalität überschreibt bzs. erweitert und die Dynamic-Action im Opener-Window zum refreshen explizit aufruft.

Dabei ist jedoch zu beachten, dass das Change-Event hierbei trotzdem nicht zündet. Wir müssen hier inen kleinen Trick anwenden, damit es klappt – nämlich das Item vor dem refreshen per JQuery aktivieren und anschl. wieder deaktivieren.

 

Lösung

Zuerst erstellen wir im Formular eine JavaScript Funktion mit dem Namen “doRefreshForPopupLov”. Diese Funktion soll später unsere Region explizit refreshen. Der Code sieht in etwa so aus:

function doRefreshForPopupLov() {
  // zuerst enablen wir das Item, in dem wir die Eigenschaft "disabled" entfernen":
  $("#P10_USR_ID").removeAttr('disabled');
  // dann refreshen wir über die dynamic-action mit Hilfe des Event-Triggers:
  apex.event.trigger($("#P10_USR_ID"),"change","");
  // und nun disablen wir das Item wieder, um den Ausgangszustand zu erhalten:
  $("#P10_USR_ID").attr('disabled', 'disabled');
}

Nun müssen wir leider etwas machen, was mir persönlich gar nicht gefällt: wir müssen das Popup-LOV Template ändern (kopieren geht bei diesem Template-Type leider nicht!) und müssen die APEX-Methode “passBack” hacken bzw. neu implementieren.

Hinweis:
Da es bei verschiendenen APEX Versionen theoretisch unterschiedliche Implementierungen geben kann, sollte die “passBack” Methode immer aus der jeweils genutzen Version kopiert werden. Dazu die Popup-LOV aufrufen → rechte Maustaste → Quelltext anzeigen → suchen nach “passBack” → Quellcode der Funktion “passBack” kopieren.

Da dieses Template leider nicht bei den verwendeten Page-Templates angezeigt wird, müssen wir es aufrufen über “Shared Components → Templates → Popup LOV”.
Hier implementieren wir nun unter “Page HTML Head” ein paar benötigte Javascript Funktionen.

ACHTUNG! Die Kopie der passBack Methode, die wie oben beschrieben wurde, kopiert ist, hier nun einfügen und umbenennen in passBackWithRefreh. Dann einfach nun die folgende Zeile an entspr. Stelle einbauen.

// NIDa-Spezifisch:
opener.doRefreshForPopupLov();

Hier nun der gesamte Quelltext für die Popup-LOV:

<script language="JavaScript">
<!--
 
// Neue APEX passBack Function mit eigener Refresh-Erweiterung
function passBackWithRefresh(pReturn, pDisplay){
  var lDisplayField = opener.$x("P10_USR_ID");
  try {
  opener.$s(lDisplayField, pReturn, pDisplay);
  } catch(e) {}
 
  // NIDa-Spezifisch:
  opener.doRefreshForPopupLov();
 
  window.close();
  if(!(lDisplayField.disabled || lDisplayField.type == 'HIDDEN')){lDisplayField.focus();}
};
 
// Die neue "passBack"-Funktion wird nun hierüber an alle Anchor-Tags der LOV-Return-Links angeheftet.
$(function(){
    $('.lovLinks a').each(function(){
        $(this).attr('href', $(this).attr('href').replace('passBack', 'passBackWithRefresh'));
    });
});
 
-->
</script>

Nun sollte alles funktionieren.

Kategorien:APEX 3, APEX 4

APEX 4.2 auf Oracle 12c manuell deinstallieren

September 4, 2015 1 Kommentar

Bei meinen ersten „Gehversuchen“ mit Oracle 12c und einer APEX 5 Installation kam ich leider in den „Genuss“, die von 12c mitgebrachte APEX 4.2 Installation deinstallieren zu müssen. Dabei lief ich leider auf diverse Schwierigkeiten, die verhinderten, dass der Aufruf von „@apxremov.sql“ durchgelaufen ist. Nach diversen Versuchen habe ich den diesen Versuch aufgegeben und APEX 4.2 von komplett manuell deinstalliert.

Tipp: Das Skript „apxremov.sql“ ruft in der 4.2 2 weitere Skripte auf. Ich habe mir einfach diese Skript angeschaut und die wichtigen Anweisungen rauskopiert und manuell ausgeführt.

Um das Ganze aber zu vereinfachen, hier eine Anleitung der manuellen Schritte.

Deinstallation in CDB

— sqlplus.exe aufrufen als SYSDBA in CDB connecten

sqlplus / as sysdba

— CURRENT_SCHEMA setzen: das sollte APEX_040200 sein

ALTER SESSION SET CURRENT_SCHEMA = APEX_040200;

begin
wwv_flow_upgrade.drop_public_synonyms;
end;

/

— speziellen undokumentierten Parameter setzen, um den Fehler ORA-28014 zu umgehen

ALTER SESSION SET „_oracle_script“ = TRUE;

DROP USER apex_040200 CASCADE;

DROP USER flows_files CASCADE;

DROP USER apex_public_user CASCADE;

DROP ROLE apex_administrator_role;

 

— Cleanup XDB

declare
cfg XMLType;
l_dad_list dbms_epg.varchar2_table;
begin

if dbms_xdb.existsresource(‚/i/‘) then
dbms_xdb.deleteresource(‚/i/‘, dbms_xdb.delete_recursive_force);
end if;

if dbms_xdb.existsresource(‚/images/‘) then
dbms_xdb.deleteresource(‚/images/‘,dbms_xdb.delete_recursive_force);
end if;

dbms_epg.get_dad_list( l_dad_list );
for i in 1..l_dad_list.count loop
if upper(l_dad_list(i)) = ‚APEX‘ then
dbms_epg.drop_dad(‚APEX‘);
end if;
end loop;

cfg := dbms_xdb.cfg_get();

if cfg.existsNode(‚/xdbconfig/sysconfig/protocolconfig/httpconfig/webappconfig/servletconfig/servlet-mappings/servlet-mapping/servlet-name[text()=“PublishedContentServlet“]‘) = 1 then
cfg := cfg.deleteXML(‚/xdbconfig/sysconfig/protocolconfig/httpconfig/webappconfig/servletconfig/servlet-mappings/servlet-mapping/servlet-name[text()=“PublishedContentServlet“]/..‘);
end if;

if cfg.existsNode(‚/xdbconfig/sysconfig/protocolconfig/httpconfig/webappconfig/servletconfig/servlet-list/servlet/servlet-name[text()=“PublishedContentServlet“]‘) = 1 then
cfg := cfg.deleteXML(‚/xdbconfig/sysconfig/protocolconfig/httpconfig/webappconfig/servletconfig/servlet-list/servlet/servlet-name[text()=“PublishedContentServlet“]/..‘);
end if;

dbms_xdb.cfg_update(cfg);
commit;
dbms_xdb.cfg_refresh;

end;

/

— SYS owned Objects:

DROP PROCEDURE validate_apex;
DROP PACKAGE wwv_flow_val;
DROP PACKAGE wwv_dbms_sql;
DROP PACKAGE wwv_flow_key;
DROP LIBRARY wwv_flow_val_lib;
DROP VIEW wwv_flow_gv$session;

Deinstallation in PDB(s)

— SQLPLUS als SYSDBA aufrufen

sqlplus / as sysdba

— connecten gegen PDB (ich habe nur eine PDB und die heißt „PDBORA12C“.

ALTER SESSION SET CONTAINER=pdbora12c;

— CURRENT_SCHEMA auf APEX_040200 setzen

ALTER SESSION SET CURRENT_SCHEMA = apex_040200;

BEGIN
wwv_flow_upgrade.drop_public_synonyms;
END;

/

DROP USER apex_040200 CASCADE;
DROP USER FLOWS_FILES CASCADE;
DROP USER APEX_PUBLIC_USER CASCADE;
DROP ROLE APEX_ADMINISTRATOR_ROLE;
DROP USER APEX_LISTENER CASCADE;
DROP USER APEX_REST_PUBLIC_USER CASCADE;

— Cleanup XDB

declare
cfg XMLType;
l_dad_list dbms_epg.varchar2_table;
begin

if dbms_xdb.existsresource(‚/i/‘) then
dbms_xdb.deleteresource(‚/i/‘, dbms_xdb.delete_recursive_force);
end if;

if dbms_xdb.existsresource(‚/images/‘) then
dbms_xdb.deleteresource(‚/images/‘,dbms_xdb.delete_recursive_force);
end if;

dbms_epg.get_dad_list( l_dad_list );
for i in 1..l_dad_list.count loop
if upper(l_dad_list(i)) = ‚APEX‘ then
dbms_epg.drop_dad(‚APEX‘);
end if;
end loop;

cfg := dbms_xdb.cfg_get();

if cfg.existsNode(‚/xdbconfig/sysconfig/protocolconfig/httpconfig/webappconfig/servletconfig/servlet-mappings/servlet-mapping/servlet-name[text()=“PublishedContentServlet“]‘) = 1 then
cfg := cfg.deleteXML(‚/xdbconfig/sysconfig/protocolconfig/httpconfig/webappconfig/servletconfig/servlet-mappings/servlet-mapping/servlet-name[text()=“PublishedContentServlet“]/..‘);
end if;

if cfg.existsNode(‚/xdbconfig/sysconfig/protocolconfig/httpconfig/webappconfig/servletconfig/servlet-list/servlet/servlet-name[text()=“PublishedContentServlet“]‘) = 1 then
cfg := cfg.deleteXML(‚/xdbconfig/sysconfig/protocolconfig/httpconfig/webappconfig/servletconfig/servlet-list/servlet/servlet-name[text()=“PublishedContentServlet“]/..‘);
end if;

dbms_xdb.cfg_update(cfg);
commit;
dbms_xdb.cfg_refresh;

end;

/

Nun können wir noch mal prüfen, ob die APEX Schemata alle entfernt wurden:

— connecten als SYSDBA gegen CDB

sqlplus / as sysdba

show con_name

— hier sollte CDB$ROOT angezeigt werden

SELECT username FROM dba_users WHERE username LIKE ‚APEX%‘ OR username LIKE ‚FLOWS%‘;

— hier sollte nun nichts mehr angezeigt werden.

 

— connecten gegen PDB(s)

ALTER SESSION SET CONTAINER = pdbora12c;

show con_name

— hier sollte PDBORA12C bzw. der Name Ihrer PDB angezeigt werden.

SELECT username FROM dba_users WHERE username LIKE ‚APEX%‘ OR username LIKE ‚FLOWS%‘;

— hier sollte nun auch nichts mehr angezeigt werden.

Nun sollten wir eine saubere, APEX-freie Datenbank haben.

Kategorien:APEX 4, APEX 5, Oracle 12c Schlagwörter: ,

Flexibler Resource-Gantt-Chart mit Oracle Apex und AnyChart / AnyGantt (2)

Januar 14, 2015 1 Kommentar

Die Standard Charting-Features von Oracle Apex bieten einen schnellen unkomplizierten Weg, um eine Vielzahl einfacher Charts zu erstellen. Sobald weitere Anforderungen dazukommen, stößt man allerdings schnell an die Grenzen der Apex-Wizards. Da der Oracle Apex – Umgebung aber die volle Funktionalität der AnyChart – Library zur Verfügung steht, lassen sich wesentlich anspruchsvollere Charts erstellen. Vereinfachend kommt hinzu, dass die AnyChart – Library über eine gute Online Dokumentation und XML-Referenz verfügt.

Im letzten Beitrag stellte ich den Rahmen für die händische Einbettung von AnyGantt in Apex vor, und in einem späteren Beitrag erläutere ich ein generisches Datenmodell für die Ressourcenplanung.

In diesem Beitrag, erweitere ich das Gantt-Beispiel, um die weiteren Punkte der Anforderungsliste:

  • A1 Der Resource-Gantt-Chart soll über Suchfelder gesteuert werden
  • A2 Wenn ein Suchfeld geändert wird, soll der Gantt-Chart „The-Ajax-Way“ aktualisiert werden
  • A3 Der Gantt-Chart soll auf deutsch sein
  • A4 Feiertage sollen markiert werden
  • A5 Es sollen mehrere Balkenfarben zur Verfügung stehen, die mit einem schönen Verlauf dargestellt werden sollen
  • A6 Im Balken soll eine Beschriftung erscheinen
  • A7 Per Maus-Hover soll bei Bedarf eine Info zum Balken ein- und ausgeblendet werden
  • A8 Per Mausklick auf den Balken, soll ein Zusatzdialog zum Gantt-Chart geöffnet werden.

 

Hier ist meine Demo-Seite zu dem Beispiel:

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

(Login über: demo/demo)

 

Die Anforderungen A1 und A2 habe ich bereits im letzten Beitrag implementiert. Alle anderen Anforderungen werden im On-Demand-Prozess „generate_gantt_xml“ umgesetzt. Letztendlich muss hierfür der AnyGantt-Syntax bedient werden. Ich erkläre die Umsetzung der Anforderungen als XML-Kommentar direkt in dem Beispiel-XML-Code weiter unten.

In meiner Demoapplication und dem Beispiel-XML-Code verwendet ich feste Datumswerte und nicht den per Ajax geposteten Datumswert P4_DAY.

Workarounds:

Generell werden im Header-Teil der XML-Datei die allgemeinen Chart-Einstellungen und die verschiedenen Balkenstyles definiert. Für jeden Balken können im Body-Teil der XML-Datei AnyChart-Attribute einzeln und dynamisch gesetzt werden (z.B. Balkentext, Hovertext, …).

Dabei stößt man allerdings auf folgendes Problem: Farbwerte können in den Styles nicht dynamisch gesetzt werden, was die Umsetzung von Anforderung A5 vor ein Problem stellt. Ich verwende dafür folgenden Workaround:

Ich ermittle dynamisch die möglichen Farbstile in dem angefordertem Zeitraum und kopiere für jeden Farbstil einen eigenes Tag „period_style“ in die XML-Datei. Jeder Stil erhält als Namen den Hex-Wert der Grundfarbe des Balkens und kann so im Body-Teil der XML-Datei für die Balken referenziert werden.

 

Für die Umsetzung der Anforderungen A6 und A8 gibt es ein weiteres Problem: In der aktuellen AnyGantt-Version legt sich der Balkentext über den Mausklick-Trigger.

Hierfür habe ich folgenden Workaround gefunden: Während des Maus-Hovers, also im Hover-Style, lösche ich den Balkentext und geben dadurch den Trigger für den Mausklick wieder frei. Bewegt sich die Maus wieder vom Balken weg, erscheint auch der Balkentext wieder.

 

Beispiel XML-Datei

<anygantt>
  <settings>
    <navigation enabled="True" position="Top" size="30">
      <buttons collapse_expand_button="false" align="Near" />
      <text>Dienstplan</text>
      <font face="Verdana" size="10" bold="true" color="White" />
      <background>
        <fill type="Gradient">
          <gradient>
            <key color="#B0B0B0" position="0" />
            <key color="#A0A0A0" position="0.3" />
            <key color="#999999" position="0.5" />
            <key color="#A0A0A0" position="0.7" />
            <key color="#B0B0B0" position="1" />
          </gradient>
        </fill>
        <border type="Solid" color="#494949" />
      </background>
    </navigation>

    <locale>

<!-- A3:
     Defining months
     Using a german locale
-->
      <date_time_format week_starts_from_monday="True">
        <months>
          <names>Januar,Februar,März,April,Mai,Juni,Juli,August,September,
                        Oktober,November,Dezember</names>
          <short_names>Jan,Feb,Mar,Apr,Mai,Jun,Jul,Aug,Sep,Okt,Nov,Dez</short_names>
        </months>
        <!--
             <time am_string="AM" short_am_string="A" pm_string="PM" short_pm_string="P" />
        -->

<!-- A4:
     Defining general weekdays
     Holidays are defined in timeline > calendar > execptions

     A3: 
     Using a german date format
 -->
        <week_days>
          <names>Sonntag,Montag,Dienstag,Mittwoch,Donnerstag,Freitag,Samstag</names>
          <short_names>So,Mo,Di,Mi,Do,Fr,Sa</short_names>
        </week_days>
        <format>
          <full>%dd.%MM.%yyyy %HH24:%mi</full>
          <date>%dd.%MM.%yyyy</date>
          <time>%HH.%mm.</time>
        </format>
      </date_time_format>
    </locale>

  </settings>
  <datagrid width="180">
    <columns>
      <column attribute_name="RowNum" width="30">
        <header>
          <text>#</text>
        </header>
      </column>
      <column attribute_name="Name" cell_align="LEFTLEVELPADDING" padding="0" 
            width="150">
        <header>
          <text>Mitarbeiter</text>
        </header>
      </column>
    </columns>
  </datagrid>

  <timeline>

<!-- A1:
     The beginning and the end of the time frame
     This is set dynamically based on the day entered by the user
-->

    <scale start="18.08.2014" end="22.09.2014"
           show_start="01.09.2014" show_end="08.09.2014"
           />

    <plot line_height="30" item_height="20" item_padding="5" >

<!-- A4:
     Render weekends properly
     note the opacity value
-->
      <non_working_days show="true">
        <fill enabled="true" type="Solid" color="#aa2222" opacity="0.1" />
      </non_working_days>
      <grid>
        <vertical>
          <line enabled="true" color="DarkSeaGreen" thickness="1" opacity="0.2"/>

<!-- Do not fill even and odd columns
      this is just a hint for the feature
-->
          <!--
          <even>
            <fill enabled="true" color="DarkSeaGreen" opacity="0.5" />
          </even>
          <odd>
            <fill enabled="true" color="White" opacity="1" />
          </odd>
           -->

        </vertical>
      </grid>
    </plot>

<!-- A4: Defining holidays
     general weekdays are defined in 
     settings > locale > date_time_format > week_days
     The actual holidays are rendered dynamically based on time frame 
     that the user has entered
     e.g. check: 
 http://ora-sql-plsql.blogspot.de/2012/04/ermittlung-von-feiertagen-per-table.html
 -->
    <calendar>
      <exceptions>

<exception is_working="false" start_date="01.01.2014" end_date="01.01.2014" />
<exception is_working="false" start_date="06.01.2014" end_date="06.01.2014" />
<exception is_working="false" start_date="27.02.2014" end_date="27.02.2014" />
<exception is_working="false" start_date="03.03.2014" end_date="03.03.2014" />
<exception is_working="false" start_date="17.04.2014" end_date="17.04.2014" />
<exception is_working="false" start_date="18.04.2014" end_date="18.04.2014" />
<exception is_working="false" start_date="21.04.2014" end_date="21.04.2014" />
<exception is_working="false" start_date="01.05.2014" end_date="01.05.2014" />
<exception is_working="false" start_date="29.05.2014" end_date="29.05.2014" />
<exception is_working="false" start_date="09.06.2014" end_date="09.06.2014" />
<exception is_working="false" start_date="19.06.2014" end_date="19.06.2014" />
<exception is_working="false" start_date="15.08.2014" end_date="15.08.2014" />
<exception is_working="false" start_date="03.10.2014" end_date="03.10.2014" />
<exception is_working="false" start_date="31.10.2014" end_date="31.10.2014" />
<exception is_working="false" start_date="01.11.2014" end_date="01.11.2014" />
<exception is_working="false" start_date="25.12.2014" end_date="25.12.2014" />
<exception is_working="false" start_date="26.12.2014" end_date="26.12.2014" />
         </exceptions>
    </calendar>
  </timeline>


  <styles>
    <period_styles>
    <!--
        A5: 
        I have found no way to set the color-values dynamically
        My workaround: Check dynamically which colors need to
        be rendered in the entered time frame and dynamically create
        an own period style for each color.
        Each period style is an exact copy of this style only distinguished
        by its color-values.        
        
        The gradients are computed via plsql using my object type UI_COLOR
        https://jmarre.wordpress.com/2014/08/16/blending-colors-in-oracle-plsql
    -->
      <period_style name="#FF3300_TIMES0">

        <bar_style>

          <middle shape="Full">
            <border enabled="true" type="Solid" thickness="1" color="#400D00" />
            <fill enabled="true" type="Gradient">
              <gradient angle="-90">

                <key color="#FF9980" position="0" />
                <key color="#FF3300" position="1" />
              </gradient>
            </fill>
          </middle>

          <labels>
    <!--
        A6: 
        Each bar defines its own title using AnyChart attributes
    -->          
            <label anchor="Center" valign="Center" halign="Center">
                 <font face="Verdana" size="10" bold="true" color="Black" />
                 <text><![CDATA[{%Title}]]></text>
            </label>
          </labels>
          <states>
        <!--
             A7:
             The specific bar style for hover events
        -->
            <hover>
              <middle>
                <border enabled="true" type="Solid" thickness="2" color="#661400" />
                <fill enabled="true" type="Gradient">
    <!--
        A5: 
        On hover the gradient is turned upside down
        Again, the gradients are computed via plsql using my object type UI_COLOR
        https://jmarre.wordpress.com/2014/08/16/blending-colors-in-oracle-plsql
    -->                
                  <gradient angle="90">
                    <key color="#FF9980" position="0" />
                    <key color="#FF3300" position="1" />
                  </gradient>
                </fill>
              </middle>

    <!-- A7, A8:
         This is a workaround:
         I could not find any way to render both for each bar: link and label
         Somehow the label overwrites the link for the bar
         The workaround here is to remove the label on hover
         This makes the link somehow visible again
    -->
              <labels>
                <label anchor="Center" valign="Center" halign="Center">
                     <text></text>
                </label>
              </labels>

            </hover>
          </states>
        </bar_style>

        <!-- 
              A7:
              The actual tooltip on mouse hover
              Each bar defines its own title using AnyChart attributes
        -->
        
        <tooltip enabled="true" name="periodTooltip">
           <text><![CDATA[{%Longtitle}
{%Name}
{%Week} {%StartDay}
{%Time}
{%Comment}]]>
           </text>
           <font color="Black"/>
           <fill enabled="true" type="Solid" color="White" opacity="0.9" />
           <border enabled="true" type="Solid" color="DarkRed" thickness="1" />
           <margin left="10" top="2" right="10" bottom="2" />
        </tooltip>
      </period_style>
      
<!-- 
   ....
   more period styles for other colors
   ----
-->   

    </period_styles>
  </styles>
  <resource_chart>
<resources>
  <resource id="100063" name="BLAKE" />
  <resource id="100061" name="CLARK" />
  <resource id="100068" name="JONES" />
  <resource id="100060" name="SCOTT" />
  <resource id="100073" name="FORD" />
  <resource id="100064" name="TURNER" />
  <resource id="100062" name="HARPER" />
</resources>

<periods>
   <period resource_id="100073" start="15.09.2014 09:00" end="15.09.2014 17:00" 
              name="test" style="#7D7DBE_TIMES0">
   <actions>
     <action type="navigateToURL" url="https://apextipps.wordpress.com/?x=110184," 
              target="_self"/></actions>
    <attributes>
      <attribute name="Title">U</attribute>
      <attribute name="Longtitle">Urlaub</attribute>
      <attribute name="StartDay">15.09.2014</attribute>
      <attribute name="Week">KW38</attribute>
      <attribute name="Time"></attribute>
      <attribute name="Comment"></attribute>
    </attributes>
   </period>   
   
   <period resource_id="100073" start="16.09.2014 09:00" end="16.09.2014 17:00" 
              name="test" style="#7D7DBE_TIMES0">
   <actions>
      <action type="navigateToURL" url="https://apextipps.wordpress.com/?x=110185," 
              target="_self"/>
   </actions>
    <attributes>
      <attribute name="Title">U</attribute>
      <attribute name="Longtitle">Urlaub</attribute>
      <attribute name="StartDay">16.09.2014</attribute>
      <attribute name="Week">KW38</attribute>
      <attribute name="Time"></attribute>
      <attribute name="Comment"></attribute>

    </attributes>
   </period>  
   
<!-- more periods -->

   </periods>
  </resource_chart>
</anygantt>

Kategorien:APEX 4, Charting

Flexibler Resource-Gantt-Chart mit Oracle Apex und AnyChart / AnyGantt (1)

November 20, 2014 2 Kommentare

Die Standard Charting-Features von Oracle Apex bieten einen schnellen unkomplizierten Weg, um eine Vielzahl einfacher Charts zu erstellen. Sobald weitere Anforderungen dazukommen, stößt man allerdings schnell an die Grenzen der Apex-Wizards. Da der Oracle Apex – Umgebung aber die volle Funktionalität der AnyChart – Library zur Verfügung steht, lassen sich wesentlich anspruchsvollere Charts erstellen. Vereinfachend kommt hinzu, dass die AnyChart – Library über eine gute Online Dokumentation und XML-Referenz verfügt.

In den folgenden Beiträgen stelle ich ein entsprechendes Beispiel für einen Resource-Gantt mithilfe von AnyGantt vor.

Hier ist meine Demo-Seite zu dem Beispiel:

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

(Login über: demo/demo)

Dabei habe ich folgende Anforderungen an den Gantt-Chart in Apex gestellt:

  • A1 Der Resource-Gantt-Chart soll über Suchfelder gesteuert werden
  • A2 Wenn ein Suchfeld geändert wird, soll der Gantt-Chart „The-Ajax-Way“ aktualisiert werden
  • A3 Der Gantt-Chart soll auf deutsch sein
  • A4 Feiertage sollen markiert werden
  • A5 Es sollen mehrere Balkenfarben zur Verfügung stehen, die mit einem schönen Verlauf dargestellt werden sollen
  • A6 Im Balken soll eine Beschriftung erscheinen
  • A7 Per Maus-Hover soll bei Bedarf eine Info zum Balken ein- und ausgeblendet werden
  • A8 Per Mausklick auf den Balken, soll ein Zusatzdialog zum Gantt-Chart geöffnet werden.

In diesem Beitrag stelle ich den Rahmen für die händische Einbettung von AnyGantt in Apex vor. Im nächsten Beitrag, erweitere ich das Beispiel, um die weiteren Punkte dieser Anforderungsliste.

Das generelle Konzept für die Einbettung von AnyGantt in Apex ist Folgendes:

  • Für den Gantt-Chart wird ein leeres DIV-Element angelegt,
  • Die AnyGantt Flash-Datei wird im Page-Header per Javascript eingebunden
  • Der XML-Inhalt des Gantt-Charts wird über einen On-Demand-Prozess erzeugt und per Javascript abgerufen
  • Mit entsprechenden Apex-Items kann die Ansicht gesteuert werden. Für diese Auswahl-Items werden onchange Dynamic Actions angelegt, die den On-Demand-Prozess aufrufen

Zunächst benötigen Sie die AnyGantt-Dateien: AnyGantt.js, AnyGantt.swf

Laden Sie diese im ApexBuilder hoch unter

  • „Shared Components → Static Files“
  • Application: „No application“
  • Erstellen Sie eine neue Apex-Page
  • Klicken Sie auf Edit Page
  • Hinterlegen Sie die JavaScript-Datei unter „JavaScript → File URLs“
  • #WORKSPACE_IMAGES#AnyGantt.js
  • Die Flashdatei AnyGantt.swf wird später dynamisch über Javascript eingebunden

Legen Sie eine HTML-Region für die Suchkriterien an und legen Sie dort Page-Items an, z.B.

  • Name: P4_DAY
  • Display As: Datepicker

Legen Sie eine Dynamic Action für diese Page-Items an

  • Identification
  • Name: „REFRESH_GANTT“
  • When
  • Event : Change
  • Selection Type: Item
  • Item(s) : <die Page-Items, die Sie vorher angelegt haben>
  • True-Action
  • Action: Execure Javascript
  • Code: loadGantt();

    Bemerkung: Die Funktion loadGantt wird weiter unten beschrieben.

Legen Sie eine HTML-Region für den Gantt-Chart an und tragen Sie unter „Region Source:“ ein

<div id=“chartDiv“ name=“chartDiv“></div>

Legen Sie einen neuen Apex Application Prozess an:

  • „Shared Components → Application Processes → Create“
  • Name: „generate_gantt_xml“
  • Point „On Demand Run“
  • Type: PL/SQL
  • Process Text

BEGIN
htp.p(‚
<anygantt>
<settings>
<navigation enabled=“True“ position=“Top“ size=“30″>
<buttons collapse_expand_button=“false“ align=“Near“ />
<text>Resource-Gantt Chart</text>
<font face=“Verdana“ size=“10″ bold=“true“ color=“White“ />
</navigation>

<locale>

<date_time_format week_starts_from_monday=“True“>
<months>
<names>Januar,Februar,März,April,Mai,Juni,Juli,August,September,Oktober,November,Dezember</names>
<short_names>Jan,Feb,Mar,Apr,Mai,Jun,Jul,Aug,Sep,Okt,Nov,Dez</short_names>
</months>

<week_days>
<names>Sonntag,Montag,Dienstag,Mittwoch,Donnerstag,Freitag,Samstag</names>
<short_names>So,Mo,Di,Mi,Do,Fr,Sa</short_names>
</week_days>
<format>
<full>%dd.%MM.%yyyy %HH24:%mi</full>
<date>%dd.%MM.%yyyy</date>
<time>%HH.%mm.</time>
</format>
</date_time_format>
</locale>

</settings>
<datagrid width=“180″>
<columns>
<column attribute_name=“RowNum“ width=“30″>
<header>
<text>#</text>
</header>
</column>
<column attribute_name=“Name“ cell_align=“LEFTLEVELPADDING“ padding=“0″ width=“150″>
<header>
<text>Mitarbeiter</text>
</header>
</column>
</columns>
</datagrid>

<timeline>

<scale start=“04.08.2014″ end=“08.09.2014″
show_start=“18.08.2014″ show_end=“25.08.2014″
/>

<plot line_height=“30″ item_height=“20″ item_padding=“5″ >

<non_working_days show=“true“>
<fill enabled=“true“ type=“Solid“ color=“#aa2222″ opacity=“0.1″ />
</non_working_days>
<grid>
<vertical>
<line enabled=“true“ color=“DarkSeaGreen“ thickness=“1″ opacity=“0.2″  />
</vertical>
</grid>
</plot>
</timeline>

<styles>
<period_styles>

</period_styles>
</styles>
<resource_chart>
<resources>
<resource id=“109697″ name=“BLAKE“ />
<resource id=“109700″ name=“CLARK“ />
<resource id=“109756″ name=“JONES“ />
<resource id=“109755″ name=“SCOTT“ />
<resource id=“109699″ name=“FORD“ />
<resource id=“109698″ name=“TURNER“ />
</resources>
<periods>
</periods>

</resource_chart>
</anygantt>
‚);
END;

Bekemerkung: Dies ist ein statischer Beispiel XML-Code, um das Gerüst für den Resource-Gantt Chart in Oracle Apex zum Laufen zu bringen. Im nächsten Beitrag erweitere ich den AnyGantt-XML-Code, um die eigentlichen Anforderungen an den Chart abzubilden.

Tragen Sie eine neue Javascript Funktion ein, die den AnyGantt-Chart initialisiert und die Funktion zum Laden des XML-Codes bereitstellt, im Page-Header unter

  • JavaScript → Function and Global Variable Declaration“

var chart= new AnyGantt(‚#WORKSPACE_IMAGES#AnyGantt.swf‘);

with(chart) {
width = „100%“;  // skaliert die Anzeige dynamisch mit dem Browserfenster
height = „500“;
name = „#CHART_NAME#“;
initText = „#FLASH_INIT#“;
resourcesLoadingText = „#FLASH_RESOURCES#“;
noDataText = „#FLASH_NO_DATA#“;
templateLoadingText = „#FLASH_TEMPLATES#“;
// To allow jq modal windows
wMode = „transparent“;
src = „/i/flashchart/anygantt_4/swf/Preloader.swf“;
}

/*
* Calls the on-demand-prozess  generate_gantt_xml and posts all necessary data
* to retrieve the content
*/
function loadGantt() {
chart.setLoading(„Loading new chart…“);

$.post(‚wwv_flow.show‘,
{
„p_request“: „APPLICATION_PROCESS=generate_gantt_xml“,
„p_flow_id“: $v(‚pFlowId‘),
„p_flow_step_id“: $v(‚pFlowStepId‘),
„p_instance“: $v(‚pInstance‘),
„p_arg_names“: [„P4_DAY“],  /* List of your search parameters */
„p_arg_values“: [$v(„P4_DAY“)] /* $v-List of your  search parameters */
},
function success(data) {

try {
chart.setData(data.toString());
chart.write(‚chartDiv‘);
}
catch(err) {
if(console && console.log) {
console.log(„Fehler: „+err);
console.log(err.message);
}
}
});
}

Kategorien:APEX 4, Charting

Multiple Select-List mit APEX 4 PlugIn

Dezember 31, 2010 2 Kommentare

For an english description of this article please to go my example Multi-Select Checkbox-List PlugIn Demo.

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 8 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('https://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=https://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, APEX 4, Oracle 11g, Oracle XE Schlagwörter: