Tuesday, January 5, 2010

Implementing Export Button Functionality Programatically

Hi All,
There is often a requirement to export a VO data or a UI table data into a CSV file. OA Framework provides export button bean for the this functionality, which works fine in almost 99% of the cases. But sometimes, you might face a scenario when ur table u have complex UIs like switcher/hide show columns etc, where the standard export functionality doesn't work or you want some columns not to come in export, or you wanna change some data on export.

In such cases, you can implement the export functionality programatically by yourself.Copy paste the following method in the page CO and pass appropriate parameters to get exported data.You can change this method, as per your requirement, to get data/format data or change data.

By default this method will bring all columns of fetch rows of VO instance,although you can use param hidden_attrib_list to pass attributes which you don't want to be included in csv file.You can call this method in the submit button event that you have made in process form request like :

//see api parameter details in the method below.
//array of Vo attr names which need not be written in csv file
String ss[]={xID,xName};
downloadCsvFile(pageContext, "XxAdatVisSearchVO",null, "MAX",ss);

/**
* @param pageContext
* @param view_inst_name is the view object instance name like VO1 etc in root AM.
* Make sure the VO instance name you have passed should be same as in Root AM.
* @param file_name_without_ext - pass for eg for abc.csv , u should pass "abc".If no
* name is passed then by default it will pick "Export.csv".
* @param max_size -pass "MAX", if u want all rows, pass null to get fetch row count
* else pass integer number like 10,20 etc , the number of rows you want to fetch.
* @param hidden_attrib_list -Array of VO attribute names which doesn't need to be shown/written in
* csv file.
*/
public void downloadCsvFile(OAPageContext pageContext,
String view_inst_name,
String file_name_without_ext,
String max_size, String[] hidden_attrib_list)
{
OAViewObject v =
(OAViewObject) pageContext.getRootApplicationModule().findViewObject(view_inst_name);

if (v == null)
{
throw new OAException("Could not find View object instance " +
view_inst_name + " in root AM.");
}
if (v.getFetchedRowCount() == 0)
{
throw new OAException("There is no data to export.");
}
String file_name = "Export";
if (!((file_name_without_ext == null) ||
("".equals(file_name_without_ext))))
{
file_name = file_name_without_ext;
}
HttpServletResponse response =
(HttpServletResponse) pageContext.getRenderingContext().getServletResponse();
response.setContentType("application/text");
response.setHeader("Content-Disposition",
"attachment; filename=" + file_name + ".csv");
PrintWriter pw = null;

try
{
pw = response.getWriter();
int j = 0;
int k = 0;
boolean bb = true;
if ((max_size == null) || ("".equals(max_size)))
{
k = Integer.parseInt(pageContext.getProfile("VO_MAX_FETCH_SIZE"));
bb = false;
}
else if ("MAX".equals(max_size))
{
bb = true;
}
else
{
k = Integer.parseInt(max_size);
bb = false;
}

//Making header
AttributeDef[] a = v.getAttributeDefs();
StringBuffer cc = new StringBuffer();
ArrayList exist_list = new ArrayList();
for (int l = 0; l < a.length; l++)
{
boolean zx = true;
if (hidden_attrib_list != null)
{
for (int z = 0; z < hidden_attrib_list.length; z++)
{
if (a[l].getName().equals(hidden_attrib_list[z]))
{
zx = false;
exist_list.add(String.valueOf(a[l].getIndex()));
}
}
}
if (zx)
{
cc.append("\"" + a[l].getName() + "\"");
cc.append(",");
}
}
String header_row = cc.toString() + "\n";
pw.write(header_row);

for (OAViewRowImpl row = (OAViewRowImpl) v.first(); row != null;
row = (OAViewRowImpl) v.next())
{
j++;
StringBuffer b = new StringBuffer();
for (int i = 0; i < v.getAttributeCount(); i++)
{
boolean cv = true;
for (int u = 0; u < exist_list.size(); u++)
{
if (String.valueOf(i).equals(exist_list.get(u).toString()))
{
cv = false;
}
}

if (cv)
{
Object o = row.getAttribute(i);

if (!(o == null))
{
if (o.getClass().equals(Class.forName("oracle.jbo.domain.Date")))
{
//formatting of date
oracle.jbo.domain.Date dt = (oracle.jbo.domain.Date) o;
java.sql.Date ts = (java.sql.Date) dt.dateValue();
java.text.SimpleDateFormat displayDateFormat =
new java.text.SimpleDateFormat("dd-MMM-yyyy");
String convertedDateString = displayDateFormat.format(ts);
b.append("\"" + convertedDateString + "\"");
}
else
{
b.append("\"" + o.toString() + "\"");
}
}
else
{
b.append("\"\"");
}
b.append(",");
}
}
String final_row = b.toString() + "\n";
pw.write(final_row);
if (!bb)
{
if (j == k)
{
break;
}
}
}
}
catch (Exception e)
{
// TODO
e.printStackTrace();
throw new OAException("Unexpected Exception occured.Exception Details :" +
e.toString());
}
finally
{
pw.flush();
pw.close();
}
}

