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.

 

Advertisements

One thought on “JDeveloper: Prevent Navigation from Field on Failed Validation

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s