APEX 18.1

Oracle APEX 18.1
Oracle apex 18.1 is inmiddels alweer twee maanden beschikbaar; hoog tijd dus om er eens iets over te schrijven. Met deze nieuwe versie van APEX heeft het team achter APEX wederom een flinke stap gezet om het leven van de ontwikkelaars gemakkelijker te maken. Ik zal een aantal grote nieuwe/aangepaste features  benoemen, los daarvan zijn er natuurlijk ook nog heel veel kleine aanpassingen gedaan.

Application Builder
In de application builder zijn de wizards aangepakt waardoor ze slimmer zijn geworden en er minder stappen nodig zijn om diverse componenten te maken. De nieuwe create app wizard (APEX blueprint) is echt een nuttige toevoeging. Je kan met deze wizard op een simpele manier een nieuwe applicatie maken zonder één regel code te gebruiken. Bij de property editor is een zogenaamd “sticky filter” gekomen, hierdoor kan je de filter waarden vasthouden. De algemene zoekfunctie is tevens verbeterd deze lijkt nu op de veel gebruikte spotlight search op een Mac computer. Op één plek zoeken binnen de gehele APEX applicatie.

REST
Er zijn diverse verbeteringen en toevoegingen aan de REST functies van APEX gedaan. Zo is er nu REST enabled SQL support, met behulp van de feature kan je SQL statements laten uitvoeren in een andere database met behulp van HTTP en REST. Het resultaat krijg je vervolgens aangeleverd in JSON formaat. Je hoeft dus geen database link meer te gebruiken. Je kan zelfs grafieken, reports en andere APEX componenten maken met behulp van deze functie. Ook kunnen er andere REST services gebruikt worden binnen de APEX applicatie. Binnen APEX is nu een complete REST Workshop gekomen. Hier kan je zelf je REST services definiëren en onderhouden. Van deze services kan je vervolgens ook documentatie laten genereren met één click.

Grafieken
De Oracle JET engine is geüpgraded naar version 4.2. Met JET kan je binnen APEX de mooiste grafieken maken die gebaseerd zijn op HTML5, CSS3 en JavaScript. Een aantal nieuwe grafieken zoals Gannt, Box-Plot en Pyramide zijn toegevoegd.

Mobile ontwikkeling
Ontwikkelen voor mobiele devices was natuurlijk al prima te doen met het Universal theme. Hier is echter ook weer een flinke stap gezet door een aantal componenten die regelmatig gebruikt worden in een mobiele UI zoals Listview, Column Toggle en Reflow Report standaard beschikbaar te maken in dit theme. Tevens zijn er nog een aantal wijzigingen in het Universal theme doorgevoerd specifiek voor mobiele devices zoals pagina headers en footers die meer consistent worden weergegeven. Wellicht de beste toevoeging voor mobiel ontwikkelen is dat er nu standaard support is voor touch based dynamic actions. Je kan nu dus dynamic actions maken voor acties zoals dubbel tikken, vegen en zoomen.

Toegankelijkheid
De manier waarop een applicatie toegankelijk gemaakt kan worden voor minder validen is veranderd. De link onderaan de pagina om om te schakelen naar de toegankelijk modus is verdwenen. Het is de bedoeling dat de applicatie meer wordt ontworpen en gebouwd volgens de regels die opgezet zijn door de W3C organisatie. Om de ontwikkelaar te helpen om dit op een juiste manier te doen, is de APEX advisor uitgebreid zodat deze ook kan testen of je applicatie wel voldoende toegankelijk is voor minder validen.

Font APEX
Font apex is een collectie van meer dan 1000 iconen die je kan gebruiken binnen je applicatie. Deze set is voor APEX 18.1 onder handen genomen o.a. door van alle iconen een 32×32 pixel versie toe te voegen.

Authenticatie
In APEX 18.1 is er een nieuwe authenticatie schema bij gekomen, het zogenaamde social sign-in schema. Hiermee is het voor ontwikkelaars mogelijk om authenticatie op te zetten voor o.a. Oracle Identity Cloud Service, Google, Facebook en generiek OAuth2. Dit alles zonder extra code!

Interactive Grid
De interactive grid is ook onder handen genomen en begint meer en meer excel achtige functies te krijgen. Dit is voor de gebruikers natuurlijk super handig maar ook aan de ontwikkelaars is gedacht. De API van de interactive grid geeft je de mogelijkheid om deze naar eigen believen uit te breiden. Extra menu’s, buttons  of processen het kan allemaal. Om hier goed mee om te kunnen gaan loont het wel om je eerst, in ieder geval een beetje, te verdiepen in de architectuur van de interactive grid.

API
Nu de term API is gevallen kan ik daar nog aan toevoegen dat de JavaScript API’s  aangepast en uitgebreid zijn. Dus nog meer info om je applicaties te kunnen tweaken!

Er valt natuurlijk nog  veel meer te vertellen over deze nieuwe versie maar ik denk dat ik nu wel genoeg redenen heb gegeven om APEX 18.1 snel uit te proberen. Mocht je dit nog niet gedaan hebben maak dan een account aan op apex.oracle.com om met deze versie wat ervaring op te doen.

Of download en installeer het in je eigen omgeving want dat kan natuurlijk ook!

 

 

Export static workspace files in APEX5

In APEX 5 the functionality for the workspace- and application files is improved tremendously.

The most important changes are:
1. It is possible to upload zip files which will be extracted in the database.
2. The files can be organized within directories.

With this new functionality it is a lot easier to push your static files from development to other environments like acceptance and production.

