Using JSF RichFaces with Spring and JPA

JSF (Java Server Faces) is the most serious successor to the famous Struts Framework. It has a large success and it is adopted in most of the recent JEE projects. JSF is a standard and it has very strong community and industry support.

JSF is a MVC (Model-View-Controller) web framework based on component driven UI design model. It allows developers to concentrate on application business logic rather than on little details of HTML.

   

Photo credit: st_dimov

Many libraries come to extend JSF framework with Ajax components to help creating rich user interfaces. The most used libraries are :

    RichFaces ( JBoss project)
    ICEfaces (ICEsoft project)
    MyFaces (Apache project)

In this article, I present a project example to explain, in first step, how to integrate JSF with Spring and JPA and, in second step, how to use RichFaces to enhance the application interface.

1 – Create a basic JSF project

You can browse the source code of the project example in this link or you can checkout the project source code from Google SVN repository in this URL https://project4example2.googlecode.com
/svn/trunk
. After you check out the project into your IDE, the project will be structured as shown in the picture.

1.1 – Add JSF dependencies

To add JSF support to the application, you need to add some dependencies to the pom.xml file as shown in the source code bellow.

	....
	<dependencies>
		.......
		<dependency>
			<groupId>javax.faces</groupId>
			<artifactId>jsf-impl</artifactId>
			<version>1.2_08</version>
		</dependency>

		<dependency>
			<groupId>javax.faces</groupId>
			<artifactId>jsf-api</artifactId>
			<version>1.2_08</version>
		</dependency>

		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
			<version>1.1.2</version>
			<scope>compile</scope>
		</dependency>
		
		<dependency>
			<groupId>com.sun.facelets</groupId>
			<artifactId>jsf-facelets</artifactId>
			<version>1.1.14</version>
		</dependency>

		<dependency>
			<groupId>javax.el</groupId>
			<artifactId>el-api</artifactId>
			<version>1.0</version>
			<scope>provided</scope>
		</dependency>

		<dependency>
			<groupId>el-impl</groupId>
			<artifactId>el-impl</artifactId>
			<version>1.0</version>
			<scope>provided</scope>
		</dependency>
		......
	<dependencies>
   

1.2 – Add JSF configuration to web.xml file

FacesServlet must be configured in web.xml, it is the central controller for the JSF application. It receives all requests for the JSF application and initializes the JSF components before the JSP or XHTML is displayed.

	......
	<context-param>
		<param-name>javax.faces.DEFAULT_SUFFIX</param-name>
		<param-value>.xhtml</param-value>
	</context-param>

	<servlet>
		<servlet-name>Faces Servlet</servlet-name>
		<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>Faces Servlet</servlet-name>
		<url-pattern>*.jsf</url-pattern>
	</servlet-mapping>
	......

1.3 – Create faces-config.xml file

faces-config.xml allows to configure the application, managed beans, convertors, validators, and navigation rules.

<?xml version="1.0" encoding="UTF-8"?>
<faces-config version="1.2" xmlns="http://java.sun.com/xml/ns/javaee"
	xmlns:xi="http://www.w3.org/2001/XInclude" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd">
	
	<managed-bean>
		<description>customer</description>
		<managed-bean-name>customer</managed-bean-name>
		<managed-bean-class>
			com.mycompany.bean.CustomerController
		</managed-bean-class>
		<managed-bean-scope>request</managed-bean-scope>
		<managed-property>
			<property-name>customerDao</property-name>
			<value>#{customerDao}</value>
		</managed-property>
	</managed-bean>
	
	<application>
		<locale-config>
            <default-locale>en</default-locale>
        </locale-config>
		
		<message-bundle>messages</message-bundle>
		
		<el-resolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
	</application>
</faces-config>

In the faces-config.xml, we specify SpringBeanFacesELResolver as el-resolver (Expression Language Resolver). The #{customerDao} el-expression (line 18) will be resolved by Spring framework, costomerDao is defined in Spring application-context.xml file.

1.4 -Create managed bean

JSF application uses a bean with each page in the application. The bean defines the properties and methods associated with the UI components used on the page. A bean can also define a set of methods that perform functions, such as validating the component’s data, for the component. The model object bean is like any other JavaBeans component: it has a set of accessor methods. The source code below present a managed bean used in this application example to add a customer.

import java.util.List;
import com.mycompany.dao.ICustomerDao;
import com.mycompany.entity.Customer;
import com.mycompany.entity.CustomerOrder;

