Monday, May 12, 2025

APEX Template Components with input items

Template Components are a relatively new feature of Oracle APEX. I won't go into details about what they are or how to create your own components. There are plenty of examples out there already, like this one.

Following these tutorials, I created my agenda Template Component based on this example I found on internet (see the agenda report on the right).

The data for this report comes from a simple TC_AGENDA table and this query:

select id
     , rownum row_num
     , icon_class
     , time
     , primary
     , short
     , description
from tc_agenda

With some tweaking for APEX (like using Font Apex icons), I ended up with:


One thing that I missed in Template Components is the ability to easily add input items. I wanted to explore the options using this example and change the fixed description into a text area where you can change the description.

The end result looks like:


So, how can you do that?

Of course, you start with the original Partial template, that looks like:

{if APEX$IS_LAZY_LOADING/}
  <div></div>
{else/}
<div class="timeline-item">
    <div class="row">
        <div class="col-4 date">
            <i class="#ICON_CLASS#"></i>
            #TIME#
            <br>
            <small class="text-primary">#PRIMARY#</small>
        </div>
        <div class="col content {case ROW_NUM/}{when 1/} border-top-0{endcase/}">
            <p class="mb-1"><span class="ff-secondary fw-semibold">#SHORT#</span></p>
<p>#DESCRIPTION#</p>
            <p></p>
        </div>
    </div>
</div>
{endif/}

It is easy enough to change the DESCRIPTION HTML paragraph to an INPUT or, in this case, TEXTAREA component. However, that does nothing with the user input when you submit the page.

To actually be able to capture and save the description entered by the user, you have to fall back on the APEX_ITEM API and how APEX handles this data. 
Note: the APEX team considers APEX_ITEM "Legacy" and may remove the API in a future release. However, as long are there is no alternative, this API will still work.

APEX_ITEM generates items with a name attribute f01, f02, etc. When APEX sees these items after a page submit, it puts the values in the APEX_APPLICATION.G_Fnn arrays.
Knowing this, you can use the name attribute without actually using APEX_ITEM.

The DESCRIPTION paragraph can now be changed to:

<input type="hidden" name="f01" value="#ID#" id="f01">
<p><textarea id="f02" name="f02" rows="4" cols="30"
class="textarea apex-item-textarea" data-resizable="true"
style="resize: both;">#DESCRIPTION#</textarea></p>

