Sunday, June 3, 2012

Configure Comparison of Row Objects at Run Time

Recently I was working in an application where I need to compare two or more row objects based on selected attributes. In this article I will explain one of the way to configure comparison of row objects at run time, so this pattern will give users the ability to select row objects for comparison and to define which of the available attributes they want to compare at run time.

So the out come of this scenario looks like below. In result page car details table will be displayed, user can select the multiple rows for comparison and click on Compare button.


Next screen will display the shuttle component, where it defines which are the attributes included in the comparison. So user can move attributes from available attributes to selected attributes to compare and click on the Process button.


Note:- In above shuttle component attributes are read from ViewObject programmatically, article written in one of my previous blog - Get ViewObject attributes are  read programmatically and display these attributes in ADF Shuttle component.

Final comparison results table, notice all selected attributes will be showed as first column values and also selected row objects are displayed.


Note:- Above dynamic table is used to display Cars Features Comparison, I followed the solution written on AMIS Technology Blog regarding Creating ADF Faces Dynamic Table with Head to Head comparison using managed bean.

You can download the sample workspace from here
[Runs with Oracle JDeveloper 11.1.2.0.0 (11g R2)]

Implementation steps

Create Fusion Web Application with business components from tables based on Cars table, create a new extended object from Cars View as shown in below image.


Note: Download the application from above link and unzip the application. schema.sql file is in application etc folder.

Open the childCarsView, select Query tab and create bind variable "Bind_CarsID".


Alter the query as below.
SELECT Cars.ID, 
       Cars.NAME, 
       Cars.MSRP, 
       Cars.BASE_ENGINE, 
       Cars.CYLINDERS, 
       Cars.DRIVE_TYPE, 
       Cars.FUEL_CAPACITY, 
       Cars.FUEL_ECONOMY, 
       Cars.FUEL_TYPE, 
       Cars.HORSE_POWER, 
       Cars.TORQUE, 
       Cars.TRANSMISSION
FROM CARS Cars
WHERE Cars.ID in (select regexp_substr(:Bind_CarsID,'[^,]+', 1, level) 
   from dual 
    connect by 
        regexp_substr(:Bind_CarsID, '[^,]+', 1, level) 
            is not null)
Arun Ramamoorthy has explained well in article how to split comma separated string and pass to IN clause of select statement.

Open the AppModule and select Data Model tab move "childCarsView" from available view objects to data model.


Create a CompareAttributes.java and paste the below code.
public class CompareAttributes {
    private String columnName;
    private String columnAliasName;

    public CompareAttributes() {
        super();
    }

    public CompareAttributes(String columnName, String columnAliasName) {
        super();
        this.columnName = columnName;
        this.columnAliasName = columnAliasName;
    }

    public void setColumnName(String columnName) {
        this.columnName = columnName;
    }

    public String getColumnName() {
        return columnName;
    }

    public void setColumnAliasName(String columnAliasName) {
        this.columnAliasName = columnAliasName;
    }

    public String getColumnAliasName() {
        return columnAliasName;
    }
}
Next go to Java tab in AppModule and click on edit java options. Generate the application module class: AppModuleImpl.java

Open the AppModuleImpl.java file and add the below methods code.
/**
 * This method reads the ViewObject attributes and populate CompareAttributes list
 * @return CompareAttributes list
 */
public List getCompareAttributes() {
	ViewObjectImpl vo = getCarsView1();
	ViewAttributeDefImpl[] attrDefs = vo.getViewAttributeDefImpls();
	int count = 0;
	List compareAttrs = new ArrayList();
	for (ViewAttributeDefImpl attrDef : attrDefs) {
		byte attrKind = attrDefs[count].getAttributeKind();
		//checks attribute kind for each element in an array of AttributeDefs
		if (attrKind != AttributeDef.ATTR_ASSOCIATED_ROW && attrKind != AttributeDef.ATTR_ASSOCIATED_ROWITERATOR) {
			String columnName = attrDef.getName();
			String columnAliasName = attrDef.getAliasName();
			//Excluding the Id and Name fields
			if (!columnName.equals("Id") && !columnName.equals("Name")) {
				compareAttrs.add(new CompareAttributes(columnName, columnAliasName));
			}
			count++;
		}
	}
	return compareAttrs;
}