Happy coding!

Monday, December 28, 2009

Scrolling Table :Table with horizontal Scrollbars and vertical scrollbars

Hi All,
I hope this is quite an interesting reading for most developers and they will try to put this feature in OAF tables.This is a very essential requirement in any of the custom OAF pages, where you show data in OATableBean and table has lot of columns, so, user has to scroll the entire page from scroll bar which is automatically rendered by browser if table width is more than page width.This is very painful for user as everytime, he scrolls the scroll-bar to see extra table columns,entire page is scrolled and he has to scroll back to the page to see other data.Similary, if you are displaying more than 10-20 rows in the table , user needs to scroll the entire page to see all the rendered rows of table.

In order to lessen the pain of the user and to have better UI design, we will render scroll bars both horizontal and vertical just surrounding the table
as , shown in the image below :


So, now lets focus how can we bring these scroll bars. In that case you can use the DIV tag to do the job for you.Put 2 RawText Bean(named DivStart, DivEnd), before and after the Table Bean in the JRAD page as shown in the image below IN JDEVELOPER:



You can use following API in process request :
public void processRequest(OAPageContext pageContext, OAWebBean webBean)
{
super.processRequest(pageContext, webBean);
//SEE ALL PARAMETER values you need to pass in the java comments of the method
addScrollBarsToTable(pageContext, webBean,"DivStart","DivEnd",true,"400",true,"400");
.
.
}


