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); }; })();