Friday, February 22, 2013

Restrict rows in a table (ADF Table Pagination)

So the user will select a department from the navigation list, and the employees will be displayed in group of N. User can click on the buttons below to traverse through the table.

You need to be aware of some of the properties of a table.


  1. AutoHeightRows: By defining this attribute, the table height is adjusted according to the content. The fetchSize and autoHeightRows attributes together control the number of rows displayed in the table. Please note that the value of the autoHeightRows attribute cannot exceed the value set for the fetchSize attribute.
  2. RangeSize: It controls the number of transactions that are required to populate the table as the user scrolls up and down.


This is the code for the af:table:

autoheightrows="5" contentdelivery="immediate" emptytext="#{bindings.EmployeesView3.viewable ? 'No data to display.' : 'Access Denied.'}" fetchsize="#{bindings.EmployeesView3.rangeSize}" id="t1" partialtriggers="::cb1 ::cb2 ::cb3 ::cb4" rowbandinginterval="0" rows="#{bindings.EmployeesView3.rangeSize}" rowselection="single" selectedrowkeys="#{bindings.EmployeesView3.collectionModel.selectedRow}" selectionlistener="#{PagingBean.rowSelected}" value="#{bindings.EmployeesView3.rangeSet}" var="row" width="100%"

This is the code in the bean:
package view;

import oracle.adf.model.BindingContext;
import oracle.adf.share.logging.ADFLogger;

import oracle.binding.BindingContainer;

import oracle.jbo.uicli.binding.JUCtrlRangeBinding;

import org.apache.myfaces.trinidad.event.SelectionEvent;

public class PagingBean {

    private int pageNumber;
    private static final int RANGE_SIZE = 5;
    private static ADFLogger _logger = ADFLogger.createADFLogger(PagingBean.class);

    /**
     * Invoked when user select a row in the table.
     * @param se
     */
    public void rowSelected(SelectionEvent se) {
        JUCtrlRangeBinding staffView = getEmployeeView();
        staffView.getIteratorBinding().setCurrentRowIndexInRange((Integer)se.getAddedSet().toArray()[0]);
    }

    /**
     * Invoked when user click the Go button to go to the target page
     * @return
     */
    public String goToPage() {
        JUCtrlRangeBinding staffView = getEmployeeView();
        int prePage = staffView.getIteratorBinding().getNavigatableRowIterator().getRangeStart() / RANGE_SIZE + 1;
        _logger.info("prePage: " + prePage);
        staffView.getIteratorBinding().getNavigatableRowIterator().scrollRange(RANGE_SIZE *
                                                                               (this.pageNumber - prePage));
        return null;
    }

    /**
     * Invoked when user click to go to the last page
     * @return
     */
    public String goLastPage() {
        JUCtrlRangeBinding staffView = this.getEmployeeView();
        long lastPageNum = Math.max(1, (staffView.getIteratorBinding().getEstimatedRowCount() - 1) / RANGE_SIZE + 1);
        _logger.info("lastPageNum: " + lastPageNum);
        if (lastPageNum <= 1)
            return null;
        int prePage = staffView.getIteratorBinding().getNavigatableRowIterator().getRangeStart() / RANGE_SIZE + 1;
        _logger.info("prePage: " + prePage);
        staffView.getIteratorBinding().getNavigatableRowIterator().scrollRange((int)(RANGE_SIZE *
                                                                                     (lastPageNum - prePage)));
        return null;
    }

    /**
     * Is go to last button enabled
     * @return
     */
    public boolean isLastEnabled() {
        JUCtrlRangeBinding staffView = this.getEmployeeView();
        long lastPageNum = Math.max(1, (staffView.getIteratorBinding().getEstimatedRowCount() - 1) / RANGE_SIZE + 1);
        _logger.info("lastPageNum: " + lastPageNum);
        long currentPage = staffView.getIteratorBinding().getNavigatableRowIterator().getRangeStart() / RANGE_SIZE + 1;
        _logger.info("currentPage: " + currentPage);
        _logger.info("Boolean" + (currentPage < lastPageNum));
        return currentPage < lastPageNum;
    }

    public void setPageNumber(int pageNumber) {
        _logger.info("pageNumber: " + pageNumber);
        this.pageNumber = pageNumber;
    }