/**
* @param pageContext the current OA page context.
* @param webBean the web bean corresponding to the region.Pass the webBean of pagelayout.
* @param preRawTextBean is name of the Rawtext bean just above the table bean.
* @param postRawTextBean is name of the Rawtext bean just below the table bean.
* @param horizontal_scroll is boolean value if horizontal scrollbar is required/not required.
* @param width is the minimum width of table beyond which horizontal scrollbar is displayed.i.e
* if width is greater than this horizontal scrollbar is displayed.If this is passed as null, defalt value
* is considered which is 400.
* @param vertical_scroll is boolean value if vertical scrollbar is required/not required.
* @param height is the minimum height of table beyond which horizontal scrollbar is displayed .i.e
* if width is greater than this horizontal scrollbar is displayed.If this is passed as null, defalt value
* is considered which is 400.
*/
public void addScrollBarsToTable(OAPageContext pageContext,
OAWebBean webBean,
String preRawTextBean,
String postRawTextBean,
boolean horizontal_scroll, String width,
boolean vertical_scroll, String height)
{
String l_height = "400";
String l_width = "400";
pageContext.putMetaTag("toHeight",
"<style type=\"text/css\">.toHeight {height:24px; color:black;}</style>");

OARawTextBean startDIVTagRawBean =
(OARawTextBean) webBean.findChildRecursive(preRawTextBean);
if (startDIVTagRawBean == null)
{
throw new OAException("Not able to retrieve raw text bean just above the table bean. Please verify the id of pre raw text bean.");
}

OARawTextBean endDIVTagRawBean =
(OARawTextBean) webBean.findChildRecursive(postRawTextBean);
if (endDIVTagRawBean == null)
{
throw new OAException("Not able to retrieve raw text bean just below the table bean. Please verify the id of post raw text bean.");
}

if (!((height == null) || ("".equals(height))))
{
try
{
Integer.parseInt(height);
l_height = height;
}
catch (Exception e)
{
throw new OAException("Height should be an integer value.");
}
}


if (!((width == null) || ("".equals(width))))
{
try
{
Integer.parseInt(width);
l_width = width;
}
catch (Exception e)
{
throw new OAException("Width should be an integer value.");
}
}

String divtext = "";
if ((horizontal_scroll) && (vertical_scroll))
{
divtext =
"<DIV style='width:" + l_width + ";height:" + l_height + ";overflow:auto;padding-bottom:20px;border:0'>";
}
else if (horizontal_scroll)
{
divtext =
"<DIV style='width:" + l_width + ";overflow-x:auto;padding-bottom:20px;border:0'>";
}
else if (vertical_scroll)
{
divtext =
"<DIV style='height:" + l_height + ";overflow-y:auto;padding-bottom:20px;border:0'>";
}
else
{
throw new OAException("Both vertical and horizintal scrollbars are passed as false,hence, no scrollbars will be rendered.");
}
startDIVTagRawBean.setText(divtext);
endDIVTagRawBean.setText("</DIV>");
}
Please Note : Height and Width passed in addScrollBarsToTable API are the minimum
height and width of the table respectively which will be displayed on the page. Hence if this display resolution is available on page the scrollbars will not be displayed or corresponding one scrollbar or both scrollbars will be displayed.If you are not able see both scrollbars, even if you pass true for both in this method, try to put less resolution like 150 and 150 etc.

Happy coding..!

Friday, December 18, 2009

Blocking User on submit Action in a OAF page

This is one of the most frequently required feature in OAF page, in certain conditions. Basically our requirement is to block user from pressing a button twice, i.e.,if a button is pressed for the first time,till the time request processing is going on for the first request on server either the button should be disabled on page or an hourglass should be shown on the page, so that user should not be able to press it again to start another request, before this request's response has been received from server. This is very essential feature where

1) You are lets say creating a account.
2) Doing a transaction.
3) Generating a report.
4) Doing a transaction which takes significant amount of time which can make user impatient and press the button again.

The standard solution for this provided by OAF is setBlockOnEverySubmit API.Whenever a submit action takes place on a page, subsequent submits can be blocked.
When using a blocking on submit technique, when the submit action takes place, the cursor becomes busy and prevents any other submit action until the current submit event has been handled.
The block on submit behavior is not enabled by default on a page. However, For Partial Page Refresh (PPR) events alone, it is enabled by default.
To implement the block on submit behavior on a specfic page, add the following code to the processRequest() of that page CO:

import oracle.apps.fnd.framework.webui.beans.OABodyBean;

public void processRequest(OAPageContext pageContext, OAWebBean webBean)
{
super.processRequest(pageContext, webBean);
OAWebBean body = pageContext.getRootWebBean();
if (body instanceof OABodyBean)
{
((OABodyBean)body).setBlockOnEverySubmit(true);
}
...
...
}

