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.
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...
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
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
Looks like another cool idea. But my quick read missed something. What is the placeholder and how do you insert it into your page?
Todd
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
The 'placeholder' is defined by the <div id='Favorites'></div> tags. This DIV is later on filled with HTML source.
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?
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.
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
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
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.
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.