JDeveloper 12.2.1.4: af:chooseDate

The new JDeveloper 12.2.1.4 has added an enhancement an a new feature to the af:chooseDate component. We are looking at both in this article.

Enhancement

The enhancement is that you can now set a default date which is shown as the selected date when the field you show the af:chooseDate for is empty. In the older JDeveloper versions, the selected date was always the current date. The image below shows the situation of how the af:chooseDate component looks like:

The af:inputText ‘Datum’ is not set and because of this, the af:chooseDate shows the current date (when I took the picture) as the selected date. Problem is, that there are use cases where you don’T want the current date to be the selected date, e.g. your use case might want to show the next Friday after the current date as the selected date. This was not easily possible.

Now, the enhancement to the af:hcooseDate component allows us to set a default date via the property ‘defaultValue’. When set to a literal value, this will be parsed as “yyyy-MM-dd hh:mm:ss” or “yyyy-MM-dd”.

I added an af:inputdate ‘Set DefaultDate’ to allow selection of a date which then will be used as the default date in the af:chooseDate for the ‘Datum’ field. The current date is marked with gray and the date set in the ‘Set Default Date’ is shown as the selected date.

As you see, the ‘Datum’ field is still empty. If you select a date like ‘12/20/2019’ you get

Sample Code

<af:inputDate label="Set Default Date:"
     Nhn ng  value="#{bindings.defaultDate1.inputValue}" id="id2"
     autoSubmit="true"/>
<af:spacer id="s1" height="10px"/>
<af:panelGroupLayout id="pgl2" layout="horizontal">
<af:inputDate label="Datum" id="id1" chooseId="cd1"/>
<af:chooseDate id="cd1"
    defaultValue="#{bindings.defaultDate1.inputValue}"
    partialTriggers="id2"/>

New Feature: “Multiple Date Selection”

The af:chooseDate component has another feature added. Now you can select multiple dates at once. This feature is very handy as you can now use one af:chooseDate to get a start date and an end date or date ranges. The image below shows the component with multiple dates selected.

The interesting thing is, that there is no implementation for this hardcoded in the component. Meaning is that we, the developers, need to implement this feature ourselves. For this ADF provides two new client events ‘load’ and ‘dateSelection’ that are triggered by the af:chooseDate component. We use these client events to implement the multi-select feature. The load event can be used to pass an array of dates to the component which are then shown as selected. This event is triggered when the component loads, as the name implies. The dateSelection event is triggered each time a user selects a date. It passes information about the keyboard state so that we can handle range selection.

To make it easier, the ADF Rich Client Demo has a sample implementation of the needed JavaScript code which I used as the base of my implementation:

/**
 * Shows a popup from an "action" type event.
 * @param {AdfActionEvent} actionEvent the event being handled
 */
var dates = [];
var minDate;
var maxDate;

function dateSelectionEventHandler(event) {
    var eventSource = event.getSource();
    var selectedDate = event.getSelectedDate();
    var modifier = event.getModifiers();

    if (modifier.indexOf(AdfRichChooseDate.MULTI_SELECTION) !=  - 1) {
        dates.push(selectedDate);
    }
    else if (modifier.indexOf(AdfRichChooseDate.RANGE_SELECTION) !=  - 1) {
        if (!minDate || (minDate.getTime() > selectedDate.getTime())) {
            minDate = selectedDate;
        }
        if (!maxDate || (maxDate.getTime() < selectedDate.getTime())) {
            maxDate = selectedDate;
        }
        var timeDiff = Math.abs(maxDate.getTime() - minDate.getTime());
        var diffDays = Math.ceil(timeDiff / (1000 * 3600 * 24)) + 1;
        dates = []
        for (var i = 0;i < diffDays;i++) {
            var selDate = new Date(minDate.getFullYear(), minDate.getMonth(), minDate.getDate() + i, 0, 0, 0, 0);
            if (!eventSource.isDisabled(selDate))
                dates.push(selDate)
        }
    }
    else if (modifier.indexOf(AdfRichChooseDate.SINGLE_SELECTION) !=  - 1) {
        minDate = null;
        maxDate = null;
        dates = []
        dates.push(selectedDate)
    }
    eventSource.setSelectedDates(dates);
}

function chooseDateLoadEventHandler(event) {
    var eventSource = event.getSource();
    eventSource.setSelectedDates(dates);
}

function processSelectedDates(event) {
    component = event.getSource();
    AdfCustomEvent.queue(component, "processSelectedDates", 
    {
        payload : dates
    },
    true);
    event.cancel();
}

There are three JavaScript funtions:

  1. dateSelectionEventHandler(event) handles the selection of one or more dates
  2. chooseDateLoadEventHandler(event) handles the inital load of hte component and allows us to set dates as selected
  3. processSelectedDates(event) is the funtion which passes the selected dates to a server event

The functions are added to the af:chooseDate compponent as client listerner

<af:chooseDate id="cd2" clientComponent="true">
    <af:clientListener type="dateSelection" 
        method="dateSelectionEventHandler"/>
    <af:clientListener type="load" method="chooseDateLoadEventHandler"/>
