Monday, June 30, 2014

ADF: jQuery Auto Suggest Implementation


Oracle 11gIn this post we will see how to integrate the one of the jquery's famous plugins, the ui autosuggest component with an Oracle Webcenter application using ADF faces. This implementation assumes that the requirements around the autosuggest doesn't meet the requirements of the af:autoSuggestBehavior. So lets first discuss why is the out of box auto suggest behaviour in behavior in adf not that desirable.



Cases where af:autoSuggestBehavior is useful

  1. If you have a large complex dataset ( in tunes of +5000 objects)
  2. If the performance / experience of the autosuggest is not a concern

Cases where custom implementation is useful

  1. The autoSuggestBehavior is not a "client only" side implementation so every change in the value triggers an expensive backend call even if there is a static list of values.
  2. Not every time we require to fetch data from backing bean, there are times and I believe for most of the cases we already know the data for which we are applying autoSuggest.
  3. The lack of flexibility to add custom behavior to suggested output is difficult.


So lets build our awesome autosuggest. Assume we have a requirement of auto-suggesting the user from a list of countries. Here's the steps

Step 1 : Add required libraries to our template


Libraries required:
  1. Add jquery core library, if not present already
  2. Add jquery ui custom library ( including the autosuggest plugin )
  3. Also we need GSON jar in classpath ( optional ). Its useful to flush out JSON in proper format

1
2
<af:resource type="javascript" source="/js/jquery-1.11.0.min.js"/>
<af:resource type="javascript" source="/js/jquery-ui-1.10.1.custom.min.js"/>

Step 2: Add a clientListener

The clientListener will fire a client event on focus to a javascript method suggestContries

1
2
3
4
5
6
<af:inputText label="#{bundle.COUNTRIES}" id="it1"
  clientComponent="true" styleClass="countriesBox"
  value="#{myBean.country}"
  immediate="false">
 <af:clientListener method="suggestCountries" type="focus"/>
</af:inputText>


Step 3: Add a method in backing bean


Lets create a method in our backing bean to initialize the data for autosuggest. The method flushes out javascript variable data to the client.The flushed out data contains the list of countries in JSON format. On executing the method we get the JSON string in a JS variable called countriesJS


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public void getCountriesJSON() {
 
 StringBuilder sb1 = new StringBuilder();
 try {
  /*
  * Get all countries:
  */
  //TODO: replace retrieveCountries method to get the set of CountryData objects
  Set<CountryData> countries = retrieveCountries();
  // create an instance of JsonArray
  JsonArray countriesJsonArray = new JsonArray();           
  sb1.append(" countriesJS=");
  Iterator<CountryData> iter = countries.iterator();

  while (iter.hasNext()) {
   CountryData countryData = iter.next();
   JsonObject countryDataObjJson = new JsonObject();

   countryDataObjJson.addProperty("label", countryData.getCountryName());
   countryDataObjJson.addProperty("value", countryData.getCountryCode().trim());

   countriesJsonArray.add(countryDataObjJson);
   }


  sb1.append(countriesJsonArray.toString());

 } catch (Exception e) {
  e.printStackTrace();
 }
 /*
  * Utility method to add the product json object to client page
  */
 addScriptOnRequest(sb1.toString());
}


Utlity method to add script on request:


1
2
3
4
5
6
public static void addScriptOnRequest(String script) {
 FacesContext context = FacesContext.getCurrentInstance();
 ExtendedRenderKitService erks = 
 Service.getRenderKitService(context, ExtendedRenderKitService.class);
 erks.addScript(context, script);
}

Step 4: Get the data on page load


Now lets add the following code to the page where you have the autosuggest input box. This piece of code will call getCountriesJSON method in your backing bean and load the JSON data on the client.


1
2
3
4
<af:document id="d1" title="#{bundle.MY_COUNTRIES}">
 <af:clientListener type="load" method="loadCountriesOnLoad"/>
 <af:serverListener type="loadCountriesEvt" method="#{myBean.getCountriesJSON}"/>
</af:document>


Add loadCountriesOnLoad Javascript method


1
2
3
4
5
6
 function loadCountriesOnLoad(evt)
{
 var customEvent = new AdfCustomEvent(evt.getSource(),"loadCountriesEvt",{}, true); 
 customEvent.preventUserInput();
 customEvent.queue(true);
}


So when page loads we will have the countriesJS is assigned to the following JSON


1
2
3
4
[
    {"label":"Afghanistan", "value":"AFG"}, 
    {"label":"Albania", "value":"ALB"}
]


Step 5: Wiring everything up


We will write the final JavaScript method mentioned on the onFocus event mentioned in Step 1
 - suggestCountries


1
2
3
4
5
6
7
8
9
10
11
12
13
14
function suggestCountries(evt) {
    var src = document.getElementById(evt.getSource().getClientId() + '::content');
    countriesComp = $(src).autocomplete( {
        "source" : countriesJS, "minLength" : 2,autoFocus: false,
        select : function (event, ui) {
           
        }
    }).autocomplete( "instance" )._renderItem = function( ul, item ) {
      return $( "<li>" )
        .append( "<a>" + item.label + "<br>" + item.value + "</a>" )
        .appendTo( ul );
    };
    
}


so here you go, we have the all client side auto suggest implemented !!! Please let me know what you think about the approach.