3.8 使用JavaScript动态改变选项
2007-08-28 21:07
253 查看
3.8 使用JavaScript动态改变选项
问题
想用JavaScript来动态改变基于应用模型所得数据的HTML select元素中的显示项。
解决方案
使用Struts logic:iterate标签为不同的选项组创建JavaScript数组,然后使用JavaScript的onchange事件处理器在运行时改变options集合。例3.8显示了一个完整的JSP页,其中使用Struts标签动态创建了JavaScript 数组。changeOptions事件处理函数使用JavaScript数组重置了select控制选项。
例3.8:使用Struts生成DHTML
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>
<html>
<head>
<title>Struts - JavaScript Example</title>
<script language="JavaScript">
// Create the array for the first set of options
fooArray = new Array();
<logic:iterate id="fooValue" indexId="ctr"
name="MyForm" property="fooList">
fooArray[<bean:write name="ctr"/>] =
new Option("<bean:write name='fooValue'/>",
"<bean:write name='fooValue'/>",
false, false);
</logic:iterate>
// Create the array for the second set of options
barArray = new Array();
<logic:iterate id="barValue" indexId="ctr"
name="MyForm" property="barList">
fooArray[<bean:write name="ctr"/>] =
new Option("<bean:write name='barValue'/>",
"<bean:write name='barValue'/>",
false, false);
</logic:iterate>
function changeOptions(var control) {
// control is the triggering control
// baz is the select control
baz = document.MyForm.baz;
baz.options.length=0;
if (control.value == 'Foo')
bazArray = fooArray;
else
bazArray = barArray;
for (i=0; i < bazArray.length; i++)
baz.options[i] = bazArray[i];
}
</script>
</head>
<body>
<html:form name="MyForm" action="processMyForm">
<html:radio property="fooBar" value="Foo"
onclick="changeOptions(this);"/> Foo<br/>
<html:radio property="fooBar" value="Bar"
onclick="changeOptions(this);"/> Bar<br/>
Baz: <html:select property="baz">
</html:select>
</html:form>
</body>
</html>
讨论
可以像使用Struts生成HTML一样生成JavaScript 。一些开发人员认为JavaScript 是“邪恶”的,而实际上它只是“小小的坏”而已。举个实用性的例子,JavaScript可使应用性更好,使用户更满意时,您肯定会使用它。但如此使用会使您的业务逻辑仅仅停留在“业务”这一层面,并不会使页面脱颖而出。Struts可以帮助您改善这一点。
可以用一个具体的例子来解释这个方法,设想现在想向一个用户询问他最喜欢的编程语言和对于所选语言最喜欢的IDE。语言将用单选按钮选择,而IDE则从一个下拉菜单中选择。如果使用的语言是Java,IDE下拉菜单将显示Eclipse,Net Beans,IDEA等选项。如果使用的语言是C#,下拉菜单会显示Visual Studio和SharpDevelop。
例3.9显示了保存这些数据的action表单。
例3.9:最喜欢的语言/IDE的ActionForm
package com.oreilly.strutsckbk;
import org.apache.struts.action.ActionForm;
public final class MyForm extends ActionForm {
private static String[] javaIdes =
new String[] {"Eclipse", "IDEA", "JBuilder",
"JDeveloper", "NetBeans"};
private static String[] csharpIdes =
new String[] {"SharpDevelop", "Visual Studio"};
public String[] getJavaIdes() {return javaIdes;}
public String[] getCsharpIdes() {return csharpIdes;}
public String getLanguage() {
return language;
}
public void setLanguage(String language) {
this.language = language;
}
public String getIde() {
return ide;
}
public void setIde(String ide) {
this.ide = ide;
}
private String language;
private String ide;
}
例3.10显示了显示输入页的JSP (favorite_language.jsp)。这个例子类似于解决方案。
例3.10:此JSP页使用Struts的DTHML
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>
<html>
<head>
<title>Struts - JavaScript Example</title>
<script language="JavaScript">
// Create the array for the first set of options
javaIdesArray = new Array();
<logic:iterate id="ide" indexId="ctr"
name="MyForm" property="javaIdes">
javaIdesArray[<bean:write name="ctr"/>] =
new Option("<bean:write name='ide'/>",
"<bean:write name='ide'/>",
false, false);
</logic:iterate>
// Create the array for the second set of options
csharpIdesArray = new Array();
<logic:iterate id="ide" indexId="ctr"
name="MyForm" property="csharpIdes">
csharpIdesArray[<bean:write name="ctr"/>] =
new Option("<bean:write name='ide'/>",
"<bean:write name='ide'/>",
false, false);
</logic:iterate>
function changeOptions(control) {
ideControl = document.MyForm.ide;
ideControl.options.length=0;
if (control.value == 'Java')
ideArray = javaIdesArray;
else
ideArray = csharpIdesArray;
for (i=0; i < ideArray.length; i++)
ideControl.options[i] = ideArray[i];
}
</script>
</head>
<body>
<html:form action="/admin/ViewFavoriteLanguage">
What's your favorite programming language?<br/>
<html:radio property="language" value="Java"
onclick="changeOptions(this);"/> Java<br/>
<html:radio property="language" value="C-Sharp"
onclick="changeOptions(this);"/> C-Sharp<br/>
<p>What's your favorite development tool?<br/>
IDE: <html:select property="ide"/>
</p>
<html:submit/>
</html:form>
</body>
</html>
head元素中嵌套的script块包含JavaScript。logic:iterate标签循环Javabean属性创建了两个JavaScript数组:一个用于Java IDE,一个用于C# IDE。每一个数组都包含Option JavaScript对象,Option对象代表了某一HTML select控件中的一个选项。这个对象在构造器中有4个参数:要显示的文本值、表单提交时传递的值、一个Boolean值表示值是否是选定的默认值、另一个Boolean值表示值是否是当前的选择。
更改选项的JavaScript函数位于logic:iterate循环之后。这个函数是纯静态的JavaScript,触发更改的单选按钮当作一个参数传递到函数。如果单选按钮控件的当前值是Java,那么select控件填入的是表示Java IDE的Option对象。否则,控件填入的是代表C# IDE的Option对象。
HTML主体包含表单使用Struts html标签来显示。Struts标签支持JavaScript通过onfunction 属性来改变监听器。对于单选按钮而言,onclick 监听器工作正常。传递到函数中的参数“this”是对HTML单选按钮的一个引用。该页最初的显示应该如图3.1所示。
图3.1:使用DHTML和Struts的表单
一旦点击单选按钮中的某一个,IDE文件下拉菜单中的选项将填入最初从form bean中得到的数据。图3.2说明了点击Java单选按钮时出现的显示界面。
图3.2:动态显示下拉菜单
类似地,如果点击C-Sharp单选按钮,下拉菜单中的值会针对相应的JavaScript数组进行改变。
JSTL可以用来代替Struts bean和logic标签。在这里,使用JSTL c:forEach和c:out 标签来代替logic:iterate和bean:write。这些标签和Struts标签以同样的方法生成JavaScript数组:
javaIdesArray = new Array( );
<c:forEach var="ide" varStatus="status"
items="${MyForm.javaIdes}">
javaIdesArray[<c:out value="${status.index}"/>] =
new Option("<c:out value='${ide}'/>",
"<c:out value='${ide}'/>",
false, false);
</c:forEach>
JavaScript编程是恼人的,特别是对于已习惯强类型和编译时检查的Java开发人员。但在这种动态客户端交互作用的指引下可以带来更加丰富的终端用户经验。
相关链接
对于JavaScript编程来说,David Flanagan所著的JavaScript: The Definitive Guide by David Flanagan (O’Reilly出版)很有价值。如果需要用业务逻辑来决定动态数据,可参见3.9节的方法。
提示:这个问题和避免使用JavaScript 不相关。相反,它说明如何从客户端的javascript事件监听器中调用一个Struts action。
例3.11:使用JavaScript提交表单
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<html>
<head>
<title>Struts - JavaScript Example</title>
<script language="JavaScript">
function getOptions(control) {
form = control.form;
form.action = "SetOptions.do?someProp=";
form.action += control.value;
form.submit();
}
</script>
</head>
<body>
<html:form action="ProcessMyForm">
<html:radio property="someProp1" value="val1"
onclick="getOptions(this);"/> Value 1<br/>
<html:radio property="language" value="val2"
onclick="getOptions(this);"/> Value 2<br/>
SomeProp2:
<html:select property="someProp2">
<html:optionsCollection property="prop2Values"/>
</html:select>
</p>
<html:submit/>
</html:form>
</body>
</html>
本节解决了3.8节中描述的问题。但是这里的解决方案并不依赖于将数据合并到JavaScript函数中,onclick事件处理器调用的函数把表单提交到不同的URL和action,而不是表单的action属性所指定的。这个备选URL控制一个Action,惟一目的是决定出显示在select控制器中的新选项集。这个Action 转发到原始的JSP页,并在那里填入基于新值的下拉菜单。
为了改变某一HTML控制中的值而创建一个单独的Action似乎有些“小题大做”。而这里的技术示范提供了一个灵活的解决方案,它在动态HTML后借助了服务器的强大功能。设想一下,要计算的一个财务数据字段来自同一页另一个字段的值,执行计算的服务应该被一个Action调用。这里提供的方案可以很好地解决这个问题。
以一个具体例子来进一步说明,3.8 节用的方法会被本节中另一详细的方法代替。这个例子提供了一个输入表单,在这里用户可以输入自己喜欢的编程语言和IDE。IDE的选项根据选择的编程语言而定。例3.12显示的是表单的JSP页(favorite_language2.jsp)。
例3.12:将表单提交到备用URL
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<html>
<head>
<title>Struts - JavaScript Example</title>
<script language="JavaScript">
function getOptions(control) {
form = control.form;
form.action = "GetIdeOptions.do?language=";
form.action += control.value;
form.submit();
}
</script>
</head>
<body>
<html:form action="ViewFavoriteLanguage">
What's your favorite programming language?<br/>
<html:radio property="language" value="Java"
onclick="getOptions(this);"/> Java<br/>
<html:radio property="language" value="C-Sharp"
onclick="getOptions(this);"/> C-Sharp<br/>
<p>What's your favorite development tool?<br/>
IDE:
<html:select property="ide">
<html:optionsCollection property="ides"/>
</html:select>
</p>
<html:submit/>
</html:form>
</body>
</html>
struts-config.xml文件中的action元素指定了表单使用的URL路径。第一个映射,/ FavoriteLanguage2,指定了转发到例3.12中JSP的action。第二个映射,/ GetIdeOptions,指定了用户点击单选按钮时调用的action。最后一个映射,/ ViewFavoriteLanguage,指定了submmit按钮按下时处理表单的action。
<action path="/FavoriteLanguage2"
name="MyForm"
scope="session"
type="org.apache.struts.actions.ForwardAction"
parameter="/favorite_language2.jsp"/>
<action path="/GetIdeOptions"
name="MyForm"
scope="session"
type="com.oreilly.strutsckbk.GetIdeOptionsAction">
<forward name="success" path="/FavoriteLanguage2.do"/>
</action>
<action path="/ViewFavoriteLanguage"
name="MyForm"
scope="session"
type="org.apache.struts.actions.ForwardAction"
parameter="/view_favorite_language.jsp"/>
这个难题的最后一部分是GetIdeOptionsAction本身,如例3.13所示。
例3.13:备用URL的action
package com.oreilly.strutsckbk;
import java.util.ArrayList;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.util.LabelValueBean;
public final class GetIdeOptionsAction extends Action {
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
MyForm myForm = (MyForm) form;
String language = myForm.getLanguage();
ArrayList ides = new ArrayList();
if (language.equals("Java")) {
ides.add(new LabelValueBean("Net Beans","Net Beans"));
ides.add(new LabelValueBean("Eclipse", "Eclipse"));
ides.add(new LabelValueBean("jEdit", "jEdit"));
}
else if (language.equals("C-Sharp")) {
ides.add(new LabelValueBean("Sharp Develop", "Sharp Develop"));
ides.add(new LabelValueBean("Visual Studio", "Visual Studio"));
}
myForm.setIdes( ides );
// Forward control to the specified success URI
return (mapping.findForward("success"));
}
}
此类负责获取MyForm中被选择的语言。然后Action设置了一个集合,其中包含相应的进入表单的IDE名称。简而言之,这个 Action直接创建了集合。在实际应用中,这些值可能来自业务层,也可能来自某一数据库。最后,Action返回成功的“转发”,循环回到最初的Action。
提示:这个方法的结果是需要在会话范围定义ActionForm。这样一来,主JSP页面就可以在从另外的Action重新提交回原始的页面时反映出数据的变化。
对于这个例子,内置的ForwardAction处理表单并直接将请求转发给JSP页。但如果是用自定义Action,就考虑继承DispatchAction并且将附属action实现为DispatchAction 的一个方法。此方式允许您把相关的代码保存在一起,使软件更容易维护。
<bean:write name="myForm" property="freeText" filtered="false"/>
在使用JSTL时允许未过滤的值:
<c:out value="${myForm.freeText}" escapeXml="false"/>
表3.4:过滤字符
但有时,您希望显示的文本中包含HTML标签。假设有一个在线日志应用程序,它允许用户输入将显示在某一页面中的文本。HTML允许用户使用标签使文本以黑体或斜体的形式出现。该文本能够包含超链接、不同的字号和图片。在其他情况下,您的应用也许会从其他资源(如另一个URL、某一XML文件、某一Web服务或者某一数据库)获取HTML模板文本。
通过将bean:write 标签的filtered属性设置为false,就可以指示Struts标签不用相应的实体替换特殊字符。首先来看过滤是怎样工作的。例如,用户将以下文本输入表单:
Struts <b>rocks</b>!
现在使用bean:write标签显示这个文本。当filtered属性设置为true时,字符实体文本取代了特殊字符,如下所示:
Struts <b>rocks</b>!
这也许不是用户想要的。他希望结果看起来是"Struts rocks!"。但是,由于允许用户输入有修饰的HTML标签,filtered的false属性曲解了原来的意思:
Struts <b>rocks</b>!
浏览器将认出标签并如期应用HTML标签。
显示一个网页时,这是一个很有用的机制。但使用这个方法必须仔细,假如数据没有过滤,HTML的页面布局会被破坏,整个网页也都会被破坏。例如,输入如下的 文本:
Struts <b>rocks<b>!
乍看起来没有问题。但注意,正斜杠丢失了,它应该出现在b(bold)元素之前。这个错误很容易忽视,它会使该页中其余的所有文本都变成黑体!
不幸的是,这个错误很难避免。最好的办法是确保输入的数据是有效的HTML。一个方法是通过XML解析器处理数据,这会检测出存在标签不对称等问题;另一个选择是通过一个可以修复任何错误的解析器来处理数据,例如JTidy。最后,如果数据来自某一不受控制的来源,比如用户,您可以完全禁止HTML。如果仍然希望用户输入文本增强显示效果,比如黑体或者斜体还有超链接,那么可以考虑使用另一个标记表单,比如WikiText或UBB Code。
<logic:iterate id="loopVar" name="MyForm" property="values">
<html:radio property="beanValue" idName="loopVar" value="value"/>
<bean:write name="loopVar" property="name"/>
<br />
</logic:iterate>
<input type="radio" name="skill" value="1"/> Beginner <br />
<input type="radio" name="skill" value="2"/> Intermediate <br />
<input type="radio" name="skill" value="3"/> Advanced <br />
在某些情况中,一组单选按钮的分组是动态的。换句话说,单选按钮的显示是多样的。例如,您想用一个向导风格的界面来进行编程语言和开发工具的投票。在第一页,设置一组单选按钮,在这里投票者可以选择他们喜欢的语言。在第二页,设置一组相关单选按钮,在这里投票者可以挑选他们喜欢的IDE。IDE选择的单选按钮集合是动态的,它基于第一页的语言选择。
首先,需要定义用于保存语言和IDE的表单。它们是简单的String属性,所以可以使用DynaActionForm:
<form-bean name="DevPollForm"
type="org.apache.struts.action.DynaActionForm">
<form-property name="language" type="java.lang.String" />
<form-property name="ide" type="java.lang.String" />
</form-bean>
然后,创建Java类以保存编程语言和相应的IDE集合,如例3.14所示。为了表明目的,这里的值都是硬编码的。Struts LabelValueBean用来保存数据中的name/value对。
例3.14:选项的JavaBean语言
package com.oreilly.strutsckbk;
import java.util.*;
import org.apache.struts.util.LabelValueBean;
public class LanguageChoices {
public LanguageChoices() {
// create the set of languages
languages = new ArrayList();
languages.add(createBean("Java"));
languages.add(createBean("C#"));
languageIdeMap = new HashMap();
// create the set of Java IDEs
LabelValueBean[] javaIdes = new LabelValueBean[] {
createBean("Eclipse"),
createBean("NetBeans"),
createBean("JDeveloper"),
createBean("IDEA") };
// create the set of C# IDEs
LabelValueBean[] csharpIdes = new LabelValueBean[] {
createBean("SharpDevelop"),
createBean("Visual Studio") };
// relate the language and IDEs
languageIdeMap.put("Java", javaIdes);
languageIdeMap.put("C#", csharpIdes);
}
private LabelValueBean createBean(String name) {
return new LabelValueBean(name, name);
}
public Map getLanguageIdeMap() {
return languageIdeMap;
}
public List getLanguages() {
return languages;
}
private List languages;
private Map languageIdeMap;
}
第一个JSP页(lang_poll_1.jsp)如例3.15所示,它显示了包含语言选择单选按钮的表单。
例3.15:通过Struts标签生成相关的单选按钮
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>
<html>
<head>
<title>Struts Cookbook - Developer Poll</title>
</head>
<body>
<jsp:useBean id="languageChoices"
class="com.oreilly.strutsckbk.LanguageChoices"
scope="application"/>
<html:form action="ProcessLanguageChoice">
What's your favorite programming language?
<p>
<logic:iterate id="lang" name="languageChoices" property="languages">
<html:radio property="language" idName="lang" value="value"/>
<bean:write name="lang" property="label"/><br />
</logic:iterate>
</p>
<html:submit value="Next >>"/>
</html:form>
</body>
</html>
通过使用jsp:useBean 标准JSP标签,将LanguageChoices对象放在应用中。同样,也可以通过Action或者Struts插件将该对象放在应用中。
在bean实例化后,创建表单。logic:iterate标签使用LanguageChoices bean的Language属性进行循环。该属性是一个org.apache.struts.util.LabelValueBeans 的java.util.List。 LabelValueBean类将一个String标签和一个String值匹配在一起。标签由label 属性访问读取,值由value属性访问读取。在这个例子中,标签和值是一样的。在实际的应用程序中,value一般是一些实体值,经常与显示文本不同。
logic:iterate标签把列表中的每个LabelValueBean作为一个由id属性"lang"指定的作用域变量显示。html:radio标签创建了实际的input type="radio" HTML元素。property属性标识ActionForm 中属性的名称,并接受单选按钮的值。idName属性标识包含单选按钮值的bean。换而言之,值由logic:iterate标签: "lang"显示。
提示:idName是Struts 1.1中添加的属性,在Struts 1.0中,单选按钮的值用运行时表达式显示。
<html:radio property="language" value="<%= lang.getValue( )
%>"/>
在创建了单选按钮之后,按扭的标号由bean:write标签生成,这个标签用来从LabelValueBean (lang)显示label属性。
例3.16显示了例3.15中JSP页生成的源代码。
例3.16:lang_poll_1.jsp的源代码
<html>
<head>
<title>Struts Cookbook - Developer Poll</title>
</head>
<body>
<form name="DevPollForm" method="post"
action="/jsc-ch03/ProcessLanguageChoice.do">
What's your favorite programming language?
<p>
<input type="radio" name="language" value="Java">
Java<br />
<input type="radio" name="language" value="C#">
C#<br />
</p>
<input type="submit" value="Next >>">
</form>
</body>
</html>
查询的第二页要求投票者选择一个喜欢的IDE,该选择基于第一页中选择的编程语言。正如第一页,选项显示为一组单选按钮。如例3.17所示,第二页和第一页很相似,但是这一页使用了一个JSTL c:forEach循环。
例3.17:通过JSTL生成相关单选按钮
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %>
<html>
<head>
<title>Struts Cookbook - Developer Poll</title>
</head>
<body>
Favorite Language: <b><c:out value="${DevPollForm.language}"/></b>
<html:form action="ProcessIdeChoice">
What's your favorite IDE?
<p>
<c:forEach var="langIde"
items="${languageChoices.languageIdeMap[DevPollForm.map.language]}">
<html:radio property="ide" idName="langIde" value="value"/>
<c:out value="${langIde.label}"/><br />
</c:forEach>
</p>
<html:submit value="Next >>"/>
</html:form>
</body>
</html>
html:radio标签的用法和例3.15中第一页一样。尽管c:forEach被logic:iterate代替,仍然可以使用单选标签的idName属性。以这种方式使用JSTL时,idName 应该和JSTL c:forEach标签的var属性的值一样。
3.12 处理未选择的复选框
问题
需要保证对应于一个HTML复选框的Boolean类型的ActionForm属性在复选框未被选择时设置为false。
解决方案
创建一个复选框输入字段,使用JavaScript设置一个Boolean隐藏字段。如果隐藏字段的值为true,就用logic:equal标签设置复选框的checked属性。例3.18中的JSP页(checkbox_test.jsp)使用了这个方法来确保始终提交一个true或false值。
例3.18:确保复选框设置
<%@ page contentType="text/html;charset=UTF-8"
language="java" %>
<%@ taglib uri="http://jakarta.apache.org/struts/tags-html"
prefix="html" %>
<%@ taglib uri="http://jakarta.apache.org/struts/tags-logic"
prefix="logic" %>
<html>
<head>
<title>Struts Cookbook - Chapter 4 : Checkbox Test</title>
</head>
<body>
<html:form method="get" action="/ProcessCheckbox">
<input type="checkbox" name="foo_"
onclick="javascript:elements['foo'].value=this.checked;"
<logic:equal name="CheckboxForm" property="foo" value="true">
checked
</logic:equal>
>
<html:hidden property="foo"/>
<html:submit/>
</html:form>
</body>
</html>
讨论
对这样的一个普通字段来说,HTML复选框会造成麻烦。如果没有选择一个复选框并且表单已经提交,那么字段提交到请求中的值会是空的。假设您的一个表单中有复 选框:
<html:form method="get" action="ProcessFoo">
<html:checkbox property="foo"/>
<html:submit/>
</html:form>
如果选择了复选框,那么最后的请求URL会是这样的: http://localhost/jsc-ch04/ProcessFoo?foo=on 由Struts处理时,ActionForm由BeanUtils.populate()方法填充。假如foo是一个boolean 属性,它的值将设置为true。
问题出现在您没有选择复选框而是强制把属性值设置为false时。如果未选择复选框,最后的URL会是这样的: http://localhost/jsc-ch04/ProcessFoo? 属性值到哪里去了?您可能希望查询请求字符串中包含"foo=off"或者"foo="。不幸的是,没有为未选择的复选框生成任何请求参数。BeanUtils.populate()被调用时,并不知道要设置属性值。
在ActionForm中实现reset()方法通常能够解决这个问题。在ActionForm填充以前,Struts请求处理器调用这个方法,该方法使您有机会设置form属性为默认值。假如HTTP请求不包含属性的name/value对 ,那么属性会保留reset()方法中所设置的值。对于复选框,将值设置为false,如下所示:
public void reset( ActionMapping mapping,
HttpServletRequest request )
{
foo = false;
}
但是,reset()方法不可能总能解决问题。假如您在向导风格的界面中使用表单,即使您不想,实现reset()还是会清除表单。您需要一个其他的选择确保表单提交时值会被发送。上面的解决方案通过实现表单中的两个字段来达到这一目的。对应于Boolean类型ActionForm属性的实际表单字段不是复选框,是一个使用html:hidden标签生成的隐藏字段。接着使用普通的HTML (input type="checkbox")创建复选框。一个JavaScript onclick事件句柄用来定义这个控件:
javascript:elements['foo'].value=this.checked;
用户点击复选框时,隐藏字段的值被设定。如果选择了复选框,值就会设置为true;否则,设置为false。为了确保表单初始显示时复选框也正确显示,用logic:equal标签来显示该字段的check属性。
相关链接
这个话题在struts-user邮件列表中经常提到。有一篇关于此话题的论文,参见http://www.mail-archive.com/struts-user@jakarta.apache.org/msg93525.html。
假如您正在使用Struts html:multibox控件来显示一组复选框,当用户清除所有的值时会发生一个类似的问题。关于此话题的讨论可以参见http://www.mail-archive.com/struts-user@jakarta.apache.org/msg96487.html。
问题
想用JavaScript来动态改变基于应用模型所得数据的HTML select元素中的显示项。
解决方案
使用Struts logic:iterate标签为不同的选项组创建JavaScript数组,然后使用JavaScript的onchange事件处理器在运行时改变options集合。例3.8显示了一个完整的JSP页,其中使用Struts标签动态创建了JavaScript 数组。changeOptions事件处理函数使用JavaScript数组重置了select控制选项。
例3.8:使用Struts生成DHTML
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>
<html>
<head>
<title>Struts - JavaScript Example</title>
<script language="JavaScript">
// Create the array for the first set of options
fooArray = new Array();
<logic:iterate id="fooValue" indexId="ctr"
name="MyForm" property="fooList">
fooArray[<bean:write name="ctr"/>] =
new Option("<bean:write name='fooValue'/>",
"<bean:write name='fooValue'/>",
false, false);
</logic:iterate>
// Create the array for the second set of options
barArray = new Array();
<logic:iterate id="barValue" indexId="ctr"
name="MyForm" property="barList">
fooArray[<bean:write name="ctr"/>] =
new Option("<bean:write name='barValue'/>",
"<bean:write name='barValue'/>",
false, false);
</logic:iterate>
function changeOptions(var control) {
// control is the triggering control
// baz is the select control
baz = document.MyForm.baz;
baz.options.length=0;
if (control.value == 'Foo')
bazArray = fooArray;
else
bazArray = barArray;
for (i=0; i < bazArray.length; i++)
baz.options[i] = bazArray[i];
}
</script>
</head>
<body>
<html:form name="MyForm" action="processMyForm">
<html:radio property="fooBar" value="Foo"
onclick="changeOptions(this);"/> Foo<br/>
<html:radio property="fooBar" value="Bar"
onclick="changeOptions(this);"/> Bar<br/>
Baz: <html:select property="baz">
</html:select>
</html:form>
</body>
</html>
讨论
可以像使用Struts生成HTML一样生成JavaScript 。一些开发人员认为JavaScript 是“邪恶”的,而实际上它只是“小小的坏”而已。举个实用性的例子,JavaScript可使应用性更好,使用户更满意时,您肯定会使用它。但如此使用会使您的业务逻辑仅仅停留在“业务”这一层面,并不会使页面脱颖而出。Struts可以帮助您改善这一点。
可以用一个具体的例子来解释这个方法,设想现在想向一个用户询问他最喜欢的编程语言和对于所选语言最喜欢的IDE。语言将用单选按钮选择,而IDE则从一个下拉菜单中选择。如果使用的语言是Java,IDE下拉菜单将显示Eclipse,Net Beans,IDEA等选项。如果使用的语言是C#,下拉菜单会显示Visual Studio和SharpDevelop。
例3.9显示了保存这些数据的action表单。
例3.9:最喜欢的语言/IDE的ActionForm
package com.oreilly.strutsckbk;
import org.apache.struts.action.ActionForm;
public final class MyForm extends ActionForm {
private static String[] javaIdes =
new String[] {"Eclipse", "IDEA", "JBuilder",
"JDeveloper", "NetBeans"};
private static String[] csharpIdes =
new String[] {"SharpDevelop", "Visual Studio"};
public String[] getJavaIdes() {return javaIdes;}
public String[] getCsharpIdes() {return csharpIdes;}
public String getLanguage() {
return language;
}
public void setLanguage(String language) {
this.language = language;
}
public String getIde() {
return ide;
}
public void setIde(String ide) {
this.ide = ide;
}
private String language;
private String ide;
}
例3.10显示了显示输入页的JSP (favorite_language.jsp)。这个例子类似于解决方案。
例3.10:此JSP页使用Struts的DTHML
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>
<html>
<head>
<title>Struts - JavaScript Example</title>
<script language="JavaScript">
// Create the array for the first set of options
javaIdesArray = new Array();
<logic:iterate id="ide" indexId="ctr"
name="MyForm" property="javaIdes">
javaIdesArray[<bean:write name="ctr"/>] =
new Option("<bean:write name='ide'/>",
"<bean:write name='ide'/>",
false, false);
</logic:iterate>
// Create the array for the second set of options
csharpIdesArray = new Array();
<logic:iterate id="ide" indexId="ctr"
name="MyForm" property="csharpIdes">
csharpIdesArray[<bean:write name="ctr"/>] =
new Option("<bean:write name='ide'/>",
"<bean:write name='ide'/>",
false, false);
</logic:iterate>
function changeOptions(control) {
ideControl = document.MyForm.ide;
ideControl.options.length=0;
if (control.value == 'Java')
ideArray = javaIdesArray;
else
ideArray = csharpIdesArray;
for (i=0; i < ideArray.length; i++)
ideControl.options[i] = ideArray[i];
}
</script>
</head>
<body>
<html:form action="/admin/ViewFavoriteLanguage">
What's your favorite programming language?<br/>
<html:radio property="language" value="Java"
onclick="changeOptions(this);"/> Java<br/>
<html:radio property="language" value="C-Sharp"
onclick="changeOptions(this);"/> C-Sharp<br/>
<p>What's your favorite development tool?<br/>
IDE: <html:select property="ide"/>
</p>
<html:submit/>
</html:form>
</body>
</html>
head元素中嵌套的script块包含JavaScript。logic:iterate标签循环Javabean属性创建了两个JavaScript数组:一个用于Java IDE,一个用于C# IDE。每一个数组都包含Option JavaScript对象,Option对象代表了某一HTML select控件中的一个选项。这个对象在构造器中有4个参数:要显示的文本值、表单提交时传递的值、一个Boolean值表示值是否是选定的默认值、另一个Boolean值表示值是否是当前的选择。
更改选项的JavaScript函数位于logic:iterate循环之后。这个函数是纯静态的JavaScript,触发更改的单选按钮当作一个参数传递到函数。如果单选按钮控件的当前值是Java,那么select控件填入的是表示Java IDE的Option对象。否则,控件填入的是代表C# IDE的Option对象。
HTML主体包含表单使用Struts html标签来显示。Struts标签支持JavaScript通过onfunction 属性来改变监听器。对于单选按钮而言,onclick 监听器工作正常。传递到函数中的参数“this”是对HTML单选按钮的一个引用。该页最初的显示应该如图3.1所示。
一旦点击单选按钮中的某一个,IDE文件下拉菜单中的选项将填入最初从form bean中得到的数据。图3.2说明了点击Java单选按钮时出现的显示界面。
类似地,如果点击C-Sharp单选按钮,下拉菜单中的值会针对相应的JavaScript数组进行改变。
JSTL可以用来代替Struts bean和logic标签。在这里,使用JSTL c:forEach和c:out 标签来代替logic:iterate和bean:write。这些标签和Struts标签以同样的方法生成JavaScript数组:
javaIdesArray = new Array( );
<c:forEach var="ide" varStatus="status"
items="${MyForm.javaIdes}">
javaIdesArray[<c:out value="${status.index}"/>] =
new Option("<c:out value='${ide}'/>",
"<c:out value='${ide}'/>",
false, false);
</c:forEach>
JavaScript编程是恼人的,特别是对于已习惯强类型和编译时检查的Java开发人员。但在这种动态客户端交互作用的指引下可以带来更加丰富的终端用户经验。
相关链接
对于JavaScript编程来说,David Flanagan所著的JavaScript: The Definitive Guide by David Flanagan (O’Reilly出版)很有价值。如果需要用业务逻辑来决定动态数据,可参见3.9节的方法。
3.9 生成动态select列表选项
问题
想以同一表单中另一字段为基础来动态改变显示在select元素中的选项,而且不使用JavaScript客户端的选项集。提示:这个问题和避免使用JavaScript 不相关。相反,它说明如何从客户端的javascript事件监听器中调用一个Struts action。
解决方案
使用JavaScript中的一个onchange或者onclick监听器来调用一个 JavaScript函数,可以将表单提交给一个Struts Action。在Action中,执行必要的业务逻辑为select选项构成一个新的集合,并将控制转发到原始的JSP页。例3.11显示了一个JSP页,用户点击单选按钮时,向一个Action 提交表单。单选按钮的值作为请求参数传给Action。例3.11:使用JavaScript提交表单
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<html>
<head>
<title>Struts - JavaScript Example</title>
<script language="JavaScript">
function getOptions(control) {
form = control.form;
form.action = "SetOptions.do?someProp=";
form.action += control.value;
form.submit();
}
</script>
</head>
<body>
<html:form action="ProcessMyForm">
<html:radio property="someProp1" value="val1"
onclick="getOptions(this);"/> Value 1<br/>
<html:radio property="language" value="val2"
onclick="getOptions(this);"/> Value 2<br/>
SomeProp2:
<html:select property="someProp2">
<html:optionsCollection property="prop2Values"/>
</html:select>
</p>
<html:submit/>
</html:form>
</body>
</html>
讨论
网页的动态交互功能的需求由业务逻辑控制时,最好使用一个Action而不是JavaScript来执行这个函数。在JavaScript 函数中编入业务规则会导致代码很难维护而且不可重复使用。最好在服务器端执行这个行为。本节解决了3.8节中描述的问题。但是这里的解决方案并不依赖于将数据合并到JavaScript函数中,onclick事件处理器调用的函数把表单提交到不同的URL和action,而不是表单的action属性所指定的。这个备选URL控制一个Action,惟一目的是决定出显示在select控制器中的新选项集。这个Action 转发到原始的JSP页,并在那里填入基于新值的下拉菜单。
为了改变某一HTML控制中的值而创建一个单独的Action似乎有些“小题大做”。而这里的技术示范提供了一个灵活的解决方案,它在动态HTML后借助了服务器的强大功能。设想一下,要计算的一个财务数据字段来自同一页另一个字段的值,执行计算的服务应该被一个Action调用。这里提供的方案可以很好地解决这个问题。
以一个具体例子来进一步说明,3.8 节用的方法会被本节中另一详细的方法代替。这个例子提供了一个输入表单,在这里用户可以输入自己喜欢的编程语言和IDE。IDE的选项根据选择的编程语言而定。例3.12显示的是表单的JSP页(favorite_language2.jsp)。
例3.12:将表单提交到备用URL
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<html>
<head>
<title>Struts - JavaScript Example</title>
<script language="JavaScript">
function getOptions(control) {
form = control.form;
form.action = "GetIdeOptions.do?language=";
form.action += control.value;
form.submit();
}
</script>
</head>
<body>
<html:form action="ViewFavoriteLanguage">
What's your favorite programming language?<br/>
<html:radio property="language" value="Java"
onclick="getOptions(this);"/> Java<br/>
<html:radio property="language" value="C-Sharp"
onclick="getOptions(this);"/> C-Sharp<br/>
<p>What's your favorite development tool?<br/>
IDE:
<html:select property="ide">
<html:optionsCollection property="ides"/>
</html:select>
</p>
<html:submit/>
</html:form>
</body>
</html>
struts-config.xml文件中的action元素指定了表单使用的URL路径。第一个映射,/ FavoriteLanguage2,指定了转发到例3.12中JSP的action。第二个映射,/ GetIdeOptions,指定了用户点击单选按钮时调用的action。最后一个映射,/ ViewFavoriteLanguage,指定了submmit按钮按下时处理表单的action。
<action path="/FavoriteLanguage2"
name="MyForm"
scope="session"
type="org.apache.struts.actions.ForwardAction"
parameter="/favorite_language2.jsp"/>
<action path="/GetIdeOptions"
name="MyForm"
scope="session"
type="com.oreilly.strutsckbk.GetIdeOptionsAction">
<forward name="success" path="/FavoriteLanguage2.do"/>
</action>
<action path="/ViewFavoriteLanguage"
name="MyForm"
scope="session"
type="org.apache.struts.actions.ForwardAction"
parameter="/view_favorite_language.jsp"/>
这个难题的最后一部分是GetIdeOptionsAction本身,如例3.13所示。
例3.13:备用URL的action
package com.oreilly.strutsckbk;
import java.util.ArrayList;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.util.LabelValueBean;
public final class GetIdeOptionsAction extends Action {
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
MyForm myForm = (MyForm) form;
String language = myForm.getLanguage();
ArrayList ides = new ArrayList();
if (language.equals("Java")) {
ides.add(new LabelValueBean("Net Beans","Net Beans"));
ides.add(new LabelValueBean("Eclipse", "Eclipse"));
ides.add(new LabelValueBean("jEdit", "jEdit"));
}
else if (language.equals("C-Sharp")) {
ides.add(new LabelValueBean("Sharp Develop", "Sharp Develop"));
ides.add(new LabelValueBean("Visual Studio", "Visual Studio"));
}
myForm.setIdes( ides );
// Forward control to the specified success URI
return (mapping.findForward("success"));
}
}
此类负责获取MyForm中被选择的语言。然后Action设置了一个集合,其中包含相应的进入表单的IDE名称。简而言之,这个 Action直接创建了集合。在实际应用中,这些值可能来自业务层,也可能来自某一数据库。最后,Action返回成功的“转发”,循环回到最初的Action。
提示:这个方法的结果是需要在会话范围定义ActionForm。这样一来,主JSP页面就可以在从另外的Action重新提交回原始的页面时反映出数据的变化。
对于这个例子,内置的ForwardAction处理表单并直接将请求转发给JSP页。但如果是用自定义Action,就考虑继承DispatchAction并且将附属action实现为DispatchAction 的一个方法。此方式允许您把相关的代码保存在一起,使软件更容易维护。
相关链接
3.8节提供了这个问题的不同解决方案,使用了动态生成的JavaScript数组。DispatchAction的内容参见6.8节。3.10 过滤文本输入
问题
想显示含有HTML标签的数据,并希望此数据能由浏览器解释为以HTML标记。解决方案
这很简单:<bean:write name="myForm" property="freeText" filtered="false"/>
在使用JSTL时允许未过滤的值:
<c:out value="${myForm.freeText}" escapeXml="false"/>
讨论
使用Struts bean:write标签生成文本时,默认情况下,任何有关HTML处理的特殊字符都会进行等价实体替换。举例来说,大于号(>)被>字符实体替换。这一特征被当作响应过滤且是默认开启的。多数情况下会得到这样的过滤,因为一个未被过滤的响应会被浏览器曲解。表3.4是字符和被bean:write标签过滤的替换实体。表3.4:过滤字符
字符名 | 字符值 | 替换实体 |
小于 | < | < |
大于 | > | > |
和号 | & | & |
双引号 | " | " |
反斜线 | / | ' |
通过将bean:write 标签的filtered属性设置为false,就可以指示Struts标签不用相应的实体替换特殊字符。首先来看过滤是怎样工作的。例如,用户将以下文本输入表单:
Struts <b>rocks</b>!
现在使用bean:write标签显示这个文本。当filtered属性设置为true时,字符实体文本取代了特殊字符,如下所示:
Struts <b>rocks</b>!
这也许不是用户想要的。他希望结果看起来是"Struts rocks!"。但是,由于允许用户输入有修饰的HTML标签,filtered的false属性曲解了原来的意思:
Struts <b>rocks</b>!
浏览器将认出标签并如期应用HTML标签。
显示一个网页时,这是一个很有用的机制。但使用这个方法必须仔细,假如数据没有过滤,HTML的页面布局会被破坏,整个网页也都会被破坏。例如,输入如下的 文本:
Struts <b>rocks<b>!
乍看起来没有问题。但注意,正斜杠丢失了,它应该出现在b(bold)元素之前。这个错误很容易忽视,它会使该页中其余的所有文本都变成黑体!
不幸的是,这个错误很难避免。最好的办法是确保输入的数据是有效的HTML。一个方法是通过XML解析器处理数据,这会检测出存在标签不对称等问题;另一个选择是通过一个可以修复任何错误的解析器来处理数据,例如JTidy。最后,如果数据来自某一不受控制的来源,比如用户,您可以完全禁止HTML。如果仍然希望用户输入文本增强显示效果,比如黑体或者斜体还有超链接,那么可以考虑使用另一个标记表单,比如WikiText或UBB Code。
相关链接
JTidy 提供了一个命令行界面和Java API用来分析和整理HTML。可以在http://jtidy.sourceforge.net找到JTidy的相关资料。UBBCode 是一个标记表单,由PHP支持。可以使用Java处理UBBCode。用来分析UBBCode的PHP函数可以用Java重写,这些可以在http://www.firegemsoftware.com/other/tutorials/ubb.php找到。3.11 生成一组相关的单选按钮
问题
想生成一组相关的单选按钮,其值基于从Collection中动态检索到的值。解决方案
把单选按钮的值作为一个可以用logic:iterate标签迭代的集合。html:radio标签的idName属性应该和iterate标签的id属性的值一样。html:radio标签的value属性用来指定idName对象的属性。此属性的值是input type = "radio" HTML控件的值。<logic:iterate id="loopVar" name="MyForm" property="values">
<html:radio property="beanValue" idName="loopVar" value="value"/>
<bean:write name="loopVar" property="name"/>
<br />
</logic:iterate>
讨论
单选按钮是HTML控件,一次只能选择一个按钮。单选按钮基于HTML input标签的name属性来分组。像其他HTML表单的input元素一样,控件的标号并不属于控件本身。开发人员使用规范的文本作为控件的标签。一般情况下,单选按钮用input标签右边的文本作为标签:<input type="radio" name="skill" value="1"/> Beginner <br />
<input type="radio" name="skill" value="2"/> Intermediate <br />
<input type="radio" name="skill" value="3"/> Advanced <br />
在某些情况中,一组单选按钮的分组是动态的。换句话说,单选按钮的显示是多样的。例如,您想用一个向导风格的界面来进行编程语言和开发工具的投票。在第一页,设置一组单选按钮,在这里投票者可以选择他们喜欢的语言。在第二页,设置一组相关单选按钮,在这里投票者可以挑选他们喜欢的IDE。IDE选择的单选按钮集合是动态的,它基于第一页的语言选择。
首先,需要定义用于保存语言和IDE的表单。它们是简单的String属性,所以可以使用DynaActionForm:
<form-bean name="DevPollForm"
type="org.apache.struts.action.DynaActionForm">
<form-property name="language" type="java.lang.String" />
<form-property name="ide" type="java.lang.String" />
</form-bean>
然后,创建Java类以保存编程语言和相应的IDE集合,如例3.14所示。为了表明目的,这里的值都是硬编码的。Struts LabelValueBean用来保存数据中的name/value对。
例3.14:选项的JavaBean语言
package com.oreilly.strutsckbk;
import java.util.*;
import org.apache.struts.util.LabelValueBean;
public class LanguageChoices {
public LanguageChoices() {
// create the set of languages
languages = new ArrayList();
languages.add(createBean("Java"));
languages.add(createBean("C#"));
languageIdeMap = new HashMap();
// create the set of Java IDEs
LabelValueBean[] javaIdes = new LabelValueBean[] {
createBean("Eclipse"),
createBean("NetBeans"),
createBean("JDeveloper"),
createBean("IDEA") };
// create the set of C# IDEs
LabelValueBean[] csharpIdes = new LabelValueBean[] {
createBean("SharpDevelop"),
createBean("Visual Studio") };
// relate the language and IDEs
languageIdeMap.put("Java", javaIdes);
languageIdeMap.put("C#", csharpIdes);
}
private LabelValueBean createBean(String name) {
return new LabelValueBean(name, name);
}
public Map getLanguageIdeMap() {
return languageIdeMap;
}
public List getLanguages() {
return languages;
}
private List languages;
private Map languageIdeMap;
}
第一个JSP页(lang_poll_1.jsp)如例3.15所示,它显示了包含语言选择单选按钮的表单。
例3.15:通过Struts标签生成相关的单选按钮
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>
<html>
<head>
<title>Struts Cookbook - Developer Poll</title>
</head>
<body>
<jsp:useBean id="languageChoices"
class="com.oreilly.strutsckbk.LanguageChoices"
scope="application"/>
<html:form action="ProcessLanguageChoice">
What's your favorite programming language?
<p>
<logic:iterate id="lang" name="languageChoices" property="languages">
<html:radio property="language" idName="lang" value="value"/>
<bean:write name="lang" property="label"/><br />
</logic:iterate>
</p>
<html:submit value="Next >>"/>
</html:form>
</body>
</html>
通过使用jsp:useBean 标准JSP标签,将LanguageChoices对象放在应用中。同样,也可以通过Action或者Struts插件将该对象放在应用中。
在bean实例化后,创建表单。logic:iterate标签使用LanguageChoices bean的Language属性进行循环。该属性是一个org.apache.struts.util.LabelValueBeans 的java.util.List。 LabelValueBean类将一个String标签和一个String值匹配在一起。标签由label 属性访问读取,值由value属性访问读取。在这个例子中,标签和值是一样的。在实际的应用程序中,value一般是一些实体值,经常与显示文本不同。
logic:iterate标签把列表中的每个LabelValueBean作为一个由id属性"lang"指定的作用域变量显示。html:radio标签创建了实际的input type="radio" HTML元素。property属性标识ActionForm 中属性的名称,并接受单选按钮的值。idName属性标识包含单选按钮值的bean。换而言之,值由logic:iterate标签: "lang"显示。
提示:idName是Struts 1.1中添加的属性,在Struts 1.0中,单选按钮的值用运行时表达式显示。
<html:radio property="language" value="<%= lang.getValue( )
%>"/>
在创建了单选按钮之后,按扭的标号由bean:write标签生成,这个标签用来从LabelValueBean (lang)显示label属性。
例3.16显示了例3.15中JSP页生成的源代码。
例3.16:lang_poll_1.jsp的源代码
<html>
<head>
<title>Struts Cookbook - Developer Poll</title>
</head>
<body>
<form name="DevPollForm" method="post"
action="/jsc-ch03/ProcessLanguageChoice.do">
What's your favorite programming language?
<p>
<input type="radio" name="language" value="Java">
Java<br />
<input type="radio" name="language" value="C#">
C#<br />
</p>
<input type="submit" value="Next >>">
</form>
</body>
</html>
查询的第二页要求投票者选择一个喜欢的IDE,该选择基于第一页中选择的编程语言。正如第一页,选项显示为一组单选按钮。如例3.17所示,第二页和第一页很相似,但是这一页使用了一个JSTL c:forEach循环。
例3.17:通过JSTL生成相关单选按钮
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %>
<html>
<head>
<title>Struts Cookbook - Developer Poll</title>
</head>
<body>
Favorite Language: <b><c:out value="${DevPollForm.language}"/></b>
<html:form action="ProcessIdeChoice">
What's your favorite IDE?
<p>
<c:forEach var="langIde"
items="${languageChoices.languageIdeMap[DevPollForm.map.language]}">
<html:radio property="ide" idName="langIde" value="value"/>
<c:out value="${langIde.label}"/><br />
</c:forEach>
</p>
<html:submit value="Next >>"/>
</html:form>
</body>
</html>
使用JSTL读取复杂属性 例3.17中,更有趣的是JSTL 表达式语言(EL)是如何用来获得所选语言的IDE集合的: ${languageChoices.languageIdeMap[DevPollForm.map.language]} EL的强大之处在于允许访问一个如同在任何组合中匹配的属性一样普通的JavaBean属性。为了理解表达式如何求值,让我们来看看如何用Java来实现: // get the language-ide map Map ideMap = languageChoices.getLanguageIdeMap( ); // get the selected language from the form bean DynaActionFrom form = (DynaActionForm) session.getAttribute("DevPollForm"); String language = form.getMap().get("language"); // get the list of IDEs from the language-ide map List ides = (List) map.get(language); DynaActionForms通过map属性显示了它内部的name/value对表。这就允许您从DevPollForm 动态表单bean获得所选择的语言。 |
3.12 处理未选择的复选框
问题
需要保证对应于一个HTML复选框的Boolean类型的ActionForm属性在复选框未被选择时设置为false。
解决方案
创建一个复选框输入字段,使用JavaScript设置一个Boolean隐藏字段。如果隐藏字段的值为true,就用logic:equal标签设置复选框的checked属性。例3.18中的JSP页(checkbox_test.jsp)使用了这个方法来确保始终提交一个true或false值。
例3.18:确保复选框设置
<%@ page contentType="text/html;charset=UTF-8"
language="java" %>
<%@ taglib uri="http://jakarta.apache.org/struts/tags-html"
prefix="html" %>
<%@ taglib uri="http://jakarta.apache.org/struts/tags-logic"
prefix="logic" %>
<html>
<head>
<title>Struts Cookbook - Chapter 4 : Checkbox Test</title>
</head>
<body>
<html:form method="get" action="/ProcessCheckbox">
<input type="checkbox" name="foo_"
onclick="javascript:elements['foo'].value=this.checked;"
<logic:equal name="CheckboxForm" property="foo" value="true">
checked
</logic:equal>
>
<html:hidden property="foo"/>
<html:submit/>
</html:form>
</body>
</html>
讨论
对这样的一个普通字段来说,HTML复选框会造成麻烦。如果没有选择一个复选框并且表单已经提交,那么字段提交到请求中的值会是空的。假设您的一个表单中有复 选框:
<html:form method="get" action="ProcessFoo">
<html:checkbox property="foo"/>
<html:submit/>
</html:form>
如果选择了复选框,那么最后的请求URL会是这样的: http://localhost/jsc-ch04/ProcessFoo?foo=on 由Struts处理时,ActionForm由BeanUtils.populate()方法填充。假如foo是一个boolean 属性,它的值将设置为true。
问题出现在您没有选择复选框而是强制把属性值设置为false时。如果未选择复选框,最后的URL会是这样的: http://localhost/jsc-ch04/ProcessFoo? 属性值到哪里去了?您可能希望查询请求字符串中包含"foo=off"或者"foo="。不幸的是,没有为未选择的复选框生成任何请求参数。BeanUtils.populate()被调用时,并不知道要设置属性值。
在ActionForm中实现reset()方法通常能够解决这个问题。在ActionForm填充以前,Struts请求处理器调用这个方法,该方法使您有机会设置form属性为默认值。假如HTTP请求不包含属性的name/value对 ,那么属性会保留reset()方法中所设置的值。对于复选框,将值设置为false,如下所示:
public void reset( ActionMapping mapping,
HttpServletRequest request )
{
foo = false;
}
但是,reset()方法不可能总能解决问题。假如您在向导风格的界面中使用表单,即使您不想,实现reset()还是会清除表单。您需要一个其他的选择确保表单提交时值会被发送。上面的解决方案通过实现表单中的两个字段来达到这一目的。对应于Boolean类型ActionForm属性的实际表单字段不是复选框,是一个使用html:hidden标签生成的隐藏字段。接着使用普通的HTML (input type="checkbox")创建复选框。一个JavaScript onclick事件句柄用来定义这个控件:
javascript:elements['foo'].value=this.checked;
用户点击复选框时,隐藏字段的值被设定。如果选择了复选框,值就会设置为true;否则,设置为false。为了确保表单初始显示时复选框也正确显示,用logic:equal标签来显示该字段的check属性。
相关链接
这个话题在struts-user邮件列表中经常提到。有一篇关于此话题的论文,参见http://www.mail-archive.com/struts-user@jakarta.apache.org/msg93525.html。
假如您正在使用Struts html:multibox控件来显示一组复选框,当用户清除所有的值时会发生一个类似的问题。关于此话题的讨论可以参见http://www.mail-archive.com/struts-user@jakarta.apache.org/msg96487.html。
相关文章推荐
- javascript使用switch case实现动态改变超级链接文字及地址
- 使用JavaScript实现动态改变控件大小
- javascript使用switch case实现动态改变超级链接文字及地址
- 使用javascript动态改变当前行的显示样式
- ios TableView那些事(三十 五)TableView 单选操作使用Autolayout实现UITableView的Cell动态布局和高度动态改变
- 使用前端框架后js动态改变样式的一些问题(select)
- 使用javascript对表格进行动态修改
- 关于使用JavaScript实现图片点击切换(附带改变导航图片 方案一)
- 使用Autolayout实现UITableView的Cell动态布局和高度动态改变
- 使用addView方法时,如何保持已有动态控件位置不被改变
- 使用Autolayout实现UITableView的Cell动态布局和高度动态改变
- js加载之使用DOM方法动态加载Javascript文件
- 使用JavaScript动态设置样式 Ver2
- 下拉列表select中使用ajax的json数据交换格式动态改变div层里面的复选框checkbox值
- 使用Javascript动态创建表格,不同的方法,巨大的运行时间差异!
- CSC动态编译,监测文件夹下改变(Windows服务形式),自动编译生成dll供web项目使用
- 解决javascript动态改变img的src属性图片不显示问题
- Javascript动态改变按钮样式
- jQuery autoComplete插件两种使用方式及动态改变参数值的方法详解
- 怎样使用JavaScript存储Adobe AIR应用程序首选项