js设置控件禁用和启用
下一代网站启用了两项关键技术:Ajax和JSON。 业务线应用程序可以从这些技术中受益,以提供更直观,响应更快的用户界面。 本文介绍如何通过基于Ajax构建可重用的JSP TagLib控件将Ajax和JSON添加到Java™平台企业版(Java EE)Web应用程序。
在本文中,我将展示如何构建一个级联的下拉控件,该控件根据其他表单字段值动态填充HTML SELECT控件中的值。 我还将介绍如何构建类似于Google提示的自动完成控件,该控件会显示一个提示列表,该列表会随着用户的输入实时更新。 您将通过集成JSON,JavaScript,CSS,HTML和Java EE技术来构建控件。
技术概述
本文开发的控件的主要设计目标是:
- 提供与现有Web应用程序的轻松集成。 控件应封装所有逻辑和JavaScript代码,以简化部署过程。
- 可配置。
- 最小化数据和页面大小的开销。
- 利用CSS和HTML标准。
- 提供跨浏览器支持(Microsoft®Internet Explorer,Mozilla Firefox)。
- 利用常见的设计模式/最佳实践来提高代码的可维护性。
为了实现易于集成和可配置控件的目标,本文的示例在可能的情况下使用可配置标签属性。 另外,您定义接口/合同以提供一种直接的方式来将自定义数据/值提供程序与控件集成在一起。
本文使用其他控件来封装常见JavaScript函数,从而最大程度地减少数据和开销。 在进行异步调用时,可以使用JSON来最大程度地减少数据交换。
本文的示例使用Web标准(包括CSS和HTML)来实现跨浏览器的支持。 对照Internet Explorer 7.x和Mozilla Firefox 2.x / 3.x对控件发出JavaScript,HTML和CSS进行了测试。
数据和值提供程序是基于常见的面向对象编程设计模式和最佳实践构建的,例如n层体系结构,适配器设计模式和基于接口的编程。
实施示例控件的技术注意事项
您在本文中开发的支持Ajax的控件有一些关键的技术注意事项,包括为Ajax控件提供值的机制,用于异步通信的数据交换格式,类设计和数据模型。
提供对异步调用的响应的机制
您有三个选项可将数据异步公开给启用Ajax的控件:
- JavaServer页面(JSP)
- 小程序
- SOAP或RESTful Web服务
本文使用Servlet是因为它们的效率和最小的开销。 JSP页面比Servlet更易于实现,但是从实现角度来看,它并不是那么干净。
数据交换格式注意事项
启用Ajax的控件的数据提供程序可以使用XML或JSON作为数据交换格式。 XML通常比JSON更具人类可读性,但是它具有以下缺点:
- 与JSON相比,数据量更大
- 在JavaScript中解析起来稍微困难些
由于这些原因,本文使用JSON。
资料模型
示例应用程序的数据模型包括两个实体:
- 状态,其中包含状态缩写和名称
- 位置,其中包含城市,邮政编码和其他位置数据
图1显示了用于本文示例页面的数据模型。
图1.数据模型
类模型
本文中的示例由数据抽象层(DAL),数据传输对象(DTO),业务逻辑层(BLL),表示层和支持帮助程序类组成。 下图显示了这些类的UML类图。
帮助程序类提供数据库和表示层支持类(请参见图2)。
图2. UML类图-助手类
数据抽象层由单个类组成,用于向业务层提供与位置相关的数据(请参见图3)。
图3. UML类图-数据抽象层类
您使用两个DTO通过三个层传递数据(请参见图4)。 StateDTO保存与州相关的数据,而LocationDTO保存与位置相关的数据,包括邮政编码,城市名称,州,纬度和经度。
图4. UML类图-数据传输对象类
业务逻辑层由值提供程序组成,这些值提供程序向支持Ajax的控件提供数据(请参见图5)。 自动完成控件的值提供者必须实现IJsonValueProvider接口。 位置服务从数据层接收DTO对象的集合,并生成相应的JSON数据以供表示层使用。
图5. UML类图-业务逻辑层类
Servlet提供了进行客户端异步调用的接口(请参见图6)。 这些Servlet与值提供程序进行交互,以向Web浏览器提供JSON数据。
图6. UML类图-业务逻辑层,Servlet类
JSP TagLib控件
您将创建以下启用Ajax的控件:
- 级联下拉控件 -根据其他表单字段或业务规则动态填充SELECT控件中的值选项。
- 自动完成控制 -用户输入时,实时显示类似于Google建议的建议列表。 使用与数据提供程序Servlet的异步通信可以动态显示建议。
除了两个JSP TagLib控件之外,您还需要第三个控件来封装所有可重用JavaScript函数,例如清除/填充值,处理键盘/鼠标事件以及支持异步通信。 图7说明了这三个控件类。
图7. UML类图-JSP TagLib控件类
构建数据提供者和数据层
LocationDataService类是一个数据提供程序,用于从数据库中检索与位置相关的数据。 它返回包含LocationDTO和StateDTO对象的TreeMap对象。 强烈建议数据提供者将结果缓存在内存中以获得最佳性能,尤其是因为数据是通过异步服务器调用消耗的。
构建一个JSP TagLib控件
通过扩展TagSupport或TagBodySupport并重写doStartTag() , doAfterBody()或doEndBody()方法来创建JSP TagLib控件,以在页面处理期间呈现控件的内容(HTML代码,JavaScript)。 清单1显示了重写的doStartTag方法的示例。
清单1. JdbcQuery类
/* (non-Javadoc)
* @see javax.servlet.jsp.tagext.TagSupport#doStartTag()
*/
@Override
public int doStartTag() throws JspException {
JspWriter out = pageContext.getOut();
try {
// An example of rendering output within a JSP page
out.print("This is a string that will be rendered");
// A more practical example
out.print("<h1 id='heading1'>This is a Heading</h1>");
} catch (IOException e) {
e.printStackTrace();
}
创建JSP TagLib控件的实现之后,必须在/ WEB-INF / tlds目录中定义TagLib库定义(TLD),如清单2所示。
清单2.示例JSP TagLib库定义文件
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN"
"http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd">
<taglib>
<tlibversion>1.0</tlibversion>
<jspversion>1.1</jspversion>
<shortname>ajax</shortname>
<info>Ajax control library</info>
<tag>
<name>sample</name>
<tagclass>com.testwebsite.controls.SampleJspTag</tagclass>
<bodycontent>JSP</bodycontent>
<info>
This is a sample control
</info>
<attribute>
<name>id</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
</tag>
</tagLib>
通过添加清单3中的代码,可以将控件放置在任何JSP页面中。
清单3.示例JSP页面
<%@ page contentType="text/html; charset=ISO-8859-5" %>
<%@ taglib prefix="ajax" uri="/WEB-INF/tlds/ajax_controls.tld"%>
<html>
<head>
<title>This is a test page</title>
<link href="core.css" rel="stylesheet" type="text/css" />
</head>
<body>
This is a test page.
<ajax:sample/>
</body>
</html>
构建Ajax页面JSP TagLib控件
<ajax:page/>控件呈现为JSP页面添加异步支持所需的标准JavaScript函数。 它还为<ajax:autocomplete/>和<ajax:dropdown/>控件呈现帮助器功能。 帮助程序功能在各自的控制部分中进行了介绍: 构建自动完成的JSP TagLib控件和构建级联的下拉JSP TagLib控件 。 在可能的情况下,最好在<ajax:page/>控件中而不是单个控件中继续支持JavaScript函数,因为这样做会减小页面大小。 另外,您可以将它们存储在外部JS文件中,但是通过减少控件中的封装,这会使部署稍微复杂化。
XMLHttpRequest对象(可在JavaScript中访问)是异步Web通信的核心。 不幸的是, XMLHttpRequest不是批准的标准,并且供应商支持略有不同。 对于Opera,Mozilla Firefox和Microsoft Internet Explorer 7.0及更高版本,只需使用new XMLHttpRequest() JavaScript语法即可。 对于早期版本的Microsoft Internet Explorer,可以使用new ActiveXObject('Microsoft.XMLHTTP')创建对象。 清单4显示了如何初始化XMLHttpRequest以支持跨浏览器。
清单4.创建XMLHttpRequest对象
var req;
function initializeXmlHttpRequest() {
if (window.ActiveXObject) {
req=new ActiveXObject('Microsoft.XMLHTTP');
}
else {
req=new XMLHttpRequest();
}
}
如前所述,您可以通过将清单5中的代码添加到tag-implementation类中来向页面呈现JavaScript代码。
清单5.呈现XMLHttpRequest对象JavaScript初始化函数
/* (non-Javadoc)
* @see javax.servlet.jsp.tagext.TagSupport#doStartTag()
*/
@Override
public int doStartTag() throws JspException {
StringBuffer html = new StringBuffer();
html.append("<script type='text/javascript' language='javascript'>");
html.append("var req;");
html.append("var cursor = -1;");
// Generate functions to support Ajax
html.append("function initializeXmlHttpRequest() {");
// Support for non-Microsoft browsers (and IE7+)
html.append("if (window.ActiveXObject) {");
// Support for Microsoft browsers
html.append("req=new ActiveXObject('Microsoft.XMLHTTP');");
html.append("}");
html.append("else {");
html.append("req=new XMLHttpRequest();");
html.append("}");
JspWriter out = pageContext.getOut();
try {
out.append(html.toString());
} catch (IOException e) {
e.printStackTrace();
}
return this.SKIP_BODY;
}
现在,可以在Web页面中全局使用req变量。 清单6说明了如何进行异步调用。
清单6.使用XMLHttpRequest对象
// If req object initialized
if (req!=null) {
// Set callback function
req.onreadystatechange=stateName_onServerResponse;
// Set status text in browser window
window.status='Retrieving State data from server...';
// Open asynchronous server call
req.open('GET',dataUrl,true);
// Send request
req.send(null);
}
当请求的就绪状态更改时,将调用req.onreadystatechange指定的函数。 req.readystate包含以下状态代码之一:
- 0 =已初始化
- 1 =打开
- 2 =已发送
- 3 =接收中
- 4 =已加载
使用XML作为数据交换格式
XMLHttpRequest对象还具有responseXML属性,以检索XML数据响应。 然后可以将JavaScript的DOM用于处理。
通常,将忽略除Loaded任何内容,因为通常在服务器响应完成之前不需要进行任何操作。 异步调用的Loaded值不能保证成功。 与任何网页请求一样,有可能找不到该页面或发生了另一个问题。 如果req.status是200 ,则出问题了。 清单7显示了如何处理服务器响应。
清单7.处理对异步请求的响应
function stateName_onServerResponse() {
if(req.readyState!=4) return;
if(req.status != 200) {
alert('An error occurred retrieving data.');
return;
}
// Obtain server response
var responseData = req.responseText;
... Processing of result
}
您现在已经很好地概述了如何进行异步调用和处理响应。 下一步是开始构建第一个控件: <ajax:autocomplete/> 。
构建自动完成的JSP TagLib控件
要构建自动完成控件,需要执行以下步骤:
- 建立一个价值提供者,为控件提供建议。
- 创建一个Servlet接口以公开用于异步调用的值提供程序。
- 创建一个JSP TagLib控件,以将所有内容封装在可在JSP页面中使用的控件中。
以下各节详细说明了这些步骤。
建立价值提供者以提供有关自动完成控制的建议
价值提供者将建议列表提供给自动完成控件。 值提供程序必须实现IJsonValueProvider接口,该接口定义单个方法getValues() ,该方法返回包含建议列表的JSONArray对象。 清单8显示了该接口。
清单8. IJsonValueProvider接口
public interface IJsonValueProvider {
JSONArray getValues(String criteria, Integer maxCount);
}
JSONObject和JSONArray
这些对象是JSON for Java ( JSON for Java中使用JSON的开源包装程序)的一部分。 请参阅相关信息了解有关该库的附加信息。
下一步是创建CityValueProvider ,此接口的实现,该接口为<ajax:autocomplete/>控件提供城市数据。 请注意有关getValues()实现的以下关键事项:
- 数据是从位置数据提供程序(一种数据抽象层(DAL)组件)中检索的,该组件将所有位置缓存在内存中。
- 需要一种两阶段方法来处理数据( TreeMap包含LocationDTO对象),因为Location Data Provider返回按邮政编码排序的TreeMap 。 需要根据CityValueProvider的城市名称对结果进行排序。
清单9演示了如何完成此工作。
清单9.城市价值提供者
package com.testwebsite.bll;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeMap;
import org.json.JSONArray;
import com.testwebsite.dal.LocationDataService;
import com.testwebsite.dto.LocationDTO;
import com.testwebsite.interfaces.IJsonValueProvider;
/** @model
* @author Brian J. Stewart (Aqua Data Technologies, Inc. http://www.aquadatatech.com) */
public class CityValueProvider implements IJsonValueProvider {
/* (non-Javadoc)
* @see com.testwebsite.interfaces.IJsonValueProvider#getValues(java.lang.String)
*/
@Override
public JSONArray getValues(String criteria, Integer maxCount) {
String cityName = "";
// If city found, make the search case insensitive
if (criteria != null && criteria.length() > 0) {
cityName = criteria.toLowerCase();
}
// Get Location data from Data Provider
TreeMap<Integer, LocationDTO>
locData = LocationDataService.getLocationData();
// The LocationDataService Data Provider returns a TreeMap containing
// LocationDTO objects that are sorted by Zip Code.
// First build a temporary TreeMap (sorted list) filtering with
// only unique city names matching the specified cityName parameter
TreeMap<String, String> cityData = this.getCityData(locData, cityName);
// Finally iterate through sorted City list
// and create JSONArray containing
// the number elements specified by the maxCount parameter
JSONArray json = this.getJsonData(cityData, maxCount);
return json;
}
/**
* The getCityData method returns a TreeMap containing Cities matching the
* specified cityName criteria. The results are sorted by City Name and filter
* out any duplicate city names.
* @param locData Location Data from which to retrieve cities
* @param cityName City Name prefix to which to search
* @return
*/
protected TreeMap<String, String> getCityData(
TreeMap<Integer, LocationDTO> locData, String cityName) {
TreeMap<String, String> cityData = new TreeMap<String, String>();
// Iterate through all data looking for matching cities
// and add to temporary TreeMap
Set<Integer> keySet = locData.keySet();
Iterator<Integer> locIter = keySet.iterator();
while (locIter.hasNext()) {
// Get current state
Integer curKey = locIter.next();
LocationDTO curLocation = locData.get(curKey);
// Get current location data
if (curLocation != null) {
String curCityName = curLocation.getCity().toLowerCase();
// Add current item if it starts with the cityName parameter
if (curCityName.startsWith(cityName)) {
cityData.put(curLocation.getCity(),
curLocation.getCity());
}
}
}
return cityData;
}
/**
* The getJsonData method returns a JSONArray contain a list of strings
* with the city name specified with a maximum number of elements as specified
* by the maxCount parameter.
* @param cityData TreeMap containing unique list of matching cities
* @param maxCount Maximum number of items to include in the JSONArray
* @return JSONArray contain sorted list of city names
*/
protected JSONArray getJsonData(TreeMap<String, String>
cityData, int maxCount) {
int count = 1;
JSONArray json = new JSONArray();
// Get city name keys
Set<String> citySet = cityData.keySet();
// Iterate through query results
Iterator<String> cityIter = citySet.iterator();
while (cityIter.hasNext()) {
// Get current item
String curCity = cityIter.next();
// Add item to JSONArray
json.put(curCity);
// Increment counter
count ++;
// If maximum number of entries has been met, then exit loop
if (count >= maxCount) break;
}
return json;
}
}
创建一个Servlet以处理对值提供者的异步请求
下一步是构建AutoCompleteServlet Servlet,这是Web浏览器调用IJsonValueProvider实现的接口。 Servlet非常简单,只有一个小例外。 为了实现“轻松集成/部署”的目标,您只需要担心实现值提供程序,而不是Servlet接口。 为了支持该目标,您可以使用反射在运行时使用<ajax:autocomplete/>控件的classname属性实例化值提供程序。 参见清单10。
清单10.自动完成的Servlet
package com.testwebsite.servlets;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.json.JSONArray;
/**
* @model
* @author Brian J. Stewart (Aqua Data Technologies, Inc. http://www.aquadatatech.com)
*/
public class AutoCompleteServlet extends HttpServlet {
/**
*
*/
private static final long serialVersionUID = -867804519793713551L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String data = "";
// Get parameters from query string
String format = req.getParameter("format");
String criteria = req.getParameter("criteria");
String maxCountStr= req.getParameter("maxCount");
String className = req.getParameter("providerClass");
// If format is not null and it's 'json'
if (format != null && format.equalsIgnoreCase("json")) {
if (className != null && className.length() > 0) {
data = this.getJsonResultAsString(criteria,
maxCountStr,
className);
}
resp.setContentType("text/plain");
}
// Write response
// Get writer for servlet response
PrintWriter writer = resp.getWriter();
writer.println(data);
writer.flush();
}
public String getJsonResultAsString(String criteria,
String maxCountStr,
String className) {
String data = "";
Integer maxCount = 10;
if (maxCountStr != null && maxCountStr.length() > 0) {
maxCount = new Integer(maxCountStr);
}
// Get dataprovider class using reflection
// Construct class
Class providerClass;
try {
// Get provider class
providerClass = Class.forName(className);
// Construct method and method param types
Class[] paramTypes = new Class[2];
paramTypes[0] = String.class;
paramTypes[1] = Integer.class;
Method getValuesMethod = providerClass.getMethod("getValues",
paramTypes);
// Construct method param values
Object[] argList = new Object[2];
argList[0] = criteria;
argList[1] = maxCount;
// Get instance of the provider class
Object providerInstance = providerClass.newInstance();
// Invoke method using reflection
JSONArray resultsArray = (JSONArray)
getValuesMethod.invoke(providerInstance,
argList);
// Convert JSONArray result to string
data = resultsArray.toString();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return data;
}
}
图8显示了来自AutoCompleteServlet Servlet的服务器响应。
图8.自动完成的Servlet响应
创建可在JSP页面中使用的JSP TagLib控件
自动完成控件呈现标准的INPUT标记,设置事件处理程序,并呈现“建议列表容器” DIV元素和用于格式化的适当CSS。 您需要添加以下支持JavaScript函数:
- 处理键盘事件- <ajax:page/>
- 处理服务器响应和异步后调用处理- <ajax:autocomplete/> ,并在<ajax:page/>呈现帮助函数
- 突出显示建议列表中的指定项目- <ajax:page/>
- 隐藏建议列表- <ajax:page/>
- 处理建议列表中的项目选择(当用户按Enter时)- <ajax:page/>
- 当控件失去焦点时处理- <ajax:page/>
让我们从onSuggestionKeyDown函数开始,在其中处理Esc键,Enter键和其他控制键。 如果用户按Esc键,则建议列表应被隐藏,而JavaScript事件链中的后续事件将被取消(例如,不应处理Key Up事件,因为该事件已经通过隐藏建议列表进行了处理); 参见清单11。
清单11.处理Esc键的代码片段
var keyCode = (window.event) ? window.event.keyCode : ev.keyCode;
switch(keyCode) {
...
// Handle ESCAPE key
case 27:
hideSelectionList(curControl, suggestionList);
ev.cancelBubble = true;
// IE
if (window.event) {
ev.returnValue = false;
}
// Firefox
else {
ev.preventDefault();
}
break;
...
如果用户按下Enter键,则应将当前项目复制到输入控件,并隐藏建议列表。 要隐藏/显示建议列表,请使用标准CSS进行格式化,并结合JavaScript来更改类名。 display属性设置为none可以隐藏控件并block显示列表。 清单12显示了JavaScript函数,您可以将其添加到<ajax:page/>控件中,因为它可以与任何<ajax:autocomplete/>控件一起使用。
清单12.用于处理Enter键的代码片段
...
// Handle ENTER key
case 13:
handleSelectSuggestItem(curControl, suggestionList);
ev.cancelBubble = true;
// IE
if (window.event) {
ev.returnValue = false;
}
// Firefox
else {
ev.preventDefault();
}
break;
...
按下事件处理程序调用handleSelectSuggestItem ,它在<ajax:page/>定义(请参见清单13)。
清单13.处理Enter键
function handleSelectSuggestItem(curControl, suggestionList) {
// Get selected node
// Cursor is a global variable that is incremented/decremented
// when the UP ARROW or DOWN ARROW key is pressed.
var selectedNode = suggestionList.childNodes[cursor];
// Get selected value
var selectedValue = selectedNode.childNodes[0].nodeValue;
// Set the value of the INPUT control
curControl.value = selectedValue;
// Finally hide the selection list
hideSelectionList(curControl, suggestionList);
}
function hideSelectionList(curControl, suggestionList) {
// If suggestion not found
if (suggestionList == null || suggestionList == undefined) {
return;
}
// Clear the suggestion list elements
suggestionList.innerHTML='';
// Toggle display to none
suggestionList.style.display='none';
curControl.focus();
}
只需按一下控制键(Shift,Alt和Ctrl)即可处理很多事情。 您需要通过执行以下操作来忽略这些键盘事件:
- 通过将EVENT对象上的returnValue设置为false (对于Internet Explorer)并在EVENT对象上执行preventDefault() (对于Firefox),可以防止在返回期间更改输入控件。
- 通过将EVENT对象上的cancelBubble属性设置为true来取消键盘事件的事件链
清单14中显示了onSuggestionKeyDown的完整代码。
清单14.完整的按键事件处理程序
function onSuggestionKeyDown(curControl, ev) {
// Get suggestion list container
var suggestionList= document.getElementById(curControl.id + '_suggest');
// Get key code of key pressed
var keyCode = (window.event) ? window.event.keyCode : ev.keyCode;
switch(keyCode) {
// Ignore certain keys
case 16, 17, 18, 20:
ev.cancelBubble = true;
// IE
if (window.event) {
ev.returnValue = false;
}
// Firefox
else {
ev.preventDefault();
}
break;
// Handle ESCAPE key
case 27:
hideSelectionList(curControl, suggestionList);
ev.cancelBubble = true;
// IE
if (window.event) {
ev.returnValue = false;
}
// Firefox
else {
ev.preventDefault();
}
break;
// Handle ENTER key
case 13:
handleSelectSuggestItem(curControl, suggestionList);
ev.cancelBubble = true;
// IE
if (window.event) {
ev.returnValue = false;
}
// Firefox
else {
ev.preventDefault();
}
break;
}
}
按键事件处理程序稍微更有趣。 如果用户按下向上箭头或向下箭头键,则突出显示的选择将发生变化。 如果用户输入的字符数最少(默认值:3),则应对服务器进行异步调用以填充建议列表。
如果用户按下向上箭头或向下箭头键,则全局cursor变量将相应地增加/减少。 cursor变量跟踪当前选定的项目。 然后调用highlightSelectedNode函数以突出显示该值。 参见清单15。
清单15.用于处理Up Arrow和Down Arrow键的key-up事件处理程序的代码片段
...
switch(keyCode) {
// Ignore ESCAPE
case 27:
// Handle UP ARROW
case 38:
if (suggestionList.childNodes.length > 0 && cursor > 0){
var selectedNode = suggestionList.childNodes[--cursor];
highlightSelectedNode(suggestionList, selectedNode);
}
break;
// Handle DOWN ARROW
case 40:
if (suggestionList.childNodes.length > 0 &&
cursor < suggestionList.childNodes.length-1) {
var selectedNode = suggestionList.childNodes[++cursor];
highlightSelectedNode(suggestionList, selectedNode);
}
break;
...
清单16显示了highlightSelectedNode函数,用于突出显示一个项目。 CSS规则为选定和未选定的项目定义。 使用JavaScript切换className 。 然后,将删除先前选择的元素的高光。
清单16.在建议列表中突出显示一个项目
function highlightSelectedNode(suggestionList, selectedNode) {
if (suggestionList == null || selectedNode == null) {
return;
}
// Iterate through all items searching for a node that
// matches the node selected
for (var i=0; i < suggestionList.childNodes.length; i++) {
var curNode = suggestionList.childNodes[i];
if (curNode == selectedNode){
curNode.className = 'autoCompleteItemSelected'
} else {
curNode.className = 'autoCompleteItem';
}
}
}
如果用户按下任何其他键并至少输入了最少字符数,则会对服务器进行异步调用以检索建议的JSON数组。 就绪状态更改后,将调用req.onreadystatechange属性中指定的函数(请参见清单17)。
清单17.处理任何其他键的代码片段
// If control not found (shouldn't happen)
// or minimum number of characters not entered
if (curControl == null ||
curControl.value.length < minChars) {
// Hide selected item
hideSelectionList(curControl, suggestionList);
return;
}
// Initialize XMLHttpRequest object
initializeXmlHttpRequest();
// If req object initialized
if (req!=null) {
// Set callback function
req.onreadystatechange=cityName_onServerResponse;
// Set status text in browser window
window.status='Retrieving State data from server...';
// Open asynchronous server call
req.open('GET',dataUrl,true);
// Send request
req.send(null);
}
调用服务器响应函数时,将检查readyState以确保其Loaded 。 还检查status 。 如果一切顺利,则使用eval JavaScript函数将JSON数组的字符串表示形式转换为数组。 然后将数组传递给populateSuggestionList函数,该函数将元素添加到“建议列表”。 清单18显示了服务器响应功能。
清单18.服务器响应处理程序(由<ajax:autocomplete/>控件动态生成)
function cityName_onServerResponse() {
// If loaded
if(req.readyState!=4) {
return;
}
// If an error occurred
if(req.status != 200) {
alert('An error occurred retrieving data.');
return;
}
// Get response and convert it to an array
var responseData = req.responseText;
var dataValues=eval('(' + responseData + ')');
// Get current control
var curControl = document.getElementById('cityName');
/// Populate suggestion list for control
populateSuggestionList(curControl, dataValues);
}
在<ajax:page/>控件中呈现的populateSuggestionList函数负责使用异步服务器调用返回的值填充建议列表。 然后遍历该数组,并为数组中的每个项目创建一个DIV元素。 DIV元素已添加到建议列表。 清单19显示populateSuggestionList 。
清单19.填充建议列表(在<ajax:page />控件中呈现)
populateSuggestionList(curControl, dataValues) {
// Get Suggest List Container for control
var container = document.getElementById(curControl.id + '_suggest');
// If container not found (shouldn't happen), then simply return
if (container == null) { return; }
// Clear suggestion list container
container.innerHTML = '';
// If no values return, hide suggestion list
if (dataValues.length < 1) {
container.style.display='none';
return;
}
// Show suggestion list
container.style.display='block';
container.style.top =
(curControl.offsetTop+curControl.offsetHeight) + 'px';
container.style.left = curControl.offsetLeft + 'px';
// Iterate through all values
// 1. Create DIV element
// 2. Set attributes and text node value
// 3. Append new element to the container
for(var i=0;i < dataValues.length;i++) {
// Get current value
var curValue= dataValues[i];
// If value is not blank
if (curValue != null && curValue.length > 0 ) {
// Create DIV element
var newItem = document.createElement('div');
// Append current value as a text node
newItem.appendChild(document.createTextNode(curValue));
// Set attributes
newItem.setAttribute('class', 'autoCompleteItem');
// Finally append new element to container
container.appendChild(newItem);
}
}
// Set first item as the selected node
cursor = 0;
// Get first node
var selectedNode = container.childNodes[cursor];
// If first node is equal to the first node, hide the selection list
if (selectedNode.childNodes[0].nodeValue == curControl.value) {
hideSelectionList(curControl, container);
}
else {
// Highlight the first node
highlightSelectedNode(container, selectedNode);
}
}
自动完成TagLib库定义条目
清单20包含自动完成控件的TagLib库定义条目(带有用于每个属性描述的嵌入式注释)。
清单20.自动完成的TagLib库定义条目
<tag>
<name>autocomplete</name>
<tagclass>com.testwebsite.controls.AutoCompleteTag</tagclass>
<bodycontent>JSP</bodycontent>
<info>
Auto-complete/suggest form input fields based on a specified value.
</info>
<!-- Unique identifier for control -->
<attribute>
<name>id</name>
<required>true</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
<!-- Minimum string length before submitting asynchronous request -->
<attribute>
<name>minimumlength</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<!-- Maximum number of items to include in suggestion list -->
<attribute>
<name>maxcount</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<!-- Width of control -->
<attribute>
<name>width</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<!-- Value of control -->
<attribute>
<name>value</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<!-- Data Url for asynchronous call. A default Servlet has been created,
but for greater flexibility, a Web Service or another Servlet can be
specified-->
<attribute>
<name>dataurl</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
<!-- Class that provides suggest value list for control
(Used if dataUrl not specified -->
<attribute>
<name>providerclass</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
</tag>
构建级联下拉JSP TagLib控件
通常,业务线应用程序包括选择列表,其值取决于其他表单字段(例如,取决于产品类别的产品名称)。
在使用Ajax和异步Web编程技术之前,您被迫将所有值呈现到Web页面(通常在JavaScript数组中)并在JavaScript中动态填充这些值。 JavaScript数组是多维数组或包含的令牌,例如| |。 用于限定级联值的字符。 或者,将刷新整个页面以检索级联选择列表的值。 当您处理大型数据集或尝试构建用户友好的Web应用程序时,这两种方法都没有吸引力。 借助Ajax和异步技术,您现在可以提供通常仅在桌面应用程序中才能提供的丰富而直观的用户体验。
以下各节描述了创建级联下拉控件的步骤:
- 创建一个可以从JavaScript调用的值提供程序/ Servlet接口。
- 创建JSP TagLib控件以将所有内容封装在可以放入任何JSP页面中的控件中。
创建值提供者和接口(Servlet)
类似于为自动完成控件创建的值提供程序,您创建了一个Servlet以返回包含值的JSON数组。 用于级联控件的值提供者需要花费更多的精力,因为需求和数据通常需要单独的Servlet或Web服务才能应用业务规则。 或者,您可以使用嵌入式JSP TagLib控件(另一个标签的主体中包含的控件),但这会使事情变得有些复杂。 使用单独的Servlet在将数据返回给客户端方面提供了更大的灵活性。 这些值可以取决于Servlet中定义的其他表单字段或其他复杂的业务规则。
清单21显示了级联下拉控件的两个值提供程序。 第一个是城市价值提供者,它取决于州的价值。 第二个价值提供者是县,取决于州和城市的价值。 两个Servlet均返回JSON数组并使用Location Data Provider(DAL组件)。 该代码类似于为自动完成控件开发值提供程序。 关键区别在于,您使用单独的Servlet来保持实现的简单性和灵活性。
清单21. CityServlet Servlet
package com.testwebsite.servlets;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.testwebsite.bll.LocationService;
public class CityServlet extends HttpServlet {
private static final long serialVersionUID = 3231866266466404450L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String data = null;
// Get parameters from query string
String format = req.getParameter("format");
String cityName = req.getParameter("cityName");
String stateName = req.getParameter("stateName");
// If format is not null and it's 'json'
if (format != null & format.equalsIgnoreCase("json")) {
// Get city data based on state name and city name prefix
data = LocationService.getCitiesAsJson(cityName, stateName);
resp.setContentType("text/plain");
}
// Write response
// Get writer for servlet response
PrintWriter writer = resp.getWriter();
writer.println(data);
writer.flush();
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
super.doPost(req, resp);
}
}
/**
* @model
* @author Brian J. Stewart (Aqua Data Technologies, Inc. http://www.aquadatatech.com)
*
*/
public class CountyServlet extends HttpServlet {
/**
*
*/
private static final long serialVersionUID = 3231866266466404450L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String data = null;
// Get parameters from query string
String format = req.getParameter("format");
String cityName = req.getParameter("cityName");
String stateName = req.getParameter("stateName");
String countyName = req.getParameter("countyName");
// If format is not null and it's 'json'
if (format != null && format.equalsIgnoreCase("json")) {
data = LocationService.getCountiesAsJson(countyName,
stateName, cityName);
resp.setContentType("text/plain");
}
// Write response
// Get writer for servlet response
PrintWriter writer = resp.getWriter();
writer.println(data);
writer.flush();
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
super.doPost(req, resp);
}
}
创建JSP TagLib控件
级联下拉控件的工作方式如下:
- 该页面使用空的SELECT控件呈现。
- 用户选择SELECT控件。 当它获得焦点时,将进行异步调用以从服务器检索值。
- 服务器将值的JSON数组发送回客户端。
- 客户端使用JSON数组中的值动态填充SELECT控件。
- 用户从列表中选择一个值并且控件失去焦点(在blur事件上)之后,将清除依赖于当前字段的控件。 这样做是为了保持数据完整性(如果State值更改,则City值可能无效)。
<ajax:page/>控件呈现<ajax:page/>所有级联下拉控件均可使用的通用功能,而<ajax:dropdown/>控件呈现特定于单个控件实例JavaScript。
为级联下拉控件呈现SELECT控件
SELECT控件的呈现非常简单。 呈现onfocus和onblur事件的事件处理程序,如清单22所示。
清单22.为级联下拉控件呈现SELECT控件
...
/**
* The getSelectControlHtml method returns the html code to render the drop down (html
* select) control.
* @return Html code for drop down (html select) control
*/
protected String getSelectControlHtml() {
StringBuffer html = new StringBuffer();
// Render dropdown/select control
html.append("<select id='");
html.append(this.getId());
// Render on focus event handler
html.append("' onfocus='");
html.append(this.getId());
html.append("_onSelect(this)'");
// Render on change event handler
html.append(" onChange='");
html.append(this.getId());
html.append("_onChange(this)'");
// Render css class if specified
if (this.getCssclass() != null && this.getCssclass().length() > 0) {
html.append(" class='");
html.append(this.getCssclass());
html.append("'");
}
// Render width if applicable (not 0/default/auto-fit)
if (this.getWidth() > 0) {
html.append(" style='width:");
html.append(this.getWidth());
html.append("px'");
}
html.append("/>");
return html.toString();
}
...
级联下拉控件的事件处理程序
在onSelect事件处理程序中,从中检索级联当前控件的控件的值,并构建URL以将异步请求发送到服务器。 接收到响应后,使用JavaScript使用JSON数组中返回的值填充SELECT标签(请参见清单23)。
清单23.选择事件处理程序
function stateName_onSelect(curControl) {
if(curControl.options.length > 0) {
return;
}
clearOptions(curControl);
// Set waiting message in control
var waitingOption = new Option('Retrieving values...','',true,true);
curControl.options[curControl.options.length]=waitingOption;
// The dataUrl is built dynamically based on the cascadeTo control
var dataUrl = '/TestWebSite/State?format=json&stateName=' +
getSelectedValue('stateName');
// Initialize the XMLHttpRequest object
initializeXmlHttpRequest();
// If initialization was successful
if (req!=null) {
// Set callback function
req.onreadystatechange=stateName_onServerResponse;
// Set status text in browser window
window.status='Retrieving State data from server...';
// Open asynchronous server call
req.open('GET',dataUrl,true);
// Send request
req.send(null);
}
}
服务器响应处理程序CONTROL-NAME_onServerResponse发生以下事情,该事件由级联下拉控制标签动态生成:
- 除非Loaded否则忽略状态更改
- 异步调用期间发生错误时通知用户
- 获取当前控件,并清除所有OPTION元素
- 获取响应数据,并将其转换为包含字符串的数组
- 使用数组填充SELECT控件
清单24由<ajax:dropdown/>控件动态呈现。
清单24.服务器响应处理程序(由控件动态生成)
function cityName_onServerResponse() {
// If not finished, then return
if(req.readyState!=4) {
return;
}
// If an error occurred notify user and return
if(req.status != 200) {
alert('An error occurred retrieving data.');
return;
}
// Get current control
var curControl = document.getElementById('cityName');
// Clear options
clearOptions(curControl);
// Get response data
var responseData = req.responseText;
// Convert to array
var dataValues=eval('(' + responseData + ')');
// Populate SELECT tag with OPTION elements
populateSelectControl(curControl, dataValues);window.status='';
}
由<ajax:page/>标记生成的populateSelectControl函数向SELECT控件添加一个空白OPTION ,以及dataValues数组中每个值的OPTION元素。 清单25显示了动态生成的代码片段。
清单25.填充SELECT控件
function populateSelectControl(curControl, dataValues) {
// Append blank option
var blankOption= new Option('','',false,true);
curControl.options[curControl.options.length]=blankOption;
// Iterate through data value array
for (var i=0;i<dataValues.length;i++) {
// Create option
var newOption= new Option(dataValues[i],dataValues[i],false,false);
// Add option to control options
curControl.options[curControl.options.length]=newOption;
}
}
在onChange事件处理程序中,将清除所有依赖于当前控件的控件(请参见清单26)。
清单26.变更事件处理程序
function stateName_onChange(curControl) {
// Array dynamically generated by the control
var toList=['cityName','countyName'];
// If no controls are dependent on this function, simply return
if (toList == null || toList.length == 0) {
return;
}
// Iterate through list of controls that are dependent on
// the current control
for (var i=0; i < toList.length; i++) {
// Get current control name
var curControlName = toList[i];
// Get current control
var curToControl = document.getElementById(curControlName);
// If control not found, then exit
if (curToControl == null) return;
// Clear the current control
clearOptions(curToControl);
}
}
清除父SELECT控件中所有项的clearOptions函数在<ajax:page/>控件中呈现(请参见清单27)。
清单27.变更事件处理程序
function clearOptions(curControl) {
// If current control is null then exit
if (curControl == null) {
alert('Unable to clear control');
return;
}
// Check if control is already blank and return if it is
if (curControl.options.length < 1) {
return;
}
// Clear the options
curControl.options.length = 0;
}
级联下拉TagLib库定义条目
清单28显示了级联下拉控件的TagLib库定义条目(带有嵌入式注释的每个属性的描述)。
清单28.级联下拉TagLib库定义条目
<tag>
<name>dropdown</name>
<tagclass>com.testwebsite.controls.DropDownTag</tagclass>
<bodycontent>empty</bodycontent>
<info>
Populates Drop Down control asynchronously cascading values.
</info>
<!-- Unique identifier for control -->
<attribute>
<name>id</name>
<required>true</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
<!-- Url for Value Provider -->
<attribute>
<name>dataurl</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<!-- Message displayed while retrieving values from Value Provider -->
<attribute>
<name>updatemessage</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
<!-- CSS class name -->
<attribute>
<name>cssclass</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
<!-- Current control value-->
<attribute>
<name>value</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<!-- Comma separated list of control id from which the current
control cascades -->
<attribute>
<name>cascadefrom</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<!-- Comma separated list of control id to which the current control cascades -->
<attribute>
<name>cascadeto</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<!-- Width of control -->
<attribute>
<name>width</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
建立测试网页
下一步是构建示例页面以测试启用Ajax的控件。 您将在Create New Contact页面上测试<ajax:autocomplete/>控件,并在Create New Employee页面上测试<ajax:dropdown/>控件。
建立新联络人
图9显示了测试“创建新联系人”页面,该页面演示了从用户角度看自动完成控件的外观。
图9. Create New Contact页面演示了如何使用自动完成控件
清单29中显示了此测试页面的JSP代码。
清单29.演示如何使用自动完成控件的示例页面
<%@ page contentType="text/html; charset=ISO-8859-5" %>
<%@ taglib prefix="ajax"
uri="/WEB-INF/tlds/ajax_controls.tld"%>
<html>
<head>
<title>New Contact Information</title>
<link href="core.css" rel="stylesheet"
type="text/css" />
<ajax:page/>
</head>
<body>
<div id="container">
<form>
<div class="dialog">
<div class="dialogTitle">
Contact Information
</div>
<div class="contentPane">
<div style="font-weight:bold">First Name:</div>
<div>
<input type="text" id="firstName"
size="40"/>
</div>
<div style="font-weight:bold">Last Name:</div>
<div>
<input type="text" id="lastName"
size="40"/>
</div>
<div style="font-weight:bold">Address:</div>
<div>
<input type="text" id="streetAddress"
size="40"/>
</div>
<div style="font-weight:bold">City:</div>
<div>
<ajax:autocomplete id="cityName" width="40"
providerclass="com.testwebsite.bll.CityValueProvider"/>
</div>
<div style="font-weight:bold">County:</div>
<div>
<input type="text" id="countyName"
size="40"/>
</div>
<div style="font-weight:bold">Zip Code:</div>
<div>
<input type="text" id="zipCode"
size="40"/>
</div>
</div>
<div class="buttonPane">
<input type="reset" />
<input type="submit" value="Save"/>
</div>
</div>
</form>
</div>
</body>
</html>
现在,城市名称字段已启用Ajax。 当用户在“城市名称”字段中输入文本时,将动态显示建议,类似于Google的自动建议。
创建新员工
图10显示了Create New Employee页面,该页面从用户的角度说明了级联下拉控件。
图10.测试页面展示了级联下拉控件
清单30显示了此页面的JSP代码。
清单30.演示使用级联下拉控件的示例页面
<%@ page contentType="text/html; charset=ISO-8859-5" %>
<%@ taglib prefix="ajax" uri="/WEB-INF/tlds/ajax_controls.tld"%>
<html>
<head>
<title>New Employee</title>
<ajax:page/>
<link href="core.css" rel="stylesheet"
type="text/css" />
</head>
<body>
<div id="container">
<form>
<table class="dialog" cellspacing="0"
cellpadding="0">
<thead>
<tr>
<td class="dialogTitle" colspan="2">
Employee Information
</td>
</tr>
</thead>
<tbody>
<tr>
<td class="fieldLabel">
Last Name:
</td>
<td class="fieldValue">
<input type="text" id="lastName"
size="40"/>
</td>
</tr>
<tr>
<td class="fieldLabel">
First Name:
</td>
<td class="fieldValue">
<input type="text" id="firstName"
size="40"/>
</td>
</tr>
<tr>
<td class="fieldLabel">
Address:
</td>
<td class="fieldValue">
<input type="text" id="streetAddress"
size="40"/>
</td>
</tr>
<tr>
<td class="fieldLabel">
State:
</td>
<td class="fieldValue">
<ajax:dropdown id="stateName" dataurl="/State"
width="240"
updatemessage="Retrieving State data from server..."
cascadeto="cityName,countyName" />
</td>
</tr>
<tr>
<td class="fieldLabel">
City:
</td>
<td class="fieldValue">
<ajax:dropdown id="cityName" dataurl="/City"
updatemessage="Retrieving City data from server..."
cascadeto="countyName" width="240"
cascadefrom="stateName" />
</td>
</tr>
<tr>
<td class="fieldLabel">
County:
</td>
<td class="fieldValue">
<ajax:dropdown id="countyName" dataurl="/County"
updatemessage="Retrieving County data from server..."
cascadefrom="stateName,cityName" width="240"/>
</td>
</tr>
<tr>
<td class="fieldLabel">
Zip Code:
</td>
<td class="fieldValue">
<input type="text" id="zipCode"
size="40" />
</td>
</tr>
</tbody>
<tfoot align="right" class="buttonPane">
<tr>
<td colspan="2">
<input type="reset" />
<input type="submit" value="Save"/>
</td>
</tr>
</tfoot>
</table>
</form>
</div>
</body>
</html>
结论
在本文中,您学习了一些异步通信技术,以及如何通过可重用的JSP TagLib控件向业务线应用程序添加JSON和Ajax。 得益于改进的用户体验以及响应更快,更直观的用户界面,业务线应用程序可以从基于Ajax的控件中受益匪浅。 代码不是很复杂。 您只需要集成关键模块(JavaScript,CSS和J2EE技术)即可构建启用Ajax的JSP控件。
您可以进一步扩展控件以执行以下操作:
- 支持与其他类型的控件进行级联(除SELECT控件外)
- 将鼠标事件处理添加到自动完成控件
- 添加编码并检查异步请求
翻译自: https://www.ibm.com/developerworks/web/library/wa-aj-auto/index.html
js设置控件禁用和启用