基于jquery ui的autocomplete(乱码处理,下拉列表内容)
最终效果图:
所需jar包列表:commons-beanutils-1.8.3.jar,commons-collections-3.2.1.jar,commons-lang-2.5.jar,commons-logging-1.1.1.jar,ezmorph-1.0.6.jar,json-lib-2.1.jar
?
jquery.ui.autocomplete.js 关键的地方已经写了注释
?
/* * jQuery UI Autocomplete 1.8.13 * * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * http://docs.jquery.com/UI/Autocomplete * * Depends: *jquery.ui.core.js *jquery.ui.widget.js *jquery.ui.position.js */(function( $, undefined ) {// used to prevent race conditions with remote data sourcesvar requestIndex = 0;//minLength:输入多少个字符后开始自动完成//delay:延迟$.widget( "ui.autocomplete", {options: {appendTo: "body",autoFocus: false,delay: 300,minLength: 1,position: {my: "left top",at: "left bottom",collision: "none"},source: null},pending: 0,_create: function() {var self = this,doc = this.element[ 0 ].ownerDocument,suppressKeyPress;this.element.addClass( "ui-autocomplete-input" ).attr( "autocomplete", "off" )// TODO verify these actually work as intended.attr({role: "textbox","aria-autocomplete": "list","aria-haspopup": "true"}).bind( "keydown.autocomplete", function( event ) {if ( self.options.disabled || self.element.attr( "readonly" ) ) {return;}suppressKeyPress = false;var keyCode = $.ui.keyCode;switch( event.keyCode ) {case keyCode.PAGE_UP:self._move( "previousPage", event );break;case keyCode.PAGE_DOWN:self._move( "nextPage", event );break;case keyCode.UP:self._move( "previous", event );// prevent moving cursor to beginning of text field in some browsersevent.preventDefault();break;case keyCode.DOWN:self._move( "next", event );// prevent moving cursor to end of text field in some browsersevent.preventDefault();break;case keyCode.ENTER:case keyCode.NUMPAD_ENTER:// when menu is open and has focusif ( self.menu.active ) {// #6055 - Opera still allows the keypress to occur// which causes forms to submitsuppressKeyPress = true;event.preventDefault();}//passthrough - ENTER and TAB both select the current elementcase keyCode.TAB:if ( !self.menu.active ) {return;}self.menu.select( event );break;case keyCode.ESCAPE:self.element.val( self.term );self.close( event );break;default:// keypress is triggered before the input value is changedclearTimeout( self.searching );self.searching = setTimeout(function() {// only search if the value has changedif ( self.term != self.element.val() ) {self.selectedItem = null;self.search( null, event );}}, self.options.delay );break;}}).bind( "keypress.autocomplete", function( event ) {if ( suppressKeyPress ) {suppressKeyPress = false;event.preventDefault();}}).bind( "focus.autocomplete", function() {if ( self.options.disabled ) {return;}self.selectedItem = null;self.previous = self.element.val();}).bind( "blur.autocomplete", function( event ) {if ( self.options.disabled ) {return;}clearTimeout( self.searching );// clicks on the menu (or a button to trigger a search) will cause a blur eventself.closing = setTimeout(function() {self.close( event );self._change( event );}, 150 );});this._initSource();this.response = function() {return self._response.apply( self, arguments );};this.menu = $( "<ul></ul>" ).addClass( "ui-autocomplete" ).appendTo( $( this.options.appendTo || "body", doc )[0] )// prevent the close-on-blur in case of a "slow" click on the menu (long mousedown).mousedown(function( event ) {// clicking on the scrollbar causes focus to shift to the body// but we can't detect a mouseup or a click immediately afterward// so we have to track the next mousedown and close the menu if// the user clicks somewhere outside of the autocompletevar menuElement = self.menu.element[ 0 ];if ( !$( event.target ).closest( ".ui-menu-item" ).length ) {setTimeout(function() {$( document ).one( 'mousedown', function( event ) {if ( event.target !== self.element[ 0 ] &&event.target !== menuElement &&!$.ui.contains( menuElement, event.target ) ) {self.close();}});}, 1 );}// use another timeout to make sure the blur-event-handler on the input was already triggeredsetTimeout(function() {clearTimeout( self.closing );}, 13);}).menu({focus: function( event, ui ) {var item = ui.item.data( "item.autocomplete" );if ( false !== self._trigger( "focus", event, { item: item } ) ) {// use value to match what will end up in the input, if it was a key eventif ( /^key/.test(event.originalEvent.type) ) {self.element.val( item.value );}}},selected: function( event, ui ) {var item = ui.item.data( "item.autocomplete" ),previous = self.previous;// only trigger when focus was lost (click on menu)if ( self.element[0] !== doc.activeElement ) {self.element.focus();self.previous = previous;// #6109 - IE triggers two focus events and the second// is asynchronous, so we need to reset the previous// term synchronously and asynchronously :-(setTimeout(function() {self.previous = previous;self.selectedItem = item;}, 1);}if ( false !== self._trigger( "select", event, { item: item } ) ) {//console.debug("userId:" + item.id);//下拉框中的内容被选中后//设置hidden的值,item为json数组中的每一项$("#uid").attr("value", item.id);//设置text的值为选中项的value值self.element.val( item.value );}// reset the term after the select event// this allows custom select handling to work properlyself.term = self.element.val();self.close( event );self.selectedItem = item;},blur: function( event, ui ) {// don't set the value of the text field if it's already correct// this prevents moving the cursor unnecessarilyif ( self.menu.element.is(":visible") &&( self.element.val() !== self.term ) ) {self.element.val( self.term );}}}).zIndex( this.element.zIndex() + 1 )// workaround for jQuery bug #5781 http://dev.jquery.com/ticket/5781.css({ top: 0, left: 0 }).hide().data( "menu" );if ( $.fn.bgiframe ) { this.menu.element.bgiframe();}},destroy: function() {this.element.removeClass( "ui-autocomplete-input" ).removeAttr( "autocomplete" ).removeAttr( "role" ).removeAttr( "aria-autocomplete" ).removeAttr( "aria-haspopup" );this.menu.element.remove();$.Widget.prototype.destroy.call( this );},_setOption: function( key, value ) {$.Widget.prototype._setOption.apply( this, arguments );if ( key === "source" ) {this._initSource();}if ( key === "appendTo" ) {this.menu.element.appendTo( $( value || "body", this.element[0].ownerDocument )[0] )}if ( key === "disabled" && value && this.xhr ) {this.xhr.abort();}},_initSource: function() {var self = this,array,url;if ( $.isArray(this.options.source) ) {array = this.options.source;this.source = function( request, response ) {response( $.ui.autocomplete.filter(array, request.term) );};} else if ( typeof this.options.source === "string" ) {url = this.options.source;this.source = function( request, response ) {if ( self.xhr ) {self.xhr.abort();}self.xhr = $.ajax({url: url,data: request,dataType: "json",autocompleteRequest: ++requestIndex,success: function( data, status ) {if ( this.autocompleteRequest === requestIndex ) {response( data );}},error: function() {if ( this.autocompleteRequest === requestIndex ) {response( [] );}}});};} else {this.source = this.options.source;}},search: function( value, event ) {value = value != null ? value : this.element.val();//中文处理,在这里进行encode,在后台decode,即可解决乱码问题value = encodeURI(value);// always save the actual value, not the one passed as an argumentthis.term = this.element.val();if ( value.length < this.options.minLength ) {return this.close( event );}clearTimeout( this.closing );if ( this._trigger( "search", event ) === false ) {return;}return this._search( value );},_search: function( value ) {this.pending++;this.element.addClass( "ui-autocomplete-loading" );this.source( { term: value }, this.response );},_response: function( content ) {if ( !this.options.disabled && content && content.length ) {content = this._normalize( content );this._suggest( content );this._trigger( "open" );} else {this.close();}this.pending--;if ( !this.pending ) {this.element.removeClass( "ui-autocomplete-loading" );}},close: function( event ) {clearTimeout( this.closing );if ( this.menu.element.is(":visible") ) {this.menu.element.hide();this.menu.deactivate();this._trigger( "close", event );}},_change: function( event ) {if ( this.previous !== this.element.val() ) {this._trigger( "change", event, { item: this.selectedItem } );}},_normalize: function( items ) {// assume all items have the right format when the first item is completeif ( items.length && items[0].label && items[0].value ) {return items;}return $.map( items, function(item) {if ( typeof item === "string" ) {return {label: item,value: item};}return $.extend({label: item.label || item.value,value: item.value || item.label}, item );});},_suggest: function( items ) {var ul = this.menu.element.empty().zIndex( this.element.zIndex() + 1 );this._renderMenu( ul, items );// TODO refresh should check if the active item is still in the dom, removing the need for a manual deactivatethis.menu.deactivate();this.menu.refresh();// size and position menuul.show();this._resizeMenu();ul.position( $.extend({of: this.element}, this.options.position ));if ( this.options.autoFocus ) {this.menu.next( new $.Event("mouseover") );}},_resizeMenu: function() {var ul = this.menu.element;ul.outerWidth( Math.max(ul.width( "" ).outerWidth(),this.element.outerWidth()) );},_renderMenu: function( ul, items ) {var self = this;$.each( items, function( index, item ) {self._renderItem( ul, item );});},_renderItem: function( ul, item) {return $( "<li></li>" ).data( "item.autocomplete", item )//.html:解析html标签,.text:不解析html标签,根据项目需要选择.append( $( "<a></a>" ).html( item.label ) ).appendTo( ul );},_move: function( direction, event ) {if ( !this.menu.element.is(":visible") ) {this.search( null, event );return;}if ( this.menu.first() && /^previous/.test(direction) ||this.menu.last() && /^next/.test(direction) ) {this.element.val( this.term );this.menu.deactivate();return;}this.menu[ direction ]( event );},widget: function() {return this.menu.element;}});$.extend( $.ui.autocomplete, {escapeRegex: function( value ) {return value.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");},filter: function(array, term) {var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" );return $.grep( array, function(value) {return matcher.test( value.label || value.value || value );});}});}( jQuery ));/* * jQuery UI Menu (not officially released) * * This widget isn't yet finished and the API is subject to change. We plan to finish * it for the next release. You're welcome to give it a try anyway and give us feedback, * as long as you're okay with migrating your code later on. We can help with that, too. * * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * http://docs.jquery.com/UI/Menu * * Depends: *jquery.ui.core.js * jquery.ui.widget.js */(function($) {$.widget("ui.menu", {_create: function() {var self = this;this.element.addClass("ui-menu ui-widget ui-widget-content ui-corner-all").attr({role: "listbox","aria-activedescendant": "ui-active-menuitem"}).click(function( event ) {if ( !$( event.target ).closest( ".ui-menu-item a" ).length ) {return;}// temporaryevent.preventDefault();self.select( event );});this.refresh();},refresh: function() {var self = this;// don't refresh list items that are already adaptedvar items = this.element.children("li:not(.ui-menu-item):has(a)").addClass("ui-menu-item").attr("role", "menuitem");items.children("a").addClass("ui-corner-all").attr("tabindex", -1)// mouseenter doesn't work with event delegation.mouseenter(function( event ) {self.activate( event, $(this).parent() );}).mouseleave(function() {self.deactivate();});},activate: function( event, item ) {this.deactivate();if (this.hasScroll()) {var offset = item.offset().top - this.element.offset().top,scroll = this.element.scrollTop(),elementHeight = this.element.height();if (offset < 0) {this.element.scrollTop( scroll + offset);} else if (offset >= elementHeight) {this.element.scrollTop( scroll + offset - elementHeight + item.height());}}this.active = item.eq(0).children("a").addClass("ui-state-hover").attr("id", "ui-active-menuitem").end();this._trigger("focus", event, { item: item });},deactivate: function() {if (!this.active) { return; }this.active.children("a").removeClass("ui-state-hover").removeAttr("id");this._trigger("blur");this.active = null;},next: function(event) {this.move("next", ".ui-menu-item:first", event);},previous: function(event) {this.move("prev", ".ui-menu-item:last", event);},first: function() {return this.active && !this.active.prevAll(".ui-menu-item").length;},last: function() {return this.active && !this.active.nextAll(".ui-menu-item").length;},move: function(direction, edge, event) {if (!this.active) {this.activate(event, this.element.children(edge));return;}var next = this.active[direction + "All"](".ui-menu-item").eq(0);if (next.length) {this.activate(event, next);} else {this.activate(event, this.element.children(edge));}},// TODO merge with previousPagenextPage: function(event) {if (this.hasScroll()) {// TODO merge with no-scroll-elseif (!this.active || this.last()) {this.activate(event, this.element.children(".ui-menu-item:first"));return;}var base = this.active.offset().top,height = this.element.height(),result = this.element.children(".ui-menu-item").filter(function() {var close = $(this).offset().top - base - height + $(this).height();// TODO improve approximationreturn close < 10 && close > -10;});// TODO try to catch this earlier when scrollTop indicates the last page anywayif (!result.length) {result = this.element.children(".ui-menu-item:last");}this.activate(event, result);} else {this.activate(event, this.element.children(".ui-menu-item").filter(!this.active || this.last() ? ":first" : ":last"));}},// TODO merge with nextPagepreviousPage: function(event) {if (this.hasScroll()) {// TODO merge with no-scroll-elseif (!this.active || this.first()) {this.activate(event, this.element.children(".ui-menu-item:last"));return;}var base = this.active.offset().top,height = this.element.height();result = this.element.children(".ui-menu-item").filter(function() {var close = $(this).offset().top - base + height - $(this).height();// TODO improve approximationreturn close < 10 && close > -10;});// TODO try to catch this earlier when scrollTop indicates the last page anywayif (!result.length) {result = this.element.children(".ui-menu-item:first");}this.activate(event, result);} else {this.activate(event, this.element.children(".ui-menu-item").filter(!this.active || this.first() ? ":last" : ":first"));}},hasScroll: function() {return this.element.height() < this.element[ $.fn.prop ? "prop" : "attr" ]("scrollHeight");},select: function( event ) {this._trigger("selected", event, { item: this.active });}});}(jQuery));
?
?JSP页面:index.jsp
?
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%><%String path = request.getContextPath();String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";%><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html> <head> <base href="<%=basePath%>"> <title>test auto complete</title><meta http-equiv="Content-Type" content="text/html;charset=utf-8"><meta http-equiv="pragma" content="no-cache"><meta http-equiv="cache-control" content="no-cache"><meta http-equiv="expires" content="0"><link rel="stylesheet" type="text/css" href="js/jqueryui/jquery-ui-1.8.13.custom.css"><script type="text/javascript" src="js/jquery1.4.4.min.js"></script><script type="text/javascript" src="js/jqueryui/jquery.ui.core.min.js"></script><script type="text/javascript" src="js/jqueryui/jquery.ui.widget.min.js"></script><script type="text/javascript" src="js/jqueryui/jquery.ui.position.min.js"></script><script type="text/javascript" src="js/jqueryui/jquery.ui.autocomplete.js"></script><style type="text/css"><%--loading图片--%>.ui-autocomplete-loading { background: white url('js/jqueryui/images/ui-anim_basic_16x16.gif') right center no-repeat; }.ui-widget .ui-corner-all{ height:30px; vertical-align: top;}.ui-autocomplete {max-height: 108px;overflow-y: auto;/* 防止出现水平滚动条 */overflow-x: hidden;/* 内容与右侧滚动条的距离 */padding-right: 0px;}/* IE 6 不支持 max-height,用height代替 */* html .ui-autocomplete {height: 130px;}</style> </head> <body><script type="text/javascript">$(function() {var cache = {},lastXhr;$( "#nickName" ).autocomplete({minLength: 1,source: function( request, response ) {var term = request.term;if ( term in cache ) {response( cache[ term ] );return;}var params = {"nickName" : term};lastXhr = $.getJSON("SearchServlet", params, function( data, status, xhr ) {cache[ term ] = data;if ( xhr === lastXhr ) {response( data );}});}});});</script><div name="form1" method="get" action="sendMessage.action"><input type="hidden" name="uid" id="uid"/><label for="nickName">昵称: </label><input id="nickName" type="text" name="nickName" style="width:250px;"/><input type="submit" value="发送"/></form></div> </body></html>
?后台代码
User.java
?
package com.gary.test.entity;import java.io.Serializable;/** * 用户信息 * @author gary * */public class User implements Serializable{private static final long serialVersionUID = 1L;private Integer id;private String nickName;private String password;private String gravatar;private Short gender;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public String getGravatar() {return gravatar;}public void setGravatar(String gravatar) {this.gravatar = gravatar;}public void setGender(Short gender) {this.gender = gender;}public Short getGender() {return gender;}public void setNickName(String nickName) {this.nickName = nickName;}public String getNickName() {return nickName;}public User(Integer id, String nickName, String password, String gravatar,Short gender) {super();this.id = id;this.nickName = nickName;this.password = password;this.gravatar = gravatar;this.gender = gender;}}
?UserDTO.java
?
package com.gary.test.util;/** * 用户信息DTO * @author gary * */public class UserDTO {//input hidden中的用户IDprivate Integer id;//下拉列表显示内容private String label;//下拉列表选中后,input text中显示的内容private String value;public UserDTO(Integer id, String label, String value) {this.id = id;this.label = label;this.value = value;}public String getLabel() {return label;}public void setLabel(String label) {this.label = label;}public String getValue() {return value;}public void setValue(String value) {this.value = value;}public void setId(Integer id) {this.id = id;}public Integer getId() {return id;}}
?AutoCompleteUtil.java
?
package com.gary.test.util;import java.util.ArrayList;import java.util.List;import com.gary.test.entity.User;/** * 自动完成工具 * @author gary * */public class AutoCompleteUtil {/** * 转化数据,entity to dto * @param userList * @return */public static List<UserDTO> convert(List<User> userList){List<UserDTO> resultList = new ArrayList<UserDTO>();int imgWidth = 30;int imgHeight = 30;//图片标签前缀final String imgTagPrefix = "<img src="images/";//图片标签后缀final String imgTagPostfix = "" border="0" width="" + imgWidth + "px;" height="" + imgHeight + "px;" />";int namePaddingLeft = 15;//昵称final String nameTagPrefix = "<span style="padding-left:" + namePaddingLeft + "px; vertical-align: top;">";final String nameTagPostfix = "</span>";int genderPaddingLeft = 15;//性别final String genderTagPrefix = "<span style="padding-left:" + genderPaddingLeft + "px; vertical-align: top;">";final String genderTagPostfix = "</span>";for (User ui : userList) {StringBuffer label = new StringBuffer();label.append(imgTagPrefix);label.append(ui.getGravatar());label.append(imgTagPostfix);label.append(nameTagPrefix);label.append(ui.getNickName());label.append(nameTagPostfix);label.append(genderTagPrefix);int gender = ui.getGender().shortValue();if(gender == 1){label.append("男");}else{label.append("女");}label.append(genderTagPostfix);UserDTO userDTO = new UserDTO(ui.getId(), label.toString(), ui.getNickName());resultList.add(userDTO);}return resultList;}}
?SearchServlet.java
?
package com.gary.test.servlet;import java.io.IOException;import java.io.PrintWriter;import java.net.URLDecoder;import java.util.ArrayList;import java.util.List;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import org.apache.commons.lang.StringUtils;import net.sf.json.JSONArray;import com.gary.test.entity.User;import com.gary.test.util.AutoCompleteUtil;import com.gary.test.util.UserDTO;/** * 搜索用户 * @author gary * */public class SearchServlet extends HttpServlet {private static final long serialVersionUID = 1L;public SearchServlet() {super();}public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {doPost(request, response);}public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {request.setCharacterEncoding("UTF-8");response.setCharacterEncoding("UTF-8");String nickName = request.getParameter("nickName");if(!StringUtils.isBlank(nickName)){nickName = URLDecoder.decode(nickName, "UTF-8");}log("nickName:" + nickName); response.setContentType("text/html");PrintWriter out = response.getWriter();//TODO:测试数据仅供演示,需改为从DAO获取查询结果List<User> userList = this.getTestData();List<UserDTO> list = AutoCompleteUtil.convert(userList); if(list.size() == 0){list.add(new UserDTO(0, "无此用户","无此用户"));}//生成JSON数组JSONArray json = JSONArray.fromObject(list);String jsonStr = json.toString();log(jsonStr);out.print(jsonStr);out.flush();out.close();}/** * 生成测试数据,模拟搜索结果 * @return */private List<User> getTestData(){List<User> list = new ArrayList<User>();for (int i = 1; i < 20; i++) {User u = new User(i, "测试用户" + i,"123", "gravatar.jpg", (short) (i % 2));list.add(u);}return list;}}