Although it is here where a problem arises. At our site we deliver our applications via SQL scripts. When APEX exports an application it does include the static application files nicely into the export file. But what if you want to deliver your static workspace files as well? There is no check-box or what so ever to tell APEX to include these files in the export. I have searched but could not find functionality in APEX to achieve this. Normally google and twitter are your best friends in this situations but to my disappointment there was also no solution available on the internet. I had to figure this one out for myself.

In the wwv_flow_api package in the apex schema is a procedure to create a static workspace file.

[code language=”sql”]
procedure create_workspace_static_file (
p_id in number default null,
p_file_name in varchar2,
p_mime_type in varchar2,
p_file_charset in varchar2 default null,
p_file_content in blob );
[/code]

With this procedure you can upload a BLOB to the APEX static workspace files. All we need to do is write a script which will take all the static workspace files and create a PL/SQL block for each of them with the above procedure. There are several ways to create a script. In this example we will use dbms_output to get it done. After running the script, save the dbms output data to create the script.

The script looks as follows, in this script it is explained what is done and why.

[code language=”sql”]
declare
— workspace to export the static file from
l_workspace varchar2(255) := ‘APEX5’;

— cursor for all static workspace files
cursor c_file is
select w.id
, w.security_group_id
, w.file_name
, w.mime_type
, w.file_charset
, w.file_content
from apex_050000.wwv_flow_company_static_files w
join apex_050000.wwv_flow_companies c
on c.provisioning_company_id = w.security_group_id
where c.display_name = l_workspace;

begin
— loop throug all files
for r_file in c_file loop
— start pl/sql block
dbms_output.put_line(‘begin’);
— set the security group id to make it possible to use the script outside APEX (toad,sqldeveloper)
dbms_output.put_line(‘ wwv_flow_api.set_security_group_id(p_security_group_id=> ‘||r_file.security_group_id||’);’);
— create varchar table to hold the data for the BLOB
dbms_output.put_line(‘ wwv_flow_api.g_varchar2_table := wwv_flow_api.empty_varchar2_table;’);
— loop through LOB in blocks of 200 to improve readabiliy of the end script
for i in 1 .. trunc(dbms_lob.getlength(r_file.file_content)/200)+sign(mod(dbms_lob.getlength(r_file.file_content),200))
loop
— save to raw file data from the blob to a varchar table
dbms_output.put_line(‘ wwv_flow_api.g_varchar2_table(‘||i||’) := ”’
||utl_raw.cast_to_varchar2(utl_raw.cast_to_raw(dbms_lob.substr(r_file.file_content,200,((i-1)*200)+1)))
||””||’;’);
end loop;
— create the create_workspace_static_file procedure with the correct parameters
dbms_output.put_line(‘ wwv_flow_api.create_workspace_static_file(‘
dbms_output.put_line(‘ p_file_name => ”’||r_file.file_name||””);
dbms_output.put_line(‘ , p_mime_type => ”’||r_file.mime_type||””);
dbms_output.put_line(‘ , p_file_charset => ”’||r_file.file_charset||””);
— convert the varchar table to a blob as input for the procedure
dbms_output.put_line(‘ , p_file_content => wwv_flow_api.varchar2_to_blob(wwv_flow_api.g_varchar2_table)’);
dbms_output.put_line(‘ );’);
dbms_output.put_line(‘end;’);
end loop;
— commit your work
dbms_output.put_line(‘commit;’);
end;
[/code]

This script will deliver output that looks like this:

[code language=”sql”]
begin
wwv_flow_api.set_security_group_id(p_security_group_id=> 5325975770000001);
wwv_flow_api.g_varchar2_table := wwv_flow_api.empty_varchar2_table;
wwv_flow_api.g_varchar2_table(1) := ‘406368617273657420225554462D38223B0A0A756C2E64726F70646F776E2C0A756C2E64726F70646F776E206C692C0A756C2E64726F70646F776E20756C207B0A206C6973742D7374796C653A206E6F6E653B0A206D617267696E3A20303B0A2070616464696E673A20303B0A7D0A0A756C2E64726F70646F776E207B0A20706F736974696F6E3A2072656C61746976653B0A207A2D696E6465783A203539373B0A20666C6F61743A206C6566743B0A7D0A0A756C2E64726F70646F776E206C69207B0A20666C6F’;
wwv_flow_api.g_varchar2_table(2) := ‘61743A206C6566743B0A206D696E2D6865696768743A203170783B0A206C696E652D6865696768743A20312E33656D3B0A20766572746963616C2D616C69676E3A206D6964646C653B0A7D0A0A756C2E64726F70646F776E206C692E686F7665722C0A756C2E64726F70646F776E206C693A686F766572207B0A20706F736974696F6E3A2072656C61746976653B0A207A2D696E6465783A203539393B0A20637572736F723A2064656661111111232334555532212111126F70646F776E20756C207B0A20766973’;
wwv_flow_api.g_varchar2_table(3) := ‘6962696C6974793A2068696464656E3B0A20706F736974696F6E3A206162736F6C7574653B0A20746F703A20313030253B0A206C6566743A20303B0A207A2D696E6465783A203539383B0A7D0A0A756C2E64726F70646F776E20756C206C69207B0A20666C6F61743A206E6F6E653B0A7D0A0A756C2E64726F70646F776E20756C20756C207B0A20746F703A203170783B0A206C6566743A203939253B0A7D0A0A756C2E64726F70646F776E206C693A686F766572203E20756C207B0A207669736962696C697479’;
wwv_flow_api.g_varchar2_table(4) := ‘3A2076697369626C653B0A7D’;
wwv_flow_api.create_workspace_static_file(p_file_name => ‘css/xxxxx.css’
, p_mime_type => ’text/css’
, p_file_charset => ‘utf-8’
, p_file_content => wwv_flow_api.varchar2_to_blob(wwv_flow_api.g_varchar2_table)
);
end;
begin
wwv_flow_api.set_security_group_id(p_security_group_id=> 5325975770000001);
wwv_flow_api.g_varchar2_table := wwv_flow_api.empty_varchar2_table;
wwv_flow_api.g_varchar2_table(1) := ‘406368617273657420225554462D38223B0D0A0D0A756C2E64726F70646F776E2C0D0A756C2E64726F70646F776E206C692C0D0A756C2E64726F70646F776E20756C207B0D0A20206865696768743A206175746F3B0D0A20206261636B67726F756E642D636F6C6F723A20236638663866383B0D0A7D0D0A0D0A756C2E64726F70646F776E207B0D0A2020666F6E742D7765696768743A20626F6C643B0D0A2020666F6E742D73697A653A20313270783B0D0A2020706F736974696F6E3A2072656C61746976653B’;
wwv_flow_api.g_varchar2_table(2) := ‘0D111110746F703A203070783B0D0A20206C6566743A203070783B0D0A2020626F726465722D72696768743A2031707820736F6C6964207267626128302C302C302C302E3135293B0D0A20206D617267696E3A203020302031367078203670783B0D0A7D0D0A0D0A756C2E64726F70646F776E206C69207B0D0A202070616464696E673A20303B0D0A7D0D0A0D0A756C2E64726F70646F776E206C693A686F766572207B0D0A20206261636B67726F756E642D636F6C6F723A20236565653B0D0A7D0D0A0D0A756C’;
wwv_flow_api.g_varchar2_table(3) := ‘2E64726F70646F776E206C692061207B0D0A20646973706C61793A20626C6F636B3B0D0A2070616464696E673A203670782031307078203470783B0D0A2077686974652D73706163653A206E6F777261703B0D0A7D0D0A0D0A756C2E64726F70646AFFAF12aA5A6C696E6B2C0D0A756C2E64726F70646F776E20613A76697369746564207B0D0A2020636F6C6F723A20233535353B0D0A2020746578742D6465636F726174696F6E3A206E6F6E653B0D0A7D0D0A0D0A756C2E64726F70646F776E20613A686F7665’;
wwv_flow_api.g_varchar2_table(4) := ‘722C0D0A756C2E64726F70646F776E20613A616374697665207B0D0A2020636F6C6F723A20233535353B0D0A7D0D0A0D0A756C2E64726F70646F776E20756C207B0D0A2020626F726465723A2031707820736F6C6964207267626128302C302C302C302E3135293B0D0A7D0D0A0D0A2E6E61762D6172726F77207B0D0A2020666C6F61743A2072696768742021696D706F7274616E743B0D0A2020706F736974696F6E3A2072656C61746976653B0D0A20206C6566743A203870783B0D0A2020746F703A20307078’;
wwv_flow_api.g_varchar2_table(5) := ‘3B0D0A7D0D0A0D0A236E61762D6C332C0D0A236E61762D6C34207B0D0A2020626F726465722D6C6566743A2031707820736F6C6964207267626128302C302C302C302E3135293B0D0A2020626F726465722D626F74746F6D3A2031707820736F6C6964207267626128302C302C302C302E3135293B0D0A7D0D0A0D0A’;
wwv_flow_api.create_workspace_static_file(p_file_name => ‘js/xxxxx.js’
, p_mime_type => ‘application/x-javascript’
, p_file_charset => ‘utf-8’
, p_file_content => wwv_flow_api.varchar2_to_blob(wwv_flow_api.g_varchar2_table)
);
end;
commit;
[/code]

It is obvious that all settings like the directory, mime-type and character set are unchanged. Save the output as a script and add this script to your other install scripts like the application export SQL.

I hope in the future there will be a check-box in APEX which makes it possible to export the static workspace files with the application export. That would make this script unnecessary and life just a bit easier ;-).