The first, hidden, item, with the name "f01" holds the Primary Key value of the row (here #ID#).
The second item, with name "f02", is the text area that holds the description (#DESCRIPTION#). 

You can now create a submit Process to update the description in the agenda table (TC_AGENDA):

declare
  v_id    tc_agenda.id%type;
  v_descr tc_agenda.description%type;
begin
for i in 1..APEX_APPLICATION.G_F01.COUNT
loop
    apex_debug.message('ID '||I||': '||APEX_APPLICATION.G_F01(i) );
    apex_debug.message('Value '||I||': '||APEX_APPLICATION.G_F02(i) );
    v_id := APEX_APPLICATION.G_F01(i);
    v_descr := APEX_APPLICATION.G_F02(i);
    update tc_agenda
    set description = v_descr
    where id = v_id
    and description <> v_descr;
end loop;
end;


Thursday, March 31, 2022

Easy APEX email using JsPDF

 In my previous post, I explained how to easily create a PDF document of a part of a web page created with APEX.

Now that we know how to create, download and possibly print the PDF, the question arises: can we email this PDF file?

So, here is our APEX again, now with button Email:



The JavaScript to create the PDF file is more or less the same. Instead of downloading the file, we now upload the file to the database, and email the file from there using APEX_MAIL.

The JavaScript function called from the Click Dynamic Action is:


function mailJsPDF() {
    // This isn't mentioned anywhere, but it is needed since 2.0!
    window.jsPDF = window.jspdf.jsPDF;
    var doc = new jsPDF ();
    doc.html(document.getElementById('container'), {
       callback: function (doc) {
         // Save blob:
         var blob = new Blob ( [doc.output('blob')], {type: 'application.pdf'} );
         // Base64 encode:
         var reader = new FileReader();
         reader.onload = function () {
            // Since it contains the Data URI, we should remove the prefix and keep only Base64 string
            var b64 = reader.result.replace(/^data:.+;base64,/, '');
            //Send the blob to the server via Ajax process
            apex.server.process(
              'UPLOAD',
               {
                 p_clob_01: b64
               },
               {
                success: function(data) {
                    alert("The invoice was mailed");
                 }
               }
            );
         };
         reader.readAsDataURL(blob);
       },
            width: 170,
        windowWidth:700,
        margin: [20, 0, 0, 20],
        html2canvas: {
          scale: .238
        }    
    });
  }

The UPLOAD process gets the uploaded CLOB, converts it back to a BLOB, and emails it as an attachment. In a real application you will probably also store the generated PDF file in the database, get the email address from the website etc.

declare
  l_clob   clob;
  l_blob   blob;
  l_id     number;
begin
  --Get the clob passed in from the Ajax process
  l_clob := apex_application.g_clob_01;
  apex_debug.message('Uploaded: '||dbms_lob.substr(l_clob, 200, 1));

  -- Covert it back to a blob
  l_blob := APEX_WEB_SERVICE.CLOBBASE642BLOB (p_clob => l_clob);

  l_id := APEX_MAIL.SEND(
        p_to        => 'you@gmail.com',
        p_from      => 'me@gmail.com',
        p_subj      => 'Invoice with attachment',
        p_body      => 'Goodday, see the attachment.',
        p_body_html => 'Goodday, see the attachment.');
  APEX_MAIL.ADD_ATTACHMENT(
        p_mail_id    => l_id,
        p_attachment => l_blob,
        p_filename   => 'invoice.pdf',
        p_mime_type  => 'application/pdf');
  COMMIT;
  APEX_MAIL.PUSH_QUEUE();
 
  --Write some JSON out for the response
  apex_json.open_object();
  apex_json.write('status', 'success');
  apex_json.close_object();  
end;


 

Monday, February 14, 2022

Easy PDF reports in Oracle APEX

 A frequent question on APEX forums is: how do I create simple reports, like tickets, invoices etc.

Of course you can use some real report generator, like Apex Office Print (AOP) or Jasper. But in some cases the solution may be really simple: an APEX region and jsPDF to create a PDF document of that region. This may work if you are sure that your report will be just one page, like most invoices for instance.

This example was built in the sample database application, on the DEMO_ORDERS table. You can create a simple invoice page, part of which is shown here.

Invoice page


This is just a master-detail page based on  DEMO_ORDERS and  DEMO_ORDER_ITEMS and some additional CSS.
Make sure that everything you want to print is in one container region with a static id, and addressee, company info, items etc. as sub-regions of this container.

Now comes the tricky part: use jsPDF to create a PDF of the container region. I say tricky, because I found it hard to find examples using the latest jsPDF version. Most examples I found were using older versions, and apparently quite a lot has changed.

Start with attaching two JavaScript libraries to your page. jsPDF needs html2canvas to work.
I attached both libraries from a CDN:

https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js
https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js

The PRINT button on your page has a JavaScript DA.


window.jsPDF = window.jspdf.jsPDF;
var doc = new jsPDF();   
doc.html(document.getElementById('container'), {
   callback: function (doc) {
     doc.save();
   },
    width: 170,
    windowWidth:700,
    margin: [200020]
});

It took me quite some time that you need "window.jsPDF = window.jspdf.jsPDF;" on the first line. This was not really mentioned anywhere.

It was also surprising that in "margin: [200020]" the last number is the left margin, but it is actually mentioned in the documentation:

html - Documentation (githack.com)

The final result is a PDF document:


You have to trust me here that the first screenshot is of an APEX page and the second of a PDF  document.

There are some hickups in the PDF, e.g. in the email address as you can see. I haven't been using jsPDF and html2canvas long enough to know if that can be improved.  


Edit: if you face this spacing issue, try this:

Add css to the page:

body {
  letter-spacing: 0.2px;
}

Change the JavaScript code to:

window.jsPDF = window.jspdf.jsPDF;
var doc = new jsPDF ();
doc.html(document.getElementById('container'), {
   callback: function (doc) {
     doc.save();
   },
    width: 170,
    windowWidth:700,
    margin: [200020],
    html2canvas: {
      scale: .238
    }    
});

The result of these changes is:




Friday, October 15, 2021

Dynamic Actions on cards in Oracle APEX cards region


Oracle APEX card templates and card regions already have quite a few options you can set via the APEX Builder.

In a card region, a card can have an action, but the options are limited to a link to another page or URL. What if you just want to click on a card and draw attention to the selected card and set some item value based on that selection? Something like this:


This approach needs some custom Dynamic Actions and CSS.


Create the card region


Let's start with the card region.

First upload some images as static application files (or any other location).

Next create a page with a card region. For this example, I simply used this query for the card region:

select 'A' combi, '#APP_IMAGES#CombiA.png' image from dual union all
select 'B''#APP_IMAGES#CombiB.png' from dual union all
select 'C''#APP_IMAGES#CombiC.png' from dual union all
select 'D''#APP_IMAGES#CombiD.png' from dual

The cards only use the Media attributes:

Source: Image URL
URL: &IMAGE.

Next, you want to make the unselected cards a bit smaller, so a selected card can get the normal card size. For that, add this class to the card:

card-unselected

(the CSS will follow later).

Add an action to the cards:

Type: Full Card
Link: Redirect to URL
Target: #

Since we do not really want to go to some other page, we need to modify the behaviour of the link a bit. To do that, set this value in the Link Attributes:

data-action=select data-value=&COMBI.


Create a Dynamic Action


Now we can create a Click Dynamic Action.

Name: Get selected card
Selection Type: jQuery Selector
jQuery Selector: [data-action='select']

The first action is a Set Value to copy the card value to a page item.

Set Type: JavaScript Expression
JavaScript Expression:  this.triggeringElement.dataset.value

The second action increases the size of the selected card to the normal size, and to make it bit more fancy, also sets a drop shadow. This action also has to reset the previously selected card to the smaller size.

Action:Execute JavaScript Code
Code
:

$(".a-CardView").removeClass( "card-selected" );
$(".a-CardView").addClass( "card-unselected" );
this.triggeringElement.parentElement.classList.remove("card-unselected");
this.triggeringElement.parentElement.classList.add("card-selected");


Add the CSS


Define CSS on the page. The padding on a-CardView-items is needed to allow box-shadow on the right most card

.card-unselected {
   height:80%;
   border-style:none;
}
.card-selected {
   height:100%;
   border-style:none;
   box-shadow: 5px 10px 8px #888888;
}
.a-CardView-items{
  padding:10px;
}


Friday, October 14, 2016

Testing Apex 5 menus with Selenium IDE

Recently I started testing other web applications using Selenium IDE. Of course, the techniques can be used for Apex web applications too.

There are so many different aspects on web pages to take into account. What works in one application doesn't work in another. My first serious challenge in using Selenium IDE for testing Apex 5 applications was to get the drop down menu's to work. You don't need to have an application to run this test scenario. You can use Apex Builder.




The test to perform is:
1- Click on the Application Builder expand menu icon.
2- Open the Workspace Utilities sub-menu
3- Click on All Workspace Utilities

As always, you need a developer tool like Firebug to get the ID or xpath of an element.
Using the ID's of the elements, the end is simply:





Important to get this to work is the use of clickAt instead of click. 
I tried mouseDown, mouseUp, mouseOver, click. Nothing worked to activate the sub-menu. In the end it was clickAt that did open the sub-menu.

Sunday, August 23, 2015

Generate an Apex menu from Designer

In my previous post I showed you how can re-use Designer domains to generate domain LOV's.
Another Designer to Apex feature is the Module Structure to Menu List conversion.

Apex 5 now uses Lists for the menu structure instead of the old Tabs in Apex 4. This makes it really easy to make a menu structure based on the Module Structure in Designer.

Start with a menu structure table in your application.

CREATE TABLE  "APX_MENU_STRUCTURE"
   (    "ID" NUMBER(*,0),
    "APP" VARCHAR2(40),
    "TYPE" VARCHAR2(1),
    "SORT_ORDER" NUMBER(*,0),
    "LABEL" VARCHAR2(50),
    "PAGE" VARCHAR2(40),
    "PARENT" VARCHAR2(40)
   )


Create a BEFORE INSERT trigger to populate the primary key ID based on a sequence. I will skip that part here.

TYPE in this table is either M (for Menu) or P (for Page), as forms can be called from a menu or from another page.

 Next step is to generate a script to populate the menu structure table. For forms called from a menu:

select 'insert into apx_menu_structure (APP,TYPE,SORT_ORDER,LABEL,PAGE,PARENT) ' ||
       'select ' ||
      '''MY_APEX_APP''' || ',' ||
      decode(mc.general_module_type,'MENU','''M''','''P''') || ',' ||
      mn.CALLED_SEQUENCE || ',' ||
      '''' || mc.short_title || ''',' ||
      '''' || mc.short_name || ''',' ||
      '''' || mp.short_name || '''' ||
     ' from dual;'
from ci_module_networks mn
   , ci_modules mp
   , ci_modules mc
   , sdd_folder_members mem -- the application system reference goes through sdd_folder_members
   , ci_application_systems app
where mn.PARENT_MODULE_REFERENCE = mp.ID   
and mn.CHILD_MODULE_REFERENCE = mc.id
and mp.general_module_type = 'MENU'
and mc.general_module_type in ('MENU','DEFAULT')
and mem.member_object = mp.irid
and mem.folder_reference = app.irid
and app.name = 'MY_APPLICATION'
start with lower(mp.IMPLEMENTATION_NAME) = 'MNUMAIN'  -- your top menu
connect by prior mc.id = mp.id
order siblings by  mn.CALLED_SEQUENCE



For forms calling forms:

select 'insert into apx_menu_structure (APP,TYPE,SORT_ORDER,LABEL,PAGE,PARENT) ' ||
        'select ' ||
       '''MY_APEX_APP''' || ',' ||
       decode(mc.general_module_type,'MENU','''M''','''P''') || ',' ||
       mn.CALLED_SEQUENCE || ',' ||
       '''' || mc.short_title || ''',' ||
       '''' || mc.short_name || ''',' ||
       '''' || mp.short_name || '''' ||
      ' from dual;'
from ci_module_networks mn
   , ci_modules mp
   , ci_modules mc
   , sdd_folder_members mem -- the application system reference goes through sdd_folder_members
   , ci_application_systems app
where mn.PARENT_MODULE_REFERENCE = mp.ID   
and mn.CHILD_MODULE_REFERENCE = mc.id
and mp.general_module_type = 'DEFAULT'
and mc.general_module_type = 'DEFAULT'
and mem.member_object = mp.irid
and mem.folder_reference = app.irid
and app.name = 'MY_APPLICATION'




Run the generated scripts and you have the whole menu structure in the table.

Last step is to create the Apex menu list.

select level
      ,label
      ,decode(type,'M','#','f?p='||app||':'||page||':'||:APP_SESSION||'::'||:DEBUG) as target
from his_apx_menu_structure
where parent like 'MNU%'   -- see note below
start with parent = 'MNUMAIN'
connect by prior page = parent
order siblings by sort_order


As I said before, the whole module structure consists of forms called from menus and form called from other forms. If you have an easy way of recognizing a menu just by it's name, you can use this simple line in the WHERE clause :

where parent like 'MNU%'   -- a menu module starts with MNU

If not, it will be a bit more complicated.

For forms calling forms you have to decide how your forms are calling other forms. It could be an extra, context sensitive, menu option, or it could be that forms are called using buttons. In any case, you can still use the menu structure table to dynamically call Apex pages from another Apex page.


Wednesday, June 3, 2015

Oracle Designer and Forms to Apex: List of Values

If you are converting your Oracle Designer and Forms to Application Express (Apex), there are some components you can easily re-use. One of them is domain List of Values.

Domains and domain values are typically stored in table CG_REF_CODES. From this table it is simple to generate Apex List of Values for each domain.


declare
  -- Modify v_flow_id to your application ID

  v_flow_id      number := 101;
  -- You can give the LOV name a prefix to easily recognize LOV's

  -- derived from a domain, e.g. DMN_GENDER
  v_lov_prefix   varchar2(10) := 'DMN_';
begin
  for r in
  (select distinct rc.rv_domain
   from cg_ref_codes rc
   where rc.rv_meaning is not null
   and not exists
     (select 1
      from   apex_application_lovs
      where  application_id = v_flow_id
      and    list_of_values_name = v_lov_prefix || rc.rv_domain
     )    
  )
  loop
    wwv_flow_api.create_list_of_values (
      p_id       => null,
      p_flow_id  => v_flow_id,
      p_lov_name => v_lov_prefix || r.rv_domain,
      p_lov_query=> 'select rv_meaning d, rv_low_value r'||unistr('\000a')||
'from   cg_ref_codes'||unistr('\000a')||
'where  rv_domain = '''||r.rv_domain||''''||unistr('\000a')||
'order by 1');
 
  end loop;
end;


That's it.

I will be posting more tips on the Forms to Apex migration.

Wednesday, March 20, 2013

Apex tree and form one one page

I had an idea to combine a tree and a form in one page. Actually, just because I never used a tree before (yes, really), and also because I couldn't find an example on internet. The Oracle Sample Trees application shows how you can use a tree node with a link to another form page. That is a waste of a lot of space on the right side of the page. What I wanted is this:


Basically, you need to have a tree, a form and a dynamic action that refreshes the form when you click on an employee node. A lot of it I picked from Scott Wesley's blog.

Create the tree region

The tree uses this query

select case when connect_by_isleaf = 1 then 0
            when level = 1             then 1
            else                           -1
       end as status,
       level,
       label||' '||name as title,
       null as icon,
       id as value,
       name as tooltip,
       'javascript:pageItemValue('''||id||''')' as link
from
select 'D' item_type,
       null label,
       to_char(d.deptno) id,
       null parent,
       d.dname name,
       null tooltip,
       null link
from dept d
union all
select 'E' item_type,
       null label,
       to_char(e.deptno)||'_'||to_char(e.empno) id,
       to_char(e.deptno) parent,
       e.ename name,
       null tooltip,
       null link
from emp e
)
start with parent is null
connect by prior id = parent
order siblings by name

Nothing special here as you can see (I included an item_type in the query for possible use later on).

Add the form region

Scott uses a classic report report region to refresh when you click on a node, where I needed a form region. My first attempt was to create a "Form on a Table or View" region, but I soon got stuck here. This kind of form uses a After Header process to fetch a row, and therefore only works on a page submit. I needed a partial page refresh. (Maybe it is possible to use this process in an Ajax call too, but I have no idea how to do that). So, I used a Tabular Form instead, based on EMP, where I would query just one record. After the region is created using the wizard (choosing Update functionality only in this case), slightly modify the query by adding a WHERE clause

select 
"ROWID",
"EMPNO",
"ENAME",
"JOB",
"SAL",
"COMM"
from "#OWNER#"."EMP"
where "EMPNO"= to_number(substr(:Pn_SELECTED_NODE,4))



(I will get back on Pn_SELECTED_NODE. Include this item in the "Page items to Submit").

I've mainly used Apex 4.1, so with the new templates of Apex 4.2, I was a bit confused on how to get the form region to the right of the tree. You just have to set New Column = Yes in the Grid Layout of the form region.


Add a hidden item and some Javascript

As In Scott's example, add a hidden page item Pn_SELECTED_NODE to the page. It is important is to set Value Protected to No for this item.

Add the Javascript to to page header:

function pageItemValue(node)
{
  $s('Pn_SELECTED_NODE', node);
}

Create a dynamic action

Add the dynamic action to Pn_SELECTED_NODE, so that the form refreshes when you click on a node.


For this refresh to work, you have to to set Enable Partial Page Refresh to Yes in the Report Attributes for the tabular form region.

Get back to the same node after submitting the page

Now, this was actually the most frustrating part. On the tree, you have to set Selected Node Page Item to Pn_SELECTED_NODE to come back to the same node you selected before.

This just wouldn't work at all. I still have no idea why, though. I could see the correct value using the Session link when running the page, but after submitting the page (saving the updates in the form), the previously selected node just wouldn't highlight again.

At this point you just start to try about anything to get it to work. And voilà, the solution: on the branch back to the page, set Pn_SELECTED_NODE:




Monday, February 4, 2013

Apex and RESTful web services

You always read that it is so easy to consume RESTful or SOAP web services in Apex. Well, yes, when you know how it works. It took me some time to figure it out, googling for bits of information on every step. If you are struggling too, this may help you to get started.

The start lies in the database, and has nothing to do with Apex. You must make sure first that the web service you need can be reached from the database. For this, you have to configure a proper ACL (Access Control List). How to do this can be found in the Apex Application Builder User's Guide, Enabling Network Services in Oracle Database 11g. If you experience problems later on, read this blog by Joel Kallman for a possible explanation why it doesn't work.

To check if you can call the web service, first call it from within the database, before going to Apex. To test if it really works in Apex, you should probably run this logged in as the Apex schema owner (e.g. APEX_040100 for apex 4.1). Since that is usually not possible for developers, we will just assume (hope) it will work later on.
I have no idea what this web service at weather.gov does, but you can use it as a demo. It is just a good example of a GET type web service with 5 parameters.

In the simplest test, you call the url directly using utl_http.request, as you would in your browser:

set scan off
select utl_http.request( 'http://graphical.weather.gov/xml/sample_products/browser_interface/ndfdXMLclient.php?lat=38.99&lon=-77.01&product=time-series&begin=2004-01-01T00:00:00&end=2013-04-20T00:00:00&maxt=maxt&mint=mint') weather
from dual;

Using Apex functionality, the URL, parameters and parameter values are split in different input parameters for procedure apex_web_service.make_rest_request:

set scan off
declare
  v_result clob;
begin
  v_result := apex_web_service.make_rest_request(
    p_url            => 'http://graphical.weather.gov/xml/sample_products/browser_interface/ndfdXMLclient.php'
   ,p_http_method    => 'GET'
   ,p_parm_name => apex_util.string_to_table('lat:lon:product:maxt:mint')
   ,p_parm_value => apex_util.string_to_table('39:-77:glance:maxt:mint')
    );
  dbms_output.put_line(v_result);  
end;
This should return a big xml document. Later on, in Apex, I will extract the maximum temperature from this xml document. The maximum temperature can be found in this node:


<temperature time-layout="k-p24h-n7-1" type="maximum" units="Fahrenheit">
  <name>Daily Maximum Temperature</name>
  <value>34</value>
  ... etc.
</temperature>


Now to Apex. I won't describe the basic steps that can be found in the User's Guide (like the fact that you create a web service reference in the Shared Components of the application).
You can use the url and parameters that you used when testing the web service in the database



The tricky part, for me anyway, was what to put in "Response XPath". This was new to me, so I had to take a crash course at w3schools.com XPath.Tutorial.
This is where the XML response you got when testing the web service in the database comes in. In this example, the XPath is:

//temperature[@type="maximum"]/value

which (sort of) means: select all value elements that are children of temperature elements that have an attribute named type with a value 'maximum'.

The REST output parameter path is now just "/*", since the XPath already goes down to the value element.

So, here is a trick to play with the XPath. You copy the part of the XML response you need and put that part as text in this query:

with c as
  (select xmltype('
                    
                       Daily Maximum Temperature
                       50
                       50
                       62
                       68
                       61
                       53
                       52
                    
                   ') xmltype001
   from dual               
  )
select extractValue(value(t),'/*') "value"
from c
   , table(xmlsequence(extract(c.xmltype001,'//temperature[@type="maximum"]/value'))) t 

Now you can easily play around with the Response XPath ('//temperature[@type="maximum"]/value') and Output parameter Path ('/*') until it works.

Once this is all done it is easy to create a page for this web service.
Create a new page and select Form, Form and Reports on Web Service. For the rest, you just follow the wizard.

There is another version of RESTful web services that doesn't use parameters (or it uses a mixed version). An example is this URL:

http://services.faa.gov/airport/status/SFO?format=xml

This uses the airport code ('SFO') as part of the URL, and format as a parameter. So, how do you put the airport code in the Apex web service reference? This is simply done by substituting a page item in the URL, e.g.

http://services.faa.gov/airport/status/&P5_AIRPORT_CODE.

In this case, you need to create a page item P5_AIRPORT_CODE manually. The wizard won't do this for you.


Wednesday, February 9, 2011

Apex security and updatable views (2)

In my previous post, I indicated that the code for table API's and instead of triggers can easily be generated.

The code generator takes the Apex generated table API package as the basis, not the table definition. I don't know if Apex can generate code for every column data type. If Apex doesn't generate code for some column, the following code generator won't either.

Before generating the instead of triggers, you have to generate the package that lies on top of the apex API package.
There are some constants in the declaration section of this script that you have to modify. An important one is v_execute. When set to TRUE, the generated CREATE PACKAGE code will be executed in the database.
set scan off
declare
-- Create a custom package that call table API's that are generated using the Apex table API generator
-- (in SQL Workshop -> Methods on Tables).
-- Modify this package, instead of the Apex generated package, to add business rules.

   -- Change these constants before generating   
   
   -- Table on which view is based
   v_base_table    constant varchar2(30) := 'emp'; 
   -- Name of the Apex generated package 
   v_pkg           constant varchar2(30) := 'pkg_emp';
   -- Run execute immediate for generated code or not
   v_execute       constant boolean      := false;  

   -- Apex generated API prefixes
   v_upd_procedure constant varchar2(30) := 'UPD_';
   v_ins_procedure constant varchar2(30) := 'INS_';
   v_del_procedure constant varchar2(30) := 'DEL_';
   
   -- For code formatting 
   v_indent        constant integer      := 3;
   cr              constant varchar2(10) := chr(10);

   v_body                   varchar2(32767);

   cursor c_args(b_procedure  in varchar2) is
      select ua.sequence
            ,lpad(' ',3*v_indent)||rpad(lower(ua.argument_name),20)||'   '||
                lower(ua.in_out)||' '||lower(ua.data_type)||','||cr code
      from   user_arguments ua
      where  ua.object_name   = upper(b_procedure||v_base_table)
      and    ua.package_name  = upper(v_pkg)
      order by 1 
   ;   

   cursor c_bind(b_procedure  in varchar2) is
      select ua.sequence
            ,lpad(' ',3*v_indent)||rpad(lower(ua.argument_name),20)||' => '||
               lower(ua.argument_name)||','||cr code
      from   user_arguments ua
      where  ua.object_name   = upper(b_procedure||v_base_table)
      and    ua.package_name  = upper(v_pkg)
      order by 1 
   ;
   
   procedure create_procedure_spec (i_procedure in varchar2)
   is
   begin
      v_body := v_body || lpad(' ',v_indent)||'procedure '||
                   lower( i_procedure||v_base_table||' (' )||cr;

      -- Procedure arguments
      for r in c_args(i_procedure)
      loop
         v_body := v_body || r.code;
      end loop;   

      v_body := rtrim(v_body,cr||',')||cr||lpad(' ',v_indent)||');';
   
   end create_procedure_spec;
   
   
   procedure create_procedure_body (i_procedure in varchar2)
   is
   begin
      v_body := v_body || lpad(' ',v_indent)||'procedure '||
                  lower( i_procedure||v_base_table||' (' )||cr;

      -- Procedure arguments
      for r in c_args(i_procedure)
      loop
         v_body := v_body || r.code;
      end loop;   

      -- Procedure body
      v_body := rtrim(v_body,cr||',')||cr||lpad(' ',v_indent)||') is' || cr ||
         lpad(' ',v_indent) || 'begin'||cr ||
         lpad(' ',2*v_indent) || '-- Add business rules code here before calling table API.' || cr ||
         lpad(' ',2*v_indent) || 'null;' || cr || cr ||
         -- Call to Apex generated API
         lpad(' ',2*v_indent)||lower( v_pkg||'.'||i_procedure||v_base_table||' (' )||cr;

      -- Bind procedure arguments to Apex generated API arguments
      for r in c_bind(i_procedure)
      loop
         v_body := v_body || r.code;
      end loop;   

      v_body := rtrim(v_body,cr||',')||cr||lpad(' ',2*v_indent)||');'||cr;
      v_body := v_body || lpad(' ',v_indent) || 'end '||lower( i_procedure||v_base_table)||';';
   end create_procedure_body;
   
   
begin
   
   /*----  Package specification ----*/
   
   v_body := 'create or replace package '||lower(v_pkg)||'_br is' || cr ||
      '-- Package generated on: ' || to_char(sysdate,'DD-MON-YYYY HH24:MI:SS') || cr ||
      '-- Manually add programmer defined code before call to table API.' || cr || 
      '-- Date         Author  Remarks'  || cr ||
      '-- ===========  ======  =======================================================' || cr ||
      '-- ' || to_char(sysdate,'dd-mon-yyyy') || '          Initially generated package'|| cr || cr
      ;
   
   -- Update procedure
   create_procedure_spec (v_upd_procedure);
  
   -- Insert procedure
   v_body := v_body || cr || cr;
   create_procedure_spec (v_ins_procedure);

   -- Delete procedure
   v_body := v_body || cr || cr;
   create_procedure_spec (v_del_procedure);

   -- End package specification
   v_body := v_body || cr || 'end;';

   dbms_output.put_line(v_body);
   
   if v_execute then
      execute immediate v_body;
   end if;   


   /*----  Package body ----*/

   v_body := null;
   v_body := 'create or replace package body '||lower(v_pkg)||'_br is'||cr; 

   -- Update procedure
   create_procedure_body (v_upd_procedure);
  
   -- Insert procedure
   v_body := v_body || cr || cr;
   create_procedure_body (v_ins_procedure);

   -- Delete procedure
   v_body := v_body || cr || cr;
   create_procedure_body (v_del_procedure);

   -- End package body
   v_body := v_body || cr || 'end;';

   dbms_output.put_line(v_body);
   
   if v_execute then
      execute immediate v_body;
   end if;   
end;

The code for generating the instead of triggers is similar, and so are the constants in the declaration section that you have to set.
set scan off
declare
-- Create instead of triggers on a view that is defined as "create view as select * from "
-- The instead of triggers call table API's that are generated using the Apex table API generator
-- (in SQL Workshop -> Methods on Tables)

   -- Change these constants before generating 

   -- View for which you are creating the instead of triggers
   v_view          constant varchar2(30) := 'emp_v';
   -- Table on which view is based
   v_base_table    constant varchar2(30) := 'emp'; 
   -- Name of the Apex generated package 
   v_pkg           constant varchar2(30) := 'pkg_emp';
   -- Run execute immediate for generated code or not
   v_execute       constant boolean      := false; 

   -- Argument in API that does not correspond to a column
   v_non_column    constant varchar2(30) := 'P_MD5'; 
   -- Apex generated API prefixes
   v_upd_procedure constant varchar2(30) := 'UPD_';
   v_ins_procedure constant varchar2(30) := 'INS_';
   v_del_procedure constant varchar2(30) := 'DEL_';
   
   -- For code formatting 
   v_indent        constant integer      := 3;
   cr              constant varchar2(10) := chr(10);
   
   v_trigger                varchar2(32767);
   v_body                   varchar2(32767);
   
   cursor c_args (b_procedure in varchar2) is
      select ua.sequence
            ,lpad(' ',3*v_indent)||rpad(lower(ua.argument_name),20)||' => '||
                 decode(b_procedure,v_del_procedure,':old.',':new.')||
                 substr(lower(ua.argument_name),3)||','||cr code
      from   user_arguments ua
      where  ua.object_name   = upper(b_procedure||v_base_table)
      and    ua.package_name  = upper(v_pkg)
      and    ua.argument_name <> v_non_column
      union
      select ua.sequence
            ,lpad(' ',3*v_indent)||rpad(lower(ua.argument_name),20)||' => '||'null'||','||cr code 
      from   user_arguments ua
      where  b_procedure      = v_upd_procedure  -- UPD procedure has non-column argument
      and    ua.object_name   = upper(b_procedure||v_base_table)
      and    ua.package_name  = upper(v_pkg)
      and    ua.argument_name = v_non_column
      order by 1 
   ;

   procedure create_procedure_body (i_procedure in varchar2)
   is
   begin
      v_body := lpad(' ',v_indent)||
                lower( v_pkg||'_br'||'.'||i_procedure||v_base_table||' (' )||cr;

      for r in c_args(i_procedure)
      loop
         v_body := v_body || r.code;
      end loop;   

      v_body := rtrim(v_body,cr||',')||cr||lpad(' ',v_indent)||');';
   end  create_procedure_body;
        
begin
   -- Instead of update
   create_procedure_body(v_upd_procedure);
   
   v_trigger := 'create or replace trigger iur_'||lower(v_view)||cr
              ||'instead of update on '||lower(v_view)||cr
              ||'for each row'||cr
              ||'begin'||cr
              ||v_body||cr
              ||'end;'; 

   dbms_output.put_line(v_trigger);
   
   if v_execute then
      execute immediate v_trigger;
   end if;   

   -- Instead of insert
   create_procedure_body(v_ins_procedure);
   
   v_trigger := 'create or replace trigger iir_'||lower(v_view)||cr
              ||'instead of insert on '||lower(v_view)||cr
              ||'for each row'||cr
              ||'begin'||cr
              ||v_body||cr
              ||'end;'; 

   dbms_output.put_line(v_trigger);
   
   if v_execute then
      execute immediate v_trigger;
   end if;   

   --
   -- Instead of delete
   
   create_procedure_body(v_del_procedure);
   
   v_trigger := 'create or replace trigger idr_'||lower(v_view)||cr
              ||'instead of delete on '||lower(v_view)||cr
              ||'for each row'||cr
              ||'begin'||cr
              ||v_body||cr
              ||'end;'; 

   dbms_output.put_line(v_trigger);
   
   if v_execute then
      execute immediate v_trigger;
   end if;   

end;