Tuesday, June 9, 2015

Clicking a button on Page Load

Assume you have the following scenario:

On Page1.jspx, you have a task flow dropped as a region. Within that task flow, you have just one fragment, that contains a GoLink component that points to Page2.jspx. What we need to do is as soon as Page1.jspx is loaded, we need to redirect to Page2.jspx by programatically creating the link click (or action) event. We can pass some parameters as well during this call.
If you are using 11gR2 and up, then you can use the following tutorial for this requirement:

https://blogs.oracle.com/jdevotnharvest/entry/jdeveloper_11g_r2_and_12c

But if you are using JDeveloper version before 11gR2, then you have to manually take care as suggested below. Here is how GoLinkView.jsff looks like:

<?xml version='1.0' encoding='UTF-8'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1" xmlns:af="http://xmlns.oracle.com/adf/faces/rich"
          xmlns:f="http://java.sun.com/jsf/core">
  <af:panelGroupLayout id="pgl1" layout="vertical">
    <af:commandLink text="Second Link" id="linkToHome" actionListener="#{GoLinkViewBean.navigateLink}" clientComponent="true"
                    partialSubmit="true" visible="false"/>
  </af:panelGroupLayout>
</jsp:root>

And the GoLinkViewBean looks like:

package view.bean;

import java.io.IOException;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;

public class GoLinkViewBean {
    public GoLinkViewBean() {
    }

    public void navigateLink(ActionEvent actionEvent) {
        // Add event code here...
        String destination = "http://127.0.0.1:7001 /GoLinkRedirectApp-ViewController-context-root/faces/Page2.jspx";
             try {
                 FacesContext.getCurrentInstance().getExternalContext().redirect(destination);
             } catch (IOException e) {
                 e.printStackTrace();
             }
    }
}

To initiate the navigateLink action event on page load, I have used javascript solution. The key is to add a clientListener on the document tag of Page1.jspx as shown below:

<?xml version='1.0' encoding='UTF-8'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1" xmlns:f="http://java.sun.com/jsf/core"
          xmlns:af="http://xmlns.oracle.com/adf/faces/rich">
    <jsp:directive.page contentType="text/html;charset=UTF-8"/>
    <f:view>
        <af:document title="Page1.jspx" id="d1">
            <af:clientListener type="load" method="onPageLoad"/>
            <af:resource type="javascript">
                function onPageLoad(){
                        var button = AdfPage.PAGE.findComponentByAbsoluteId("r1:0:linkToHome");
                        AdfActionEvent.queue(button,true);
                }
            </af:resource>         
            <af:form id="f1">
                <af:region value="#{bindings.RedirectTaskFlow2.regionModel}" id="r1"/>
            </af:form>
        </af:document>
    </f:view>
</jsp:root>

So as soon as the Page1.jspx is called, after the page is loaded, the javascript takes control, find the goLink component in the nested region and adds the action even in the even queue. Once that is done, the action event is trigged on the component to redirect the request to Page2.jspx. Please not that if you remove the clientListener tag, the javascript will fail as it will run even before the page is loaded and will not be able to find the component. 

Thats all.


Tuesday, September 23, 2014

Multiple Document Upload

Hi.

I faced one requirement where we need to allow the user to upload more than one document in one go. I am using 11.1.2.4 and in this release the following feature is not available:

http://andrejusb.blogspot.com/2013/04/multiple-file-upload-unlimited-file.html

So, if you are using a release earlier than PS6, or not using 12C yet, the only option left is to do it programatically. This blog explains how to do that in detail. The application that I created looks like this:


In the above figure:

1. BaseProject: Contains the interface UploadedFileDetails used in Model & ViewController project. This interface defines common method used to fetch uploaded file metadata.

2. Model Project: Contains the BC4J components, like DocumentEO, DocumentVO and AM used by the View layer to insert document to the DB.

3. ViewController Project: Contains the UI page, MultipleDocumentUpload.jspx, its managed bean MultipleDocumentUpload, and FileUpload class that stores the uploaded file before it is moved to the DB.

 When the MultipleDocumentUpload.jspx page is run, the UI looks like this:



The 'Add File' button allows you to add one more input file component, and 'Remove File' component removed the recently added input file component. Lets add another input file component to upload 2 files at once so click 'Add File' button once and select some files to upload in both the input file components, as shown below:



Now, click the 'insert' button to insert the records in the DB. The table gets populated with 2 records as shown below:



Now, since the documents are in the DB, you can use the download button to download each uploaded file.

This way, you can upload as many files as you want at run time.

Here is the code to implement this functionality:

The DOCUMENT table looks like this:

CREATE TABLE document (
  document_id   NUMBER        NOT NULL,
  document_name VARCHAR2(200) NULL,
  document_type VARCHAR2(200) NULL,
  document      BLOB          NULL
);

And here is DOCUMENT_SEQ used to generate unique document IDs:

CREATE SEQUENCE document_seq
  MINVALUE 1
  MAXVALUE 9999999999999999999999999999
  INCREMENT BY 1
  NOCYCLE
  NOORDER
  NOCACHE
/

Now, in the BaseProject I have defined an interface, UploadedFileDetails, that is used both in Model and ViewController project to fetch uploaded file details:

package base.interfaces;

import oracle.jbo.domain.BlobDomain;

public interface UploadedFileDetails {
   
    public String getFileName();
    
    public String getFileType();
    
    public Long getFileLength();
    
    public BlobDomain getFileContents();
    
}

In the Model project, I created DocumentEO over DOCUMENT table, DocumentVO based on DocumentEO, and AppModule application module. The following method is added to DocumentEOImpl to generate the document ID from sequence:

package model.entity;
...
public class DepartmentEOImpl extends EntityImpl {
...
    protected void create(oracle.jbo.AttributeList attributeList) { 
          super.create(attributeList);
          for (AttributeDef def : getEntityDef().getAttributeDefs()) {
              String sequenceName = (String)def.getProperty("SequenceName");
              if (sequenceName != null) {
                  SequenceImpl s =
                      new SequenceImpl(sequenceName, getDBTransaction());
                  populateAttributeAsChanged(def.getIndex(),
                                             s.getSequenceNumber());
              }
          }
    }
}

And on DocumentEO's DocumentID attribute, the following non-translatable property is added:


The AppModuleImpl looks like this:

package model.am;

import base.interfaces.UploadedFileDetails;

import java.util.ArrayList;

import model.am.common.AppModule;

import oracle.jbo.Row;
import oracle.jbo.domain.BlobDomain;
import oracle.jbo.server.ApplicationModuleImpl;
import oracle.jbo.server.ViewObjectImpl;
// ---------------------------------------------------------------------
// ---    File generated by Oracle ADF Business Components Design Time.
// ---    Sun Sep 21 21:44:29 PDT 2014
// ---    Custom code may be added to this class.
// ---    Warning: Do not modify method signatures of generated methods.
// ---------------------------------------------------------------------
public class AppModuleImpl extends ApplicationModuleImpl implements AppModule {
    /**
     * This is the default constructor (do not remove).
     */
    public AppModuleImpl() {
    }

    /**
     * Container's getter for DepartmentVO1.
     * @return DepartmentVO1
     */
    public ViewObjectImpl getDepartmentVO1() {
        return (ViewObjectImpl)findViewObject("DepartmentVO1");
    }
    
    public void insertDocumentDetails(ArrayList documentList){
        
        ViewObjectImpl departmentVO = getDepartmentVO1() ;
        
        for(int i = 0; i < documentList.size(); i++ ){
            UploadedFileDetails uploadedFile = ((UploadedFileDetails)documentList.get(i));
            if(uploadedFile != null){
                Long uploadedFileLength = uploadedFile.getFileLength();
                System.out.println("Lalit >> Uploaded File Index = " + i);
                if(uploadedFileLength > 0){
                    System.out.println("Lalit >> Uploaded File Index = " + i);
                    System.out.println("Lalit >> Uploaded File Length = " + uploadedFileLength);
                    System.out.println("LALIT: Uploaded File Name = " + uploadedFile.getFileName()  );
                    System.out.println("LALIT: Uploaded File Type = " + uploadedFile.getFileType()  );
                    BlobDomain uploadedFileContent = uploadedFile.getFileContents();
                    
                    Row row = departmentVO.createRow();
                    row.setAttribute("DocumentName", uploadedFile.getFileName());
                    row.setAttribute("DocumentType", uploadedFile.getFileType() );
                    row.setAttribute("Document", uploadedFileContent);
                    departmentVO.insertRow(row);
                    System.out.println("LKAPOOR:: INSERTING DOCUMENT ROW");
                }
            }else{
                    System.out.println("Lalit >> Uploaded File Index = " + i + " has no content.");            
            }
        
        }
        this.getDBTransaction().commit();
        
    }
    
}

The insertDocumentDetails method is used to insert document records in the DB.

In the ViewController project, the MultipleDocumentUpload.jspx looks like this:

<?xml version='1.0' encoding='UTF-8'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1" xmlns:f="http://java.sun.com/jsf/core"
          xmlns:af="http://xmlns.oracle.com/adf/faces/rich">
    <jsp:directive.page contentType="text/html;charset=UTF-8"/>
    <f:view>
        <af:document title="MultipleDocumentUpload.jspx" id="d1">
            <af:messages id="m1"/>
            <af:form id="f1" usesUpload="true">
                <af:panelStretchLayout id="psl1" binding="#{pageFlowScope.MultipleDocumentUpload.stretchLayoutBinding}"
                                       styleClass="AFStretchWidth">
                    <f:facet name="bottom">
                        <af:panelGroupLayout id="pgl3">
                            <af:commandButton text="Print File Metadata" id="cb1" partialSubmit="true"
                                              actionListener="#{pageFlowScope.MultipleDocumentUpload.handlePrintMetadata}"
                                              rendered="false"/>
                            <af:spacer width="10" height="10" id="s4"/>
                            <af:commandButton text="Insert " id="cb4"
                                              actionListener="#{pageFlowScope.MultipleDocumentUpload.handleInsertDocuments}"
                                              partialSubmit="true"/>
                        </af:panelGroupLayout>
                    </f:facet>
                    <f:facet name="center">
                        <af:panelStretchLayout id="psl2" topHeight="250px" startWidth="0px" endWidth="0px"
                                               bottomHeight="0px" styleClass="AFStretchWidth">
                            <f:facet name="bottom"/>
                            <f:facet name="center">
                                <af:panelCollection id="pc1">
                                    <f:facet name="menus"/>
                                    <f:facet name="toolbar"/>
                                    <f:facet name="statusbar"/>
                                    <af:table value="#{bindings.DepartmentVO1.collectionModel}" var="row"
                                              rows="#{bindings.DepartmentVO1.rangeSize}"
                                              emptyText="#{bindings.DepartmentVO1.viewable ? 'No data to display.' : 'Access Denied.'}"
                                              fetchSize="#{bindings.DepartmentVO1.rangeSize}" rowBandingInterval="0"
                                              filterModel="#{bindings.DepartmentVO1Query.queryDescriptor}"
                                              queryListener="#{bindings.DepartmentVO1Query.processQuery}"
                                              filterVisible="true" varStatus="vs"
                                              selectedRowKeys="#{bindings.DepartmentVO1.collectionModel.selectedRow}"
                                              selectionListener="#{bindings.DepartmentVO1.collectionModel.makeCurrent}"
                                              rowSelection="single" id="t1"
                                              binding="#{pageFlowScope.MultipleDocumentUpload.documentTable}"
                                              columnStretching="last">
                                        <af:column sortProperty="#{bindings.DepartmentVO1.hints.DocumentId.name}"
                                                   filterable="true" sortable="true"
                                                   headerText="#{bindings.DepartmentVO1.hints.DocumentId.label}"
                                                   id="c1">
                                            <af:outputText value="#{row.DocumentId}"
                                                           shortDesc="#{bindings.DepartmentVO1.hints.DocumentId.tooltip}"
                                                           id="ot2">
                                                <af:convertNumber groupingUsed="false"
                                                                  pattern="#{bindings.DepartmentVO1.hints.DocumentId.format}"/>
                                            </af:outputText>
                                        </af:column>
                                        <af:column sortProperty="#{bindings.DepartmentVO1.hints.DocumentName.name}"
                                                   filterable="true" sortable="true"
                                                   headerText="#{bindings.DepartmentVO1.hints.DocumentName.label}"
                                                   id="c2">
                                            <af:outputText value="#{row.DocumentName}"
                                                           shortDesc="#{bindings.DepartmentVO1.hints.DocumentName.tooltip}"
                                                           id="ot3"/>
                                        </af:column>
                                        <af:column sortProperty="#{bindings.DepartmentVO1.hints.DocumentType.name}"
                                                   filterable="true" sortable="true"
                                                   headerText="#{bindings.DepartmentVO1.hints.DocumentType.label}"
                                                   id="c3">
                                            <af:outputText value="#{row.DocumentType}"
                                                           shortDesc="#{bindings.DepartmentVO1.hints.DocumentType.tooltip}"
                                                           id="ot4"/>
                                        </af:column>
                                        <af:column sortProperty="#{bindings.DepartmentVO1.hints.Document.name}"
                                                   sortable="true"
                                                   headerText="#{bindings.DepartmentVO1.hints.Document.label}" id="c4"
                                                   rendered="false">
                                            <af:outputText value="#{row.Document}"
                                                           shortDesc="#{bindings.DepartmentVO1.hints.Document.tooltip}"
                                                           id="ot5"/>
                                        </af:column>
                                        <af:column id="c5">
                                            <af:commandLink text="Download" id="cl1" partialSubmit="true">
                                                <af:fileDownloadActionListener filename="#{row.DocumentName}"
                                                                               contentType="#{row.DocumentType}"
                                                                               method="#{pageFlowScope.MultipleDocumentUpload.handleDownload}"/>
                                            </af:commandLink>
                                        </af:column>
                                    </af:table>
                                </af:panelCollection>
                            </f:facet>
                            <f:facet name="start"/>
                            <f:facet name="end"/>
                            <f:facet name="top">
                                <af:panelGroupLayout id="pgl1" layout="vertical" partialTriggers="cb2 cb3">
                                    <af:forEach var="row" items="#{pageFlowScope.MultipleDocumentUpload.items}"
                                                varStatus="vStatus">
                                        <af:panelGroupLayout id="pgl4" layout="horizontal">
                                            <af:outputText value="#{vStatus.count}" id="ot1"/>
                                            <af:spacer width="10" height="10" id="s1"/>
                                            <af:inputFile label="Label #{vStatus.index}" id="if1"
                                                          value="#{pageFlowScope.MultipleDocumentUpload.items[vStatus.index].inputFile}"
                                                          autoSubmit="true"
                                                          valueChangeListener="#{pageFlowScope.MultipleDocumentUpload.inputFileValueChangeListener}">
                                                <f:attribute name="indexValue" value="#{vStatus.index}"/>
                                            </af:inputFile>
                                        </af:panelGroupLayout>
                                        <af:separator id="s2"/>
                                    </af:forEach>
                                </af:panelGroupLayout>
                            </f:facet>
                        </af:panelStretchLayout>
                    </f:facet>
                    <f:facet name="start"/>
                    <f:facet name="end"/>
                    <f:facet name="top">
                        <af:panelGroupLayout id="pgl2" layout="horizontal">
                            <af:commandButton text="Add File" id="cb2"
                                              actionListener="#{pageFlowScope.MultipleDocumentUpload.handleAddFileAction}"
                                              partialSubmit="true"/>
                            <af:spacer width="10" height="10" id="s3"/>
                            <af:commandButton text="Remove File" id="cb3"
                                              actionListener="#{pageFlowScope.MultipleDocumentUpload.handleRemoveFile}"
                                              partialSubmit="true"/>
                        </af:panelGroupLayout>
                    </f:facet>
                </af:panelStretchLayout>
            </af:form>
        </af:document>
    </f:view>
