Skip to main content

Custom Typeahead / autosuggest Search in ADF

For custom type ahead search there are many different solutions available out there. We can choose depending on what we are looking for. Let me first describe my use case.

My autosuggest/typeahead search should show the filtered list of items once I entered the search text(This is the basic requirement, that can be achieved easily with the readily available autosuggest in ADF). When I select one item from the list, I should be able to get the selected item along with all additional information like id,name,date etc, instead of getting only id or name.

The normal autosuggest solution given by Frank Nimphius available here
http://www.oracle.com/technetwork/developer-tools/jdev/autosuggest-090094.html
This one can do the basic autosuggest which can give the selected item's id and label. But what If you need to have access to addition information, while selecting one of the item from the list, like city ,country,department..etc. So I have customized the solution provided by Frank a bit.

Solution:
I recommend reading Frank's solution before reading further, as that will give a basic idea of it.
We are using af:inputText as the input component. af:popup to show the list items in a popup aligned to make it look like a normal drop down. af:clientListener to capture a javascript event whenever user types in the words. af:serverListener to call a backing bean method to do the filtering of the list once the user finished typing in.
       

TypeAheadSearch.js
The same js provided by Frank, we are going to use , but we are going to chage few parts of it.

                var typingTimer;
                var doneTypingInterval = 2000;// 2 seconds
                function handleKeyUpOnSuggestField(evt){
                    // start the popup aligned to the component that launched it
                    suggestPopup = evt.getSource().findComponent("suggestPopup"); 
                    inputField = evt.getSource();                 
                    //don't open when user "tabs" into field
                    if (suggestPopup.isShowing() == false && evt.getKeyCode()!= AdfKeyStroke.TAB_KEY){
                      hints = {align:AdfRichPopup.ALIGN_AFTER_START, alignId:evt.getSource().getClientId()+"::content"};
                      suggestPopup.show(hints);
                      //disable popup hide to avoid popup to flicker on
                      //key press                 
                      suggestPopup.hide = function(){}
                    }
                    //suppress server access for the following keys
                    //for better performance
                    if (evt.getKeyCode() == AdfKeyStroke.ARROWLEFT_KEY ||
                        evt.getKeyCode() == AdfKeyStroke.ARROWRIGHT_KEY ||
                        evt.getKeyCode() == AdfKeyStroke.ARROWDOWN_KEY ||
                        evt.getKeyCode() == AdfKeyStroke.SHIFT_MASK ||
                        evt.getKeyCode() == AdfKeyStroke.END_KEY ||
                        evt.getKeyCode() == AdfKeyStroke.ALT_KEY ||
                        evt.getKeyCode() == AdfKeyStroke.HOME_KEY) {
                        return false;
                    }
                    if (evt.getKeyCode() == AdfKeyStroke.ESC_KEY){
                        hidePopup(evt);
                        return false;
                    }                                     
                    // get the user typed values
                    valueStr = inputField.getSubmittedValue();
                    //only if the entered text length is >=3 
                    if(valueStr.length >= 3){                   
                    if(typingTimer){                       
                        clearTimeout(typingTimer);
                        typingTimer = null;
                    }
                    typingTimer = setTimeout(function(){  
                     // query suggest list on the server                    
           AdfCustomEvent.queue(inputField,"suggestServerListener",{filterString:valueStr},true); 
                    },doneTypingInterval);                                                                                         
                    // put focus back to the input text field
                    setTimeout("inputField.focus();",400);
                    }
                    else if(valueStr.length < 3){
                      hidePopup(evt);
                    }
                }
                //TAB and ARROW DOWN keys navigate to the suggest popup we need to handle this in                     //the key down event as otherwise the TAB doesn't work
                function handleKeyDownOnSuggestField(evt){             
                if (evt.getKeyCode() == AdfKeyStroke.ARROWDOWN_KEY) {                 
                          selectList   = evt.getSource().findComponent("joblist");                     
                          selectList.focus();
                          return false;
                    }
                    else{
                        return false;
                    }                 
                }
                //method called when pressing a key or a mouse button on the list
                function handleListSelection(evt){
                 if(evt.getKeyCode() == AdfKeyStroke.ENTER_KEY ||
                    evt.getType() == AdfUIInputEvent.CLICK_EVENT_TYPE){                                           
                       var list = evt.getSource();
                       evt.cancel(); 
                       //instead of single value, a json string object will be set by the server
                       var listValue = list.getProperty("value");                    
                       var employeeObject = JSON.parse(listValue);                       
                        // call a server listener once the selection happens
                         AdfCustomEvent.queue(evt.getSource().findComponent("suggestPopup"),"afterSelectOnList",
                                          // Send single parameter
                                         {rowId:employeeObject.rowId},true); 
                       
                       hidePopup(evt);
                       inputField = evt.getSource().findComponent("suggestField");
                       inputField.setValue(employeeObject.displayValue);          
                       inputIdField = evt.getSource().findComponent("idOfsuggestField");
                       inputIdField.setValue(employeeObject.rowId); 
                  }
                  //cancel dialog
                  else if (evt.getKeyCode() == AdfKeyStroke.ESC_KEY){
                    hidePopup(evt);
                 }
                }
                //function that re-implements the node functionality for the
                //popup to then call it
                function hidePopup(evt){
                    var suggestPopup = evt.getSource().findComponent("suggestPopup");
                    //re-implement close functionality
                    suggestPopup.hide = AdfRichPopup.prototype.hide;
                    suggestPopup.hide();
                }             