/**
 * @author abdelkafi samer
 */
public class CustomerController {
private ICustomerDao customerDao;
private String customerId;
private String taxId;
private String name;
private String adresse;
.............
.....
private List<CustomerOrder> customerOrders;
private List<Customer> customers;
    
public void save(){
    	Customer customer= new Customer(Integer.parseInt(taxId), name, adresse, city, state, zip, phone, null);
    	customerDao.save(customer);
}

public ICustomerDao getCustomerDao() {
	return customerDao;
}


// getters and setters
................
.......
}

1.5 – Create facelets files

Using facelets template increase re-use and simplify maintenance on your JavaServer Faces project. The picture bellow prent the template used in this application example. To browse the source code click on the file name of the page.

template.xhtml

header.xhtml
menu.xhtml
customerEdit.xhtml

The picture below presents the basic JSF screen of the application.

2 – Add RichFaces Support to the application

In this part, I present a way to enhance JSF application user interface by using RichFaces library.

2.1 – Add richfaces dependencies

In the pom.xml file we need to add RichFaces dependencies as shown in the source code bellow.

<dependencies>
	...........
	<dependency>
		<groupId>org.richfaces.ui</groupId>
		<artifactId>richfaces-ui</artifactId>
		<version>3.3.3.Final</version>
	</dependency>

	<dependency>
		<groupId>org.richfaces.framework</groupId>
		<artifactId>richfaces-api</artifactId>
		<version>3.3.3.Final</version>
	</dependency>

	<dependency>
		<groupId>org.richfaces.framework</groupId>
		<artifactId>richfaces-impl</artifactId>
		<version>3.3.3.Final</version>
	</dependency>
		<dependency>
		<groupId>com.uwyn</groupId>
		<artifactId>jhighlight</artifactId>
		<version>1.0</version>
	</dependency>
	..........
</dependencies>

2.2 – Add reachfaces pram and filter to web.xml file

RichFaces need some parameters (SKIN, VIEW_HANDLERS, LoadScriptStrategy …) to be specified in web.xml file.


	<context-param>
		<param-name>org.richfaces.SKIN</param-name>
		<param-value>blueSky</param-value>
	</context-param>
	
	<context-param>
		<param-name>org.ajax4jsf.VIEW_HANDLERS</param-name>
		<param-value>com.sun.facelets.FaceletViewHandler</param-value>
	</context-param>

	<context-param>
		<param-name>org.richfaces.LoadScriptStrategy</param-name>
		<param-value>ALL</param-value>
	</context-param>
	
	<context-param>
	    <param-name>org.richfaces.LoadStyleStrategy</param-name>
	    <param-value>ALL</param-value>
	</context-param>
	
	<filter>
		<display-name>RichFaces Filter</display-name>
		<filter-name>richfaces</filter-name>
		<filter-class>org.ajax4jsf.Filter</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>richfaces</filter-name>
		<servlet-name>Faces Servlet</servlet-name>
		<dispatcher>REQUEST</dispatcher>
		<dispatcher>FORWARD</dispatcher>
		<dispatcher>INCLUDE</dispatcher>
	</filter-mapping>


2.3 – Modify Facelets files

RichFaces has many components, you can explore them from this link.

To use Richfaces component we need to add xmlns (xml namespaces) to the facelets file and replace basic JSF tag by rich faces one.

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml"
	xmlns:ui="http://java.sun.com/jsf/facelets"
	xmlns:f="http://java.sun.com/jsf/core"
	xmlns:h="http://java.sun.com/jsf/html"
	xmlns:a4j="http://richfaces.org/a4j"
	xmlns:rich="http://richfaces.org/rich">

<ui:composition template="./template/template.xhtml">
  
<ui:define name="body">
 