But there are exceptions to this API functionality, what if my page has multiple buttons and I want this nature only on press of one of the button.Situations like this the standard solution is using OAProcessing page(See dev guide for OAProcessing page implementation, its explained well!), which is typically used in long running processed.But what if user is not ready to leave the page at all and asks you to block the navigation somehow? What if you are mobile browser, where OAProcessing page does not work fine. Situations like this can be taken care through javascript.
I must repeat again, use this javascript solution only when your requirement is not fulfilled by standard setBlockOnEverySubmit API provided by framework.Ok here are the steps for the javascript solution, which will block the action by making the button disabled as soon as it is pressed and releasing it as soon as your action is finished :

1) Add a button bean , instead of submit button bean. Lets say its id is "item1" in property inspector. Set destination uri property in property inspector as :
javascript:document.getElementById('item1').disabled=true;submitForm('DefaultFormName',0,{'XXX':'Y'});

2) In process form request, you can catch the event as :

public void processFormRequest(OAPageContext pageContext,
OAWebBean webBean)
{
super.processFormRequest(pageContext, webBean);

.// the js button click event
// don't use pageContext.getParameter() API to get parameter values
// in R12 because its doing validation on JS paremeters.
// so,instead of getting parameter value from pagecontext
//getting directly from http request.
if(pageContext.getRenderingContext()
.getServletRequest().getParameter("XXX")!=null)
{
// ur button event logic in process form request
......

//remove this parameter from http request
pageContext.removeParameter("XXX");
}
}

I hope this helps to people which are facing such situations. Happy coding..!

Tuesday, December 15, 2009

Migration of AOL setups from one instance to Another.

Hi All,
Typically this is the requirement of every Apps project whether its a upgrade/implementation/support. You might create AOL objects like profiles,alerts,AK regions etc. , here I am listing all the LDT commands which will help you in migration.LDT download command extract the AOL object in a .ldt file and then u can put that in target instance and call upload LDT command to upload it in target instance.
I think there are numerous websites where this is listed, but still commands like ldt of alert, ak region is still hard to find. Hence, I am consolidating all of them at one place :

function scripts
------------------
$FND_TOP/bin/FNDLOAD apps/apps 0 Y DOWNLOAD $FND_TOP/patch/115/import/afsload.lct XXADAT_MAT_REQ_HIST.ldt FUNCTION FUNCTION_NAME=XXADAT_MAT_REQ_HIST

$FND_TOP/bin/FNDLOAD apps/apps 0 Y UPLOAD $FND_TOP/patch/115/import/afsload.lct XXADAT_MAT_REQ_HIST.ldt


responsibility scripts
------------------------
FNDLOAD apps/apps O Y DOWNLOAD $FND_TOP/patch/115/import/afscursp.lct XXADAT_PROD_TIME_BOOKING.ldt FND_RESPONSIBILITY RESP_KEY="XXADAT_PROD_TIME_BOOKING"

$FNDLOAD apps/apps O Y UPLOAD $FND_TOP/patch/115/import/afscursp.lct file_name.ldt


menu scripts
-------------
FNDLOAD apps/apps O Y DOWNLOAD $FND_TOP/patch/115/import/afsload.lct AHL_PRD_MBENCH_USER_SUB_MENU.ldt MENU MENU_NAME="AHL_PRD_MBENCH_USER_SUB_MENU"

$FNDLOAD apps/apps O Y UPLOAD $FND_TOP/patch/115/import/afsload.lct file_name.ldt

lookup scripts
--------------
FNDLOAD apps/apps 0 Y DOWNLOAD $FND_TOP/patch/115/import/aflvmlu.lct AHL_PRD_DEFERRAL_TYPE.ldt FND_LOOKUP_TYPE APPLICATION_SHORT_NAME ="XXADAT" LOOKUP_TYPE="AHL_PRD_DEFERRAL_TYPE"

FNDLOAD apps/apps 0 Y UPLOAD $FND_TOP/patch/115/import/aflvmlu.lct XXADAT_NO_CONCURRENT_LOGIN_WOS.ldt