Only the highlighted parts are updated. I have used setTimeout available in javascript to trigger the server event only after the user is done typing. For example: If the user gives a pause of 2 seconds, it will trigger the server listener to filter the list. This part will be missing in Frank's solution. Server event is triggered every time a key is pressed.

In the server listener once we have the list of filtered items ready, we will populate the List of SelectItems
  //filteredList - this is having the list of filtered employees
    private List populateList(List filteredList) {
        ArrayList _list = new ArrayList();
        for (Employee employee: filteredList) {
            CustomSelectItem si =
                new CustomSelectItem(employee.getPersonNumber(),
                                     employee.getDisplayName());
            si.setValue(si.getJsonString());
            si.setLabel(employee.getPersonNumber() + " " +
                        employee.getDisplayName() + " " +
                        (employee.getCity() != null ?
                         employee.getCity() : "") + " " +
                        (employee.getCountry() != null ?
                         employee.getCountry() : ""));
            _list.add(si);
        }
        return _list;

    }

My use case is Employees list. Employee will be having many details (Id,Name, City,Country) . When any one of the item from the list is selected, I want the Name to be displayed in the input box, not the id. But I want the id and some more additional attribute value of the selected item also at the background. And I want to show lot more additional information in the list like City Country..etc
For this instead of setting a single value on the SelectItem, I am setting a Json string of an object. How do i construct the json string? 
I have used a custom class which extends javax.faces.model.SelectItem to have additional attributes to the value part. But you can use any other way to construct the jsonString like you can use Jackson library to convert java object to json string and you can use SelectItem itself instead of a custom class. The objective is to pass json string of a java object with values that we want each item to have.

Now in javascript, in the second highlighted part "handleListSelection" - we are going to parse this json, and use the values from the object according to our need. JSON.parse(listValue);  is to parse the json string to a javascript object. Further to that, we have queued a server listener to call a bean method. this is to call a bean method once an item is selected from the list. This can be used to access the additional attribute values that we pass as part of json.

So this json part that we have done above gives full control on displaying what we need and passing all the values that we need to the server side bean method. It is like a selection listener able to have access to all the values of the selected row in the list.

Json string sample:
"{ "id" : "1",
     "Name" : "2",
     "City" : "Los Angeles"
}"

So, obviously we can achieve anything with ADF :)

Next: In the next blog we will see how to add a loading gif to indicate the search is progressing.

Comments

Popular posts from this blog

Oracle ADF - show or hide popup programmatically

Objective here is to open popup from backing bean . You can bind the af:popup like below     <af:popup id="p1" contentDelivery="immediate"               clientComponent="true"               binding="#{backingBeanScope.employee.myPopup}" > Then in java you can use the myPopup to hide and show the popup  private RichPopup myPopup ;//bound to the UI component  public void showOrHidePopup(RichPopup popup,Boolean visible){  if(visible){  RichPopup.PopupHints hints = new RichPopup.PopupHints();    myPopup.show(hints);  }  else{   myPopup.hide();  } That's all, Now you can call this method from any events like actionListener of a commandLink

Oracle ADF - Menu Skinning

Let's see how to style menu in oracle adf I assume that you have a page with menus like below The default style for the menu with alta ui theme looks like below If you want to change the look and feel of the menu, then we need to add few component level style selectors. I assume that you have a css file ready to put the custom styles that we are going to use.With the below seectors you can play around to change the look and feel /*menu hover*/ af|menu:hover{ border : 1px solid rgb(0,198,198) ; background-color: white; } /*menu hover --> menu text hover*/ af|menu:hover af|menu::bar-item-text:hover{     color: rgb(0,205,205);   } /*menu pressed*/ af|menu:depressed{ border : 1px solid rgb(0,198,198) ; background-color: rgb(0,205,205); color: White; } /*menu pressed and hover --> menu text hover, menu pressed --> menu text*/ af|menu:depressed:hover-target af|menu::bar-item-text, af|menu:depressed af|menu::bar...

Oracle Visual Builder Cloud Service- How to use switch case in action chain

