Monday, July 26, 2010

Enabling LOVs for Dynamic ViewObject attributes

In one of my previous posts, I talked about the model driven approaches for building dynamic User Interfaces. This post is in continuation of the earlier post. This time, I'll discuss about building 'Dynamic Form' containing LOV(List of Values) enabled fields (built using dynamic ViewObject).

Defining Dynamic Entity Objects and View Objects

First step is to to build EntityObject and corresponding ViewObject dynamically, based on the meta-data supplied by the run time. Below given sample code illustrates the APIs used for building EntityObjects and corresponding ViewObject, on the fly.

EntityDefImpl newEntity = new EntityDefImpl(DYNAMIC_EO);
AttributeDefImpl newAttrDef1 =
    newEntity.addAttribute("DepartmentId", "DEPARTMENT_ID",
               Number.class, false, false, true);
//Other attribute defn goes here...
newEntity.resolveDefObject();
newEntity.registerDefObject();
ViewDefImpl newView = new ViewDefImpl(DYNAMIC_VO);
newView.addEntityUsage("e", DYNAMIC_EO, false, false);
newView.addAllEntityAttributes("e");
newView.setFetchSize((short)30);

newView.setSelectClauseFlags(ViewDefImpl.CLAUSE_GENERATE_RT);
newView.setWhereClauseFlags(ViewDefImpl.CLAUSE_GENERATE_RT);
newView.setFromClauseFlags(ViewDefImpl.CLAUSE_GENERATE_RT);
buildLOVForDepartmentId(newView);

Enabling LOVs for Dynamic ViewObject attributes

Cool...Next step is to define LOV for desired attributes programmatically. Apparently, you may need to have following two items defined on the ViewObject to enable LOV for specific attributes.

1. ViewAccessor definition - For accessing Data Source View Object(destination view object) for LOV.
2. ListBinding definition - This defines meta data used for the LOV, such as the columns displayed on the LOV component, return attribute mappings etc.

The following sample code illustrates programmatic definition of LOV for 'DepartmentId' attribute.

private void buildLOVForDepartmentId(ViewDefImpl newEmpViewDef) {
  String viewAccessorName = "DepartmentIdVA";
  String lovName = "LOV_DepartmentId";
  String listDataSourceViewDefName = "model.DepartmentsView";

  ViewAccessorDef vdef = new ViewAccessorDef();
  vdef.setName(viewAccessorName);
  vdef.setViewDefFullName(listDataSourceViewDefName);
  vdef.setRowLevelBinds(true);

  newEmpViewDef.addViewAccessorDef(vdef);

  ListBindingDef listBindingDef =
    buildListBindingDef(newEmpViewDef.getDefManager(),
    viewAccessorName, lovName,
    new String[] { "DepartmentId" },
    new String[] { "DepartmentId" },
    new String[] { "DepartmentName" });
  newEmpViewDef.addListBindingDef(listBindingDef);
  int clmnIndex = newEmpViewDef.getAttributeIndexOf("DepartmentId");
  AttributeDefImpl _voAttrDef =
  (AttributeDefImpl)newEmpViewDef.getAttributeDef(clmnIndex);
  _voAttrDef.setLOVName(lovName);
  _voAttrDef.setProperty(AttributeHints.ATTRIBUTE_CTL_TYPE,
       AttributeHints.CTLTYPE_COMBO);

}

Build the User Interface

Our model is in place, now you may need to wire the model with <dynamic:form> as shown below.

You can download the sample workspace from here.
[Runs with Oracle JDeveloper 11g R1 PS2 + HR Schema]

How to run this sample?

Run the main.jspx. Main page display a drop down list. Select the 'Employee' item from the list, and then click on the action button displayed below. This action invokes 'dynamicEntityBased-tf' task flow. The default activity for this task flow is to set up the required ViewObjects used by the view. As the next step in the sequence, control moves to 'dynamicForm' page and data is being rendered using <dynamic:form> component. This UI is generated on the fly based on the item selected from the drop down. Please check out the 'dynamicEntityBases-tf' task flow to understand the underlying execution path.


Learn More ...

There are a lot more points like this. If  you are curious to learn the internals of the ADF Business Components and ADF Binding Layer,  the following book is for you - Oracle ADF Real World Developer’s Guide.
More details about this book can be found in this post- http://jobinesh.blogspot.in/2012/07/pre-order-your-copy-of-oracle-adf-real.html

29 comments:

Anonymous said...