<f:view >

	<a4j:form>
		<rich:panel>
        <f:facet name="header">
        	<h:outputText value="Edit Customer"/>
        </f:facet>
        <h:panelGrid columns="3" title="Customer" rowClasses="s1row" columnClasses="wfcol1,wfcol2,wfcol3">
        <h:outputText value="Name: " />
			<h:inputText value="#{customer.name}" id="name" required="true"/>
			<rich:message for="name" />
			
			<h:outputText value="Phone: " />
			<h:inputText value="#{customer.phone}" id="phone" required="true"/>
			<rich:message for="phone" />
			
			<h:outputText value="Address: " />
			<h:inputText value="#{customer.adresse}" id="adresse" required="true"/>
			<rich:message for="adresse" />
			
			<h:outputText value="TaxId: " />
			<h:inputText value="#{customer.taxId}" id="taxId" required="true">
				<f:validateLongRange minimum="0" maximum="100"/>
			</h:inputText>
			<rich:message for="taxId" />
			
			<h:outputText value="City: " />
			<h:inputText value="#{customer.city}" id="city" required="true"/>
			<rich:message for="city" />
			
			<h:outputText value="State: " />
			<h:inputText value="#{customer.state}" id="state" required="true"/>
			<rich:message for="state" />
			
			<h:outputText value="Zip: " />
			<h:inputText value="#{customer.zip}" id="zip" required="true">
				<f:validateLongRange minimum="0" maximum="9999"/>
			</h:inputText>
			<rich:message for="zip" />
			
			<f:facet name="footer"> 
				<a4j:commandButton action="#{customer.save}"  value="save"/>
			</f:facet>
			</h:panelGrid>
    	</rich:panel>
				
	</a4j:form>
	
</f:view>

</ui:define>	
</ui:composition>

</html>

This picture presents an application screen after Richfaces integration. This screen uses Ajax to communicate with the server. The save button doesn’t reload the whole page, it just call the save method in the managed bean and update the screen.

Advertisements

Implement Autocomplete fields with JQuery

JQuery library contains some good Ajax components that can help developers to develop better interactive screens. JQuery is not dedicated for JEE Web application it doesn’t have TagLibrary like AjaxTag, ICEFaces, StrutsLayout… But, jQuery can give more flexibility to manage some specific behavior or constraint that developer may have in some screens.
In this post I will present how to implement an Autocomplete field using JQuery with a servlet in the server side.

  
Photo Credit : Lupyo

First, we need to download the zip file from the jQuery Autocomplete Plugin page.
Next, we add to the web project jquery.js, jquery.autocomplete.js and jquery.autocomplete.css as shown in the picture right side.
After copying files into project, we create the servlet which will respond to the JQuery request by text. This response is formatted this way.
obj0.val0|obj1.val1|......|obj1.valn
obj1.val0|obj2.val1|......|obj2.valn
..............
......
objm.val0|objm.val1|......|objm.valn

AutoCompleteJQuery.java is the servelet name which will create the formatted data response to the JQuery autocomplete field. The code source of the servlet is presented below.

  

Web project with JQuery Plugin

public class AutoCompleteJQuery extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
         PrintWriter out = response.getWriter();
        UserDao userDao= new UserDao();
        try {
             List<User> userDaos = userDao.selectAll();
             String q = request.getParameter("q");

             for(User user :userDaos ){
                 if(user.getName().toLowerCase().startsWith(q))
                 out.println(user.getCode() + "|" + user.getName() + "|" + user.getEmail());
             }
        } finally {
            out.close();
        }
    }
}

Next, we add import js and css files and modify the code of the autocomplete field. In this sample the autcomplete field will contain the users list. When a user from the list is selected the read only user id input will be filled.

<%@page contentType="text/html" pageEncoding="windows-1252"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
    "http://www.w3.org/TR/html4/loose.dtd">

<html>
    <head>
        <link rel="stylesheet" type="text/css" href="css/jquery.autocomplete.css"  />
        <link rel="stylesheet" type="text/css" href="css/global.css"  />
        <script type="text/javascript" src="js/jquery.js"></script>
        <script type='text/javascript' src='js/jquery.autocomplete.js'></script>
        <title>Autocomplete with JQuery Page</title>
    </head>
    <body>
        <strong>Autocomplete with JQuery</strong><br><br>
        User <input type="text" name="user_id" id="user_id" readonly="true" size="5" />
        <input type="text" name="user" id="user" />
        <script type="text/javascript">
            $("#user").autocomplete("AutoCompleteJQuery", {
                formatItem: function(data) {
                    return data[1];
                },
                formatResult: function(data) {
                    return data[1];
                }
            }).result(function(event, data) {
                if (data) {
                    $("#user_id").attr("value", data[0]);
                }
            }).setOptions({
                max: '100%'
            });
        </script>
    </body>
</html>

Finally, we run the project and test the auto complete field. The first picture bellow shows the autcomplete field giving the list of users names which start with the character “a”. The list has a scroll bar because it has more than 10 items. The second picture presents the result of selecting one user from the list. The value of data[0] is filled to the user id input and the data[1] is written in the autocomplete field.

