ADF 12c – Allow user to personalize the form items at run time using MDS based Change Persistence

3

The requirement is easily written down: the web page contains a form with multiple form items. Each user should be allowed to personalize the form at run time. This personalization entails the ability to hide and show form items. When user has decided to hide and show specific items – this decision should be persisted across sessions so that when the user logins at a later date, the form items she has hidden are still invisible and the displayed ones displayed.

This article describes how to implement this particular requirement in an ADF 12c Fusion Web Application. Note: the same steps and code will do the job for ADF 11g.

 

ADF has built in support for customization – both at design time and run time. Customization can be defined at various levels (or layers as the terminology is). One of these layers – typically the most fine grained – is the user level. Customizations at user level are frequently called personalizations.

In order for an ADF web application to support customization and personalization, we have to configure a number of things:

  • a user-level customization class has to be configured in the adf-config.xml
  • ADF Security has to be configured – so users can be identified (so their personalizations can be stored and retrieved)
  • customizations has to be enabled in the properties of the View project
  • the specific customization we are interested in (personalizing the visible or rendered attribute on inputText components) has to be enabled in the adf-config.xml file
  • a managed bean is configured that receives the updated form item definitions and sends them to the ADF Change Manager that writes them to MDS
  • a simple JSF page is developed with a form and a shuttle component to indicate which form items are to be hidden or displayed

The page looks as follows in this example:

image

 
This video demonstrates the objective of this article and an overview of the implementation:

The implementation is described below:

1. User-level Customization Class has to be Configured in the adf-config.xml

image

2. ADF Security has to be configured – so users can be identified (so their personalizations can be stored and retrieved)

Default settings will do:

image

image

image

image

image

Next I have created a role and assigned to newly created users to this role:

image

3. User Customizations have to be enabled in the properties of the View project (across sessions)

image

4. the specific customization we are interested in (personalizing the visible or rendered attribute on inputText components) has to be enabled in the adf-config.xml file

Open the adf-config.xml file and open the source tab to add the following snippet:

  <adf-faces-config xmlns="http://xmlns.oracle.com/adf/faces/config">
        <persistent-change-manager>
      <persistent-change-manager-class>oracle.adf.view.rich.change.MDSDocumentChangeManager</persistent-change-manager-class>
    </persistent-change-manager>
    <taglib-config>
      <taglib uri="http://xmlns.oracle.com/adf/faces/rich">
        <tag name="panelFormLayout">
          <persist-operations>ALL</persist-operations>
        </tag>
        <tag name="inputText">
          <attribute name="visible">
            <persist-changes>
              true
            </persist-changes>
          </attribute>
          <attribute name="rendered">
            <persist-changes>
              true
            </persist-changes>
          </attribute>
        </tag>
      </taglib>
    </taglib-config>
</adf-faces-config>

The file now looks like this:

image

 

5. The FormManipulator managed bean

A managed bean is configured that receives the updated form item definitions and sends them to the ADF Change Manager that writes them to MDS.

The real magic of this article is probably in this bean. The definition is not spectacular:

package nl.amis.adfformmanipulation.view;


import java.util.ArrayList;
import java.util.List;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.model.SelectItem;

import oracle.adf.view.rich.component.rich.RichForm;
import oracle.adf.view.rich.component.rich.input.RichInputText;
import oracle.adf.view.rich.component.rich.layout.RichPanelFormLayout;
import oracle.adf.view.rich.context.AdfFacesContext;

import org.apache.myfaces.trinidad.change.AttributeComponentChange;
import org.apache.myfaces.trinidad.change.AttributeDocumentChange;
import org.apache.myfaces.trinidad.change.ChangeManager;
import org.apache.myfaces.trinidad.change.ComponentChange;
import org.apache.myfaces.trinidad.change.DocumentChange;
import org.apache.myfaces.trinidad.change.ReorderChildrenComponentChange;
import org.apache.myfaces.trinidad.context.RequestContext;
import org.apache.myfaces.trinidad.util.ComponentReference;

public class FormManipulator {
  
    private ComponentReference formRef;
     
    public RichPanelFormLayout getForm()
    {
      if (formRef!=null)
      {
        return (RichPanelFormLayout) formRef.getComponent();
      }
      return null; 
    }
     
    public void setForm(RichPanelFormLayout form)
    {
      this.formRef= ComponentReference.newUIComponentReference(form);
    }

    public List<SelectItem> getComponents() {
        List<SelectItem> fields = new ArrayList<SelectItem>();
        for (UIComponent field : getForm().getChildren()) {
            SelectItem si =
                new SelectItem(field, (String)field.getAttributes().get("label"));
            fields.add(si);
        }
        return fields;
    }

    public List<UIComponent> getDisplayed() {
        List o = new ArrayList();
        for (UIComponent field : getForm().getChildren()) {
            if ((Boolean)field.getAttributes().get("visible")) {
              o.add(field);
            }
        }
        return o;
    }
    
    public void setDisplayed(List<UIComponent> visible) {
        for (UIComponent c : visible) {
            _addAttributeChange(c, "visible",Boolean.TRUE);        
        }
        // for every element in getComponents that is not in visible we have to set visible to false (or rendered to false)
        List<UIComponent> out = new ArrayList<UIComponent>();
        out.addAll(getForm().getChildren());
        out.removeAll(visible);
        for (UIComponent c : out) {
            _addAttributeChange(c, "visible",Boolean.FALSE);
        }
        // refresh the form to have all show&hide take effect
        AdfFacesContext.getCurrentInstance().addPartialTarget(getForm());
    }

    private void _addAttributeChange(UIComponent uic, String attribName,
                                            Object attribValue) {
        FacesContext fc = FacesContext.getCurrentInstance();
        ChangeManager cm =
            RequestContext.getCurrentInstance().getChangeManager();
        ComponentChange cc =
            new AttributeComponentChange(attribName, attribValue);
        // add the change to the change manager; this persists the change to MDS  
        cm.addComponentChange(fc, uic, cc);
        // now also apply the change to the actual UI component itself
        cc.changeComponent(uic);
    }
}

image

The parent RichPanelFormLayout component that contains the items that are to be hidden or displayed is bound to the form property (note: the approach to binding the UI Component is based on http://www.ateam-oracle.com/rules-and-best-practices-for-jsf-component-binding-in-adf/).  The getComponents() method returns the entries for the shuttle component in the form of SelectItem elements. These are used to populate the shuttle. See how the select items are created from the UI Component children of the getForm().

The values of the shuttle (the already selected item displayed in the right side container) are derived from and pushed to the displayed property on the bean. The initial set of selected item is taken as all children under the form that have their visible attribute set to true.

image

When the outcome of the shuttle manipulation is pushed to method setDisplayed() – all items received by this method are made visible. All children under getForm() that are not in the visible list are made invisible. To make an item visible or invisible, we use the local method _addAttributeChange. This method retrieves the current change manager and registers the attribute change for the inputText item with it. This will make the change manager write this change to MDS. Subsequently, the change is affected immediately to the inputText component – in the last line of the method (cc.changeComponent()).

Finally, method setDisplayed() ensures that the rich panel form layout is registered as partial target to make sure that any changes to the items in the form are refreshed to the client.

This bean is configured in faces-config.xml:

image

 

6. Form page with shuttle for item selection

A simple JSF page is developed with a form and a shuttle component to indicate which form items are to be hidden or displayed

image

 

    The most interesting aspects have been marked. The selectManyShuttle that reads all its elements from the components property on the formManipulator bean and discusses the currently selected set of items with the value property on the same bean. The button does nothing in particular except make sure the current settings in the shuttle are posted to the server – and therefore to the formManipulator bean. Note that the Gender item is initially invisible. That can be changed at run time by users who like to add that item to their display.

    <jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1" xmlns:f="http://java.sun.com/jsf/core"
              xmlns:h="http://java.sun.com/jsf/html" xmlns:af="http://xmlns.oracle.com/adf/faces/rich">
        <jsp:directive.page contentType="text/html;charset=UTF-8"/>
        <f:view>
            <af:document id="d1">
                <af:form id="f1">
                    <af:panelStretchLayout topHeight="320px" id="psl1">
                        <f:facet name="top">
                            <af:panelHeader text="Simple Form Page with Persistent Form Field Manipulation capability"
                                            id="ph1">
                                <af:panelBox text="Set order of form fields" id="pb12" disclosed="true">
                                    <af:panelHeader text="manipulate form fields" id="ph2"></af:panelHeader>
                                    <af:selectManyShuttle label="Select Form Items to Display" id="sms1"
                                                          leadingHeader="Hidden Items" trailingHeader="Displayed Items"
                                                          value="#{formManipulator.displayed}">
                                        <f:selectItems value="#{formManipulator.components}" id="si3"/>
                                    </af:selectManyShuttle>
                                    <af:button text="Apply Changes" id="b1"/>
                                </af:panelBox>
                            </af:panelHeader>
                        </f:facet>
                        <f:facet name="center">
                            <af:panelFormLayout id="pfl1" binding="#{formManipulator.form}" partialTriggers="b1">
                                <af:inputText label="First Name" id="it1"/>
                                <af:inputText label="Last Name" id="it2"/>
                                <af:inputText label="Gender" id="it3" visible="false"/>
                                <af:inputText label="Home Town" id="it4"/>
                                <f:facet name="footer"/>
                            </af:panelFormLayout>
                        </f:facet>
                    </af:panelStretchLayout>
                </af:form>
            </af:document>
        </f:view>
    </jsp:root> 
    
    
    

    Note: at this point, we need to make sure that his web page is accessible to our users. The steps are:

    – create a page definition for the JSF page

    – configure the security settings for this web  page and grant access to role admin

    image

     

    Running the personalizable web application

    The application can be run on the integrated weblogic server to get a first taste.

    Because of the configured security, we need to enter credentials:

    image

    The initial display of the form:

    image

    Let’s hide the First Name element:

    image

    and press apply:

    image

    The change is now persisted to MDS as a persistent personalization for user joe. When the user logs out and returns at a later moment, the change will still be there. The MDS document that contains the change is a document specific to the current JSF page (theForm.jspx) and the current user (joe). On the file system in my development environment, the file can be found here:

    /.jdeveloper/system12.1.3.0.41.140521.1008/o.mds.ide.deploy.base/adrs/ADFFormManipulationDemo/AutoGeneratedMar/mds_adrs_writedir/mdssys/cust/user/joe

    It looks like this:

    image

    At this point, when I login as user jane, the change will not be applied. After all, it is joe’s customization. Jane can create her own customizations – that are stored in a similar document and directory as joe’s:

    /.jdeveloper/system12.1.3.0.41.140521.1008/o.mds.ide.deploy.base/adrs/ADFFormManipulationDemo/AutoGeneratedMar/mds_adrs_writedir/mdssys/cust/user/jane

    image

     

    Resources

    Download the JDeveloper project with the sources discussed in this article: ADF12cFormManipulationDemo.

    Articles with adjacent topics include:

    About Author

    Lucas Jellema, active in IT (and with Oracle) since 1994. Oracle ACE Director and Oracle Developer Champion. Solution architect and developer on diverse areas including SQL, JavaScript, Kubernetes & Docker, Machine Learning, Java, SOA and microservices, events in various shapes and forms and many other things. Author of the Oracle Press book Oracle SOA Suite 12c Handbook. Frequent presenter on user groups and community events and conferences such as JavaOne, Oracle Code, CodeOne, NLJUG JFall and Oracle OpenWorld.

    3 Comments

    1. Hi, It is very helpful. But I need to login with the data in my login table and I am forced to use adf security. Will you please reply me how to do the same.

    2. Andrej Flieger on

      Very helpful blog Entry!!
      But in our case we need to amend the EAR file prior to deployment with the information to what db-based MDS-Repository the application should connect. How could we acheive that using either the wls-maven-plugin or the weblogic-maven-plugin?
      I tried some configurations folloiwing oracle documentation at
      http://docs.oracle.com/middleware/1213/core/MAVEN/config_maven_repo.htm#MAVEN9010
      http://docs.oracle.com/middleware/1213/wls/WLPRG/maven.htm#WLPRG700

      But nothing works.
      I posted my issues under:
      https://community.oracle.com/people/1445814/blog/2015/05/27/prepare-deployment-for-mds-usage?sr=inbox&customTheme=otn
      Any Ideas?