Lets quickly see, how to use a switch case in action chain Consider that we are going to have a menu with items saying "Create Note", "Export Notes","More Settings". When each menu item is clicked or chosen, we need to do certain action / action chain. For this, first let's create a page and drag and drop the "Menu Button". When you drop it in the title secion you will get options to choose where you want to place the menu "startControl","endControl","Default". Let's choose it as end control to place it at the end of the title bar. In the page structure choose the "Menu" and go the "Data" tab. Add and Edit the menu items (oj-option). here we have the menu item id's ad "newNote","export","settings" When the menu is selected we need to do some action. For that we need to map an action to the menu. In the page structure choose the "Menu...

Oracle ADF - Table pagination problem from JDeveloper 11.1.1.7

Most of us would have tried to implement pagination on af:table From Jdeveloper 11.1.1.7, pagination feature is directly available for af:table with a property called "scrollPolicy". We need to have two properties in af:table scrollPolicy="page" autoHeightRows="0" Once this is set the pagination feature should be shown.But table pagination does not show up, until unless we have a floating layout around the table. When we try this, we will get a message in the log saying below falling back to scrolling mode since we need parent component to flow and authHeightRows=0 Logs looks as below This is because of the layout around the table. The table should be in a container in flow mode. For example panelGroupLayout or showDetailHeader. So the af:table should be surrounded with a panelGroupLayout When you surround table with panelGroupLayout, set the layout to "horizontal",or "vertical",or "scroll" . Because w...

Oracle ADF - button skinning / button style

How to style a commandButton in Oracle ADF Normally buttons looks like below in the alta-ui skin If you want to style your button to look like bootstap buttons (most of the morder sites use bootstrap like buttons nowadays), you can use component selectors to skin af:commandButton component. Example style classes are below /***************** Button style start **********/ af|commandButton.success{     background-color: #4dbd74;     background-image: none;     border-radius : 5px;     border : 1px solid #3ea863;     color: white;     text-shadow : none; } af|commandButton.success:hover{     background-color: #3ea863;     background-image: none;     border-radius : 5px;     border : 1px solid #3ea863;     color: white;      text-shadow : none; } af|commandButton.warning{     background-color: #ffc107;     background-im...

Oracle ADF - Simple Progress indicator or show loading image in custom typeahead / autosuggest

In the last blog Custom Autosuggest / typeahead we saw how to construct a custom typeahead / autosuggest using an input text box and a popup with select items. If the list to be filtered is smaller we wouldn't need to show a loading symbol to the user, while doing the filter process in the backing bean. But what if the the list too big to filter and the filtering process would take some time like 3 to 4 seconds. Or what if the result of search need to be fetched from an external rest end point or from an external soap service ? It would take some considerable amount of time to fetch the data. User's expect some quick response from the page, when they try to do something. So it's better to show them that the process is happening at the back end. A simple way to do that is to have loading gif image ready, and show it when the popup opens and before triggering the server event to filter the list. Once the filtered items are ready and about to be returned to the client then ...

Oracle ADF - Dialog skinning / Modern Dialog with a simple form

Lets see how to style af:dialog in oracle adf. We usually use dialog within a popup. The default dialog without any style looks like below in alta skin If you want to play with the styles of dialog to make it as a plain dialog without header and footer, you can use the component level selectors like below af|dialog{     background-color: #454d54;     border:none ; } af|dialog::header-start,af|dialog::header-end,af|dialog::header,af|dialog::footer-start,af|dialog::footer-end,af|dialog::footer{     display:none; } af|dialog::content,af|dialog::content-start,af|dialog::content-end{     border-top:none; } you can use these selectors according to your need with the above styles the dialog would look like below In addition to the css above, i have updated few things in the jspx page to make it look better with buttons aligned and form aligned to the center along with some styles for the buttons (style for buttons can be found he...

Oracle Visual Builder Cloud Service - Take photo action

Let's see how to use Take photo action in an action chain.It's a mobile application, and let's assume that you want to take photo, while clicking on a button. We are going to use "Take Photo" action for this. I have a page with a button called "Take Photo" in it. Lets add an event for this button click. When you create an event an action chain is created. Go to the action chain, and drag and drop the "Take Photo" action You can change the source for the "Take Photo" action. It has options as "Camera","photoLibrary","savedPhotoAlbum". Let's go with "camera" option. Explore the other options and change as you need. This action returns the file path of the captured photo. You are free to play around with that file path. You can use it to show the captured image, or use it to process the image. We are going to use the filepath and show the image For that let us create ...

How to enable ADF Faces Extension for css editor in JDeveloper

This article is to show you how to enable adf faces extensions for css editor in jdeveloper 11.1.1.9 You have an ADF Fusion Application and you have created a css file to write custom skin css properties. You are typing the adf faces components to customize their look and feel like af|panelFormLayout But the editor is not showing up the typeaheads? I mean the css editor is not helping you by listing out the options while you type af| ? Then check whether ADF extensions are enabled or not and if not enable it. how to do it ? Go to Tools --> Preferences --> CSS Editor And enable the ADF Faces Extension checkbox Click Ok Now go back to the css file. In the editor, now it will help you with adf extensions.