Download Interactive Report in de achtergrond

Bij de klant waar ik momenteel werk wil men de data van meerdere Apex interactive reports (IR) downloaden. Een IR heeft hiervoor een standaard functionaliteit, die over het algemeen prima toereikend is. In dit geval betreft het echter rapporten met soms wel honderdduizenden regels. Mijn eerste reactie hierop was dat dit soort rapportages in een BI-systeem thuis horen, maar de klant wil dit toch met de IR doen.
De applicatie is bereikbaar via IBM Tivoli Access Manager en WebSEAL. Deze WebSEAL heeft een zeer korte time-out periode. Daardoor komt het geregeld voor dat terwijl de data nog wordt opgebouwd aan de serverkant deze time-out periode al voorbij is. Dit resulteert dan in een “no respond error”. Deze time-out periode laten aanpassen blijkt geen optie. Om de downloadfunctionaliteit toch aan te kunnen bieden, heb ik een oplossing bedacht om dit voor elkaar te krijgen.
Daarvoor moeten de volgende stappen genomen worden:

  1. Verzamel IR gegevens.
  2. Creëer Oracle scheduler job om export te maken.
  3. Procedure om csv-bestand aan te maken.
  4. Procedure om csv-bestand op te halen.

Verzamel IR gegevens
Wat is er nodig om het rapport buiten Apex om te kunnen opbouwen?

Apex voorziet in diverse API’s. Ook voor IR zijn er een aantal (kijk hier voor alle API’s). Voor het opvragen van de query worden de volgende API’s gebruikt:

Op de pagina met het IR is een button gecreëerd die het IR ID ophaalt en dit doorgeeft aan de procedure die met dit ID de overige gegevens kan ophalen.