    public int getPageNumber() {
        int i = getEmployeeView().getIteratorBinding().getNavigatableRowIterator().getRangeStart() / RANGE_SIZE + 1;
        _logger.info("i: " + i);
        return pageNumber =
                getEmployeeView().getIteratorBinding().getNavigatableRowIterator().getRangeStart() / RANGE_SIZE + 1;
    }

    private JUCtrlRangeBinding getEmployeeView() {
        BindingContext bindingCtx = BindingContext.getCurrent();
        BindingContainer bindings = bindingCtx.getCurrentBindingsEntry();
        return (JUCtrlRangeBinding)bindings.getControlBinding("EmployeesView3");
    }
}

Download the sample application from File Cabinet: TablePagination.rar

Thursday, February 21, 2013

ADF Logger (ADFLogger)

As per Duncan, install the logging template in JDeveloper. To install these:
  1. Open  Tools --> Preferences from the JDeveloper menu
  2. Expand the Code Editor --> Code Templates node in the preferences navigator
  3. Select the More Actions --> Import menu option as shown here and import the loggingTemplates.xml file from the File Cabinet.

Now say you have defined a class while developing an application in ADF. So inside the class, type 'lgdef' and press Ctrl+Enter. This will create a logging object automatically with the name _logger.
private static ADFLogger _logger = ADFLogger.createADFLogger(BCPagingMB.class);
In the above example, BCPagingMB is the name of the class.

Now wherever you were using sop statement, type 'lgi', press Ctrl+Enter, enter the message that you want to display.

Then in the IntegratedWebLogicServer Log, click on Actions and go to Configure Oracle Diagnostic Logging. Now I had written the code in the view package. So search for view in the RootLogger tree. Select it. Click the plus button to add a Transient Logger. Enter the Logger name as the name of the package (in this case, view), and set the logger level as Info.

You can read more about ADFLogger in Duncan Mills's blog.

Wednesday, February 20, 2013

Different ways of setting Sequence Number in ADF

1. In EmployeesEO, change the type of EmployeeID to be Number, Default Value to be Expression, and set the value as
(new oracle.jbo.server.SequenceImpl("HR.EMPLOYEES_SEQ", adf.object.getDBTransaction())).getSequenceNumber()

2. Override the create() in EmployeesEOImpl class as follows:
    protected void create(AttributeList attributeList) {
        super.create(attributeList);
        SequenceImpl seq = new SequenceImpl("EMPLOYEES_SEQ", getDBTransaction());
        Number seqNextval = seq.getSequenceNumber();
        setEmployeeId(seqNextval);
    }

3. Override the initDefaults() in EmployeesEOImpl class as follows:
    protected void initDefaults() {
        super.initDefaults();
        SequenceImpl sequence = new SequenceImpl("EMPLOYEES_SEQ", getDBTransaction());
        DBSequence seqNextval = new DBSequence(sequence.getSequenceNumber());
    }

Saturday, February 16, 2013

'Hello World!' Program for ADF

There will be an inputtext component, the user will click on Submit button, and then output will be displayed below the button.

A managed bean should be created for each of the inputtext and outputtext components so that we can access the value. This is the code created in the managed bean:

public class test {
    private RichInputText input;
    private RichOutputText output;

    public test() {
    }

    public String cb1_action() {
        // Add event code here...
        String test_input = (String)input.getValue();
        System.out.println("value: "+test_input);
        String value = "Hello " +test_input+ "!!";
        output.setValue(value);
        return null;
    }

    public void setInput(RichInputText input) {
        this.input = input;
    }

    public RichInputText getInput() {
        return input;
    }

    public void setOutput(RichOutputText output) {
        this.output = output;
    }

    public RichOutputText getOutput() {
        return output;
    }
}

Now imagine there was no Submit button, and you have to get the result as soon as you press TAB button after entering a string in the inputtext component. In this case, you have to write a method in valueChangeListener property.

    public void displayValue(ValueChangeEvent valueChangeEvent) {
        // Add event code here...
        String test_input = (String)valueChangeEvent.getNewValue();
        System.out.println("value: "+test_input);
        String value = "Hello " +test_input+ "!!";
        output.setValue(value);
    }
Please note that in this case, don't set any method in attributeChangeListener property. Also set the autoSubmit property of inputText component to be True, and the partialTriggers property of the outputtext component to be the inputText component.

Download the sample application from File Cabinet: HelloWorld.rar

