Skip to main content

Reusable Module Components in APEX

When building a serious application you often need to repeat certain regions in a couple of pages, for instance customer or order information. In APEX you can ofcourse copy a region to another page, but that enhances your maintenance effort when changes are needed on this copied region. Another solution is to group all the pages that share this region in one application and define the region on the famous Page 0. But maybe there is a more elegant way to solve this....

As an example I use the 'My Favorite Tasks' region I described in the previous post. First we set the page alias to 'MYFAV', to circumvent a static reference to a page number. In the region header of 'My Favorite Tasks' we set a HTML tag '<snap>' and '</snap>' in the footer (this <snap> is just an example, it can be anything, as long as it isn't a regular HTML tag). Now define a new HTML region on the page where you need to 'reference' this 'My Favorite Tasks' region and set the region source to <div id='Favorites'></div>. Now we created a placeholder for future use...

Now let's fill this placeholder with the contents of the 'My Favorite Tasks' region.
Set the Page HMTL Body Attribute to : onload="ShowFavs();". This will fire a javascript function when the page is loaded. You probably have to set the value of 'Cursor Focus' to 'Do not focus cursor' otherwise you'll get a 'You may not declaratively set cursor focus' error. Next define the javascript function in the HTML Header section.

<script type="text/javascript">
function ShowFavs(){
var get = new htmldb_Get(null,$v('pFlowId'),null,'MYFAV');
gReturn = get.get(null,'<snap>','</snap>');
get = null;
$s('Favorites',gReturn);
return;
}
</script>


This function will request the 'MYFAV' page. Then the page is 'stripped' so only the HTML between the tags is returned. And this HTML is rendered in the placeholder...

So with this simple and elegant solution you can define 'region building blocks' (even more meaningful than 'My Favorite Tasks') and assemble pages using this building blocks! So re usability increases and your maintenance effort decreases.

In a next post I will show how you even can put regions anywhere on a page without defining a placeholder at all...

Comments

Stew said…
Roel,

Looks like another cool idea. But my quick read missed something. What is the placeholder and how do you insert it into your page?
Anonymous said…
ingenious - nice blog!
Todd
Patrick Wolf said…
Roel,

interesting approach, but I'm not sure if from a performance point of view it's the best. Because it means that for each page request it does one or more additional FULL page requests with all the APEX session overhead to get this data and if you have multiple regions on your lookup (MYFAV) page it will render them without being used by the caller.

I think the page 0 approach is still better for the consumed resources.

Greetings
Patrick
Roel said…
@Stew
The 'placeholder' is defined by the <div id='Favorites'></div> tags. This DIV is later on filled with HTML source.
tine said…
Hello Roel,

I thought about using this approach, too.

I have some kind of input template, where the users can select a period of time for which data shall be shown, which I would like to use on severeal pages.

That's why I thought of using an approach like you described. But I didn't get it working, because if I want to use the entered period of time to generate the report, the "page" with the input template has to be submitted.
I have not been able to tell ApEx, that the page to be submitted is this input template "page" and not the current page.

Unfortunately I am not very strong with JavaScript right now. So my question is: did you get this part working?
If Yes, could be so kind an write and give some instructions on this part, too?
Roel said…
@Patrick

To reduce the consuming of resources the 'reusable module component' (RMC) should be the only region on the page. Otherwise there will be a lot of useless page rendering indeed.

The restriction on the "Page 0 approach" is that a region will always be rendered on the same location on your pages (but probably is what you need most of the time...), this RMC approach gives you more freedom in placing the "same" region in multiple locations on your page.
Apart from that the "Page 0 approach" results in only one request and the RMC approach needs an extra (small) request for every RMC (although I can't measure it using Firebug, YSlow or WebDeveloper Toolbar). So you have to weight out both approaches on their pros and cons.
Roel said…
@tine

In my approach (and the Page 0 approach also) the region is an integrated part of the page. And you can only submit a whole page. But you need a submit (or - partial page - refresh) of the page to show the data you want in your report.
In your report you can refer to an item in the 'template' - which in fact comes form another page - to restrict the result set.
Or didn't I get the point?

HTH
Roel said…
@tine

I tried what you were describing and now I get the point!
I created an item P_YEAR with as type 'Select List with Submit' - with a couple of years as contents of the LOV - on page 15 and embedded it in page 5 the way I described in this post. And then, when I select another year, page 15 pops up, and not page 5!

So I defined another item P_PAGE on page 15 and fill this item when calling page 15 in the Javascript function using:
get.add( 'P_PAGE', $v('pFlowStepId'));

Next on Page 15 I changed the item type from 'Select List with Submit' to 'Select List' and set
'HTML Form Element Attributes' to onchange="location.href='f?p=&APP_ID.:&P_PAGE.:&SESSION.::NO::P_YEAR:' +this.options[selectedIndex].value;"

Now the P_PAGE (e.g. Page 5) will be shown with P_YEAR as the parameter...

HTH
Doug said…
Roel,

I've used this method for quite some time in our commercial HAWCS product. We have several reports and graphs that are used various times throughout the product, and on different sections of the page.

There are, however, a couple of things I have done differently.

First, I've created completely stripped down template to use for the pages that will be "screen scraped". This makes the render of the called page faster as it doesn't have to worry about retrieving and rendering items that will not be shown.

Second, I have used an ASYNC. GET instead of the standard HTMLDB get.

Here is the code I use....



htmldb_Get.prototype.GetAsync=function(A){
try{
p=new XMLHttpRequest()
}
catch(C)
{
p=new ActiveXObject("Msxml2.XMLHTTP")
}
try{
var B=new Date();
p.open("POST",this.base,true);
if(p){p.onreadystatechange=A;
p.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
p.send(
this.queryString==null?this.params:this.queryString);
return p}
}
catch(C){return false}
};


function get_dashboard(p_page_id){
if ( $x('DASHBOARD') != null) {
var get = new htmldb_Get(null ,$x('pFlowId').value, null, p_page_id); // Replace last parameter with a valid page to scrape.
get.GetAsync(f_AsyncReturn);
get = null;
}
return;
}

function f_AsyncReturn(){
if(p.readyState == 1){
$x('DASHBOARD').innerHTML = '< img src="#APP_IMAGES#loading.gif" />';
}else if(p.readyState == 2){
}else if(p.readyState == 3){
}else if(p.readyState == 4){
var rText = p.responseText;
var vStart = rText.indexOf('< htmldb:BOX_BODY>')+17;
var vEnd = rText.indexOf('< /htmldb:BOX_BODY>')-vStart;
var inner = rText.substr(vStart, vEnd);
$x('DASHBOARD').innerHTML = inner;
}else{return false;}
}




In the above code "DASHBOARD" is the region into which the "screen scraped" content will be placed. And instead of using < snap>< /snap> tags, I have used < html:BOX_BODY> tags.

The benefit of the asynchronous get is that if the user decides to change his mind and navagate somewhere else in the application, there is less risk navigation being blocked until the GET returns.
tine said…
Hello Roel,

thank you for efforts :).
That was the point I didn't get working because of my knowledge lack in JavaScript.

I will give your solution a try.

Popular posts from this blog

Refresh selected row(s) in an Interactive Grid

In my previous post I blogged about pushing changed rows from the dabatase into an Interactive Grid. The use case I'll cover right here is probably more common - and therefore more useful!

Until we had the IG, we showed the data in a report (Interactive or Classic). Changes to the data where made by popping up a form page, making changes, saving and refreshing the report upon closing the dialog. Or by clicking an icon / button / link in your report that makes some changes to the data (like changing a status) and ... refresh the report.  That all works fine, but the downsides are: The whole dataset is returned from the server to the client - again and again. And if your pagination size is large, that does lead to more and more network traffic, more interpretation by the browser and more waiting time for the end user.The "current record" might be out of focus after the refresh, especially by larger pagination sizes, as the first rows will be shown. Or (even worse) while you…

Dockerize your APEX development environment

Nowadays Docker is everywhere. It is one of the main components of Continuous Integration / Continuous Development environments. That alone indicates Docker has to be seen more as a Software Delivery Platform than as a replacement of a virtual machine.

However ...

If you are running an Oracle database using Docker on your local machine to develop some APEX application, you will probably not move that container is a whole to test and production environments. Because in that case you would not only deliver a new APEX application to the production environment - which is a good thing - but also overwrite the data in production with the data from your development environment. And that won't make your users very excited.
So in this set up you will be using Docker as a replacement of a Virtual Machine and not as a Delivery Platform.
And that's exactly the way Martin is using it as he described in this recent blog post. It is an ideal way to get up and running with an Oracle database …

apex_application.g_f0x array processing in Oracle 12

If you created your own "updatable reports" or your custom version of tabular forms in Oracle Application Express, you'll end up with a query that looks similar to this one:
then you disable the "Escape special characters" property and the result is an updatable multirecord form.
That was easy, right? But now we need to process the changes in the Ename column when the form is submitted, but only if the checkbox is checked. All the columns are submitted as separated arrays, named apex_application.g_f0x - where the "x" is the value of the "p_idx" parameter you specified in the apex_item calls. So we have apex_application.g_f01, g_f02 and g_f03. But then you discover APEX has the oddity that the "checkbox" array only contains values for the checked rows. Thus if you just check "Jones", the length of g_f02 is 1 and it contains only the empno of Jones - while the other two arrays will contain all (14) rows. So for processing y…