ak region download script
-------------------------------
java oracle.apps.ak.akload apps apps THIN "(DESCRIPTION = (ADDRESS = (PROTOCOL = TCP)(Host = < instance IP > )(Port = 1541)) (CONNECT_DATA = (SID=DEV)))" DOWNLOAD XXADAT_EM_LOV.ldt GET CUSTOM_REGION AHL XXADAT_EM_LOV

ak region upload script
------------------------
java oracle.apps.ak.akload apps apps THIN "(DESCRIPTION = (ADDRESS = (PROTOCOL = TCP)(Host = < instance IP > )(Port = 1620)) (CONNECT_DATA = (SID=RTST)))" UPLOAD XXADAT_EM_LOV.ldt UPDATE CUSTOM_REGION

profile scripts
---------------
FNDLOAD apps/apps O Y DOWNLOAD $FND_TOP/patch/115/import/afscprof.lct XXADAT_BUDGET_MANHOURS_NROUTINE.ldt PROFILE PROFILE_NAME="XXADAT_BUDGET_MANHOURS_NROUTINE" APPLICATION_SHORT_NAME="XXADAT"

FNDLOAD apps/apps O Y UPLOAD $FND_TOP/patch/115/import/afscprof.lct XXADAT_BUDGET_MANHOURS_ROUTINE.ldt

message scripts
----------------
FNDLOAD apps/apps 0 Y DOWNLOAD $FND_TOP/patch/115/import/afmdmsg.lct XXADAT_MIN_EXP_MSG.ldt FND_NEW_MESSAGES APPLICATION_SHORT_NAME="XXADAT" MESSAGE_NAME="XXADAT_MIN_EXP_MSG"

FNDLOAD apps/apps 0 Y UPLOAD $FND_TOP/patch/115/import/afmdmsg.lct XXADAT_MIN_EXP_MSG.ldt

CONCURRENT PROGRAM
--------------------
FNDLOAD apps/appsrtst O Y DOWNLOAD $FND_TOP/patch/115/import/afcpprog.lct XXADAT_CL_COMP_WO_F_VISIT.ldt PROGRAM APPLICATION_SHORT_NAME="XXADAT" CONCURRENT_PROGRAM_NAME="XXADAT_CL_COMP_WO_F_VISIT"

FNDLOAD apps/apps O Y UPLOAD $FND_TOP/patch/115/import/afcpprog.lct XXADAT_CL_COMP_WO_F_VISIT.ldt

alert
------------
FNDLOAD apps/apps 0 Y DOWNLOAD $ALR_TOP/patch/115/import/alr.lct XXADAT_INST_LOC_UPDATE.ldt ALR_ALERTS APPLICATION_SHORT_NAME='XXADAT' ALERT_NAME= 'XXADAT_INST_LOC_UPDATE'

PS:This ldt command brings all alerts in the instance in the ldt file, so u need to remove all except the one u want to move.


FNDLOAD apps/apps 0 Y UPLOAD $ALR_TOP/patch/115/import/alr.lct XXADAT_RESET_NR_TRACKING_NO_WHN_DEF.ldt

Workflow Upload
-----------------
WFLOAD apps/apps 0 Y UPLOAD XXCLOSNR.wft

In all these commands apps/apps is username/password of apps database user. Also, in all upload commands you can give full location from where your ldt/wft file should be picked to upload. I hope this helps.

Monday, December 7, 2009

Programatic PPR in OAF.

I was recently helping a friend in a customization in OAF, where through personalization, he wanted to put a ppr action in a seeded page in a seeded item. He was trying to do that customization using fire action, which was not acceptable by customer due to obvious reasons. Since, I think this section is missing in developers' guide, and is quite simple to approach, here is code you can write in process request of CO to attach programmatic PPR. lets take a simple example of attaching PPR to a message choice bean :

//In Process Request()
{
//Please attach PPR only to those UIX beans which support it
//otherwise it may not work
OAMessageChoiceBean mcb=(OAMessageChoiceBean)webBean.findChildRecursive("");
FireAction firePartialAction = new FirePartialAction("empPositionChange");
mcb.setAttributeValue(PRIMARY_CLIENT_ACTION_ATTR,firePartialAction);
}