Thursday, February 14, 2013

Dependent or Cascading List of Values (LOV) in ADF

I have built this sample on Regions, Countries and Locations table of HR Schema. In addition to it, I have created one more table TEST_RCL. This is the creation script for the same table:

create table TEST_RCL
(
  ID       NUMBER(3),
  REGION   NUMBER(3),
  COUNTRY  VARCHAR2(3),
  LOCATION NUMBER(4)
)
Now the main content. Create a view criteria in CountriesVO where you need to assign attribute RegionID to a bind parameter.

Similarly create a view criteria in LocationsVO too.

Now imagine the actual situation. User should see all the regions in the LOV. Once he/she selects a region, the Countries LOV should display the values related to the region selected in LOV.

So for Country attribute of TestRclVO, create a LOV.
1. Keep the data source to be the CountriesVO.

2. Click on Edit in the View Accessors screen, shuttle the view criteria from the list of available view criterias
3. Set the Value of the regid parameter to be 'Region'.

Follow the related steps for the LocationsVO. And then test the scenario in Model Tester.

Download the sample application from File Cabinet: DependentLOV.rar

Sunday, February 10, 2013

Synchronizing ADF Tree with Table Data

Enabling auto-synchronization between iterators of the same type in a page
Okay. So the user will select the Region and then the Country from tree, and based on the selection, the Locations will be displayed in a table-form structure.

Got the idea from one of the videos of Grant Ronald. Download the sample application from File Cabinet: SyncTableTree.rar

When a tree table is displayed, the framework executes the iterator binding for the top level collection during the initial page load. When you expand a parent node, the framework identifies the accessor attribute for the child nodes from the tree binding definition, and then executes the child view object using the accesor attribute in the parent row.

Saturday, February 9, 2013

Pass Parameters inside an ADF Bounded Taskflow

So there is a 'home' page which is displaying the list of locations. User can either click on the location ID to update the record, or create a location by clicking Create button.

First define an input parameter 'Action' in the 'child' taskflow. Set its value as #{pageFlowScope.Action}.

Drag the CreateInsert operation to the task flow diagram to create a method call activity. Change fixed-outcome of the CreateInsert method call activity to done.

Add an expression to the router: #{pageFlowScope.Action == 'New'}



Now in the 'main' taskflow, click on the 'child' taskflow call and in the Property Inspector, set the value of the parameter as #{requestScope.Action}

Add a setPropertyListener property to the Create button. Set the From property to #{'New'}, To property to #{requestScope.Action} and Type as 'action'.

Download the sample application from File Cabinet: PassParaBoundTskflow.rar


Friday, February 8, 2013

Perform Delete and Commit by a single button click

Create a bean named DeleteCommitBean in UI project.

Add a method named onDeleteItem that calls the ADFUtil.invokeEL() helper method to execute the EL expression that you pass in as a parameter. In this case, it will execute a Delete and a Commit. Note that the Delete and Commit methods must be in the Page Definition Bindings for any page that uses them.
    public void onDeleteItem(ActionEvent actionEvent) {
        ADFUtil.invokeEL("#{bindings.Delete.execute}");
        ADFUtil.invokeEL("#{bindings.Commit.execute}");
        System.out.println("Deleted");
    }
Register the bean as managed bean in adfc-config.xml pageflow file.

In the UI, add a button from the Component Palette. Modify the text as Delete and set the ActionListener as
#{FODCommitDeleteBean.onDeleteItem}
You can also click on 'Edit' to modify the same.

Code snippet of invokeEL method in ADFUtil class
public class ADFUtil {

    public static Object invokeEL(String el) {
        return invokeEL(el, new Class[0], new Object[0]);
    }

    public static Object invokeEL(String el, Class[] paramTypes, Object[] params) {
        FacesContext facesContext = FacesContext.getCurrentInstance();
        ELContext elContext = facesContext.getELContext();
        ExpressionFactory expressionFactory = facesContext.getApplication().getExpressionFactory();
        MethodExpression exp = expressionFactory.createMethodExpression(elContext, el, Object.class, paramTypes);
        return exp.invoke(elContext, params);
    }
}

  • FacesContext is a class that contains all of the per-request state information related to the processing of a single JSF request, and the rendering of the corresponding response. It is passed to, and potentially modified by, each phase of the request processing life cycle.
  • ELContext is a class that is used to evaluate an expression.
  • ExpressionFactory is a class that parses a String into a value expression or a method expression for later evaluation.