l_report_id := apex_ir.get_last_viewed_report_id (p_page_id => l_page_id, p_region_id => l_region_id);
ir_package.get_ir(l_page_id, l_region_id, l_report_id, l_report_name);

In de procedure ir_package.get_ir worden de query en filtergegevens opgehaald vervolgens wordt er een Oracle job gestart die zorgt voor de opbouw van het csv-bestand.

De query en bind variabelen worden in een BLOB kolom gezet. Indien er alleen tekst in deze kolom komt kan dit natuurlijk ook in een CLOB kolom gezet worden, met het voordeel dat je geen CLOB-BLOB conversie hoeft uit te voeren. In onze applicatie kunnen er echter ook andere documenten worden weggeschreven. Vandaar de keuze voor een BLOB.
APP_USER_FILES
USF_USR_LOGIN_NAME" VARCHAR2(30 CHAR)
USF_REP_CODE" VARCHAR2(10 CHAR)
USF_FILE_NAME" VARCHAR2(255 CHAR)
USF_FILE_MIMETYPE" VARCHAR2(255 CHAR)
USF_FILE_CHARSET" VARCHAR2(128 CHAR)
USF_FILE_BLOB" BLOB
USF_FILE_COMMENTS" VARCHAR2(4000 CHAR)

Create dbms_scheduler job en program
Het aanmaken van het csv-bestand wordt in de achtergrond uitgevoerd middels de Oracle scheduler. Hiervoor wordt eerst een program aangemaakt met een herkenbare naam en een aantal parameters.
l_user := v('APP_USER');
l_program_name := l_report_name||l_user;
dbms_scheduler.create_program
(program_name => l_program_name
,program_action => 'ir_package.download_ir'
,program_type =>'STORED_PROCEDURE'
,number_of_arguments => 2
,enabled => false);
dbms_scheduler.define_program_argument
(program_name => l_program_name
,argument_position => 1
,argument_type => 'varchar2');
dbms_scheduler.define_program_argument
(program_name => l_program_name
,argument_position => 2
,argument_type => 'varchar2');
dbms_scheduler.enable
(name => l_program_name);

Daarna wordt een job gestart die het daadwerkelijke csv-bestand aanmaakt. Hier wordt de user meegegeven omdat de apex user op het moment dat deze job draait niet meer bekend is. Je wil het rapport natuurlijk wel bij de juiste gebruiker afleveren.
l_job_name := dbms_scheduler.generate_job_name(l_report_name||'_');
dbms_scheduler.create_job
(job_name => l_job_name
,program_name => l_program_name
,comments => 'Create csv '||l_report_name||' for '||l_user
,enabled => false
,auto_drop => true);
dbms_scheduler.set_job_argument_value
(job_name => l_job_name
,argument_position => 1
,argument_value => l_user);
dbms_scheduler.set_job_argument_value
(job_name => l_job_name
,argument_position => 2
,argument_value => l_rep_code);
dbms_scheduler.enable
(name => l_job_name);

Procedure om csv-bestand aan te maken
In deze procedure wordt met behulp van dbms_sql de data opgehaald om het csv-bestand op te bouwen.
Open cursor
l_cursor := dbms_sql.open_cursor;
dbms_sql.parse(l_cursor, l_query, dbms_sql.native);

Activeer de bind variabelen
for i in 1..l_para_count
loop

dbms_sql.bind_variable(l_cursor, l_para_name, l_para_value);
end loop;

Open BLOB om het csv-bestand in op te slaan. Let bij het openen van de BLOB dat de cache parameter op TRUE staat. Indien deze op FALSE staat kan het, vooral bij grote rapporten, een grote performance impact hebben.
dbms_lob.createtemporary( l_csv, TRUE );
dbms_lob.open( l_csv, dbms_lob.lob_readwrite );

Definieer de rapport kolommen
dbms_sql.describe_columns2(l_cursor, l_col_count, l_desc_tbl );
for i in 1 .. l_col_count loop
dbms_sql.define_column(l_cursor, i, l_col_val, 32767 );
end loop;

Schrijf de kolomkoppen naar de BLOB
for i in 1 .. l_col_count loop
l_col_val := l_desc_tbl(i).col_name;
if i = l_col_count then
l_col_val := '"'||l_col_val||'"'||chr(10);
else
l_col_val := '"'||l_col_val||'";';
end if;
l_raw := utl_raw.cast_to_raw( l_col_val );
dbms_lob.writeappend( l_csv, utl_raw.length( l_raw ), l_raw );
end loop;

Schrijf de regels naar de BLOB. Om te zorgen dat het csv-bestand goed gelezen kan worden, worden alle kolommen tussen quotes geplaatst. Als scheidingsteken wordt  ;  gebruikt.
l_cursor_status := sys.dbms_sql.execute(l_cursor);
-- write result set to CSV file
loop exit when dbms_sql.fetch_rows(l_cursor) <= 0;
for i in 1 .. l_col_count loop
dbms_sql.column_value(l_cursor, i, l_col_val);
if i = l_col_count then
l_col_val := '"'||l_col_val||'"'||chr(10);
else
l_col_val := '"'||l_col_val||'";';
end if;
l_raw := utl_raw.cast_to_raw( l_col_val );
dbms_lob.writeappend( l_csv, utl_raw.length( l_raw ), l_raw );
end loop;
end loop;

Sluit cursor en BLOB
dbms_sql.close_cursor(l_cursor);
dbms_lob.close( l_csv );