//In process form request
if ("empPositionChange".equals(pageContext.getParameter(OAWebBeanConstants.EVENT_PARAM)))
{
//ur logic on PPR

//if PPR is attached in a table/hgrid child then we can find the row
//reference by
String rowReference =pageContext.getParameter(OAWebBeanConstants.EVENT_SOURCE_ROW_REFERENCE);
}

I hope this helps. Happy oaf extensions :)!

Monday, November 30, 2009

Setting Query Dynamically in View Object. (Why in some cases even after using setQuery(), VO picks default query.)

Couple of time while making view object in OAF pages, you have conditions where instead of where clause or order by clause, you need to change the entire source of sql query of View Object.
There can be n number of such scenarios, for example you are using a querybean in a search page, and in a certain condition, you have to change your query, your VO is attached to a LOV and in certain condition you need the query to change in order to optimize the VO sql query etc. Following points always keep in mind while using setQuery():

1) The new query you want to set in the view object should have same column name and types as the original VO query.

2)As per OAF coding standards all coding related to setting where clause, order by clause or setQuery should be written in ViewObjectImpl class, so in such case, you should generate ViewObjectImpl class.

3)Most Important :Always call vo.setFullSqlMode(FULLSQL_MODE_AUGMENTATION) before calling setquery() on the view object.This ensures that the order by or the WHERE clause, that is generated by OA Framework, can be correctly
appended to your VO. The behavior with FULLSQL_MODE_AUGMENTATION is as follows:

a) The new query that you have programmatically set takes effect when you call setQuery and execute the query.
b) If you call setWhereClause or if a customer personalizes the criteria for your query region, BC4J augments the whereClause on the programmatic query that you set.

For example:
select * from (your programmatic query set through setQuery)
where (your programmatic where clause set through setWhereClause)
order by (your programmatic order set through setOrderBy)
The same query is changed as follows if a customer adds a criteria using personalization:
select * from (your programmatic query set through setQuery)
where (your programmatic where clause set through setWhereClause) AND
(additional personalization where clause)
order by (your programmatic order set through setOrderBy)_

Warning: If you do not set FULLSQL_MODE_AUGMENTATION, the whereClause and/or the
orderBy, which was set programmatically, will not augment on the new query that you set using setQuery. It will instead augment on your design time VO query.


Due to timing issues, always use the controller on the query region/LOV region while using setQuery().

Friday, September 25, 2009

Dynamically changing multiselect to single select in Table in OAF

I would like to share a small piece of code, which recently a friend shared with me, this is a typical requirement, which might look too trivial at first shot, but is quite easy to implement. Suppose you have a table where in you have a requirement to have single or multi selection based on a dynamic conditon eg, some selection or press of a button you wanna change the multiselection to single selection and vice versa.

In such scenario, in the action of the event you redirect to same page and use following code in process request on the base of parameter you set in session/pagecontext :
// Hiding the Multiple Select Table Action and add single selection
OAAdvancedTableBean tabBean = (OAAdvancedTableBean)webBean.
findIndexedChildRecursive("[table item id]");

//Since the multiselect is at the end, get the total count -1 for removing
int count = tabBean.getIndexedChildCount(pageContext.getRenderingContext());
System.out.println("Child Count "+count);
tabBean.removeIndexedChild(count-1);

OAMultipleSelectionBean sel = (OAMultipleSelectionBean)tabBean.getTableSelection();
OASingleSelectionBean singleSelection = new OASingleSelectionBean() ;
singleSelection.setText("Select");

//Set the attribute for the single select
singleSelection.setViewAttributeName("SelectFlag");
tabBean.setTableSelection(singleSelection);

I hope this helps anybody who tries to implement such scenario!