</jsp:root>

The FileUpload file is used to store the uploaded document details. It also implements the UploadedFileDetails interface and looks like this:

package view.bean;

import base.interfaces.UploadedFileDetails;

import java.io.Serializable;

import oracle.adf.view.rich.component.rich.input.RichInputFile;

import oracle.jbo.domain.BlobDomain;

import org.apache.myfaces.trinidad.model.UploadedFile;

public class FileUpload implements Serializable, UploadedFileDetails{

    @SuppressWarnings("compatibility:4466058592478245489")
    private static final long serialVersionUID = 1L;
    private UploadedFile inputFile;
    private BlobDomain inputFileContent;
    
    public FileUpload() {
        super();
    }

    public void setInputFile(UploadedFile inputFile) {
        this.inputFile = inputFile;
    }

    public UploadedFile getInputFile() {
        return inputFile;
    }

    public void setInputFileContent(BlobDomain inputFileContent) {
        this.inputFileContent = inputFileContent;
    }

    public BlobDomain getInputFileContent() {
        return inputFileContent;
    }
    
    public String getFileName(){
        return inputFile.getFilename();
    }
    
    public String getFileType(){
        return inputFile.getContentType();
    }
    
    public Long getFileLength(){
        return getInputFileContent().getLength();
    }
    
    public BlobDomain getFileContents(){
        return getInputFileContent();
    }
}

And MultipleDocumentUpload managed bean looks like this:

package view.bean;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import java.io.OutputStreamWriter;

import java.sql.SQLException;

import java.util.ArrayList;

import java.util.HashMap;
import java.util.List;

import java.util.Map;

import java.util.concurrent.ConcurrentHashMap;

import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
import javax.faces.event.ValueChangeEvent;
import javax.faces.model.SelectItem;

import oracle.adf.model.BindingContext;
import oracle.adf.model.binding.DCBindingContainer;
import oracle.adf.model.binding.DCIteratorBinding;
import oracle.adf.share.ADFContext;
import oracle.adf.share.security.SecurityContext;
import oracle.adf.view.rich.component.rich.data.RichTable;
import oracle.adf.view.rich.component.rich.input.RichInputFile;
import oracle.adf.view.rich.component.rich.layout.RichPanelStretchLayout;

import oracle.binding.BindingContainer;
import oracle.binding.OperationBinding;

import oracle.jbo.Row;
import oracle.jbo.domain.BlobDomain;

import oracle.jbo.uicli.binding.JUCtrlHierBinding;

import org.apache.myfaces.trinidad.model.CollectionModel;
import org.apache.myfaces.trinidad.model.UploadedFile;

public class MultipleDocumentUpload {
    private RichPanelStretchLayout stretchLayoutBinding;
    
    private ArrayList list = new ArrayList();
    private ArrayList<BlobDomain> blobList = new ArrayList<BlobDomain>();
    private RichTable documentTable;

    public MultipleDocumentUpload() {
        
        list.add(new FileUpload());
        System.out.println("Lalit >>size = " + list.size());

    }

    public void setStretchLayoutBinding(RichPanelStretchLayout stretchLayoutBinding) {
        this.stretchLayoutBinding = stretchLayoutBinding;
    }

    public RichPanelStretchLayout getStretchLayoutBinding() {
        return stretchLayoutBinding;
    }

    public List getItems() {
        return list;
    }


    public void handlePrintMetadata(ActionEvent actionEvent) {
        // Add event code here...
        for(int i = 0; i < list.size(); i++ ){
            FileUpload uploadedFile = ((FileUpload)list.get(i));
            if(uploadedFile != null){
                UploadedFile inputFile = uploadedFile.getInputFile();
                if(inputFile != null){
                    System.out.println("LALIT >> index = " + i);
                    System.out.println("LALIT: FILE NAME = " + inputFile.getFilename()  );
                    BlobDomain uploadedFileContent = ((FileUpload)list.get(i)).getInputFileContent();
                    if(uploadedFileContent != null)
                        System.out.println("LALIT: FILE SIZE = " + uploadedFileContent.getLength()   );
                    
                    System.out.println("LALIT: FILE CONTENT TYPE  = " + inputFile.getContentType()  );
                }
            }
        }
    }

