Since JSF 2.0 has introduced the ajax support it is quite simple to implement functional widgets like a suggestion input box. The following short tutorial shows how to build your own suggestion widget using JSF 2.0 and it’s ajax capabilities.
1. THE HTML FORM
First you need a simple input field where the user can type in a search phrase. This can be done wit the h:inputText element. After the user typed in some character a suggestion widget should start a backend search and provide the user with a list of suggestions (you can see this behaviour when searching for something on google.com). To connect your inputText element with a backend component you can use the new build-in ajax feature provided by JSF 2.0. So your input element looks something like this:
<h:panelGroup layout="block" style="margin-bottom:5px;">
<h:inputText value="#{suggestController.input}">
<f:ajax event="keyup" render="suggest_box" />
</h:inputText>
</h:panelGroup>
2. DEFINE THE SUGGEST BOX
With the ‘render’ attribute of the f:ajax tag you specify which part of your page should be rerendered after the ajax event was fired. In this case an element with the ID ‘suggest_box’ will be updated. This is the part where you can display the results of your backend search:
<h:panelGroup id="suggest_box">
<ul>
<ui:repeat var="entry" value="#{suggestController.result}">
<li>#{entry}</li>
</ui:repeat>
</ul>
</h:panelGroup>
To bind you backend component (suggestController) to the event just use the listener tag:
<f:ajax event="keyup" render="suggest_box"
listener="#{suggestController.search}" >
</f:ajax>
Your suggest controller implementation can look like this:
....
public void search(AjaxBehaviorEvent event) {
....
result=new ArrayList<String>();
....
}
public List<String> getResult() {
return result;
}
If you are working with EL 2.2 support (GlassFish 3.1) you can also pass params as method arguments:
<f:ajax event="keyup" render="suggest_box"
listener="#{suggestController.search('abc')}" >
</f:ajax>
public void search(String param) {
..
}
3) LAYOUT
Now you can do some layout stuff. Typically the suggestion box should only be displayed with a user typed in a search phrase. And the suggestion box should hidden if the focus is lost. This can be done with some css definition:
.suggestbox {
position: relative;
}
.suggestbox .resultlist {
display: block;
position: absolute;
z-index: 1;
left: 2px;
top: 22px;
border: 1px solid #CCC;
background-color: #EEE;
}
To hide the resultlist you can use the f:ajax event ‘blur’
<f:ajax event="blur" render="suggest_box"
listener="#{suggestController.reset}" />
The listener is bound to a backend method which resets the result list. So you can display the complete box in order of the current result. This is the complete code:
JSF Code:
<h:panelGroup layout="block">
<!-- Process references -->
<h:panelGroup layout="block" style="margin-bottom:5px;">
<h:inputText value="#{suggestController.input}">
<f:ajax event="keyup" render="suggest_box"
listener="#{suggestController.search('')}" />
<f:ajax event="blur" render="suggest_box"
listener="#{suggestController.reset}" />
</h:inputText>
</h:panelGroup>
<h:panelGroup id="suggest_box">
<h:panelGroup id="suggest_resultlist"
rendered="#{! empty suggestController.result}">
<ul>
<ui:repeat var="entry" value="#{suggestController.result}">
<li>#{entry}</li>
</ui:repeat>
</ul>
</h:panelGroup>
</h:panelGroup>
</h:panelGroup>
SuggestController:
@Named("suggestController")
@RequestScoped
public class SuggestController implements Serializable {
private static final long serialVersionUID = 1L;
private List<String> result=null;
private String query = null;
private String input =null;
public SuggestController() {
super();
result = new ArrayList<String>();
}
public String getQuery() {
return query;
}
public void setQuery(String query) {
this.query = query;
}
public String getInput() {
return input;
}
public void setInput(String input) {
this.input = input;
}
public void reset(AjaxBehaviorEvent event) {
result=new ArrayList<String>();
}
public void search(String query) {
result=new ArrayList<String>();
result.add("Hallo " + System.currentTimeMillis());
result.add("World "+ + System.currentTimeMillis());
}
public List<String> getResult() {
return result;
}
}
4) DELAY AJAX EVENTS
Maybe you run into a problem with the onblur ajax call to hide your overlay section. If the section contains h:command links or buttons the action and actionListeners will not be called because the ajax blur event hides the overlay to early.
With a trick from BalusC you can delay the blur event a little bit. So just add the following jQuery code into your page:
$(document).ready(function() {
$(".suggestinput").each(function(index, input) {
var onblur = input.onblur;
input.onblur = null;
$(input).on("blur", function(event) {
delay(function() { onblur.call(input, event); }, 300);
});
// turn autocomplete of
$(this).attr('autocomplete','off');
});
});
var delay = (function() {
var timer = 0;
return function(callback, timeout) {
clearTimeout(timer);
timer = setTimeout(callback, timeout);
};
})();