For more information about EL Expressions, refer to this post 'ADF UI - ADFUtil class to evaluate, set and invoke EL expressions'

Monday, February 4, 2013

Let's Validate Email ID in ADF

1. Drag an inputText component. Label it as Email. Drop a validate Regular Expression component over it.

validateregexp messagedetailnomatch="Enter a Valid Email ID" pattern="[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}"

2. We will validate the above scenario by calling a PL/SQL function. This is quite interesting.

    public String validateEmail() {
        // Add event code here...
        BindingContainer bindings = getBindings();

        FacesContext facesContext = FacesContext.getCurrentInstance();
        UIViewRoot root = facesContext.getViewRoot();
        RichInputText inputText = (RichInputText)root.findComponent("it6");
        
        String myString = inputText.getValue().toString();
        System.out.println(myString.toString());
        OperationBinding operationBinding = bindings.getOperationBinding("validateEmailByPlsql");
        operationBinding.getParamsMap().put("email", myString);
        Object result = operationBinding.execute();
        System.out.println(result.toString());
        if (result.toString().equals("FAILURE")) {
            FacesContext fctx = FacesContext.getCurrentInstance();
            fctx.addMessage("it6", new FacesMessage(FacesMessage.SEVERITY_ERROR, "Email is not Valid", null));
        } else {
            FacesContext fctx = FacesContext.getCurrentInstance();
            fctx.addMessage("it6", new FacesMessage(FacesMessage.SEVERITY_INFO, "Email Valid", null));
        }
        return null;
    }

    public String validateEmailByPlsql(String email) {
        return (String)callStoredFunction(VARCHAR2, "xx_check_email(?)", new Object[] { email });
    }

    protected Object callStoredFunction(int sqlReturnType, String stmt, Object[] bindVars) {
        CallableStatement st = null;
        try {
            // 1. Create a JDBC CallabledStatement
            st = getDBTransaction().createCallableStatement("begin ? := " + stmt + ";end;", 0);
            // 2. Register the first bind variable for the return value
            st.registerOutParameter(1, sqlReturnType);
            if (bindVars != null) {
                // 3. Loop over values for the bind variables passed in, if any
                for (int z = 0; z < bindVars.length; z++) {
                    // 4. Set the value of user-supplied bind vars in the stmt
                    st.setObject(z + 2, bindVars[z]);
                }
            }
            // 5. Set the value of user-supplied bind vars in the stmt
            st.executeUpdate();
            // 6. Return the value of the first bind variable
            System.out.println(st.getObject(1).toString());
            return st.getObject(1);
        } catch (SQLException e) {
            throw new JboException(e);
        } finally {
            if (st != null) {
                try {
                    // 7. Close the statement
                    st.close();
                } catch (SQLException e) {
                }
            }
        }
    }

Now define a function 'xx_check_email' in your database which will be taking one String parameter and validate whether the String is a proper email ID or not.

For more details, check the section Invoking Stored Procedure and Functions from Developer's Guide: http://docs.oracle.com/cd/E24382_01/web.1112/e16182/bcadvgen.htm#insertedID6

Now suppose we have an employee record and while creating it, we have to supply the emailID. Then we will press Commit button. In this case I am showing you 2 ways to validate the same.

1. Create a validation rule on email attribute. Set the rule type as Method, with a proper error message in Failure Handling tab. A method will be generated in EntityImpl class
    public boolean validateEmail(String email) {
        int atpos = email.indexOf('@');
        int dotpos = email.lastIndexOf('.');
        if (atpos == -1 || dotpos == -1) {
            return false;
        }
        return true;
    }
2. Domain: I am having some problem while validating the same with domains. I am getting this error message: 'Cannot convert testmailid of type class java.lang.String to class model.common.DomainEmail'.

3. We can validate in the setter method of email.
    public void setEmail(String value) {

        int atpos = value.indexOf('@');
        int dotpos = value.lastIndexOf('.');
        if (atpos == -1 || dotpos == -1) {
            throw new oracle.jbo.JboException("Invalid Email ID");
        }
        setAttributeInternal(EMAIL, value);
    }

Saturday, February 2, 2013

Lifecycle of an Entity Object in ADF


