2017-02-28

Delaying Attribute Validations in ADF for LOV-based fields until commit

I think many ADF developers have encountered the unlazy validation of JSF components during their UI implementations. Especially when creating a new record, the auto submit / partial trigger reactions are quite unpleasant for users. Take this for example:


In this, the user inserted the fields in tab-order. After leaving the field salary (doing a auto submit, triggering the job field), the job validator executes and marks a red border to inform the user of the issue. As the user has never inserted the field up to this point, this is not very nice. One valid change is to remove the mandatory flag from the attribute and delegate the validation towards entity level. But then, you will lose the "red border at attribute" response and get a FacesMessage leaving the user in question, which attribute to change to solve the invalid entity.

Andrejus Baranovskis has written a nice article, how to solve this issue when encountering attributes on the same entity, the validation is occuring on:

http://andrejusb.blogspot.de/2017/02/setting-invalid-fields-for-ui-in-adf-bc.html

So let us extend the usecase a bit.

Issue:

For many List of Value components, users do not want to see the key (for example JobId) in the input text, but the looked up value (JobTitle in this case).



To implement this, there are many options to take. To combine the lov requirement and the delayed attribute validation, let me show you one implementation, that works and is quite declarative.

Define the following use cases:

  • Users want to be able to change the Job of an employee using a list of values showing the title, not the id of the job
  • When changing the salary of an employee, the job will be nullified
  • An empty job leads to a validation, but this should only occur on save button or navigation

The second use case defines the usage of partial triggers / auto submit on JSF side later on.

Solution:

To start, we need the base Entity (Employees), a View Object on that entitity (EmployeesView) and a Lookup View Object (JobsLookupView).


Next create a Transient Attribute on Employees Entity, representing the JobName. This will be the target Attribute for the delayed validation and is the base attribute for the lov.

This Attribute uses an expression referring a ViewAccessor on JobsLookupView:




Expression:

JobId != null ? JobsLookupView.getAllRowsInRange().find{it['jobId']==JobId}['jobTitle'] : null

Next, we will add the entity validation (referring Andrejus' blog entry):


(opt. add failure message)

Add dependencies on Entity attributes:




Additionally remove the mandatory flag from the JobId Attribute (to allow entity validation to do the not null check)

Add LOV to EmployeesView JobName Attribute:


(rem: We can use the Employee-level view accessor in this case because we do not use dynamic query components in this example, so no additional view object instances needed).

Create UI

Finally just create a simple Form Layout on a page, removing the JobId Attribute and ensuring the InputListOfValues Component for JobName.




Then update the following attributes for Salary resp. JobName Components:



Additionally, drag the CreateInsert Operation onto the page, to create a new record.

When running the app, and creating a new employee again tabbing through the form, we will see, there is no validation when tabbing out of the salary field (because the showRequired=true instead of mandatory=true).



When commit or navigation is triggered, the entity validation will fire, but because of the special validation attribute, we will see this on the single attribute instead of a FacesMessage.


This is especially helpful, if you are editing data inside a popup.


TL;DR / Result:

Please feel free to checkout a demo application implementing the shown at the german ADFCommunity github repository

https://github.com/ADFCommunityDE/DelayedAttributeValidationOnLookup.git

Any questions regarding this topic or want to see a feature/idea implemented in ADF? Just send me a message ;)

mail:     mke@team-pb.de
twitter:  @MarkusKlenke

Cheers!

2017-02-13

Individual Frame Layout in ADF 12c via custom RichRenderer Class

In one of our Forms to ADF modernization projects, one of our customers wanted to keep the UI structure more or less the same. For many components, there are best practices to map one Oracle Forms UI Component to ADF Structures, but in our case, we had to provide a solution to the following UI-Container Pattern:

<<NamedFramePattern>>
The idea of this pattern is to provide help for the user to see a categorization of UI Items. In general ADF, this might be done via UI Categorization via UI Hints at View Object Level. Unfortunately, this resolves into af:group containers on UI, which are interpreted by the parent container to make sense out of the items. So all in all, we are limited by the frameworks default implementations.

To solve the problem, we thought about the general solution in free html/css context. Here the problem is easy to solve. See

http://jsfiddle.net/ZgEMM/685/

for a sample solution. To reach the explicit HTML code you need out of JSF components is (in most cases) not possible.

Again, ADF supports the creation of own declarative components, but typically we just join other JSF compontents to our liking.

So we have to go one step further. The next steps show you how to create a "component/renderer" mashup to achieve the NamedFramePattern in ADF.

1. Create a declarative container component that has only a panel group layout and a content facetRef. Be sure to add a ComponentClass to your Declarative component for easier access and uniqueness afterwards.







(The Group layout inside is necessary, since all declarative components are not rendered per definition of the UIXBaseComponent-Renderer because it is only a reference for a JSF include).

2. Create a RichRenderer that is based on the renderer for ADF Group Layouts and add additional HTML code to the rendered feature. This can be done by overriding the encodeAll(...) method of the parent class.


package de.teampb.ir.templates.nf;

import java.io.IOException;

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

import javax.faces.context.ResponseWriter;

import oracle.adf.view.rich.component.rich.fragment.RichDeclarativeComponent;
import oracle.adf.view.rich.component.rich.layout.RichPanelFormLayout;
import oracle.adf.view.rich.render.ClientComponent;
import oracle.adf.view.rich.render.RichRenderer;

import oracle.adfinternal.view.faces.renderkit.rich.DeclarativeComponentRenderer;

import org.apache.myfaces.trinidad.bean.FacesBean;
import org.apache.myfaces.trinidad.context.RenderingContext;
import org.apache.myfaces.trinidad.render.CoreRenderer;

import oracle.adfinternal.view.faces.renderkit.rich.PanelGroupLayoutRenderer;

public class NamedFrameRenderer extends PanelGroupLayoutRenderer {
    public NamedFrameRenderer() {
        super();
    }

    private static final String DEFAULT_COLOR = "#AAAAAA";

    private static final String IMPORTANT_COLOR = "#FFAAAA";

    @Override
    protected void encodeAll(FacesContext facesContext, RenderingContext renderingContext, UIComponent uIComponent,
                             ClientComponent clientComponent, FacesBean facesBean) throws IOException {
        ResponseWriter rw = facesContext.getResponseWriter();
        if (uIComponent.getParent() instanceof NamedFrame) {
            NamedFrame nf = (NamedFrame) uIComponent.getParent();
            rw.startElement("div", null);
            rw.writeAttribute("style", "border: 2px solid " + getColor(nf) + "; margin: 5px; padding: 5px", null);
            rw.startElement("h2", null);
            rw.writeAttribute("style",
                              "text-indent: 30px;\n" + "    margin-top: -10px;\n" + "    height: 10px;\n" +
                              "    line-height: 20px;\n" + "    font-size: 15px;", null);
            rw.startElement("span", null);
            rw.writeAttribute("style", "background-color:#FFFFFF; color:#000000;", null);
            rw.writeText(nf.getTitle(), null);
            rw.endElement("span");
            rw.endElement("h2");
            super.encodeAll(facesContext, renderingContext, uIComponent, clientComponent, facesBean);
            rw.endElement("div");
        } else
            super.encodeAll(facesContext, renderingContext, uIComponent, clientComponent, facesBean);
    }

    private String getColor(NamedFrame nf) {
        if ("important".equals(nf.getType())) {
            return IMPORTANT_COLOR;
        } else
            return DEFAULT_COLOR;
    }

}

The idea is that the renderer overrides the default Renderer for Panel Group layouts. So we have to keep the default implementation (the inside super.encodeAll line).

(The HTML code could be beautified by using classes instead of inline style, but I think you get the idea)

3. Add renderer to faces-Config.xml in the consuming ViewController Project.


4. Use declarative component as usual to achieve the layout addition by the renderer.






As you can see from the last image, since the JDeveloper design view uses the internal renderer classes we also get the advantage of what-you-see-is-what-you-get development. The resulting ADF application then looks as follows:




Please feel free to download the sources at

https://github.com/TEAMPB/ADF_POC.git

and pull the sources. To run the application, you need access to an HR schema. Then Run the hr-main.xml Task-Flow.

Cheers!