Autocomplete avec Typeahead et Liferay

Sometimes you have to add an autocomplete module on your page to search contacts or other datas. If you want to use the very good Typeahead plugin in Liferay to have search suggestions in Ajax the solution is here. The ajax calls a resourceMapping on the controller to get the contacts in JSON format. In this resourceMapping function you can call whatever you want (Liferay service, LDAP, search engine…)

One thing is to manage with a local cache for not calling to many times the server when you change a letter.

The JSP/Javascript that you need for implement this behaviour:

<!—Typeahead javascript bundle-->

<script src="https://twitter.github.io/typeahead.js/releases/latest/typeahead.bundle.js"/>

<!—Liferay ressource URL for the ajax call-->

<portlet:resourceURL var="searchContactsURL" />

<input type="text" name="myInputSearch" id="myInputSearch"/>

<script type="text/javascript">      

$(document).ready(function(){

       // URL ajax de recherche, Input field of the search

       autocompleteAjax ('${ searchContactsURL }', '#myInputSearch', 3);

});

/**

 * Autocomplete function using the "typeahead.js" plugin.

 * @param ajaxURL, URL of the search: return JSON data

 * @param inputField, input field to search datas

 */

function autocompleteAjax(ajaxURL, inputField, minLetters) {

       var autocompleteName = "contacts";

       // Cache for the results

       var searchCache = [];

       // To search again if we enter less than "minLetters" caracters: we have to clean the cache

       $(inputField).keyup(function() {

             if($(this).val().length < minLetters){

                    searchCache = [];

             }

       });

       /**

        * Function to search if a search pattern is already in the cache.

        */

       var isInCache = function(pattern) {

             var substrRegex = new RegExp(pattern, 'i');

             var match = false;

             $.each(searchCache, function(i, contact) {

                    if (substrRegex.test(contact.firstname)

                                  || substrRegex.test(contact.name)

                                  || substrRegex.test(contact.firstname + " " + contact.name)) {

                           match = true;

                    }

             });

             return match;

       };

       /**

        * Function to sort datas in the cache to keep only which matches with the search pattern.

        */

       var sortResults = function(pattern) {

             var matches = [];

             var substrRegex = new RegExp(pattern, 'i');

             $.each(searchCache, function(i, contact) {

                    if (substrRegex.test(contact.firstname)

                                  || substrRegex.test(contact.name)

                                  || substrRegex.test(contact.firstname + " " + contact.name)) {

                           matches.push(contact);

                    }

             });

             return matches;

       };

 

       /**

        * Function to make an ajax call to search contacts. Only if the search pattern is not already in the cache.

        */

       var ajaxAutocompleteSearch = function() {

             return function findMatches(searchPattern, comboBox) {

                    //to remove useless white space

                    searchPattern = $.trim(searchPattern).replace(/ {2,}/g,' ');

                    // If the search pattern is not in the cache make an ajax call to get the contacts

                    if (!isInCache(searchPattern)) {

                           $.ajax({

                                  type : "POST",

                                  async : false,

                                  dataType : "json",

                                  url : ajaxURL,

                                  data : {

                                        pattern : searchPattern

                                  },

                                  success : function(contacts) {

                                        // To stock the results in the cache

                                        searchCache = searchCache.concat(contacts);

                                        comboBox(contacts);// To display the suggestions

                                  }

                           });

                    } else {

                           // To sort suggestions of the cache

                           comboBox(sortResults(searchPattern));

                    }

             };

       }

       // Initialisation of typeahead

       $(inputField).typeahead(

                    {

                           hint : true,

                           highlight : true,

                           minLength : minLetters

                    },

                    {

                           name : autocompleteName,

                           displayKey : 'email',

                           templates : {

                                  empty : [ '<div class="empty-message">', 'No contact found', '</div>' ].join('\n'),

                                  suggestion : function(contact) {

                                        return contact.firstname + " " + contact.name + " (" + contact.email + ")";

                                  }

                           },

                           source : ajaxAutocompleteSearch()

                    });

}

</script>

 

The Java function in our controller with a JSON return:

import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.annotate.JsonSerialize.Inclusion;
…
@ResourceMapping
public void searchContacts(ResourceRequest request, ResourceResponse response, String pattern) throws Exception {
	ObjectMapper mapper = new ObjectMapper();
	mapper.setSerializationInclusion(Inclusion.NON_NULL);
	
	List<Contact> listContacts =  service.getContacts(pattern);
	
	mapper.writeValue(response.getWriter(), listContacts);
	response.getWriter().flush();
}

 

You need a model Object for the mapping. In my example I have this one:

public class Contact {
	private String firstname;
	private String name;
	private String email;
	
	public Contact(String firstname, String name, String email) {
		this.nom = firstname;
		this.name = name;
		this.email = email;
	}

	public String getFirstname () {
		return firstname;
	}

	public void setFirstname(String firstname) {
		this.firstname = firstname;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}
}

 

Blogs