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.
Unknown 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 Gault 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.
Unknown 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

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

Filtering in the APEX Interactive Grid

Remember Oracle Forms? One of the nice features of Forms was the use of GLOBAL items. More or less comparable to Application Items in APEX. These GLOBALS where often used to pre-query data. For example you queried Employee 200 in Form A, then opened Form B and on opening that Form the Employee field is filled with that (GLOBAL) value of 200 and the query was executed. So without additional keys strokes or entering data, when switching to another Form a user would immediately see the data in the same context. And they loved that. In APEX you can create a similar experience using Application Items (or an Item on the Global Page) for Classic Reports (by setting a Default Value to a Search Item) and Interactive Reports (using the  APEX_IR.ADD_FILTER  procedure). But what about the Interactive Grid? There is no APEX_IG package ... so the first thing we have to figure out is how can we set a filter programmatically? Start with creating an Interactive Grid based upon the good old Employ

Stop using validations for checking constraints !

 If you run your APEX application - like a Form based on the EMP table - and test if you can change the value of Department to something else then the standard values of 10, 20, 30 or 40, you'll get a nice error message like this: But it isn't really nice, is it? So what do a lot of developers do? They create a validation (just) in order to show a nicer, better worded, error message like "This is not a valid department".  And what you then just did is writing code twice : Once in the database as a (foreign key) check constraint and once as a sql statement in your validation. And we all know : writing code twice is usually not a good idea - and executing the same query twice is not enhancing your performance! So how can we transform that ugly error message into something nice? By combining two APEX features: the Error Handling Function and the Text Messages! Start with copying the example of an Error Handling Function from the APEX documentation. Create this function