Schrijf BLOB naar app_user_files
insert into app_user_files( usf_usr_login_name
, usf_rep_code
, usf_file_name
, usf_file_mimetype
, usf_file_charset
, usf_file_blob
, usf_file_comments)
values ( p_user
, l_rep_code
, l_file_name
, 'text/csv'
, null
, l_csv
, 'Ready');

Procedure om het csv-bestand op te halen
Op de “home” pagina van de applicatie staan diverse gebruikers specifieke gegevens. Daar is een rapport toegevoegd die alle beschikbare csv-bestanden voor de ingelogde gebruiker laat zien en die de mogelijkheid biedt om deze te downloaden. De link roept hiervoor de procedure get_user_file aan. In overleg met de klant is de keuze gemaakt om van ieder rapport maximaal één versie te bewaren, hierdoor zal er per rapport maximaal een download beschikbaar zijn.
procedure get_user_file
(p_usf_rep_code in varchar2,
p_usf_file_name in varchar2)
as
l_mime varchar2(2000) ;
l_length number;
l_file_name varchar2 (2000) ;
l_lob blob;
begin
select usf.usf_file_mimetype
, usf.usf_file_blob
, lower(usf.usf_file_name)
, dbms_lob.getlength(usf.usf_file_blob)
into l_mime
, l_lob
, l_file_name
, l_length
from app_user_files usf
where usf.usf_usr_login_name = v('APP_USER')
and usf.usf_rep_code = p_usf_rep_code
and usf.usf_file_name = p_usf_file_name;
/*--*/
/*-- set up HTTP header*/
/*--*/
/*-- use an NVL around the mime type and*/
/*-- if it is a null set it to application/octect*/
/*-- application/octect may launch a download window from windows*/
owa_util.mime_header(nvl(l_mime, 'application/octet'), false) ;
/*-- set the size so the browser knows how much to download*/
htp.p('Content-length: ' || l_length) ;
/*-- the filename will be used by the browser if the users does a save as*/
htp.p('Content-Disposition: attachment; filename="'||l_file_name||'"') ;
/*-- close the headers*/
owa_util.http_header_close;
/*-- download the BLOB*/
wpg_docload.download_file(l_lob) ;
exception
when others then htp.p('Fout'||SQLCODE||' -ERROR- '||SQLERRM);
end get_user_file;

Tenslotte
Ik heb niet de complete code van de procedures in dit artikel gekopieerd, omdat er op meerdere plekken applicatie specifieke zaken gebeuren die niets toevoegen aan dit artikel. Wil je deze oplossing gebruiken, maar kom je er niet uit? Dan mag je mij altijd mailen en zal ik je proberen verder te helpen.

Apex 5 komt eraan

Het releasemoment van APEX 5 komt naderbij.

Ik heb zojuist een mail ontvangen van het Oracle Apex support team waarin gewaarschuwd wordt om de applicaties op de early adopter 2 site veilig stellen omdat deze omgeving zal worden vervangen door de early adaptor 3 versie. Eerder is ook gesproken over een public beta versie die door een geselecteerde groep gebruikers kan worden getest op hun eigen site. Ik ben benieuwd hoe breed beschikbaar deze versie komt want het zou heel goed zijn om je eigen applicaties te kunnen upgraden en zo APEX 5 verder te testen.

b2ap3_thumbnail_Screenshot-from-2015-01-14-092126.png

Inmiddels zijn maar liefst 6028 workspaces op de early adapter 2 site aangemaakt. Zie dit twitter bericht van Joel R. Kallman.

b2ap3_thumbnail_Screenshot-from-2015-01-14-090649.png

Ik kan niet wachten om met de ThemeRoller aan de gang te gaan. Het lijkt mij super handig om je theme kleuren te kunnen opbouwen vergelijkbaar met de ThemeRoller van jQuery UI.

 

Oracle Apex 5 EA

Een hele korte blog tussendoor met naar mijn mening wel heel goed nieuws.

Voor hen die het gemist hebben, er is een early adaptor versie van Oracle Apex 5 beschikbaar.
Je kan een workspace aanvragen via deze link.

Nu nog afwachten tot versie 5 als echte release wordt vrijgegeven.

 

My First APEX plug-in

In an attempt to reach a broader audience, this will be my first blog in English. I hope this will help more people to read and use my tips and tricks. For me it will be an extra challenge!

For some time now (since version 4.0), APEX has offered the plug-in functionality and although I used several plug-ins, I still did not build any myself. This is mainly because existing plug-ins were adequate or I built the needed functionality directly into the application. To be able to reuse code, it is better to use plug-ins instead of coding everything into pages. In this blog I will build my first plug-in.