/**
 * Clean up the childCarsView existing rows from collection.
 */
public void cleanUpChildCarsView1() {
	ViewObject childEmpVO = (ViewObject)this.getChildCarsView1();
	//avoid validation when navigating rows
	childEmpVO.setRowValidation(false);
	while (childEmpVO.hasNext()) {
		childEmpVO.setCurrentRow(childEmpVO.next());
		childEmpVO.removeCurrentRowFromCollection();
	}
}

/**
 * This method will populate the childCarsView 
 * carRowKeysList contains CARS table primiary key - Id and
 * which will be passed as In Clause params 
 * @param carRowKeysList
 */
public void populateSelectedRows(List carRowKeysList) {
	if (carRowKeysList != null && carRowKeysList.size() > 0) {
		//Clean up the childCarsView 
		cleanUpChildCarsView1();
		String carsSelectedString = "";
		ViewObject childcarsVo = getChildCarsView1();
		for (int i = 0; i < carRowKeysList.size(); i++) {
			carsSelectedString += carRowKeysList.get(i) + ",";
		}
		childcarsVo.setNamedWhereClauseParam("Bind_CarsID",
											 carsSelectedString.substring(0, (carsSelectedString.length() - 1)));
		childcarsVo.executeQuery();
	}
}
Go back to AppModule.xml and select Java tab in client interface section move getCompareAttributes and populateSelectedRows from available to selected block.


In ViewController project, create a bounded taskflow as "Compare-btf". Open the Compare-btf taskflow, from component palette drop views as "CarsDetails", "CarsCompare". Generate CarsDetails.jsff, CarsCompare.jsff pages and "CarsDetailsBean.java", "CarsCompareBean.java" respectively. Add the EmployeeDetailsBean.java, CarsCompareBean.java  file in taskflow with scope as viewScope.

Draw the control flow case from CarsDetails to CarsCompare, outcome as compare and draw the control flow case from CarsCompare to CarsDetails, outcome as back.

Open CarsDetails.jsff page
  • From data control palette drag and drop CarsView1->Table as ADF Read Only Table, set RowSelection as "muliptle".
  • Surround the table with panel collection component and add Compare button with text as "Compare". 
  • Create the actionListener method for Compare button as "fectchSelectedCarKeys" and action as "compare".
  • Add the binding for table as "carsTable".
Open the CarsDetailsBean.java file and copy the below methods code.
/**
 * This method will get selected rows primiary key - Id values
 * @param actionEvent
 */
public void fectchSelectedCarKeys(ActionEvent actionEvent) {
	RowKeySet selectedCars = getCarsTable().getSelectedRowKeys();
	if (selectedCars.size() > 0) {
		DCBindingContainer dcBindings = (DCBindingContainer)BindingContext.getCurrent().getCurrentBindingsEntry();
		DCIteratorBinding deptIter = dcBindings.findIteratorBinding("CarsView1Iterator");
		RowSetIterator deptRSIter = deptIter.getRowSetIterator();


		Iterator selectedCarsIter = selectedCars.iterator();
		List selectedRowKeys = new ArrayList();
		while (selectedCarsIter.hasNext()) {
			Key key = (Key)((List)selectedCarsIter.next()).get(0);
			Row currentRow = deptRSIter.getRow(key);
			selectedRowKeys.add(currentRow.getAttribute("Id"));
		}
		ADFContext.getCurrent().getPageFlowScope().put("selectedCarsRowKeys", selectedRowKeys);
	}
}
Open CarsCompare.jsff page.
  • From component palette drop the shuttle component and Bind to list value as "#{viewScope.CarsCompareBean.shuttleList}", looks like below code.
  • Add the shuttle component value as "#{viewScope.CarsCompareBean.selectedAttributes}"
  • Go to Bindings tab and create method action for "getCompareAttributes".
<af:selectManyShuttle label="" id="sms1" leadingHeader="Available Attributes"
					  trailingHeader="Selected Attributes"
					  value="#{viewScope.CarsCompareBean.selectedAttributes}" valuePassThru="true">
	<f:selectItems value="#{viewScope.CarsCompareBean.shuttleList}" id="si1"/>