This is an extract taken from book Oracle ADF Real World Developer's Guide.

When you create an instance of an entity object by using the createInstance2(DBTransaction, AttributeList) method on the entity definition, its state is New. Entity objects in the New state will qualify in order to participate in the transaction.

When a client invokes finder methods on an entity definition or on an entity based view object, the framework creates entity rows for each row from the database. The state of these entity rows will be marked as Unmodified. Later when the client modifies some attributes, the state is transitioned to Modified and they become eligible to participate in the transaction.

When a client calls the remove method on an entity object, entity state becomes Deleted and it will be added to the transaction cycle.

The transaction commit cycle takes place in two phases, posting changes to the database and then committing the database transaction. On successful commit to the database, all the entities in the state New and Modified will be transitioned to Unmodified, and Deleted entity rows will be transitioned to the Dead state.


Create a row at the end of an ADF table

Okay, so the user will select a department name, and based on that, employees will be displayed. The user will click on Create button post which a blank row will be inserted where the user will have to enter valid values, and after that click on Commit to save data in database.

Okay. Some things to remember. Set the PartialSubmit property of the button to be True and the PartialTrigger of the table to be set as the ID of the button. This is the code that is executed when the user presses Create button.

import oracle.adf.model.BindingContext;
import oracle.binding.BindingContainer;
import oracle.jbo.NavigatableRowIterator;
import oracle.jbo.Row;
import oracle.jbo.uicli.binding.JUCtrlHierBinding;
import oracle.adf.model.binding.DCIteratorBinding;

    public String cb1_action() {
        // Add event code here...
        BindingContainer bc = BindingContext.getCurrent().getCurrentBindingsEntry();
        JUCtrlHierBinding hierBinding = (JUCtrlHierBinding)bc.get("EmployeesView3");
        DCIteratorBinding dciter = hierBinding.getDCIteratorBinding();
        NavigatableRowIterator nav = dciter.getNavigatableRowIterator();
        Row newRow = nav.createRow();
        newRow.setNewRowState(Row.STATUS_INITIALIZED);
        Row lastRow = nav.last();
        int lastRowIndex = nav.getRangeIndexOf(lastRow);
        nav.insertRowAtRangeIndex(lastRowIndex + 1, newRow);
        dciter.setCurrentRowWithKey(newRow.getKey().toStringFormat(true));
        return null;
    }
Now some concepts about inserting row in different places of the table:

1. User wants to create a new row as the first row of the table. In that case

newRow.setNewRowState(Row.STATUS_INITIALIZED);
nav.insertRowAtRangeIndex(0, newRow);
dciter.setCurrentRowWithKey(newRow.getKey().toStringFormat(true));
2. User wants to create a new row before the current selected row

newRow.setNewRowState(Row.STATUS_INITIALIZED);
nav.insertRow(newRow);
dciter.setCurrentRowWithKey(newRow.getKey().toStringFormat(true));
3. User wants to create a new row after the current selected row

newRow.setNewRowState(Row.STATUS_INITIALIZED);
Row currentRow = nav.getCurrentRow();
int currentRowIndex = nav.getRangeIndexOf(currentRow);
nav.insertRowAtRangeIndex(currentRowIndex+1, newRow);
dciter.setCurrentRowWithKey(newRow.getKey().toStringFormat(true));
Infact if the CreateInsert operation is dragged into the page and created as a button, the 2nd point would have been achieved.

If you set the EditingMode as 'clickToEdit', the rows will appear as read-only. Once the user clicks on a particular row, he/she can edit the row. But make sure that the row selection of the table component must be set as Single.

Difference between AutoSubmit and PartialSubmit
autoSubmit: Related to an input component (such as inputText and selectOneChoice) or a table select component (such as tableSelectOne). The component automatically submits the form it is enclosed in
partialSubmit: Releated to a command component. The page partially submits when the button or link is clicked.

If a component has its immediate attribute set to true, then the validation, conversion, and events associated with the component are processed during this phase.

setNewRowState
This method should be used to create a row and then to mark it temporary (STATUS_INITIALIZED) so that the user can use the created Row to fill UIs like Table UIs with valid default values for rows. Then when the Row values are updated, UIs should once again call this method to turn the Row into new (STATUS_NEW) state before any setAttribute calls on the Row, so that the changes are validated and posted.

Download the sample application from File Cabinet: CreateEmployee.rar