    public void inputFileValueChangeListener(ValueChangeEvent valueChangeEvent) {
        // Add event code here...
        
        UploadedFile file;
        file = (UploadedFile)valueChangeEvent.getNewValue();
        
        Object source = valueChangeEvent.getSource();
        RichInputFile inputFile = (RichInputFile)source;
        Map attrObjMap = inputFile.getAttributes();
        Object indexObjAttr = attrObjMap.get("indexValue");
        System.out.println("Lalit:: attribute value = " + indexObjAttr);
                
        FileUpload toUpdateFile = (FileUpload)list.get(Integer.parseInt(indexObjAttr.toString()) );
        toUpdateFile.setInputFileContent(createBlobDomain(file));
        
        System.out.println("LKapoor>> updated file content>> " + toUpdateFile.getInputFileContent().getLength());
        
    }
    
    private BlobDomain createBlobDomain(UploadedFile file) {

        InputStream in = null;
        BlobDomain blobDomain = null;
        OutputStream out = null;

        try {
            in = file.getInputStream();

            blobDomain = new BlobDomain();
            out = blobDomain.getBinaryOutputStream();
            byte[] buffer = new byte[8192];
            int bytesRead = 0;

            while ((bytesRead = in.read(buffer, 0, 8192)) != -1) {
                out.write(buffer, 0, bytesRead);
            }

            in.close();

        } catch (IOException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.fillInStackTrace();
        }

        return blobDomain;
    }

    public void handleAddFileAction(ActionEvent actionEvent) {
        // Add event code here...
        list.add(new FileUpload());
        
    }

    public void handleRemoveFile(ActionEvent actionEvent) {
        // Add event code here...
        list.remove(list.size() - 1);
        
    }

    public void handleInsertDocuments(ActionEvent actionEvent) {
        // Add event code here...
        BindingContext bindingctx = BindingContext.getCurrent();
        BindingContainer bindings = null;
        bindings = bindingctx.getCurrentBindingsEntry();
        DCBindingContainer bindingsImpl = (DCBindingContainer)bindings;
        OperationBinding insertDocsCall = null;
        insertDocsCall = bindingsImpl.getOperationBinding("insertDocumentDetails");
        
        insertDocsCall.getParamsMap().put("documentList", list);
        
        insertDocsCall.execute();
    }

    public void handleDownload(FacesContext facesContext, OutputStream outputStream) {
        // Add event code here...
        
        try {
            BufferedWriter w = new BufferedWriter(new OutputStreamWriter(outputStream, "UTF-8"));

            CollectionModel collectionModel = (CollectionModel)getDocumentTable().getValue();
            JUCtrlHierBinding tableBinding = null;
            tableBinding = (JUCtrlHierBinding)collectionModel.getWrappedData();
            DCIteratorBinding iteratorBinding = tableBinding.getDCIteratorBinding();
            Row row = iteratorBinding.getCurrentRow();

            if (row != null) {
                BlobDomain payloadCanonical = (BlobDomain)row.getAttribute("Document");
                if (payloadCanonical != null) {

                    InputStream inStream = payloadCanonical.getBinaryStream();

                    int length = -1;
                    int size = payloadCanonical.getBufferSize();
                    byte[] buffer = new byte[size];

                    while ((length = inStream.read(buffer)) != -1) {
                        outputStream.write(buffer, 0, length);
                        outputStream.flush();
                    }

                    inStream.close();
                    outputStream.close();

                } else
                    w.write("null");
            } else {
                w.write("null");
            }

        } catch (Exception e) {
            e.printStackTrace();
        }  

    }

    public void setDocumentTable(RichTable documentTable) {
        this.documentTable = documentTable;
    }

    public RichTable getDocumentTable() {
        return documentTable;
    }
}

The MultipleDocumentUpload managed bean's scope is set as pageFlowScope.
That's all required to implement the multiple document upload functionality.

Tuesday, April 2, 2013

ADF RC Notes.

Using Secured Properties:

Here is an example of how to set secured property on the client side:



<?xml version='1.0' encoding='UTF-8'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1" xmlns:f="http://java.sun.com/jsf/core"
          xmlns:af="http://xmlns.oracle.com/adf/faces/rich">
    <jsp:directive.page contentType="text/html;charset=UTF-8"/>
    <f:view>
        <af:document title="Page1" id="d1">
            <af:form id="f1">
                <af:panelBox text="PanelBox1" id="pb1">
                    <f:facet name="toolbar"/>
                    <af:panelGroupLayout id="pgl1">
                        <af:commandButton text="Say Hello" id="cb1" unsecure="disabled">
                            <af:clientListener method="sayHello" type="action"/>
                        </af:commandButton>
                    </af:panelGroupLayout>
                </af:panelBox>
                <br/>
                <af:subform id="s1">
                    <af:panelGroupLayout id="pgl2">
                        <af:outputText id="greeting" clientComponent="true"/>
                        <af:commandLink text="Link" id="cl1"/>
                    </af:panelGroupLayout>
                </af:subform>
            </af:form>
            <af:resource type="javascript">
            function sayHello(actionEvent)
            {
                var component=actionEvent.getSource();
                //Find the client component for the "greeting" af:outputText
                var id = component.getId();
                var greetingComponent=component.findComponent("s1:greeting");
                //Set the value for the outputText component
                alert("Hello from " + id );
                greetingComponent.setValue("Hello World");
                component.setProperty("disabled", true);

            }
            </af:resource>
        </af:document>
    </f:view>
</jsp:root>

Note 1:

If we add an actionListener to the command button, then that action listener is not getting called in the above case. Without clientListener, its getting called. When I commented out everything in the java script function, then the actionListener is getting called:

<?xml version='1.0' encoding='UTF-8'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1" xmlns:f="http://java.sun.com/jsf/core"
          xmlns:af="http://xmlns.oracle.com/adf/faces/rich">
    <jsp:directive.page contentType="text/html;charset=UTF-8"/>
    <f:view>
        <af:document title="Page1" id="d1">
            <af:form id="f1">
                <af:panelBox text="PanelBox1" id="pb1">
                    <f:facet name="toolbar"/>
                    <af:panelGroupLayout id="pgl1">
                        <af:commandButton text="Say Hello" id="cb1" unsecure="disabled"
                                          actionListener="#{Page1Bean.handleSayHello}" partialSubmit="true">
                            <af:clientListener method="sayHello" type="action"/>
                        </af:commandButton>
                    </af:panelGroupLayout>
                </af:panelBox>
                <br/>
                <af:subform id="s1">
                    <af:panelGroupLayout id="pgl2">
                        <af:outputText id="greeting" clientComponent="true"/>
                        <af:commandLink text="Link" id="cl1"/>
                    </af:panelGroupLayout>
                </af:subform>
            </af:form>
            <af:resource type="javascript">
            function sayHello(actionEvent)
            {
                //var component=actionEvent.getSource();
                //Find the client component for the "greeting" af:outputText
                //var id = component.getId();
                //var greetingComponent=component.findComponent("s1:greeting");
                //Set the value for the outputText component
                //alert("Hello from " + id );
                //greetingComponent.setValue("Hello World");
                //component.setProperty("disabled", true);
         

            }
            </af:resource>
        </af:document>
    </f:view>
</jsp:root>

So, something in this java script is blocking the call to actionListener. My guess is that call to alert is causing this. The following java script does not stopped the call to actionListener:

            function sayHello(actionEvent)
            {
                var component=actionEvent.getSource();
                //Find the client component for the "greeting" af:outputText
                var id = component.getId();
                //var greetingComponent=component.findComponent("s1:greeting");
                //Set the value for the outputText component
                alert("Hello from " + id );
                //greetingComponent.setValue("Hello World");
                //component.setProperty("disabled", true);
               
            }
Next try would be to uncomment setProperty call:
            function sayHello(actionEvent)
            {
                var component=actionEvent.getSource();
                //Find the client component for the "greeting" af:outputText
                var id = component.getId();
                //var greetingComponent=component.findComponent("s1:greeting");
                //Set the value for the outputText component
                alert("Hello from " + id );
                //greetingComponent.setValue("Hello World");
                component.setProperty("disabled", true);
               
            }

As soon as I did it, the actionListener is not getting called. Seems like the event is getting cancelled.
So in this case, we need to manually raise the even at the server side.,

Working with Disconnected Properties

Consider the following page:

<?xml version='1.0' encoding='UTF-8'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1" xmlns:f="http://java.sun.com/jsf/core"
          xmlns:af="http://xmlns.oracle.com/adf/faces/rich">
    <jsp:directive.page contentType="text/html;charset=UTF-8"/>
    <f:view>
        <af:document title="Page1" id="d1">
            <af:form id="f1">
                <af:panelBox text="PanelBox1" id="pb1">
                    <f:facet name="toolbar"/>
                    <af:panelGroupLayout id="pgl1">
                        <af:commandButton text="Say Hello" id="cb1" unsecure="disabled"
                                          actionListener="#{Page1Bean.handleSayHello}" partialSubmit="true">
                            <af:clientListener method="sayHello" type="action"/>
                        </af:commandButton>
                    </af:panelGroupLayout>
                </af:panelBox>
                <br/>
                <af:subform id="s1">
                    <af:panelGroupLayout id="pgl2">
                        <af:outputText id="greeting" clientComponent="true"/>
                    </af:panelGroupLayout>
                </af:subform>
            </af:form>
            <af:resource type="javascript">
            function sayHello(actionEvent)
            {
                var component=actionEvent.getSource();
                var greetingComponent=component.findComponent("s1:greeting");
               
                greetingComponent.setValue("Hello World");
                greetingComponent.setProperty("submittedValue", "Hello One");
                alert("Hello from " + greetingComponent.getProperty("submittedValue") );
              
            }
            </af:resource>
        </af:document>
    </f:view>
</jsp:root>
Here, in the alert, the submitted value is visible, but outputtext is updated with the value "Hello World", so the disconnected property is available on client side (in javascript, say alert), but not moved to server side. However, actionListener is getting called in this case.

Working with Bonus Attributes

Consider the following page:

<?xml version='1.0' encoding='UTF-8'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1" xmlns:f="http://java.sun.com/jsf/core"
          xmlns:af="http://xmlns.oracle.com/adf/faces/rich">
    <jsp:directive.page contentType="text/html;charset=UTF-8"/>
    <f:view>
        <af:document title="Page1" id="d1">
            <af:form id="f1">
                <af:panelBox text="PanelBox1" id="pb1">
                    <f:facet name="toolbar"/>
                    <af:panelGroupLayout id="pgl1">
                        <af:commandButton text="Say Hello" id="cb1" unsecure="disabled"
                                          actionListener="#{Page1Bean.handleSayHello}" partialSubmit="true">
                            <af:clientListener method="sayHello" type="action"/>
                            <af:clientAttribute name="TestBonusAttr1" value="#{adfFacesContext.skinFamily}"/>
                        </af:commandButton>
                    </af:panelGroupLayout>
                </af:panelBox>
                <br/>
                <af:subform id="s1">
                    <af:panelGroupLayout id="pgl2">
                        <af:outputText id="greeting" clientComponent="true"/>
                    </af:panelGroupLayout>
                </af:subform>
            </af:form>
            <af:resource type="javascript">
            function sayHello(actionEvent)
            {
                var component=actionEvent.getSource();
                alert("Hello from " + component.getProperty("TestBonusAttr1") );
            
            }
            </af:resource>
        </af:document>
    </f:view>
</jsp:root>

Bonus attributes are used to return values from server to client. So in the above example, we are fetching the skin family name in javascript function using bonus attribute.
The above alert shows "Hello from fusionFx".

ADF Lifecycle Notes

If a command button's immediate property is set to true, then after Apply Request Values phase, all other phases are skipped for all other components on the page, and flow goes straight to Render Response phase.

If there are 2 input components, and 2 command buttons, and immediate property is set to true for 1 command button and 1 input component, and if the immediate command button is pressed, then conversion, validation will happen for only that immediate input text field. The conversion/ validation for other input field will be skipped. If any error happens during conversion/validation if the immediate input field, neither the valueChangeListener of the immediate input text field, nor the action listener of the immediate command button will execute. As soon as error happens, the control moves to Render Response phase. So the following statement in Web User Interface Guide, Page 4-8 is not correct:

If an immediate editableValueHolder component fails validation, any immediate actionSource component will still execute.

Optimized Lifecycle (PPR)

Consider the following form:

            <af:form id="f1">
                <af:inputText label="Required Field" required="true"/>
                <af:selectBooleanRadio id="show" autoSubmit="true" text="Show" value="#{Page1Bean.show}" group="a"/>
                <af:selectBooleanRadio id="hide" autoSubmit="true" text="Hide" value="#{Page1Bean.hide}" group="a"/>
                <af:panelGroupLayout partialTriggers="show hide" id="panel">
                    <af:outputText value="You can see me!" rendered="#{Page1Bean.show}"/>
                </af:panelGroupLayout>
            </af:form>

Here
  • inputTextField is set as required.
  • selectBooleanRadio's autosubmit is set to true.
  • Radion buttons are set as Partial Trigger on PGL.

Here inputTextField is set as required, but that field's validation is not fired when we just toggle show/hide radio button. Nice explaination is given in Web User Interface Guide, as shown below:

Because the autoSubmit attribute is set to true on the radio buttons, when they are selected, a SelectionEvent is fired, for which the radio button is considered the root. Because the panelGroupLayout component is set to be a target to both radio components, when that event is fired, only the selectOneRadio (the root), the panelGroupLayout component (the root’s target), and its child component (the outputText component) are processed through the lifecycle. Because the outputText component is configured to render only when the Show radio button is selected, the user is able to select that radio button and see the output text, without having to enter text into the required input field above the radio buttons.

So, in the above  case, the inputText field is isolated from the radio button changes. If we go for full page refresh, then the input text field will complain as usual.

Subform

Consider the following form:

            <af:form id="f1">
                <af:subform id="s1" default="true">
                    <af:panelGroupLayout id="pgl1">
                        <af:inputText label="Label 1" id="it1" required="true" />
                        <af:commandButton text="Subform 1" id="cb1"/>
                    </af:panelGroupLayout>
                </af:subform>
                <af:subform id="s2">
                    <af:panelGroupLayout id="pgl2">
                        <af:inputText label="Label 1" id="it2" required="true"/>
                        <af:commandButton text="Subform 2" id="cb2"/>
                    </af:panelGroupLayout>
                </af:subform>
                <af:commandButton text="DefaultForm" id="cb3"/>
            </af:form>

Here, for the first subform, the default attribute is set to true.
There is a button on each form(Subform 1, Subform 2, and default Form).
When the page renders, and DefaultForm button is clicked, the it1 complains for missing value.
Thats because of the subform's default attribute.
Subform1 and Subform 2 command button causes required validation failure in their respectative subforms only.

Event Handling Notes

In addition to server-side action and value change events, ADF Faces components also invoke client-side action and value change events, and other kinds of server and client events. Some events are generated by both server and client components (for example, selection events); some events are generated by server components only (for example, launch events); and some events are generated by client components only (for example, load events).
By default, most client events are propagated to the server. Changes to the component state are automatically synchronized back to the server to ensure consistency of state, and events are delivered, when necessary, to the server for further processing.
However, you can configure your event so that it does not propagate. In addition, any time you register a client-side event listener on the server-side Java component, the ADF Faces framework assumes that you require a JavaScript component, so a client-side component is created.

Here is an example of how an actionEvent is being cancelled from being propagated to the server:

<?xml version='1.0' encoding='UTF-8'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1" xmlns:f="http://java.sun.com/jsf/core"
          xmlns:af="http://xmlns.oracle.com/adf/faces/rich">
    <jsp:directive.page contentType="text/html;charset=UTF-8"/>
    <f:view>
        <af:document title="Page1" id="d1">
            <af:resource type="javascript">
            function showAlert(actionEvent) {
                  var component = actionEvent.getSource();
                  var id = component.getId();
                  alert("Hello from " + id );
                  actionEvent.cancel();
              }
            </af:resource>
            <af:form id="f1">
                        <af:commandButton text="Subform 1" id="cb1" actionListener="#{Page1Bean.handleSayHello}">
                    <af:clientListener type="action" method="showAlert"/>
                </af:commandButton>
            </af:form>
        </af:document>
    </f:view>
</jsp:root>

If we comment out actionEvent.cancel() line, then actionListener, ="#{Page1Bean.handleSayHello},  is called. But if we dont, then actionListener is not called.

Canceling an event may also block some default processing. For example, canceling an AdfUIInputEvent event for a context menu will block the browser from showing a context menu in response to that event.

Using Client and Server Listener

Consider the following page:

<?xml version='1.0' encoding='UTF-8'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1" xmlns:f="http://java.sun.com/jsf/core"
          xmlns:af="http://xmlns.oracle.com/adf/faces/rich">
    <jsp:directive.page contentType="text/html;charset=UTF-8"/>
    <f:view>
        <af:document title="Page1" id="d1">
            <af:resource type="javascript">
              function buttonPressed(event) {
                  var source = event.getSource();
                  var target = source.findComponent("eventTarget");
                  AdfCustomEvent.queue(target, "callFromClient", {},false);
              }
            </af:resource>
            <af:form id="f1">
                <af:inputText label="Event Target" id="eventTarget">
                    <af:serverListener type="callFromClient" method="#{Page1Bean.callFromClientToServer}"/>
                </af:inputText>
                <af:commandButton text="commandButton 1" id="cb1">
                    <af:clientListener method="buttonPressed" type="action"/>
                </af:commandButton>
            </af:form>
        </af:document>
    </f:view>
</jsp:root>
Here client listener is queueing an event on the target input text field. The server listener registered on the input component receives the custom event, and the associated bean method executes. The source can also work as target. So, instead of using a separate text field as target, we can add clientListener and serverListener both to command button and still the bean method will be called. The event is queued to the component passed as first parameter to AdfCustomEvent.queue() method.

Marshalling and Unmarshalling of data between JavaScript on the client(browser) and java code on server happens using JSON (Java Script Object Notation) and XML.

Note on Client Behavior Tags

Declarative client behavior tags like showPopupBehavior cancel server side event delivery automatically. Therefore, any actionListener or action attributes on the parent component will be ignored. This cannot be disabled. If you want to also trigger server-side functionality, you should use either a client-side event, or add an additional client listener that uses AdfCustomEvent and af:serverListener to deliver a server-side event.

The Poll Component

Consider the following example:

<?xml version='1.0' encoding='UTF-8'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1" xmlns:f="http://java.sun.com/jsf/core"
          xmlns:af="http://xmlns.oracle.com/adf/faces/rich">
    <jsp:directive.page contentType="text/html;charset=UTF-8"/>
    <f:view>
        <af:document title="Page1" id="d1">
            <af:form id="f1">
                <af:inputText label="Event Target" id="eventTarget" value="#{Page1Bean.inputValue}"
                              binding="#{Page1Bean.targetInput}"/>
                <af:poll id="p1" pollListener="#{Page1Bean.pollListener}"/>
            </af:form>
        </af:document>
    </f:view>
</jsp:root>

And here is the poll listener:

package view;
import javax.faces.event.ActionEvent;
import javax.faces.event.ValueChangeEvent;
import oracle.adf.view.rich.component.rich.input.RichInputText;
import oracle.adf.view.rich.context.AdfFacesContext;
import oracle.adf.view.rich.render.ClientEvent;
import org.apache.myfaces.trinidad.event.PollEvent;
public class Page1Bean {
    private String inputValue;
    public void setInputValue(String inputValue) {
        this.inputValue = inputValue;
    }
    public String getInputValue() {
        return inputValue;
    }
    public void pollListener(PollEvent pollEvent) {
        // Add event code here...
        this.setInputValue(new Long(System.currentTimeMillis()).toString());
        AdfFacesContext.getCurrentInstance().addPartialTarget(getTargetInput());
    }
    public void setTargetInput(RichInputText targetInput) {
        this.targetInput = targetInput;
    }
    public RichInputText getTargetInput() {
        return targetInput;
    }
}

The poll listener is just updating the inputText field value with current time in millisecond value. The poll component is used with default values. The input text field is updated after every 5 seconds. Do consider to configure the oracle.adf.view.rich.poll.TIMEOUT context-parameter when using the poll component.

Conversion and Validation Notes

We can add more than one validator tags to a component.
Only one converted can be added to a component.
ADF Faces Converters/Validators extends standard JSF Converters/Validators.
ADF Faces validators/converters can operate on both the client and the server side.
A backing bean method can not work as a converter.

Naming Containers

pageTemplate, subform, table, and tree are examples of naming containers.