</af:selectManyShuttle>
  • Add the button, name as "Process" and  create the actionListener method as "processAction".
  • Go to Bindings tab and create method action for "populateSelectedRows".
  • Create a Tree bindings as shown below.
  • Add the below dynamic table code to the page
<af:table var="row" rowBandingInterval="0" value="#{viewScope.CarsCompareBean.rows}" rowSelection="single"
		  id="t1b" styleClass="AFStretchWidth" partialTriggers=":::cb1">
	<af:forEach items="#{viewScope.CarsCompareBean.columns}" var="col">
		<af:column headerText="#{col['label']}" sortable="true" sortProperty="#{'name'}" id="c1b">
			<af:outputText value="#{row[col['name']]}" id="ot1b"/>
		</af:column>
	</af:forEach>
</af:table>
Open the CarsCompareBean.java and add the below methods code.
//Holds selected values in shuttle component
List selectedAttributes;
//Populates the shuttle component
List shuttleList;
//Populates the comparison table columns
private List> columns;
//Populates the comparison table rows
private List> rows;

public CarsCompareBean() {
}

public void setSelectedAttributes(List selectedAttributes) {
	this.selectedAttributes = selectedAttributes;
}

public List getSelectedAttributes() {
	return selectedAttributes;
}

public void setShuttleList(List shuttleList) {
	this.shuttleList = shuttleList;
}

public BindingContainer getBindings() {
	return oracle.adf.model.BindingContext.getCurrent().getCurrentBindingsEntry();
}

/**
 * This method will read the attributes from CarsView1
 * and popluate the shuttle component
 * @return
 */
public List getShuttleList() {
	BindingContainer bindings = getBindings();
	OperationBinding operBind = bindings.getOperationBinding("getCompareAttributes");
	List compareAttr = (List)operBind.execute();

	List shuttleList = new ArrayList();
	for (int i = 0; i < compareAttr.size(); i++) {
		CompareAttributes attr = compareAttr.get(i);
		SelectItem item = new SelectItem(attr.getColumnName(), attr.getColumnName(), attr.getColumnAliasName());
		shuttleList.add(item);
	}
	return shuttleList;
}

/**
 * This method will populate the dynamic table
 * @param actionEvent
 */
public void processAction(ActionEvent actionEvent) {
	List selectedCarsKeysList = (List)ADFContext.getCurrent().getPageFlowScope().get("selectedCarsRowKeys");
	BindingContainer bindings = getBindings();
	OperationBinding operBind = bindings.getOperationBinding("populateSelectedRows");
	operBind.getParamsMap().put("carRowKeysList", selectedCarsKeysList);
	operBind.execute();
	
	//Calling the populateDynamicTable
	populateDynamicTable();
}

/**
 * This method will populate the dynamic table based in selected row Objects
 * and attributes selected for comparison
 */
private void populateDynamicTable() {
	List attributes = getSelectedAttributes();
	columns = new ArrayList>();
	rows = new ArrayList>();

	Map column = new HashMap();
	column.put("label", "Cars Name");
	column.put("name", "header");
	columns.add(column);

	for (int i = 0; i < attributes.size(); i++) {
		Map row = new HashMap();
		row.put("header", attributes.get(i).toString());
		row.put("attribute", attributes.get(i).toString());
		rows.add(row);
	}
	DCBindingContainer dcBindings = (DCBindingContainer)getBindings();
	DCIteratorBinding iter = dcBindings.findIteratorBinding("ChildCarsView1Iterator");
	Row[] depts = iter.getAllRowsInRange();
	for (Row dept : depts) {
		column = new HashMap();
		column.put("label", dept.getAttribute("Name").toString());
		column.put("name", dept.getAttribute("Name").toString());
		columns.add(column);
		for (Map trow : rows) {
			trow.put(column.get("name"), dept.getAttribute((String)trow.get("attribute")));
		}
	}

}

public void setColumns(List> columns) {
	this.columns = columns;
}

public List> getColumns() {
	return columns;
}

public void setRows(List> rows) {
	this.rows = rows;
}

public List> getRows() {
	return rows;
}
Last step create a index.jspx page and drop Compare-btf as region into the page.

2 comments: