JDeveloper: Prevent Navigation from Field on Failed Validation

This post shows how to implement a use case where you don’t want the user to leave a field which has a validation error. ADF does a good job enforcing that all validations are checked at the time you submit the data. However, if you want the user to correct an error in a field directly, you have to implement it yourself.

Use Case

A validation error can be a mandatory field which the user has not filled in or a where the user has filled in wrong data. In such a case you want that the user must correct the error before she/he is allowed to move to the next field.

Implementation

We start with a fragment whihc holds two af:inputText components. One we use to simulate a mandatory input text and the other for a normal input text field.

selection_973

The ‘Test’ button is used to submit the form. If we hit the Test button without any data in the fields we get

selection_974

as hte ID field is marks required. One you enter something into this field you can successfully submit the form

selection_975

That is the normal behavior ADF gives you out of the box. The use case  we implement here requires, that the user can’t leave the field once an error is shown for the field. So if the user sets the cursor to the ID field he can’t navigate away from it until he fixes the error. He can’t even click hte button. ADF would allow the user to leave the ID field to enter some value into the Name field.

So how do we prevent the user leaving the field by clicking in another field or clicking on a button?

We use an af:clientListener which listens for the ‘blur’ event checking if the field contains a value. If not, we set the focus back to the component and cancel the ongoing even. Setting the focus back to the component is essential as the blur event tell us that we loose the focus. This only happens if the user navigates away from the field.

 function setFocusWhenValidateFail(event) {
   var element = event.getSource();
   var val = element.getValue();
   var cid = element.getClientId();
   console.log("Value=" + val);
   if (val == null) {
     element.focus();
     event.cancel();
   }
}

The JavaScript function above shows this. This function is called via an af:clientListener we added to the af:inputText component

 <af:inputText label="ID (mandatory)" id="it1" required="true" 
     value="#{bindings.myId1.inputValue}" autoSubmit="true">
   <af:clientListener method="setFocusWhenValidateFail" type="blur"/>
 </af:inputText>

This is it. But wait, what if we like to check if the value entered by the user is valid?

Now, this add another complexity. For this we need to either code the validation in JavaScript or we have to call a bean method which can do the complex check on hte server side model. For easy checks it’s no problem to implement them in JavaScript in the ‘else’ part of the function above.

To call a server side method we need to add an af:serverListener which calls a bean method

 <af:inputText label="ID (mandatory)" id="it1" required="true" 
     value="#{bindings.myId1.inputValue}" autoSubmit="true">
   <af:clientListener method="setFocusWhenValidateFail" type="blur"/>
   <af:serverListener type="validateServerListener" 
       method="#{viewScope.InputValidationBean.handleValidationEvent}"/>
 </af:inputText>

and we change the JavaScript function

 function setFocusWhenValidateFail(event) {
   var element = event.getSource();
   var val = element.getValue();
   var cid = element.getClientId();
   console.log("Value=" + val);
   if (val == null) {
     element.focus();
     event.cancel();
   }
   else {
     console.log("call server with " + cid + " and " + val)
     //call bean method validateServerListener
     AdfCustomEvent.queue(element, "validateServerListener", 
       {
         fcid : cid, fvalue : val
       }, false);
     event.cancel();
   }
 }

Note that we add two parameters to the call to the bean method. The first parameter fcid is the client id of the component which calls the bean method. The second parameter fvalue is the value the user has entered into the field. We see why we need the parameters when we talk about the bean method.

In the bean we implement the custom validateServerListener method. First we get the two parameters from the ClientEvent and log them to the console. At this point we can e.g. call an application module method with the parameter  we got. In this simple same we just check the value for a specific value, in this case ’88’. When the value is ’88’ we add a  FacesMessage to the component with the clientId we got as second parameter.

However, just adding the FacesMessage wont be enough. At this point he focus has already shifted out of the component and the message would not displayed until the component is selected again. The event.cancel() in the JavaScript function does not prevent that the focus is shifted out of the component. The solution is to reset the focus to the component from the bean again. For this we add JavaScript code which will be executed after the event has finished. The JavaScript searches for the component by it’s Id and then calls component.focus()  to set the focus back to this component.

   public void handleValidationEvent(ClientEvent ce) {
        // get client id from event
        Object obj = ce.getParameters().get("fcid");
        String clientId = "";
        if (obj != null) {
            clientId = obj.toString();
        }

        // get field value from event     
        String val = "";
        Object obj2 = ce.getParameters().get("fvalue");
        if (obj2 != null) {
            val = obj2.toString();
        }
        
        logger.info("client id =" + clientId + " value=" + val);
        // do a check if hte value if OK
        // here we check against 88 to keep it simple. You can check against other model values here too!
        if ("88".equals(val)) {
            // check failed -> add message to the component and set the focus back to the component
            FacesContext facesContext = FacesContext.getCurrentInstance();
            FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Error", "Wrong vlaue");
            facesContext.addMessage(clientId, msg);
            // javascript to set focus to component identified by it's clientId
            String script = "var t=document.getElementById('" + clientId + "::content');t.focus();";
            writeJavaScriptToClient(script);
        }
    }

    //generic, reusable helper method to call JavaScript on a client
    private void writeJavaScriptToClient(String script) {
        FacesContext fctx = FacesContext.getCurrentInstance();
        ExtendedRenderKitService erks = null;
        erks = Service.getRenderKitService(fctx, ExtendedRenderKitService.class);
        erks.addScript(fctx, script);
    }

Sample Application

You can download the sample application from GitHub BlogNoNavigationValidation. The sample doesn’t use a model layer, so no DB connection is needed.

 

JDeveloper: Execute Bean Method on Hitting Enter in af:inputText Component

This blog article describes a common use case. A user enters some value into an field on the page and his enter. This should trigger a method on the server (e.g. a bean method).

Use case
Hitting ENTER on a af:intputText component should trigger an action (e.g. executing a bean method).

Implementation
The sample we implement in this blog shows a page with a splitter component. On the left side we see an input text component which is used to enter a value. On the right side we see the result of the search as a table of countries from the HR schema. The search condition used is to show all countries which names starting with the value entered in the input text component.

Sample Application

Sample Application

As you see there is no button ot other command component to execute the search. The search is triggered by hitting enter in the input text field on the left side of the splitter.

Filtered Output after hitting Enter

Filtered Output after hitting Enter

To implement this we need to use JavaScript af:clientListener to handle the keyboard input. The javascript method then queues a server action using a af:serverlistener.

<af:inputText label="Country" id="it1" value="#{bindings.SelCountryName1.inputValue}" autoSubmit="true">
    <af:clientListener method="handleEnterEvent" type="keyPress"/>
    <af:serverListener type="EnterEvent" method="#{SendEnterBean.handleEnterEvent}"/>
</af:inputText>

The af:clientListener listens for keyPress event and in the JavaScript method checks weather the key pressed was the enter key or not. If it was the enter key it queues an event for a server side method in a bean. The method name is given in the af:serverListener type=”EnterEvent” method=”#{SendEnterBean.handleEnterEvent}”. The method listen for the event type send from the client AdfCustomEvent.queue(comp, “EnterEvent”, {fvalue:comp.getSubmittedValue()}, false);. This type is defined in the serverListener as type property.

<af:resource type="javascript">
    function handleEnterEvent(evt) {
      var _keyCode = evt.getKeyCode();
      //check for Enter Key
      if (_keyCode == AdfKeyStroke.ENTER_KEY ){    
          var comp = evt.getSource();
          AdfCustomEvent.queue(comp, "EnterEvent", {fvalue:comp.getSubmittedValue()}, false);
          evt.cancel();
      }
   }
</af:resource>

The server side method now calles the “executeWithParams” method on the courntries iterator which filters the countries table for countries starting with the value given by the input field.

    public void handleEnterEvent(ClientEvent ce) {
        _logger.info("Got event " + ce.getType());
        // get the binding container
        BindingContainer bindings = BindingContext.getCurrent().getCurrentBindingsEntry();
        // get an Action or MethodAction
        OperationBinding method = bindings.getOperationBinding("ExecuteWithParams");
        if (method == null) {
        _logger.info("Method ExecuteWithParams not found in current bindings");
        return;
        }

        // get the parameter from the event
        String message = (String) ce.getParameters().get("fvalue");
        //This can be used too if one doesn't like to send the parameter to the method
        // get an ADF attributevalue from the ADF page definitions
        //AttributeBinding attr = (AttributeBinding)bindings.getControlBinding("SelCountryName1");
        //String v = (String)attr.getInputValue();

        //Set the parameter
        Map params;
        Map map = method.getParamsMap();
        map.put("bindName", message);

        method.execute();
        // check for errors
        if (!method.getErrors().isEmpty()){
            Exception ex =(Exception) method.getErrors().get(0);
            _logger.warning("Error: " + ex.getLocalizedMessage());
        }

        // PPR refresh a jsf component
        AdfFacesContext.getCurrentInstance().addPartialTarget(countriesTable);
    }

The missing part is how the countries table get filtered. For this in the model layer we created a ViewObject “CountriesView” for the COUNTRIES table of the HR schema. Then we define a ViewCriteria “CountriesByNameVC”:

ViewCriteria CountriesByNameVC

ViewCriteria CountriesByNameVC

In the data model of the application module we use the view object instance CountriesView1 and select the ViewCriteria as query.

Edit View Instance CountriesView1 to use ViewCriretia CountriesByNameVC

Edit View Instance CountriesView1 to use ViewCriretia CountriesByNameVC

Now in the ViewController project we only need to store the value entered by the user in the af:inputText field and add add a method binding for the “executeWithParams” method which we use in the bean method to filter the table.

Final pageDef

Final pageDef

and the source for the pageDef file:

<pageDefinition xmlns="http://xmlns.oracle.com/adfm/uimodel" version="11.1.1.60.13" id="BSE2BPageDef" Package="de.hahn.blog.sendenter2bean.view.pageDefs">
  <parameters/>
  <executables>
    <variableIterator id="variables">
      <variable Name="SelCountryName" Type="java.lang.String"/>
    </variableIterator>
    <iterator Binds="CountriesView1" RangeSize="25" DataControl="BSE2BAppModuleDataControl" id="CountriesView1Iterator"/>
  </executables>
  <bindings>
    <tree IterBinding="CountriesView1Iterator" id="CountriesView1">
      <nodeDefinition DefName="de.hahn.blog.sendenter2bean.model.dataaccess.CountriesView" Name="CountriesView10">
        <AttrNames>
          <Item Value="CountryId"/>
          <Item Value="CountryName"/>
          <Item Value="RegionId"/>
        </AttrNames>
      </nodeDefinition>
    </tree>
    <attributeValues IterBinding="variables" id="SelCountryName1">
      <AttrNames>
        <Item Value="SelCountryName"/>
      </AttrNames>
    </attributeValues>
    <action IterBinding="CountriesView1Iterator" id="ExecuteWithParams" RequiresUpdateModel="true" Action="executeWithParams">
      <NamedData NDName="bindName" NDValue="#{bindings.SelCountryName1.inputValue}" NDType="java.lang.String"/>
    </action>
  </bindings>
</pageDefinition>

You can download the sample, which is build using JDeveloper 11.1.1.5.0 and uses the HR schema, from ADF Sample Source Code Repository