JSF 2.0 – ajax suggestion box

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

 

Leave a Reply

Your email address will not be published. Required fields are marked *