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.
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.
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
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?
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",
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 :)
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
Post a Comment