随着 Struts Recipes 一书的合著者 Danilo Gurovich 一道经历使用动态选择的元素轻松创建单选按钮的五个步骤。
这个诀窍跟在我上一篇文章 “Struts 的动态复选框” 之后。像动态复选框一样,带有动态选择元素的单选按钮可以给 Web 页面添加许多麻烦,而使用 Struts,也可以很容易地创建它们。
在这篇文章中,我将介绍通过嵌套 Struts 标记 <logic:iterate/>
和 <html:radio/>
创建单选按钮组。然后我用一个特定的表单 bean 指向这个标记并对保存单选按钮值的 String[]
数组进行迭代,把相同的 name
属性分配给每个 value
属性。
请参阅 下载区 获得完整的示例源代码;如果需要下载 Struts 框架,请参阅 参考资料。
五步诀窍
为了简单起见,我用与上次在动态复选框中使用的相同工作示例来演示单选按钮。我的简单的用户界面使用单选按钮元素来显示喜玛拉雅山顶峰高度的 String[]
数组,第二个 selectedMountain String
数组代表选中的单选按钮。在创建了按钮之后,调用一个 javascript 函数对单选按钮进行预选。
根据喜玛拉雅这个示例,用 Struts 创建动态选择的单选按钮的诀窍包含五个部分:
- 一个假数据 类,其中容纳
mountains
和 selectedMountain
数据。 - 一个表单 bean,包含单选按钮的
String[]
数组和特意选中的按钮的 String
。 - 一个带有表单的 JSP,显示处于所需状态的单选按钮。
- 一个 JSP,显示表单中选中的元素。
- 一个
Action
类,从表单页面转到显示页面。
单选按钮诀窍和上次学的动态复选框诀窍的主要区别是:Struts 没有提供自动创建 selected
值的工具,而这通常是创建动态单选按钮所需要的。虽然用内置在 Struts JSP 标记中的功能预先选中 checkbox
和 select
输入类型相当简单,但是 radio button
输入类型要求不同的解决方案。幸运的是,使用来自表单 bean 的信息和几行 javascript 代码就可以实现这个要求,如第 3 步所示。
第 1 步. 创建数据层
我做好了一个假的数据类,将来自业务层的数据呈现到应用程序视图层,视图层是我要考虑的事。名为 FakeData
的类包含两个静态方法,如清单 1 所示:
清单 1. FakeData.java /** * class FakeData -- represents the business logic */ public class FakeData { /** * data for mountains */ public static final String[] MOUNTAINS = {"Everest", "K2", "Kangchenjunga", "Lhotse", "Makalu", "Cho Oyu"}; /** * data for selected mountain */ public static final String SELECTED_MOUNTAIN = "Kangchenjunga"; } |
创建假的数据层是一种有用的用户界面开发实践,因为最终应用程序要使用的持续存储层对于前端开发人员来说,通常是看不到的。所以没有必要等待后台团队把工作完成,可以方便地开发一个假的数据层来模拟最终要发送过来的 API 和功能。使用假数据层,可以进行用户界面开发并降低对其他团队的依赖。有了假数据层,还可以定义到项目其他部分的 API 连接,并确保在集成所有部分的时候问题较少。
第 2 步. 创建表单 bean
最终要填充应用程序的值可能来自比清单 1 所示的框架复杂得多的框架。对于更漂亮的示例,有一个好消息,就是清单 2 中的表单 bean 不必做任何繁重的工作,所以它只是一个带有 getter 和 setter 方法的简单 Java™ 对象。实际的值是在调用构造函数时插入的。
清单 2. RadioTestForm.java package com.strutsrecipes; import org.apache.struts.action.ActionForm; /** * Radio Button Test Form to show an array of radio buttons and */ public class RadioTestForm extends ActionForm { // ------------------------------ FIELDS ------------------------------ /** * The selected Mountain */ private String selectedMountain; /** * The list of mountains for the radio button */ private String[] mountains; // --------------------------- CONSTRUCTORS --------------------------- /** * Constructor -- using FakeData... */ public RadioTestForm() { this.selectedMountain = FakeData.SELECTED_MOUNTAIN; this.mountains = FakeData.MOUNTAINS; } // --------------------- GETTER / SETTER METHODS --------------------- /** * Getter for the mountains * * @return the mountains array */ public String[] getMountains() { return this.mountains; } /** * Setter for the mountains * * @param m the Mountains array */ public void setMountains(String[] m) { this.mountains = m; } /** * Getter for selectedMountain * * @return the selected mountain */ public String getSelectedMountain() { return this.selectedMountain; } /** * Setter for selectedMountain * * @param sm the selectedMountain */ public void setSelectedMountain(String sm) { this.selectedMountain = sm; } } |
为了清晰起见,我包含了表单 bean 的所有 Java 代码。注意,Kangchenjunga
同时列出在 selectedMountain
和 mountains
字段中,在构造函数中实例化,并用 FakeData
类来填充。现在,我已经有了足够的信息,可以把 Kangchenjunga
传递到 JSP,作为 preselected 的初始值。
第 3 步. 创建单选按钮 JSP
清单 3 包含表单页面的 JSP 代码,表单页面中包含单选按钮和预选的值。请注意 Java 文件和逻辑、HTML、bean 标记与表单底部的 javascript 函数之间的关系。我对 mountains
集合进行迭代,以创建单选按钮。这个工作完成之后,我添加 javascript 并将 selectedMountain
的值填充到其中,并与单选按钮数组进行比较,以选择正确的按钮。
清单 3. 包含单选按钮和预选值的 JSP <%@ taglib uri="/tags/struts-bean" prefix="bean" %> <%@ taglib uri="/tags/struts-html" prefix="html" %> <%@ taglib uri="/tags/struts-logic" prefix="logic" %> <html:html locale="true"> <head> <title><bean:message key="testForm.title"/></title> <html:base/> </head> <body> <h3><bean:message key="testForm.heading"/></h3> <html:form action="/FormAction" name="testForm" type="com.strutsrecipes.RadioTestForm"> <h4><bean:message key="testForm.instruction"/></h4> <!-- gets the selected radio button --> <bean:define id="selectedRadio" property="selectedMountain" name="testForm"/> <!-- creates the radio button list --> <logic:iterate id="mountain" property="mountains" name="testForm"> <%-- you need this hack to get the value of the mountains to the page --%> <bean:define id="mountainValue"> <bean:write name="mountain"/> </bean:define> <html:radio property="selectedMountain" value="<%=mountainValue%>" styleId="<%=mountainValue%>"/> <bean:write name="mountain"/><br/> </logic:iterate><br/> <html:submit/> <html:reset/> <script type="text/javascript"> <!-- //Either of the following works. //Uncomment the one you wish to try and comment the other out. //var selectedRadio = document.getElementById("<bean:write //name="selectedRadio"/>"); var selectedRadio = document.forms["testForm"].elements["<bean:write name="selectedRadio"/>"]; selectedRadio.checked=true; --> </script> </html:form> </body> </html:html> |
第 4 步. 创建显示页面
显示页面基本与复选框诀窍中使用的页面相同,区别只是不需要迭代,因为只选中了一个值。需要做的只是列出 bean 中选中的 mountain
,如清单 4 所示:
清单 4. 显示页面的 JSP <%@taglib uri="http://jakarta.apache.org/struts/tags-html" prefix="html"%> <%@taglib uri="http://jakarta.apache.org/struts/tags-bean" prefix="bean"%> <%@taglib uri="http://jakarta.apache.org/struts/tags-logic" prefix="logic"%> <%-- html code, etc... --%> <bean:write name="mountain"/><br/> <hr size=5 color="black"/> <%-- some more html code, etc... --%> |
第 5 步. 编写 Action 类
正像我在 前一篇文章 提到过的,Action
类比起这个诀窍中的其他组件来说,做的工作并不多。在这里要做的仅仅是得到 String
数组和 selectedMountain
的值,并让显示页面能够使用它们。
清单 5. Action 类 ipackage com.strutsrecipes; 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 javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; /** * The Action for the Radio Button test */ public final class RadioTestAction extends Action { // -------------------------- OTHER METHODS -------------------------- /** * The Action for the Radio Button test */ public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response ) throws ServletException, Exception { // Extract attributes needed String selectedMountains = ((RadioTestForm) form).getSelectedMountain(); System.out.println("htmlString RETURNED*********\n" + selectedMountains); //Save your htmlString in the session HttpSession session = request.getSession(); session.setAttribute(Constants.MOUNTAINS, selectedMountains); return (mapping.findForward("success")); } } |
诀窍内部
诀窍背后真正起作用的是 javascript。首先,我在清单 5 中,为 JSP 表单内的 selectedMountain
字段定义了一个 JSP 脚本变量,如下所示:
<bean:define id="selectedRadio" property="selectedMountains" name="testForm"/> |
在表单下面,我创建了一个 javascript 函数,包含以下两行:
var selRadio = document.getElementById("<bean:write name="selectedRadio"/>"); selRadio.checked=true; |
在客户端脚本内部,我创建了 selRadio
javascript 变量然后对文档内的所有元素进行查找(find),查找 id
(或者预编译代码中的 styleId
)与 selectedRadio
变量匹配的元素。我通过把 <html:radio/>
标记的 styleId
属性设置成与它的名称/值匹配来做到这点。在 javascript 函数迅速地在页面上的 ID 之间迭代的时候,只是把单一单选按钮设置为 selected。
替代方法
用如下所示的 javascript 方法也能产生同样效果:
var selectedRadio = document.forms["testForm"].elements["<bean:writename="selectedRadio"/>"]; selectedRadio.checked=true; |
这个脚本只区分表单元素的 name
属性而不是 id
属性。两种实现都可以工作,所以具体选择哪一种取决于个人需要和偏好。JSP 表单页面的实际输出看起来像清单 6:
清单 6. JSP 表单页面的输出 <input type="radio" name="selectedMountain" value="Everest" id="Everest"/> Everest<br/> <input type="radio" name="selectedMountain" value="K2" id="K2"/> K2<br/> <input type="radio" name="selectedMountain" value="Kangchenjunga" checked="checked" id="Kangchenjunga"/> Kangchenjunga<br/> <input type="radio" name="selectedMountain" value="Lhotse" id="Lhotse"/> Lhotse<br/> <input type="radio" name="selectedMountain" value="Makalu" id="Makalu"/> Makalu<br/> <input type="radio" name="selectedMountain" value="Cho Oyu" id="Cho Oyu"/> Cho Oyu<br/> |
结束语
正像在上一篇中的结束语中我提到过的,Struts 中关于创建动态元素的信息的缺乏,是我与 George Franciscus 合著 Struts Recipes 的一个强烈动机。关于创建动态单选按钮的信息的缺乏很大程度上是因为它固有的设计缺陷:服务器端和客户端的信息和功能耦合得太紧密。使用 javascript 和服务器端变量,就像我在这里演示的这样,似乎是提供这个功能的惟一方式,但是仍然要冒着客户机和服务器对彼此知道得太多的风险。
是否要冒 “耦合” 的风险的决策要您自己来决定。做这项决策的一个好过程是向自己解释它的利弊。如果解释之后还愿意冒这个风险 —— 而且同伴也不笑话您,那么就可以冒险了。