The purpose of the plug-in is to display notifications that can be controlled by a dynamic action. As a basis I will use the noty jQuery plug-in (http://needim.github.io/noty/) which is a good start to build the APEX plug-in around.

How to start? First, go to the shared components of your application and click on plug-ins under the User Interface heading. So far for the easy part ;-), although building a plug-in is not that complicated I discovered to my surprise. I will guide you through the different steps below.

  1. Shared components
  2. Plug-ins
  3. Create a new plug-in
  4. Enter the name for the plug-in.

    The name you provide here is used within your application. It is good practice to use your domain as a prefix. This creates a globally unique name. With this name you are able to share the plug-in without worrying about duplicates. I bet there are more plug-ins called ‘notification’ out there ;-). Choose the Type and Category as shown in the screenshot.
  5. Now create the plug-in, we will finish it later.
  6. Add Custom Attributes. These are the parameters to pass on to the Javascript function that will start the real action on your page. I did not create parameters for all available options in noty. For a start I just took those that are essential.

    Label Type Required Depending on Description
    Theme Select list yes  Select one of the themes you have created or use the default.
    Layout Text yes Where does the notification pop-up
    Type Text yes  alert, succes, error, warning, information or confirm
    Text Textarea yes  The text for the notification.
    Sticky notification Text no  Will the notification stick on the page.
    Hide after x seconds Integer no  Hide the notification after x seconds, only for not sticky notifications.
    Max visible notifications Text no The maximum number of visible notifications. More will be queued and become visible when other notifications disappear.
    Item receiving confirm answer Page Item/ Column no Item for confirm answer (Ok/Cancel)
  7. Now we build a wrapper around the noty jQuery plug-in. This is a Javascript function that is executed by APEX. In the wrapper we will receive the Custom Attributes defined in step 6 and pass them on to the noty jquery plug-in as parameters. There is a possibility in this function to use the value of an item for the attributes. To enable this you just put the item name between ‘#’s. (eg #P1_TEXT#).

    /**
    * Notification plugin v1.0 – http://www.orcado.nl
    *
    * Based on jQuery plugin noty
    * noty – jQuery Notification Plugin v2.1.2
    * Contributors: https://github.com/needim/noty/graphs/contributors
    * Examples and Documentation – http://needim.github.com/noty/
    * Licensed under the MIT licenses:
    * http://www.opensource.org/licenses/mit-license.php
    *
    **/
    function com_orcado_notification(){

    function replacePlaceholders(pText) {
    // replace all #page_item_name# placeholders
    var lSearchPattern = new RegExp(“#w+#”, “g”),
    lFoundList,
    lPageItem,
    lFinalText = pText;
    // search as long as the text contains a placeholder
    while (lFoundList = lSearchPattern.exec(pText)) {
    // get value, but search without the #
    lPageItem = $x(lFoundList[0].replace(/#/g, “”));
    if (lPageItem) {
    lFinalText = lFinalText.replace(lFoundList[0], $v(lPageItem));
    }
    }
    return lFinalText;
    }; // replacePlaceholders

    // It’s better to have named variables instead of using
    // the generic ones, makes the code more readable
    var lTheme = this.action.attribute01,
    lLayout = this.action.attribute02,
    lType = this.action.attribute03,
    lText = this.action.attribute04,
    lSticky = ((this.action.attribute05===”Y”)?false:this.action.attribute06*1000),
    lMax = this.action.attribute07;
    lAnswer = this.action.attribute08;

    if (replacePlaceholders(lType)==’confirm’) {
    var n = noty({
    text: replacePlaceholders(lText).replace(/n/g,’
    ‘),
    type: replacePlaceholders(lType),
    dismissQueue: true,
    layout: replacePlaceholders(lLayout),
    theme: lTheme,
    timeout: lSticky,
    maxVisible: replacePlaceholders(lMax),
    buttons: [
    {addClass: ‘btn btn-primary btn-noty-ok’, text: ‘Ok’, onClick: function($noty) {
    $s(lAnswer, ‘Ok’);
    $noty.close();
    }
    },
    {addClass: ‘btn btn-danger btn-noty-cancel’, text: ‘Cancel’, onClick: function($noty) {
    $s(lAnswer, ‘Cancel’);
    $noty.close();}
    }
    ]
    });
    }
    else { var n = noty({
    text: replacePlaceholders(lText).replace(/n/g,’
    ‘),
    type: replacePlaceholders(lType),
    dismissQueue: true,
    layout: replacePlaceholders(lLayout),
    theme: lTheme,
    timeout: lSticky,
    maxVisible: replacePlaceholders(lMax)
    });
    }
    }

    First, we assign the generic variables to named ones. This is not required but it makes the code more readable. Then we call the noty plug-in with the right parameters. As you can see, a bit of Javascript knowledge is needed here.

  8. Upload Files used by the plug-in.

    Name Description
    com_orcado_jquery.noty.js Javascript wrapper around the noty jquery plug-in
    custom.js Custom theme (based on default.js)
    default.js Default theme
    jquery.noty.js noty jquery plug-in
    layout.js Javascript which will set the css for the available layouts
  9. What remains is the PL/SQL code for rendering and executing the plug-in. You can see that you need to register the Javascript libraries and the function with attributes in the dynamic action

    function render_noty_notification (
    p_dynamic_action in apex_plugin.t_dynamic_action,
    p_plugin         in apex_plugin.t_plugin )
    return apex_plugin.t_dynamic_action_render_result
    is
    l_theme          varchar2(4000) := p_dynamic_action.attribute_01;
    l_layout         varchar2(4000) := p_dynamic_action.attribute_02;
    l_type           varchar2(4000) := p_dynamic_action.attribute_03;
    l_text           varchar2(4000) := p_dynamic_action.attribute_04;
    l_sticky         varchar2(4000) := p_dynamic_action.attribute_05;
    l_wait_seconds   number         := to_number(p_dynamic_action.attribute_06);
    l_max            varchar2(4000) := p_dynamic_action.attribute_07;
    l_answer         varchar2(4000) := p_dynamic_action.attribute_08;
    l_result         apex_plugin.t_dynamic_action_render_result;
    begin
    — During plug-in development it’s very helpful to have some debug information
    if apex_application.g_debug then
    apex_plugin_util.debug_dynamic_action (
    p_plugin         => p_plugin,
    p_dynamic_action => p_dynamic_action );
    end if;
    — Register the javascript and CSS library the plug-in uses.
    apex_javascript.add_library (
    p_name      => ‘jquery.noty’,
    p_directory => p_plugin.file_prefix,
    p_version   => null );
    apex_javascript.add_library (
    p_name      => ‘com_orcado_jquery.noty’,
    p_directory => p_plugin.file_prefix,
    p_version   => null );
    apex_javascript.add_library (
    p_name      => ‘default’,
    p_directory => p_plugin.file_prefix,
    p_version   => null );
    apex_javascript.add_library (
    p_name      => ‘layout’,
    p_directory => p_plugin.file_prefix,
    p_version   => null );
    — Register the function and the used attributes with the dynamic action framework
    l_result.javascript_function := ‘com_orcado_notification’;
    l_result.attribute_01 := l_theme;
    l_result.attribute_02 := l_layout;
    l_result.attribute_03 := l_type;
    l_result.attribute_04 := l_text;
    l_result.attribute_05 := l_sticky;
    l_result.attribute_06 := l_wait_seconds;
    l_result.attribute_07 := l_max;
    l_result.attribute_08 := l_answer;

    return l_result;
    end render_noty_notification;

  10. Save the plug-in by clicking Apply Changes

  11. 1. You finished building the plug-in so now we are able to use it. To do this, create a Dyamic action based on the plug-in we just built.
  12. Enter the values in the parameter fields:
  13. And you are done. The plug-in provides for nice notifications the way you specify in the dynamic action.
  14. See here for a demo
Currency Converter APEX plug-in

Na een vraag op het oracle forum over het converteren van valuta, heb ik besloten om hier een plug-in voor te schrijven.
De plug-in is niet echt heel spannend maar doet wel wat die moet doen.

Op basis van de koers die opgehaald wordt bij rate-exchange.appspot.com wordt de berekening uitgevoerd. Dit is een gratis webservice die de koers als JSON formaat terug geeft. Hier zat meteen de grootste uitdaging. Door de “same-origin policy” standaard mag je in javascript geen sites buiten het aanroepend domein benaderen. Wat je doet is een “Cross-origin resource sharing” aanroep en dat mag dus niet.
De aanroep resulteert in een No ‘Access-Control-Allow-Origin’ header is present on the requested resource.” error.

Een uitzondering hierop wordt gemaakt indien de URL via JSONP kan worden aan geroepen. Door het toevoegen van “&callback=?” aan de URL werkt de site van rate-exange op die manier. Nu krijg je wel de JSON data binnen.
De code hiervoor ziet er dan alsvolgt uit

var url = “http://rate-exchange.appspot.com/currency?from=”+$v(action.attribute01)+”&to=”+$v(action.attribute02)+”&callback=?”;

jQuery.ajax({  type: “GET”,
url: url ,
dataType: “json”,
success: function(data) {
$s(action.attribute04,($v(action.attribute03)*data.rate).toFixed(2));
}
});

Waarbij action.attributeXX de parameters van de plug-in zijn.

Voor de selectlists van de valuta keuzes wilde ik ook een webservice gebruiken. Dit hoeft niet via javascript maar kan “gewoon” door een select die de (XML) data van de webservice kan vertalen. Er is een webservice met deze informatie beschikbaar bij de ISO organisatie via deze URL.

select distinct extractvalue(value(x), ‘CcyNtry/CcyNm/text()’) ccynm
, extractvalue(value(x), ‘CcyNtry/Ccy/text()’)   ccy
from table(xmlsequence(extract(sys.xmltype.createxml(sys.urifactory.geturi(‘http://www.currency-iso.org/dam/downloads/table_a1.xml’) .getclob()), ‘//ISO_4217/CcyTbl/CcyNtry’))) x
order by ccynm

Dit werkt op apex.oracle.com echter niet, omdat je er een ACL voor moet aanmaken die toestemming geeft om de externe site te benaderen. In de demo gebruik ik dan ook STATIC selectlists.

Kijk op apex-plugin.com om de plugin te downloaden en hier voor een demo ervan.

 

Notification plug-in

Today I published a new version (1.1) of my notification plug-in.

In this version I added the possibility using free form text or predefined select lists for a few parameters.

Visit apex-plugin.com to get it.

v() en nv() mooie functies maar….

Inleiding

 

Zoals zo vaak bij het schrijven van een blog item wordt deze meestal getriggerd door een probleem waar ik tijdens het werk tegen aanloop. Ook dit item is er weer een uit die categorie.
De foutmelding die ik doorkreeg was net zo simpel als kort “De zoek opdracht op pagina xx performed niet goed”.
Ik hou wel van dit soort meldingen omdat het altijd weer een leuke uitdaging is om een performance probleem op te lossen en het resultaat direct bemerkt wordt door de klant.

Elementen betrokken bij het probleem

En dan begint het zoeken…

 

De verwachting is dat het probleem niet in de hoofdpagina met de “iframe container”, waarin de tabbladen worden geopend, zou zitten want die werkt voor de overige tabbladen prima. Daarbij komt nog dat een performance probleem “meestal” in een query zit.  Uiteindelijk de query opgezocht die gebruikt wordt.
select ..
      , ..
      , its.its_oni_package.oni_radio_group_score(p_score_type =>ind.ind_score_type, .. )
   from its_onderzoek_indicatoren oni
  where ..

De query is voor de leesbaarheid versimpeld. De query heb ik naar sqldeveloper gekopieerd om wat makkelijker te kunnen testen. Als eerst maar eens die functie uit query gehaald en jawel hoor de query performde meteen prima. Weer een stapje dichter bij het probleem. Dan maar eens die functie uitpluizen. In de functie zit een cursor die gebruik maakt van de nv()functie.

select .. 
  from its_onderzoek_indicatoren oni 
       . 
       . 
  join ios_elementen elm 
    on oni.oni_one_elm_id = elm.elm_id 
 where oni.oni_one_ond_nr = nv('p10105_ond_nr') 
   and elm.elm_id         = nv('p10105_element')

Voor het gemak heb ik deze functies vervangen door bind variabelen in mijn sqldeveloper query en ik kon geen performance probleem vaststellen. huuh, hoe kan dat nu? Het blijkt dat ik het probleem al opgelost had voordat ik exact wist wat het was.

select .. 
  from its_onderzoek_indicatoren oni 
       . 
       . 
  join ios_elementen elm 
    on oni.oni_one_elm_id = elm.elm_id
 where oni.oni_one_ond_nr = :p10105_ond_nr 
   and elm.elm_id         = :p10105_element

het is duidelijk dat het gebruik van nv()  functie het performance probleem creëert.

En dan nog oplossen…

 

Nu had ik het probleem dat ik wel wist waar het probleem zat maar hoe ga ik het nu oplossen en toch gebruik maken van deze nv() functie? Wat dacht je van een scalar subquery? De scalar subquery expression is een subquery die exact één kolom waarde van één record terug geeft. Om dit te testen is de query als volgt aangepast
select .. 
  from its_onderzoek_indicatoren oni 
       . 
       . 
  join ios_elementen elm 
    on oni.oni_one_elm_id = elm.elm_id 
 where oni.oni_one_ond_nr = (select nv('p10105_ond_nr')  from dual) 
   and elm.elm_id         = (select nv('p10105_element') from dual)
Dit heb ik voor het gemak direct in het package gedaan zodat het wat makkelijker te testen is. Vervolgens dezelfde zoekopdracht gegeven als waar ik mee begonnen ben en jawel hoor het probleem is opgelost!

Conclusie

 

– Indien mogelijk gebuik dan in je apex applicatie altijd bind variabelen ipv de n() en nv() functies.
– Scalar subqueries zijn een zeer handige oplossing als je de n() en nv() functies wel wil/moet gebruiken.
– Nu nog even mijn eigen applicaties controleren op het gebruik van de n() en nv() functies en aanpassen!
Apex bug fixing tijdens applicatie release

Bij mijn huidige opdrachtgever hebben we een prachtig systeem vanaf scratch mogen opbouwen. Inmiddels zijn we in een stadium aanbeland dat we niet meer in een projectgroep werken maar volledig in productie. Dit verandert ook de werkwijze op veel vlakken. Één daarvan is de manier hoe we moeten omgaan met bug fixing terwijl er op hetzelfde moment ook gewerkt wordt aan een nieuwe release. Gelukkig heeft APEX hiervoor de Build Option.
Met deze optie kan je diversen applicatie onderdelen in- of excluden. Hoe je de Build Option nu moet gebruiken, zal ik hieronder stap voor stap uitleggen.

 

Creëer Build Option.

Dit doe je bij de Shared Components
Hier kan je een nieuwe Build Option aanmaken.

buildoption1

 

Gebruik van de Build Option.

Je kunt de Build Option op verschillende plaatsen gebruiken.

Kortom op alles wat je op een pagina plaatst en de pagina zelf kan de de Build Option gebruiken.
Je geeft bij het betreffende object aan wanneer die moet worden gebruikt, wanneer de Build Option aan of uit staat.

buildoption3

buildoption4.

 

Productie of niet?

Op de bovenstaande manier kun je dus nieuwe objecten aan een pagina toevoegen en daarbij aangeven dat die bij een bepaalde Build Option horen.
Dit is natuurlijk allemaal heel handig maar hoe kun je nu aangeven welke release wanneer actief is?
Ook daar is over nagedacht. Bij de Build Option in de shared components kun je de status en default on export opgeven.

buildoption2

Met de status geef je aan of deze Build Option in de applicatie moet worden gebruikt of niet (include/exclude).
met Default on Export geef je aan of de Build Option mee moet worden genomen tijdens een export of deploy (via sqldeveloper).
Hierdoor is het dus mogelijk om aan een release te werken en toch een pagina op te leveren om een bug op te lossen.

 

Niet declaratief

Wat nu als je in bijvoorbeeld in een LOV bepaalde regels wel of niet wil tonen, afhankelijk van de Build Option. Je zou dan kunnen kiezen om 2 verschillende item’s te maken met ieder zijn eigen LOV en het juiste item tonen afhankelijk van de Build Option. Dit is echter niet altijd even handig omdat je de 2 item niet dezelfde naam kan geven. dit kan weer problemen geven met de verdere afhandeling op de pagina.
Ik heb dit opgelost door de Build Option aan te roepen via een apex api. In deze api is de functie apex_util.get_build_option_status aanwezig.

select case when apex_util.get_build_option_status(p_application_id    => 200,
p_build_option_name => ‘IBIS 2.4 (Signalen)’) = ‘EXCLUDE’
then ‘(Signaal)’
else ‘Signaal’
end
,
case when apex_util.get_build_option_status(p_application_id    => 200,
p_build_option_name => ‘IBIS 2.4 (Signalen)’) = ‘EXCLUDE’
then ‘SIG_exclude’
else ‘SIG’
end
from dual

Op deze manier kan ik dus 1 item gebruiken en in de query van de LOV de juiste gegevens selecteren op basis van de Build Option. De api kan natuurlijk op vele manieren worden gebruikt dit dient slechts als voorbeeld.

 

Conclusie

Je kunt met de standaard apex tools dus “gewoon” bugs oplossen terwijl er ondertussen wordt gebouwd aan een nieuwe release.

Hier komt de sidebar

Volg ons op

© Orcado B.V. | 1999 - 2018