</af:chooseDate>

To pass the selected dates to a server event a button is used like

<af:button text="Selected Dates" id="b2">
    <af:clientListener method="processSelectedDates" type="action"/>
    <af:serverListener type="processSelectedDates"
        method="#{viewScope.ChooseDateBean.procressSelectedDates}"/>
 </af:button>

The image below shows the selected dates once the button ‘Selected Dates’ is clicked.

This JavaScript function simply passes the array of selected dates as payload from the client to the server. There the dates are just stored in a bean variable. This variable is used to show the dates in an af:outputText.

The same works for a range selection as shown below

And after the button ‘Selected Dates’ is clicked we get

The sample can be downloaded from GitHub BlogChooseDate or directly as a zip. The sample is written using JDeveloper 12.2.1.4 and doesn’t use any DB connection.

JDeveloper: Info about the clicked cell in an af:table

JDeveloper allows to easily create tables with the af:table component. The table allows easy access to the selected row or rows. However, if you are interested in which cell of a table has been clicked, ADF needs some tweaking. This blog is about how to tweak an af:table to get exactly this info.

Use Case

You like to know which cell in an af:table a user has clicked, e.g. to get some detailed information about the clicked item or cell in the selected row. The sample I show get the information about the current row, and column of the cell and the value of the cell clicked. The final sample will show the info like

How to do it?

The normal af:table component doesn’t give information about the cell a user has clicked on. The ADF pivot table offers this but is complex to use.

We use JavaScript in form of a clientListener to intercept the click on a cell and a serverListener to call a bean method to get more data on the cell. This article 011. ADF Faces RC – How-to use the Client and Server Listener Component shows how to use clientListener and serverListener in detail.

As we are interested in the selected cell, we add a clientListerer to each af:outputText which shows the column value in the af:table which fires on the click event. The clientListener calls a JavaScript method. In the JavaScript method, we build a payload of the UIComponent which is used to show the column value and the column name of the cell. To get this information we have several possible ways:

  1. We can use our knowledge of the DOM tree and get the column via the parent of the component which fired the event. The parent component should be the af:column.
  2. We add a client attribute to the component which shows the cell value adding the column name from the af:column as EL

In this sample, we choose the second solution. With this information, we call the serverListener from the JavaScript method. The serverListener method is implemented in a request scope bean and uses the information passed to get the details about the clicked cell we show in the UI.

Implementation

The sample uses the HR DB schema and only needs one table, Employees in this case. We create a simple page with the table in read-only mode, sortable and filterable. As you see in the image above the table is just build be dragging the Employees VO onto the page and drop it as a read-only table.

Now we add a clientListener and a serverListener to each outputText component which is used to show the cell value

In the image above we see the listener for two columns. In addition, we add an af:clientAttribute with the name ‘columnName’ which we pass the EL of the af:column headerText property.

Next, we add an af:resource component to the af:document where we specify the JavaScript for the clientListener method ‘clientCellSelectionCall’. We use a JavaScript file to code the method. We could have added the method to the page directly, but if we want to reuse the pattern, it’s better to use a JavaScript file

The file is located in the public_html folder (Web Content) in a subdirectory ‘javascript’

The method code is

The click event on the af:outputText component triggers a call to the javascript method ‘clientCellSelectionCall’ (via the clientListener) with the source of the event, the af:outputText component. The method reads the clientAttribute added (line 3) and calls the serverListener of type ‘cellSelection’. This event is defined by the af:serverListener on the af:outputText. The component which triggered the event and the column name added as client attribute are passed to the serverListener.

The serverListener is a bean method defined in a request scope bean on the af:outputText component as

method="#{TableCellSelectionBean.handleTableCellSelection}"

In the bean, the method looks like

public void handleTableCellSelection(ClientEvent event) {
  // get payload which is the ui component which fired the event
  UIComponent ui = (UIComponent)event.getParameters().get("payload");
  // get the column from the event which is sent too
  String column = (String)event.getParameters().get("column");
  RichOutputText rt = (RichOutputText)ui;
  // get current row key
  DCBindingContainer bindingContainer = (DCBindingContainer)BindingContext.getCurrent().getCurrentBindingsEntry();
  DCIteratorBinding binding = bindingContainer.findIteratorBinding("EmployeesViewIterator");
  Row currentRow = binding.getCurrentRow();
  Key key = currentRow.getKey();
  // compile info about clicked cell
  String out = "Payload:" + ui + "
  column: "+ column + "
  val: " + rt.getValue() + "
  key: "+key.toString();
  logger.info(out);
  setCellInfo(out);
}

Here we get the component which triggered the event (as payload) and the name of the column. Using this information we can get e.g. to the value of the column (via the UI component). The row of the cell we get via the current row of the iterator. With this information, we get the key of the row. We can get much more information here, like historical data about the current employee’s salary, if the salary cell was clicked.

We just create a string from the information which we show in the UI to the user

Here are some images of different cells clicked in the UI:

Download

You can download the sample, which was built using JDeveloper 11.1.1.9, from GitHub BlogTableCellSelection. The sample uses the HR DB schema.

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