|
|
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"
οnclick="getOptions(this);"/> Value 1<br/>
<html:radio property="language" value="val2"
οnclick="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"
οnclick="getOptions(this);"/> Java<br/>
<html:radio property="language" value="C-Sharp"
οnclick="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:过滤字符
字符名 | 字符值 | 替换实体 |
小于 | < | < |
大于 | > | > |
和号 | & | & |
双引号 | " | " |
反斜线 | / | ' |
但有时,您希望显示的文本中包含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。
相关链接
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获得所选择的语言。 |
html:radio标签的用法和例3.15中第一页一样。尽管c:forEach被logic:iterate代替,仍然可以使用单选标签的idName属性。以这种方式使用JSTL时,idName 应该和JSTL c:forEach标签的var属性的值一样。