what about creating LOV view dynamically? using
ViewDefImpl lov = new ViewDefImpl("model.lov.TestLOV");
lov.setQuery("select ID,VALUE from lov_test"); createViewObjectForDef("TestLOV", lov);

String viewAccessorName = "TextLOV";
String listDataSourceViewDefName = "model.lov.TestLOV";
ViewAccessorDef vdef = new ViewAccessorDef();
vdef.setName(viewAccessorName); vdef.setViewDefFullName(listDataSourceViewDefName);
vdef.setRowLevelBinds(true);

results in
JBO-25002: Definition model.lov.TestLOV of type View Definition is not found

Jobinesh said...

Please try the code the I pasted in the post ( alternatively you can copy from the uploaded sample http://adf-samples.googlecode.com/files/DynamicUIWithLOV.zip)
.If you are able to reproduce the issue, get me back, will take a look

Anonymous said...

Your post is great but I'm trying to do it a little bit different. Instead of using existing view object (in your case model.DepartmentsView ) for LOV display I'm trying to create this object on the fly using

ViewDefImpl lov = new ViewDefImpl("model.lov.TestLOV");
lov.setQuery("select ID,VALUE from lov_test"); createViewObjectForDef("TestLOV", lov);

then adding it using:

String viewAccessorName = "TextLOV";
String lovName = "LOV_Id";
String listDataSourceViewDefName = "model.lov.TestLOV";
ViewAccessorDef vdef = new ViewAccessorDef();
vdef.setName(viewAccessorName); vdef.setViewDefFullName(listDataSourceViewDefName);
vdef.setRowLevelBinds(true);

newEmpViewDef.addViewAccessorDef(vdef);
ListBindingDef listBindingDef =
buildListBindingDef(newEmpViewDef.getDefManager(),
viewAccessorName, lovName,
new String[] { "Id" },
new String[] { "Id" },
new String[] { "Text" });
newEmpViewDef.addListBindingDef(listBindingDef);
int clmnIndex = newEmpViewDef.getAttributeIndexOf("Id");
AttributeDefImpl _voAttrDef =
(AttributeDefImpl)newEmpViewDef.getAttributeDef(clmnIndex);
_voAttrDef.setLOVName(lovName);
_voAttrDef.setProperty(AttributeHints.ATTRIBUTE_CTL_TYPE,
AttributeHints.CTLTYPE_COMBO);

but running the page I get JBO-25002: Definition model.lov.TestLOV of type View Definition is not found
as if the view object I've made on fly isn't visible.....any ideas?

Jobinesh said...

When do you create ViewObject? It should be in place before the binding layer tries to get data. Does it work if you click on browser refresh button?

Anonymous said...

I'm creating ViewObject in backing bean method that is executed before navigating to the page where with dynamic component. Refresh button doesn't make any diference.

Jobinesh said...

Sorry, a sample app may help to understand the scenario better. The code you posted looks OK for me.

Anonymous said...

Your posts are great and of big help. Not sure if this is the right way to ask you a question but will try. The question is related to lov in a table. We have an af:table on which one of the columns has a LOV. A new row is inserted in the table, using LOV a value is selected which fetches couple of more columns. The data is displayed on the row and some more data is entered on the row. so far so good. Insert another row, table gets refreshed. For the previous entered row,column on which LOV is enabled data gets cleared, all other columns data is displayed correctly. We have reviewed bug:9163629 solution to avoid table refresh behaviour, not sure if this resloves the issue. We are using JDev11.1.1.4. Any help or gudance will be appreciable?

Jobinesh said...

Please check
1.If the LOV enabled column is populated from another LOV enabled columns
2. Please see whether RowImpl or EntityImpl class contains some setters which resets the LOV column value

Anonymous said...

Thanks for the prompt response. For both suggestions the answer is no. Any other suggestions?

Jobinesh said...

sorry,not able to think of anything else. Probably you can set a break point at setter of LOV field and see what make sit null. If you can send me simple test case(built using HR schema), I can take a look

Anonymous said...

Thanks for the help. One question which I started asking myself "Is af:inputListOfValues supported for af:table"? When we select ADF-Table, in the "Edit - Table" popup for LOV enabled attribute we do not see component "Input List of Values" for selection. Any ideas?

Jobinesh said...

Its supported. One more point, check whether you have any value change listener for LOV field(or any other fields) and whether that causes this behaviora

Anonymous said...

Hi Jobinesh,
Thank you for the excellent post. I was trying to create something on my own based on your post.

I was trying to add 2 dynamic LOVs to my Entity based view object(also dynamic). When I try to run the page it always says Definition of type -1 not found exception. However when I add only 1 LOV it works fine.

I think when the 2 LOV is getting added the query for 1st LOV is lost or getting refreshed.

Can you please point me in some direction here.

Hope you got what I am trying to say else please let me know I will try to be more specific.

Jobinesh said...

Can you reproduce the issue in may sample and send that back to me. will take a a look

Anonymous said...

Hi Jobinesh,
Thank you for the response. Your sample is slightly different than what I am trying to do. In a nut shell my requirement is to create a dynamic updateable form which has 2 LOVs or Dropdown boxes. The LOV should also be created based on the queries coming from metadata table dynamically.

Rolling Stone said...

Hi jobinesh,
The project i am working on doesn't use adf bc.we used programmatic view object or bind ui controls directly with java bean.I have created lov based on two programmatic vo.but the data is not populating in the table after clicking search.Is it possible to create lov on programmatic vo?i have implemented the java based approach as described in the adf demo,though it is working correctly but it is not generic and easy to use.

Jobinesh said...

well, if your source for table is not a ViewObject, then the approach you followed looks like the best one for me. The Java DataControl is currently missing the LOV support that you have for a view object

ByteCode said...

Hi,

I've found your posts while searching Google of how to implement dynamic UI. I've encountered the same error as one user posted here "Hi Jobinesh,
Thank you for the excellent post. I was trying to create something on my own based on your post.

I was trying to add 2 dynamic LOVs to my Entity based view object(also dynamic). When I try to run the page it always says Definition of type -1 not found exception. However when I add only 1 LOV it works fine.

I think when the 2 LOV is getting added the query for 1st LOV is lost or getting refreshed.

Can you please point me in some direction here.

Hope you got what I am trying to say else please let me know I will try to be more specific."

Has this issue been solved?

I'm using ADF 11g with JSF pages, but the Dynamic components does not seem to work, maybe they work only in JSPX?

Kind regards.

ByteCode said...

Problem solved! Only after decompiling the ViewDefImpl and seeing it's abnormal behaviour.

Jobinesh said...

ByteCode
Can you post the issue and solution here which may help others and me as well.

ByteCode said...

Hi,
Of course. So, here are the details.

The problem (as stated by another person above):

If you try to add multiple LOVs using the method described in this blog, you get an Exception: "Definition of type -1 not found exception ...".
The method works if you add only one LOV.
The cause:
When you add the View Accessor for accessing the VO specified as LOV using this code
newEmpViewDef.addViewAccessorDef(vdef); ADF needs to add an extra attribute for the View Accessor (in our case "DepartmentIdVA").

When you do the

int clmnIndex = newEmpViewDef.getAttributeIndexOf("DepartmentId");
AttributeDefImpl _voAttrDef =
(AttributeDefImpl)newEmpViewDef.getAttributeDef(clmnIndex);

ADF actually builds the View Accessors and thir attributes, and sets one internal flag (in ViewDefImpl) to "true". This flag is interpreted as
"Ok, I've built the View Accessors and their attributes, I don't need to build again when 'getAttributeIndexOf' is called".

So what happens is the following (if you use a FOR loop to add the LOVs):

1. build the first ViewAccessorDef and ListBindingDef
2. call "int clmnIndex = newEmpViewDef.getAttributeIndexOf("DepartmentId");"
3. this time it adds the View Accessor and it's attribute to the ViewDefImpl;
4. build second ViewAccessorDef and ListBindingDef (this time it will not add the attribute for this VA because of that flag i've told above).

So when you run the page, it expects to find the attribute for the second LOV which was not created.

The solution:

Do not call either "getAttributeIndexOf" or "getAttributeDef" (if not sure about this last one) until you did not add all the LOVs you need.
Add all the LOVs you need first, and only after that you can call these methods.

Best regards.

ByteCode said...

This is an example from my application which works

private void createDynamicLOVS(ViewDefImpl viewDef, Row[] rows, Integer moduleId) throws Exception {


ArrayList attrWithLOVs = new ArrayList(4);

for (int i = 0; i < rows.length; i++) {
Row r = rows[i];

if (!(r instanceof AdfConfigDetailViewRowImpl))
return;

AdfConfigDetailViewRowImpl row = (AdfConfigDetailViewRowImpl)r;

String colName = row.getAdfcfgdColumnName();
String lovSQL = row.getAdfcfgdColumnLovSql();
String flagField = row.getAdfcfgdColumnFlagType();

if ((lovSQL != null) && (!lovSQL.equals("")) && (!lovSQL.equals("XNA"))) {
String voName = colName + "_" + moduleId + "_LOV_VO";


createDynamicVOFromQuery2(lovSQL, voName);

String lovValueCol = "VALUE";
String lovDisplayCol = "LABEL";

if (flagField != null && flagField.equals("Y"))
lovDisplayCol = "VALUE";

buildLOVForAttribute(viewDef, colName, voName, lovValueCol, lovDisplayCol);
attrWithLOVs.add(colName);
}
}

for (String s : attrWithLOVs) {
int clmnIndex = viewDef.getAttributeIndexOf(s);
AttributeDefImpl _voAttrDef = (AttributeDefImpl)viewDef.getAttributeDef(clmnIndex);

_voAttrDef.setLOVName("LOV_" + s);

int clmnIndexVA = viewDef.getAttributeIndexOf(s + "VA");
if (clmnIndexVA >= 0) {
AttributeDefImpl _voAttrDefVA = (AttributeDefImpl)viewDef.getAttributeDef(clmnIndexVA);
_voAttrDefVA.setProperty(AttributeHints.ATTRIBUTE_DISPLAY_HINT,
AttributeHints.ATTRIBUTE_DISPLAY_HINT_HIDE);
}
}

}

private void buildLOVForAttribute(ViewDefImpl viewDef, String colName, String listDataSourceViewName,
String lovValueCol, String lovDisplayCol) {


String viewAccessorName = colName + "VA";
String lovName = "LOV_" + colName;

ViewAccessorDef vdef = new ViewAccessorDef();
vdef.setName(viewAccessorName);
vdef.setViewDefFullName(listDataSourceViewName);
vdef.setRowLevelBinds(true);

viewDef.addViewAccessorDef(vdef);

ListBindingDef listBindingDef =
buildListBindingDef(viewDef.getDefManager(), viewAccessorName, lovName, new String[] { colName },
new String[] { lovValueCol }, new String[] { lovDisplayCol });

viewDef.addListBindingDef(listBindingDef);
}

Jobinesh said...

Thank you ByteCode for sharing this information !

ByteCode said...

You're welcome. I hope that this solution will help others with similar problem.

Kind regards.

ByteCode said...

Hi, now I have a bigger problem with this dynamic table. My page consists of one list with different links and the dynamic (editable)table. Clicking one link calls setupEntityAndView with a database view name and then I refresh the table with this new database view. One of the views has a varchar PK, the other one has a number PK. I select the first view and select one row in the table. After that I select another view (link), the table is refreshed with the new columns, but it remebers the previously selected row from the other view and tries to make that row selected and obviously it fails on conversion because on the first view PK is varchar, on the second is number. I've tried all the possible ways to clear the cached key first but with no success. Table columns are added with an statement. Can you help me with that? Where is the right place to clear the table cache, and how to clear it? resetStampState/addpartialTarget did not do the job for me.

Jobinesh said...

ByteCode
Comp. by default holds sticky ref to the data model. Can you try clearing table comp , before changing VO -
// get hold of the table component
RichTable tableComp = getTableComponent();
List children = tableComp.getChildren();
children.clear();

ByteCode said...

Thank you very much, it worked. The thing is, I've tried before to clear the children list, but it didn't work. This time I did clear the children and also force a page refresh and now it is working.

Anonymous said...

Hi Jobinesh,

I have 2 view accessors and one view accessor's(say first) bind variable is the out come of 2nd view accessor(say second) one field of a returned row(only one row will be returned).How to implement this ?

I am thinking of writing the code of accessing the secondVA and getting the value and setting it to the firstVA at firstVA's getter method.Is this right approach ?

Thanks in Advance,
naga

Vamsidhar Kadiyala said...

Its great that you play allot with ViewObject definitions programmatically.

Probably you could help me...

I'm trying to export View Object data into a file (CSV format), i'm done mostly. But for LOV attributes, instead of Id, I want to render the display value.

Eg: Consider EmployeeVO has a departmentId, and its has an LOV definition that uses DepartmentVO.
Now i want to render Department Name instead of departmentId in my export file.

How to access department name if I have DepartmentVo instance, departmentId AttributeDef instance.

hope I'm clear.

thanks for your time.

Vamsi.