Autocomplete screen step1

Autocomplete screen step2

I tried the JQuery Plugin in a JEE project. It works fine with FireFox 3.6. But, I found one inconvenient with IE7 (Internet Explorer version 7). The scroll bar is not displayed where the list has more than 10 items. To solve this problem, I made some changes to the jquery.autocomplete.js source code. The changes I have made are highlighted in the code source below.

/*
 * jQuery Autocomplete plugin 1.1
 *
 * Copyright (c) 2009 Jörn Zaefferer
 *
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 *
 * Revision: $Id: jquery.autocomplete.js 15 2009-08-22 10:30:27Z joern.zaefferer $
 */

;(function($) {
	
$.fn.extend({
	autocomplete: function(urlOrData, options) {
		var isUrl = typeof urlOrData == "string";
		options = $.extend({}, $.Autocompleter.defaults, {
			url: isUrl ? urlOrData : null,
			data: isUrl ? null : urlOrData,
			delay: isUrl ? $.Autocompleter.defaults.delay : 10,
			max: options && !options.scroll ? 10 : 150
		}, options);
		
		// if highlight is set to false, replace it with a do-nothing function
		options.highlight = options.highlight || function(value) { return value; };
		
		// if the formatMatch option is not specified, then use formatItem for backwards compatibility
		options.formatMatch = options.formatMatch || options.formatItem;
		
		return this.each(function() {
			new $.Autocompleter(this, options);
		});
	},
	result: function(handler) {
		return this.bind("result", handler);
	},
	search: function(handler) {
		return this.trigger("search", [handler]);
	},
	flushCache: function() {
		return this.trigger("flushCache");
	},
	setOptions: function(options){
		return this.trigger("setOptions", [options]);
	},
	unautocomplete: function() {
		return this.trigger("unautocomplete");
	}
});

$.Autocompleter = function(input, options) {

	var KEY = {
		UP: 38,
		DOWN: 40,
		DEL: 46,
		TAB: 9,
		RETURN: 13,
		ESC: 27,
		COMMA: 188,
		PAGEUP: 33,
		PAGEDOWN: 34,
		BACKSPACE: 8
	};

	// Create $ object for input element
	var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass);

	var timeout;
	var previousValue = "";
	var cache = $.Autocompleter.Cache(options);
	var hasFocus = 0;
	var lastKeyPressCode;
	var config = {
		mouseDownOnSelect: false
	};
	var select = $.Autocompleter.Select(options, input, selectCurrent, config);
	
	var blockSubmit;
	
	// prevent form submit in opera when selecting with return key
	$.browser.opera && $(input.form).bind("submit.autocomplete", function() {
		if (blockSubmit) {
			blockSubmit = false;
			return false;
		}
	});
	
	// only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
	$input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) {
		// a keypress means the input has focus
		// avoids issue where input had focus before the autocomplete was applied
		hasFocus = 1;
		// track last key pressed
		lastKeyPressCode = event.keyCode;
		switch(event.keyCode) {
		
			case KEY.UP:
				event.preventDefault();
				if ( select.visible() ) {
					select.prev();
				} else {
					onChange(0, true);
				}
				break;
				
			case KEY.DOWN:
				event.preventDefault();
				if ( select.visible() ) {
					select.next();
				} else {
					onChange(0, true);
				}
				break;
				
			case KEY.PAGEUP:
				event.preventDefault();
				if ( select.visible() ) {
					select.pageUp();
				} else {
					onChange(0, true);
				}
				break;
				
			case KEY.PAGEDOWN:
				event.preventDefault();
				if ( select.visible() ) {
					select.pageDown();
				} else {
					onChange(0, true);
				}
				break;
			
			// matches also semicolon
			case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA:
			case KEY.TAB:
			case KEY.RETURN:
				if( selectCurrent() ) {
					// stop default to prevent a form submit, Opera needs special handling
					event.preventDefault();
					blockSubmit = true;
					return false;
				}
				break;
				
			case KEY.ESC:
				select.hide();
				break;
				
			default:
				clearTimeout(timeout);
				timeout = setTimeout(onChange, options.delay);
				break;
		}
	}).focus(function(){
		// track whether the field has focus, we shouldn't process any
		// results if the field no longer has focus
		hasFocus++;
	}).blur(function(e) {

            if ($.browser.mozilla) { // no specific test for Mozilla browser
                 hasFocus=0;
                 if (!config.mouseDownOnSelect) {
                     hideResults();
                 }
            } else {
                var position = $("#autocompleteDiv").position();
                if ( e.pageX < position.left || e.pageX > (position.left + $("#autocompleteDiv").width())
                    || e.pageY < position.top || e.pageY > (position.top + $("#autocompleteDiv").height())){ // test for blur the div zone in IE
                    hasFocus=0;
                    if (!config.mouseDownOnSelect) {
                         hideResults();
                    }
                } else {
                    this.focus();
                }

            }
          
	}).click(function() {
		// show select when clicking in a focused field
		if ( hasFocus++ > 1 && !select.visible() ) {
			onChange(0, true);
		}
	}).bind("search", function() {
		// TODO why not just specifying both arguments?
		var fn = (arguments.length > 1) ? arguments[1] : null;
		function findValueCallback(q, data) {
			var result;
			if( data && data.length ) {
				for (var i=0; i < data.length; i++) {
					if( data[i].result.toLowerCase() == q.toLowerCase() ) {
						result = data[i];
						break;
					}
				}
			}
			if( typeof fn == "function" ) fn(result);
			else $input.trigger("result", result && [result.data, result.value]);
		}
		$.each(trimWords($input.val()), function(i, value) {
			request(value, findValueCallback, findValueCallback);
		});
	}).bind("flushCache", function() {
		cache.flush();
	}).bind("setOptions", function() {
		$.extend(options, arguments[1]);
		// if we've updated the data, repopulate
		if ( "data" in arguments[1] )
			cache.populate();
	}).bind("unautocomplete", function() {
		select.unbind();
		$input.unbind();
		$(input.form).unbind(".autocomplete");
	});
	
	
	function selectCurrent() {
		var selected = select.selected();
		if( !selected )
			return false;
		
		var v = selected.result;
		previousValue = v;
		
		if ( options.multiple ) {
			var words = trimWords($input.val());
			if ( words.length > 1 ) {
				var seperator = options.multipleSeparator.length;
				var cursorAt = $(input).selection().start;
				var wordAt, progress = 0;
				$.each(words, function(i, word) {
					progress += word.length;
					if (cursorAt <= progress) {
						wordAt = i;
						return false;
					}
					progress += seperator;
				});
				words[wordAt] = v;
				// TODO this should set the cursor to the right position, but it gets overriden somewhere
				//$.Autocompleter.Selection(input, progress + seperator, progress + seperator);
				v = words.join( options.multipleSeparator );
			}
			v += options.multipleSeparator;
		}
		
		$input.val(v);
		hideResultsNow();
		$input.trigger("result", [selected.data, selected.value]);
		return true;
	}
	
	function onChange(crap, skipPrevCheck) {
		if( lastKeyPressCode == KEY.DEL ) {
			select.hide();
			return;
		}
		
		var currentValue = $input.val();
		
		if ( !skipPrevCheck && currentValue == previousValue )
			return;
		
		previousValue = currentValue;
		
		currentValue = lastWord(currentValue);
		if ( currentValue.length >= options.minChars) {
			$input.addClass(options.loadingClass);
			if (!options.matchCase)
				currentValue = currentValue.toLowerCase();
			request(currentValue, receiveData, hideResultsNow);
		} else {
			stopLoading();
			select.hide();
		}
	};
	
	function trimWords(value) {
		if (!value)
			return [""];
		if (!options.multiple)
			return [$.trim(value)];
		return $.map(value.split(options.multipleSeparator), function(word) {
			return $.trim(value).length ? $.trim(word) : null;
		});
	}
	
	function lastWord(value) {
		if ( !options.multiple )
			return value;
		var words = trimWords(value);
		if (words.length == 1) 
			return words[0];
		var cursorAt = $(input).selection().start;
		if (cursorAt == value.length) {
			words = trimWords(value)
		} else {
			words = trimWords(value.replace(value.substring(cursorAt), ""));
		}
		return words[words.length - 1];
	}
	
	// fills in the input box w/the first match (assumed to be the best match)
	// q: the term entered
	// sValue: the first matching result
	function autoFill(q, sValue){
		// autofill in the complete box w/the first match as long as the user hasn't entered in more data
		// if the last user key pressed was backspace, don't autofill
		if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) {
			// fill in the value (keep the case the user has typed)
			$input.val($input.val() + sValue.substring(lastWord(previousValue).length));
			// select the portion of the value not typed by the user (so the next character will erase)
			$(input).selection(previousValue.length, previousValue.length + sValue.length);
		}
	};

	function hideResults() {
                //alert('hideResults');
		clearTimeout(timeout);
		timeout = setTimeout(hideResultsNow, 200);
	};

	function hideResultsNow() {
		var wasVisible = select.visible();
                //alert('hideResultsNow');
		select.hide();
		clearTimeout(timeout);
		stopLoading();
		if (options.mustMatch) {
			// call search and run callback
			$input.search(
				function (result){
					// if no value found, clear the input box
					if( !result ) {
						if (options.multiple) {
							var words = trimWords($input.val()).slice(0, -1);
							$input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") );
						}
						else {
							$input.val( "" );
							$input.trigger("result", null);
						}
					}
				}
			);
		}
	};

	function receiveData(q, data) {
		if ( data && data.length && hasFocus ) {
			stopLoading();
			select.display(data, q);
			autoFill(q, data[0].value);
			select.show();
		} else {
			hideResultsNow();
		}
	};

	function request(term, success, failure) {
		if (!options.matchCase)
			term = term.toLowerCase();
		var data = cache.load(term);
		// recieve the cached data
		if (data && data.length) {
			success(term, data);
		// if an AJAX url has been supplied, try loading the data now
		} else if( (typeof options.url == "string") && (options.url.length > 0) ){
			
			var extraParams = {
				timestamp: +new Date()
			};
			$.each(options.extraParams, function(key, param) {
				extraParams[key] = typeof param == "function" ? param() : param;
			});
			
			$.ajax({
				// try to leverage ajaxQueue plugin to abort previous requests
				mode: "abort",
				// limit abortion to this input
				port: "autocomplete" + input.name,
				dataType: options.dataType,
				url: options.url,
				data: $.extend({
					q: lastWord(term),
					limit: options.max
				}, extraParams),
				success: function(data) {
					var parsed = options.parse && options.parse(data) || parse(data);
					cache.add(term, parsed);
					success(term, parsed);
				}
			});
		} else {
			// if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
			select.emptyList();
			failure(term);
		}
	};
	
	function parse(data) {
		var parsed = [];
		var rows = data.split("\n");
		for (var i=0; i < rows.length; i++) {
			var row = $.trim(rows[i]);
			if (row) {
				row = row.split("|");
				parsed[parsed.length] = {
					data: row,
					value: row[0],
					result: options.formatResult && options.formatResult(row, row[0]) || row[0]
				};
			}
		}
		return parsed;
	};

	function stopLoading() {
		$input.removeClass(options.loadingClass);
	};

};

$.Autocompleter.defaults = {
	inputClass: "ac_input",
	resultsClass: "ac_results",
	loadingClass: "ac_loading",
	minChars: 1,
	delay: 400,
	matchCase: false,
	matchSubset: true,
	matchContains: false,
	cacheLength: 10,
	max: 100,
	mustMatch: false,
	extraParams: {},
	selectFirst: true,
	formatItem: function(row) { return row[0]; },
	formatMatch: null,
	autoFill: false,
	width: 0,
	multiple: false,
	multipleSeparator: ", ",
	highlight: function(value, term) {
		return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
	},
    scroll: true,
    scrollHeight: 180
};

$.Autocompleter.Cache = function(options) {

	var data = {};
	var length = 0;
	
	function matchSubset(s, sub) {
		if (!options.matchCase) 
			s = s.toLowerCase();
		var i = s.indexOf(sub);
		if (options.matchContains == "word"){
			i = s.toLowerCase().search("\\b" + sub.toLowerCase());
		}
		if (i == -1) return false;
		return i == 0 || options.matchContains;
	};
	
	function add(q, value) {
		if (length > options.cacheLength){
			flush();
		}
		if (!data[q]){ 
			length++;
		}
		data[q] = value;
	}
	
	function populate(){
		if( !options.data ) return false;
		// track the matches
		var stMatchSets = {},
			nullData = 0;

		// no url was specified, we need to adjust the cache length to make sure it fits the local data store
		if( !options.url ) options.cacheLength = 1;
		
		// track all options for minChars = 0
		stMatchSets[""] = [];
		
		// loop through the array and create a lookup structure
		for ( var i = 0, ol = options.data.length; i < ol; i++ ) {
			var rawValue = options.data[i];
			// if rawValue is a string, make an array otherwise just reference the array
			rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue;
			
			var value = options.formatMatch(rawValue, i+1, options.data.length);
			if ( value === false )
				continue;
				
			var firstChar = value.charAt(0).toLowerCase();
			// if no lookup array for this character exists, look it up now
			if( !stMatchSets[firstChar] ) 
				stMatchSets[firstChar] = [];

			// if the match is a string
			var row = {
				value: value,
				data: rawValue,
				result: options.formatResult && options.formatResult(rawValue) || value
			};
			
			// push the current match into the set list
			stMatchSets[firstChar].push(row);

			// keep track of minChars zero items
			if ( nullData++ < options.max ) {
				stMatchSets[""].push(row);
			}
		};

		// add the data items to the cache
		$.each(stMatchSets, function(i, value) {
			// increase the cache size
			options.cacheLength++;
			// add to the cache
			add(i, value);
		});
	}
	
	// populate any existing data
	setTimeout(populate, 25);
	
	function flush(){
		data = {};
		length = 0;
	}
	
	return {
		flush: flush,
		add: add,
		populate: populate,
		load: function(q) {
			if (!options.cacheLength || !length)
				return null;
			/* 
			 * if dealing w/local data and matchContains than we must make sure
			 * to loop through all the data collections looking for matches
			 */
			if( !options.url && options.matchContains ){
				// track all matches
				var csub = [];
				// loop through all the data grids for matches
				for( var k in data ){
					// don't search through the stMatchSets[""] (minChars: 0) cache
					// this prevents duplicates
					if( k.length > 0 ){
						var c = data[k];
						$.each(c, function(i, x) {
							// if we've got a match, add it to the array
							if (matchSubset(x.value, q)) {
								csub.push(x);
							}
						});
					}
				}				
				return csub;
			} else 
			// if the exact item exists, use it
			if (data[q]){
				return data[q];
			} else
			if (options.matchSubset) {
				for (var i = q.length - 1; i >= options.minChars; i--) {
					var c = data[q.substr(0, i)];
					if (c) {
						var csub = [];
						$.each(c, function(i, x) {
							if (matchSubset(x.value, q)) {
								csub[csub.length] = x;
							}
						});
						return csub;
					}
				}
			}
			return null;
		}
	};
};

$.Autocompleter.Select = function (options, input, select, config) {
	var CLASSES = {
		ACTIVE: "ac_over"
	};
	
	var listItems,
		active = -1,
		data,
		term = "",
		needsInit = true,
		element,
		list,
                tabl,
                trTabl,
                tdTable;
	
	// Create results
	function init() {
		if (!needsInit)
			return;
		element = $("<div id='autocompleteDiv'/>")
                .hide()
		.addClass(options.resultsClass)	
		.appendTo(document.body);
                tabl = $("<table/>").appendTo(element);                
                tabl.css({height : '100%' , width : '100%'});
                trTabl = $("<tr/>").appendTo(tabl);
                tdTable = $("<td/>").appendTo(trTabl);
		list = $("<ul/>").appendTo(tdTable).mouseover( function(event) {
			if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') {
	            active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event));
			    $(target(event)).addClass(CLASSES.ACTIVE);            
	        }
		}).click(function(event) {
			$(target(event)).addClass(CLASSES.ACTIVE);
			select();
			// TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus
			input.focus();
			return false;
		}).mousedown(function() {
			config.mouseDownOnSelect = true;
		}).mouseup(function() {
			config.mouseDownOnSelect = false;
		});
		
		if( options.width > 0 )
			element.css("width", options.width);
		
		needsInit = false;
	} 
	
	function target(event) {
		var element = event.target;
		while(element && element.tagName != "LI")
			element = element.parentNode;
		// more fun with IE, sometimes event.target is empty, just ignore it then
		if(!element)
			return [];
		return element;
	}

	function moveSelect(step) {

		listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE);
		movePosition(step);
        var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
        if(options.scroll) {
            var offset = 0;
            listItems.slice(0, active).each(function() {
				offset += this.offsetHeight;
			});
            
            if((offset + activeItem[0].offsetHeight - element.scrollTop()) > element.height()) {
                element.scrollTop(offset + activeItem[0].offsetHeight - element.innerHeight());
            } else if(offset < element.scrollTop()) {
                  element.scrollTop(offset);
            }
        }
	};
	
	function movePosition(step) {
		active += step;
		if (active < 0) {
			active = listItems.size() - 1;
		} else if (active >= listItems.size()) {
			active = 0;
		}
	}
	
	function limitNumberOfItems(available) {
		return options.max && options.max < available
			? options.max
			: available;
	}
	
	function fillList() {
		list.empty();
		var max = limitNumberOfItems(data.length);
                
                if(max > 10) {
                    element.css({position :'absolute', height: options.scrollHeight, overflow: 'auto'})
                } else {
                    element.css({position :'absolute', height:  '', overflow: 'auto'})
                }
                
		for (var i=0; i < max; i++) {
			if (!data[i])
				continue;
			var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term);
			if ( formatted === false )
				continue;
			var li = $("<li/>").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0];
			$.data(li, "ac_data", data[i]);
		}
		listItems = list.find("li");
		if ( options.selectFirst ) {
			listItems.slice(0, 1).addClass(CLASSES.ACTIVE);
			active = 0;
		}
		// apply bgiframe if available
		if ( $.fn.bgiframe )
			list.bgiframe();
	}
	
	return {
		display: function(d, q) {
			init();
			data = d;
			term = q;
			fillList();
		},
		next: function() {
			moveSelect(1);
		},
		prev: function() {
			moveSelect(-1);
		},
		pageUp: function() {
			if (active != 0 && active - 8 < 0) {
				moveSelect( -active );
			} else {
				moveSelect(-8);
			}
		},
		pageDown: function() {
			if (active != listItems.size() - 1 && active + 8 > listItems.size()) {
				moveSelect( listItems.size() - 1 - active );
			} else {
				moveSelect(8);
			}
		},
		hide: function() {
			element && element.hide();
			listItems && listItems.removeClass(CLASSES.ACTIVE);
			active = -1;
		},
		visible : function() {
			return element && element.is(":visible");
		},
		current: function() {
			return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]);
		},
		show: function() {
			var offset = $(input).offset();
			element.css({
				width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(),
				top: offset.top + input.offsetHeight,
				left: offset.left
			}).show();
            if(options.scroll) {
                element.scrollTop(0);
                element.css({
			maxHeight: options.scrollHeight
			//,overflow: 'auto'
                });
				
                if($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
					var listHeight = 0;
					listItems.each(function() {
						listHeight += this.offsetHeight;
					});
					var scrollbarsVisible = listHeight > options.scrollHeight;
                    element.css('height', scrollbarsVisible ? options.scrollHeight : listHeight );
					if (!scrollbarsVisible) {
						// IE doesn't recalculate width when scrollbar disappears
						listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) );
					}
                }
                
            }
		},
		selected: function() {
			var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);
			return selected && selected.length && $.data(selected[0], "ac_data");
		},
		emptyList: function (){
			list && list.empty();
		},
		unbind: function() {
			element && element.remove();
		}
	};
};

$.fn.selection = function(start, end) {
	if (start !== undefined) {
		return this.each(function() {
			if( this.createTextRange ){
				var selRange = this.createTextRange();
				if (end === undefined || start == end) {
					selRange.move("character", start);
					selRange.select();
				} else {
					selRange.collapse(true);
					selRange.moveStart("character", start);
					selRange.moveEnd("character", end);
					selRange.select();
				}
			} else if( this.setSelectionRange ){
				this.setSelectionRange(start, end);
			} else if( this.selectionStart ){
				this.selectionStart = start;
				this.selectionEnd = end;
			}
		});
	}
	var field = this[0];
	if ( field.createTextRange ) {
		var range = document.selection.createRange(),
			orig = field.value,
			teststring = "<->",
			textLength = range.text.length;
		range.text = teststring;
		var caretAt = field.value.indexOf(teststring);
		field.value = orig;
		this.selection(caretAt, caretAt + textLength);
		return {
			start: caretAt,
			end: caretAt + textLength
		}
	} else if( field.selectionStart !== undefined ){
		return {
			start: field.selectionStart,
			end: field.selectionEnd
		}
	}
};

})(jQuery);

The picture bellow explains the changes I have made to make scroll bar work under IE7. I changed the scroll bar to the div and I added a table markup because when the table size is bigger than the fixed div size the scroll bar appears in IE7.

Modification of JQuery plugin for IE7

Modification of JQuery plugin for IE7