联合使用 CSS、JavaScript 和 JSF 精心打造 Ajax 应用程序,第 2 部分: 动态 JSF 表单

探索标准 JSF 组件中的 CSS 支持

在这个由两部分组成的系列文章的 第 1 部分 中,作者和 Java™ 开发人员 Andrei Cioroianu 展示了如何使用 JavaServer Faces (JSF) 组件的样式属性以及如何为这些属性设置默认值。在本系列的第二个部分中,我们将学习如何使用标准 JSF 组件的 JavaScript 相关属性。学习几种基于文档对象模型(Document Object Model,DOM)API、JavaScript™ 和层叠样式表(Cascading Style Sheets,CSS)的 Web 技术。了解如何才能不必刷新 Web 页面即可隐藏和显示可选 JSF 组件,如何实现在 Web 浏览器内执行的客户端验证,以及如何开发能够为 Web 表单的输入元素显示帮助消息的自定义组件。

Andrei Cioroianu, 高级 Java 开发人员和顾问, Devsphere

Andrei Cioroianu 是 Devsphere 公司的一名高级 Java 开发人员和顾问,该公司专门提供定制 Java EE 开发服务以及 Ajax/JSF 顾问服务。您可以通过 www.devsphere.com 的联系表单与 Andrei 联系。



2008 年 6 月 05 日

处理事件和更新用户界面

很多 JSF HTML 组件都拥有 JavaScript 相关的属性,它们允许您指定在发生特定 UI 事件时将在 Web 浏览器中执行的代码片断。例如,标准 JSF 组件支持七类鼠标事件:

  • onmouseover
  • onmouseout
  • onmousemove
  • onmousedown
  • onmouseup
  • onclick
  • ondblclick

当 UI 组件获得或丢失键盘焦点,它将生成可以通过 onfocusonblur 属性捕获的事件。onkeydownonkeyuponkeypress 事件在按下或释放某个键时触发。此外,<h:form> 组件接收 onsubmitonreset 属性,输入组件拥有 onchangeonselect 属性,它们可以用来在表单元素的状态发生更改时,调用 JavaScript 函数。

还可以使用直接包含在 JSF 页面中的 HTML 元素的 JavaScript 相关属性,而不是由 JSF 组件呈现的 HTML 元素。例如,<body> 标记拥有 onloadonunload 属性。onload 事件在 Web 浏览器完成加载某个页面时触发。onunload 事件在用户离开页面时发生。

典型的 JavaScript 事件处理程序使用 Web 浏览器中的 DOM API 来更新 JSF 组件呈现的 HTML 元素的属性。使用 DOM Core API,可以轻松地找到表示 HTML 元素的对象。例如,可以使用 document.getElementById(...) 查找其 ID 已知的元素。

DOM HTML API 扩展了 DOM Core API,添加了一些特定于 HTML 文档的方法和属性。使用 document.forms.myFormId 获得表示 Web 浏览器中表单的对象,然后使用 myForm.elements 可获得表示表单的元素的对象数组。一个非常有用的属性是 className,它允许您更改 HTML 元素的 class 属性。

DOM HTML 规范(参见 参考资料)描述表示客户端上页面元素的对象的所有标准属性和方法。大多数 Web 浏览器,包括 IE、Firefox、Netscape、Safari 和 Opera,支持其他属性,如 innerHTML,它允许更改 HTML 元素的内容。

本节中的示例展示如何使用 JSF HTML 组件的 JavaScript 相关属性,以及如何使用 DOM HTML API 更新用户界面。

在 JSF 页面中放置脚本

使用 HTML 的 <script> 元素(参见清单 1),可以将 JavaScript 代码像插入到任何常规 Web 页面一样插入到 JSF 页面。借助 Web 浏览器中的 document.write(),可以使用 JavaScript 代码生成 HTML 内容,但是很少需要这么做。在大多数情况下,会将 <script> 元素放到页面的头部内,其中将包含从事件属性调用的 JavaScript 函数,如 onclickonsubmitonchange。如果用户的浏览器中禁用了 JavaScript 脚本,还可以使用 <noscript> 元素警告他们。

清单 1. 使用 <script> 标记
<html>
    <head>
        <script type="text/javascript">
            function myEventHandler(...) {
                ...
            }
        </script>
    </head>
    <body>
        <noscript>
            This page requires JavaScript.
        </noscript>
        ...
    </body>
</html>

如果希望在多个页面中调用相同的函数,可以将 JavaScript 代码放到 .js 文件中。外部脚本必须使用 <script> 标记的 src 属性导入到 Web 页面中(参见清单 2)。在这种情况下,请确保没有将 /faces/ 前缀添加到脚本的 URL,如果在 src 属性中使用相对 URI,可能发生这种事情。避免此问题的最简单方法是使用 .faces 后缀。如果更喜欢使用 /faces/ 请求 JSF 页面,请为 JavaScript 文件指定一个绝对 URI,包括 <script> 标记的 src 属性中的上下文路径。

清单 2. 导入外部脚本
<script type="text/javascript"
    src="${pageContext.request.contextPath}/scripts/MyScript.js">
</script>
<script type="text/javascript"
    src="<%=request.getContextPath()%>/AnotherScript.js">
</script>

隐藏和显示可选 JSF 组件

在本系列的 第 1 部分 中,您了解了如何使用 styleClass 属性设置服务器端的 JSF 组件样式类。还可以使用 JavaScript 和 DOM 设置或更改客户端的样式类。以下示例展示如何使用 display CSS 属性折叠和展开一组可选组件。一个简单的搜索表单(参见图 1)包含一个必填的文本字段、两个复选框和一个下拉列表。用户选中 More Options 时,就会显示包含 Match Case 和 Language 组件的面板。如果用户取消选中 More Options,则可选组件将被折叠起来。

图 1. SearchForm 示例
SearchForm 示例

SearchForm.jsp 示例(参见清单 3)使用标准 JSF 组件构建 Web 表单。可选组件被放置到 <h:panelGrid> 容器中,该容器被呈现为 HTML 表格。使用名为 updatePanelClass() 的 JavaScript 函数,使可选面板在客户端显示或隐藏。顾名思义,此函数更改了 <h:panelGrid> 呈现的 <table> 元素的样式类。每次用户更改 More Options 复选框的状态时,就会调用 updatePanelClass() 函数,因为 updatePanelClass() 调用被编码在 <h:selectBooleanCheckbox> 组件的 onclick 属性中。

清单 3. SearchForm.jsp 示例
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>

<f:view>
<html>
<head>
    <title>Search Form</title>
    ...
</head>
<body onload="initForm()">
    <h1>Search Form</h1>
    <h:form id="searchForm">
        <h:panelGrid columns="1" border="0" cellspacing="5">
            <h:panelGroup>
                <h:outputLabel value="Text: " for="text"/>
                <h:inputText id="text" value="#{searchBean.text}"
                    required="true" requiredMessage="Required" size="20"/>
                <h:message for="text"/>
            </h:panelGroup>
            <h:panelGroup>
                <h:selectBooleanCheckbox id="moreOptions"
                    value="#{searchBean.moreOptions}"
                    onclick="updatePanelClass()"/>
                <h:outputLabel value="More Options" for="moreOptions"/>
            </h:panelGroup>
            <h:panelGrid id="optionsPanel"
                    columns="1" border="0" cellspacing="5">
                <h:panelGroup>
                    <h:selectBooleanCheckbox id="matchCase"
                        value="#{searchBean.matchCase}"/>
                    <h:outputLabel value="Match Case" for="matchCase"/>
                </h:panelGroup>
                <h:panelGroup>
                    <h:outputLabel value="Language: " for="language"/>
                    <h:selectOneMenu id="language" value="#{searchBean.language}">
                        <f:selectItem itemValue="English" itemLabel="English"/>
                        <f:selectItem itemValue="Spanish" itemLabel="Spanish"/>
                        <f:selectItem itemValue="French" itemLabel="French"/>
                    </h:selectOneMenu>
                </h:panelGroup>
            </h:panelGrid>
            <h:commandButton id="search" value="Search"
                action="#{searchBean.searchAction}"/>
        </h:panelGrid>
    </h:form>
</body>
</html>
</f:view>

清单 4 中显示了 SearchForm.jsp 页面产生的 HTML。观察标准的 JSF 组件如何向 HTML 元素的 ID 添加 searchForm: 前缀,这些 HTLM 元素由嵌套在 ID 为 searchForm 的 JSF 表单中的组件呈现。

清单 4. SearchForm.jsp 产生的 HTML
<html>
<head>
    <title>Search Form</title>
    ...
</head>
<body onload="initForm()">
    <h1>Search Form</h1>
    
<form id="searchForm" name="searchForm" method="post" 
    action="/jsf12js/SearchForm.faces" 
    enctype="application/x-www-form-urlencoded">
...
<table border="0" cellspacing="5">
<tbody>
<tr>
<td><label for="searchForm:text">Text: </label>
<input id="searchForm:text" type="text" 
    name="searchForm:text" size="20" /></td>
</tr>
<tr>
<td><input id="searchForm:moreOptions" type="checkbox" 
    name="searchForm:moreOptions" checked="checked" 
    onclick="updatePanelClass()" />
<label for="searchForm:moreOptions">More Options</label></td>
</tr>
<tr>
<td><table id="searchForm:optionsPanel" border="0" cellspacing="5">
<tbody>
<tr>
<td><input id="searchForm:matchCase" type="checkbox" 
    name="searchForm:matchCase" />
<label for="searchForm:matchCase">Match Case</label></td>
</tr>
<tr>
<td><label for="searchForm:language">Language: </label>
<select id="searchForm:language" name="searchForm:language" size="1">
    <option value="English">English</option>
    <option value="Spanish">Spanish</option>
    <option value="French">French</option>
</select></td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td><input id="searchForm:search" type="submit" 
    name="searchForm:search" value="Search" /></td>
</tr>
</tbody>
</table>
...
</form>
</body>
</html>

清单 5 显示 SearchForm.jsp 页面的 <style> 元素,该页面的报头包含样式类和 JavaScript 函数。visible 类仅设置可选面板的左侧边界,不需要任何其他设置,因为默认显示 HTML 表格。hidden 类将 display CSS 属性设置为 none,不再显示该表格。

清单 5. SearchForm.jsp 的样式类
<style type="text/css">
    .visible { margin-left: 40px; }
    .hidden  { display: none; }
</style>

updatePanelClass() 函数(参见清单 6)使用 document.getElementById() 定位 searchForm:moreOptions 复选框和 searchForm:optionsPanel 表格。panel 对象表示 <h:panelGrid id="optionsPanel"> 呈现的 <table id="searchForm:optionsPanel"> 元素,checkbox 对象表示 <h:selectBooleanCheckbox id="moreOptions"> 呈现的 <input id="searchForm:moreOptions"> 元素。updatePanelClass() 函数从 checked DOM 属性获得 checkbox 对象的状态,并使用 className DOM 属性设置 panel 表格的样式类。

清单 6. SearchForm.jsp 的 updatePanelClass() 函数
function updatePanelClass() {
    var checkbox = document.getElementById("searchForm:moreOptions");
    var panel = document.getElementById("searchForm:optionsPanel");
    panel.className = checkbox.checked ? "visible" : "hidden";
}

SearchForm.jsp 页面的报头还包含 initForm() 函数(如清单 7 所示),该函数的调用被编码在 <body> 元素的 onload 属性中。此函数定位表单的文本字段,并调用 focus(),以便此页面加载到浏览器中时,用户不必先单击此组件,即可开始输入文本。然后,initForm() 调用 updatePanelClass() 来初始化可选面板的类。

清单 7. SearchForm.jsp 的 initForm() 函数
function initForm() {
    var text = document.getElementById("searchForm:text");
    text.focus();
    updatePanelClass();
}

SearchForm.jsp 示例的输入组件的值与某个简单 bean 的属性绑定在一起,Search 按钮触发名为 searchAction() 的操作方法。SearchBean 类的代码如清单 8 所示。

清单 8. SearchBean
package jsfcssjs;

public class SearchBean implements java.io.Serializable {
    private String text;
    private boolean moreOptions;
    private boolean matchCase;
    private String language;
    
    public SearchBean() {
    }
    
    public String getText() {
        return text;
    }
    
    public void setText(String text) {
        this.text = text;
    }

    ...

    public String searchAction() {
        System.out.print("Text: " + text);
        if (moreOptions) {
            if (matchCase)
                System.out.print(", Match Case");
            System.out.print(", Language: " + language);
        }
        System.out.println();
        return null;
    }
    
}

实现客户端验证

JSF 框架提供了几种在服务器端运行的验证器。当 JSF 验证器发现一个错误时,将向用户返回此表单,以便改正。要尽量减少表单提交失败,也可以使用 JavaScript 代码在客户端验证用户输入。以下示例展示如何测试在文本区组件中输入数据的最大长度。在用户键入文本时还将显示当前数据长度。当用户单击 Submit 按钮时,将使用 JavaScript 函数验证输入。

ClientValidation 示例
ClientValidation 示例

ClientValidation.jsp 示例(参见清单 9)的 JSF 表单包含使用 onkeyup 属性的 <h:inputTextarea> 组件,因此每次用户按键时都将调用一个名为 validateText() 的 JavaScript 函数。每次页面加载到浏览器中时也将调用此函数,因为 <body> 标记的 onload 属性包含 validateText() 调用。即使用户输入在客户端已经过验证,<h:inputTextarea> 组件也会使用 required 属性和 <f:validateLength> 验证器在服务器端验证输入的文本,以防用户的浏览器禁用了 JavaScript。为了阻止恶意用户,服务器端验证也是必需的。

清单 9. ClientValidation.jsp 示例
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>

<f:view>
<html>
...
<body onload="validateText()">
    <h1>Client-Side Validation</h1>
    <h:form id="validForm" onsubmit="return validateForm()">
        <h:panelGrid columns="1" border="0" cellspacing="5">
            <h:panelGroup>
                <h:outputText value="Text (max #{textBean.maxLength} chars): "/>
                <f:verbatim><span id="charCount"></span></f:verbatim>
            </h:panelGroup>
            <h:panelGroup>
                <h:inputTextarea id="textArea" value="#{textBean.text}"
                        required="true" rows="5" cols="30"
                        onkeyup="validateText()">
                    <f:validateLength maximum="#{textBean.maxLength}"/>
                </h:inputTextarea>
                <h:message for="textArea"/>
            </h:panelGroup>
            <h:commandButton id="submit" value="Submit"
                action="#{textBean.submitAction}"/>
        </h:panelGrid>
    </h:form>
</body>
</html>
</f:view>

validateText() 函数(如清单 10 中所示)借助 document.forms.validForm 定位页面的表单,并借助 form.elements["validForm:textArea"] 获得表示文本区的对象。然后,JavaScript 代码测试输入的文本的长度,并且如果用户输入无效则返回错误消息。此外,validateText() 通过设置来自 ClientValidation.jsp<span id="charCount"> 元素的 innerHTML 属性显示当前长度。还将使用 span 元素的 className DOM 属性更新样式类。

清单 10. ClientValidation.jsp 的 validateText() 函数
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>

<f:view>
<html>
<head>
    <title>Client-Side Validation</title>
    <style type="text/css">
        .valid { color: green; }
        .error { color: red; }
    </style>
    <script type="text/javascript">
        function validateText() {
            var form = document.forms.validForm;
            var textArea = form.elements["validForm:textArea"];
            var length = textArea.value.length;
            var maxLength = <h:outputText value="#{textBean.maxLength}"/>;
            var error = null;
            if (length == 0)
                error = "Text cannot be empty.";
            else if (length > maxLength)
                error = "Text cannot have more than "
                    + maxLength + " characters."
            var span = document.getElementById("charCount");
            span.innerHTML = length;
            span.className = (error == null) ? "valid" : "error";
            return error;
        }
        ...
    </script>
</head>
...
</html>
</f:view>

maxLength 变量在清单 10 中的 JavaScript 代码中初始化,方法是使用与 ID 为 textBean 的 bean 具有相同名称的属性。此属性的值是在 faces-config.xml 文件中指定的(参见清单 11)。

清单 11. 在 faces-config.xml 中配置 TextBean
<faces-config xmlns="http://java.sun.com/xml/ns/javaee" ... version="1.2">

    <managed-bean>
        <managed-bean-name>textBean</managed-bean-name>
        <managed-bean-class>jsfcssjs.TextBean</managed-bean-class>
        <managed-bean-scope>request</managed-bean-scope>
        <managed-property>
            <property-name>maxLength</property-name>
            <value>100</value>
        </managed-property>
    </managed-bean>
    ...
</faces-config>

ClientValidation.jsp 示例包含另一个名为 validateForm() 的 JavaScript 函数(参见清单 12)。此函数在单击 Submit 按钮时调用。观察 <h:form>onsubmit 属性中使用的 return 关键字,它正好位于 validateForm() 调用之前。如果返回的值为 false,意味着用户输入无效,Web 浏览器不会向服务器提交表单数据。这是期望的结果,因为 validateForm() 借助 alert() 通知用户出错,并且没有理由提交无效输入。

清单 12. ClientValidation.jsp 的 validateForm() 函数
function validateForm() {
    var error = validateText();
    if (error)
        alert(error);
    return error == null;
}

如果用户输入有效,则 validateForm() 返回 true,并且 Web 浏览器将表单数据提交给服务器,JSF 框架在服务器中调用 TextBean 类的 submitAction() 方法(参见清单 13)。

清单 13. TextBean
package jsfcssjs;

public class TextBean implements java.io.Serializable {
    private String text;
    private int maxLength;
    
    ...

    public String submitAction() {
        System.out.println("Length: " + text.length());
        return null;
    }

}

以上为 <h:inputTextarea> 组件实现的最大长度验证被编码在 ClientValidation.jsp 示例中。这是实现此目的的最简单方法,但是这些代码不能被不作更改地重用。当实现非特定于具体页面的功能时,将 JavaScript 代码移动到外部 .js 文件中是一个不错的想法,这样能够从此应用程序的任何页面调用其函数。此外,可以开发自定义 JSF 组件来设置基于 JavaScript 的机制,甚至可以向现有组件添加自定义属性。下一节将展示如何实现增强标准 JSF 组件的普通 UI 功能。

使用自定义属性启用新 UI 功能

本系列的 第 1 部分 展示如何构建自定义组件来设置 JSF 组件的默认样式。在本节,您将看到如何使用与设置 JavaScript 相关属性相同的技术。此外,此处提供的样例使用了自定义属性,它是使用 <f:attribute> 标记添加到标准 JSF 组件的。

开发自定义 JSF 组件

下面,您将看到如何构建自定义组件,它修改每个包含 <f:attribute name="helpOnFocus" value="..."> 的嵌套组件的 onfocusonblur 属性(参见清单 14)。当表单元素获得和丢失键盘焦点时,将在 Web 浏览器中评估 onfocusonblur 属性的 JavaScript 表达式。拥有标记 <js:helpOnFocus> 的自定义组件更改了每个嵌套输入组件的 onfocus 属性,以在呈现的 <input> 元素获得焦点时,在 <div id="helpOnFocus"> 元素中显示帮助消息。嵌套组件的 onblur 属性也发生更改,以在 <input> 元素丢失键盘焦点时清除帮助消息。

清单 14. 在 JSF 页面中使用 <js:helpOnFocus> 组件
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
<%@ taglib prefix="js" uri="/js.tld" %>
...
<script type="text/javascript" src=".../HelpOnFocus.js">
</script>
...
<js:helpOnFocus>
    ...
    <h:inputText ...>
        <f:attribute name="helpOnFocus" value="... Help message ..."/>
    </h:inputText>
    ...
</js:helpOnFocus>
...
<div id="helpOnFocus">
</div>
...

SetupComponent 类(在 第 1 部分 中介绍过)遍历 JSF 组件树,允许在呈现嵌套组件之前更改其属性。HelpOnFocusComponent 类(参见清单 15)扩展了 SetupComponent,并实现了 setup() 方法,每个 rendered 属性为 true 的嵌套组件都将调用此方法。getAttribute() 方法从组件的属性映射中检索单个属性的值。insertCall() 方法包含给定属性中的函数调用,保留任何现有值。如果此属性已经包含此函数调用,则不会执行任何操作,当应用程序在一次 post 之后向用户返回相同的表单时就会发生这种情况。在这种情况下,自定义组件已经在处理前一个请求期间将 onfocusonblur 属性添加到嵌套组件。属性已经包含 JavaScript 调用,因为 JSF 框架在请求之间保存了所有组件的状态。

清单 15. HelpOnFocusComponent
package jsfcssjs;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;

import java.util.Map;

public class HelpOnFocusComponent extends SetupComponent {

    protected void setup(FacesContext ctx, UIComponent comp) {
        Map<String, Object> attrMap = comp.getAttributes();
        String helpOnFocus = getAttribute(attrMap, "helpOnFocus");
        if (helpOnFocus != null) {
            String helpParam[] = new String[] {
                    EncodeUtils.encodeString(helpOnFocus).toString() };
            insertCall(attrMap, "onfocus", "showHelpOnFocus", helpParam);
            insertCall(attrMap, "onblur", "clearHelpOnBlur", null);
        }
    }
    
    protected String getAttribute(Map<String, Object> attrMap, String attrName) {
        Object attrValue = attrMap.get(attrName);
        if (attrValue != null)
            return attrValue.toString();
        else
            return null;
    }
    
    protected void insertCall(Map<String, Object> attrMap, String attrName,
            String functionName, String functionParams[]) {
        String attrValue = getAttribute(attrMap, attrName);
        if (attrValue != null && attrValue.indexOf(functionName) != -1)
            return;
        StringBuilder buf = EncodeUtils.encodeCall(
                functionName, functionParams);
        if (attrValue != null && attrValue.length() > 0) {
            buf.append(';');
            buf.append(attrValue);
        }
        attrMap.put(attrName, buf.toString());
    }
    
    public String getFamily() {
        return "HelpOnFocus";
    }

}

HelpOnFocus.js 文件(如清单 16 所示)包含 showHelpOnFocus()clearHelpOnBlur() 函数,这些函数的调用被编码在 onfocusonblur 属性中。setInnerHTML() 函数将给定的 content 插入到具有给定 id 的 HTML 元素中。

清单 16. HelpOnFocus.js 文件
function setInnerHTML(id, content) {
    document.getElementById(id).innerHTML = content;
}
    
function showHelpOnFocus(msg) {
    setInnerHTML("helpOnFocus", msg);
}

function clearHelpOnBlur() {
    setInnerHTML("helpOnFocus", "");
}

类似于任何自定义 JSF 组件,HelpOnFocusComponent 必须在 faces-config.xml 中配置(参见清单 17)。

清单 17. 在 faces-config.xml 中配置 HelpOnFocusComponent
<faces-config xmlns="http://java.sun.com/xml/ns/javaee" ... version="1.2">
    ...
    <component>
        <component-type>HelpOnFocusComponent</component-type>
        <component-class>jsfcssjs.HelpOnFocusComponent</component-class>
        <component-extension>
            <component-family>HelpOnFocus</component-family>
        </component-extension>
    </component>

</faces-config>

清单 18 展示了 HelpOnFocusTag 类,它实现自定义组件的标记处理程序。UIComponentELTag 基类是 JSF 1.2 API 的一部分。如果希望在基于 JSF 1.1 的应用程序中使用此自定义组件,则标记处理程序的超类必须是 UIComponentTag

清单 18. HelpOnFocusTag 类
package jsfcssjs;

import javax.faces.webapp.UIComponentELTag;

public class HelpOnFocusTag extends UIComponentELTag {

    public String getComponentType() {
        return "HelpOnFocusComponent";
    }

    public String getRendererType() {
        return null;
    }

}

自定义标记的名称和属性是在 js.tld 文件中指定的(如清单 19 所示)。HelpOnFocusTag 处理程序不包含其属性的任何设置方法,因为它是从 UIComponentELTag 继承来的。

清单 19. js.tld 文件
<?xml version="1.0" encoding="UTF-8" ?>
<taglib xmlns="http://java.sun.com/xml/ns/javaee" ... version="2.1">
    <tlib-version>1.0</tlib-version>
    <short-name>js</short-name>
    <uri>/js.tld</uri>
    <tag>
        <name>helpOnFocus</name>
        <tag-class>jsfcssjs.HelpOnFocusTag</tag-class>
        <body-content>JSP</body-content>
        <attribute>
            <name>id</name>
            <required>false</required>
            <rtexprvalue>true</rtexprvalue>
        </attribute>
        <attribute>
            <name>binding</name>
            <required>false</required>
            <deferred-value>
                <type>jsfcssjs.HelpOnFocusComponent</type>
            </deferred-value>
        </attribute>
        <attribute>
            <name>rendered</name>
            <required>false</required>
            <deferred-value>
                <type>boolean</type>
            </deferred-value>
        </attribute>
    </tag>
</taglib>

JavaScript 编码实用工具

以上所示的 HelpOnFocusComponent 类使用了 EncodeUtils 类提供的 helper 方法。encodeString() 方法(参见清单 20)避开 backspace、TAB、CR、LF 和非 ASCII 字符,以便返回的值可以在 JavaScript 代码中用作字符串字符。此外,"&<> 字符被替换成 &quot;&amp;&lt;&gt;,以便编码的字符串可以包含在 HTML 页面中。两个引号字符用作分隔符,例如,如果向 encodeString() 传递 abc \ " & < > [TAB] [LF] [CR] 123,则返回值将为 "abc \\ &quot; &amp; &lt; &gt; \t \n \r 123"

清单 20. EncodeUtils 类的 encodeString() 方法
package jsfcssjs;

public class EncodeUtils {

    public static StringBuilder encodeString(String str) {
        if (str == null)
            return null;
        StringBuilder buf = new StringBuilder();
        buf.append('"');
        int n = str.length();
        for (int i = 0; i < n; i++) {
            char ch = str.charAt(i);
            switch (ch) {
                case '\\': buf.append("\\\\"); break;
                case '\'': buf.append("\\\'"); break;
                case '"':  buf.append("&quot;"); break;
                case '&':  buf.append("&amp;"); break;
                case '<':  buf.append("&lt;"); break;
                case '>':  buf.append("&gt;"); break;
                case '\t': buf.append("\\t"); break;
                case '\r': buf.append("\\r"); break;
                case '\n': buf.append("\\n"); break;
                default: {
                    if (' ' <= ch && ch <= '~')
                        buf.append(ch);
                    else {
                        buf.append("\\u");
                        for (int j = 3; j >= 0; j--) {
                            int h = (((int) ch) >> (j*4)) & 0x0f;
                            buf.append((char) (h<10 ? '0'+h : 'a'+h-10));
                        }
                    }
                }
            }
        }
        buf.append('"');
        return buf;
    }
    ...
}

encodeCall() 方法(参见清单 21)使用给定的参数构建了一个函数调用。

清单 21. EncodeUtils 类的 encodeCall() 方法
public class EncodeUtils {
    ...
    public static StringBuilder encodeCall(
            String functionName, String functionParams[]) {
        StringBuilder buf = new StringBuilder();
        buf.append(functionName);
        buf.append('(');
        if (functionParams != null)
            for (int i = 0; i < functionParams.length; i++) {
                if (i > 0)
                    buf.append(',');
                buf.append(functionParams[i]);
            }
        buf.append(')');
        return buf;
    }
    
}

为了更好地理解编码方法,考虑清单 22 的示例,它包含一个已经拥有 onfocus 属性的输入组件。还使用 <f:attribute>helpOnFocus 属性添加到此组件,并且整个表单是由 <js:helpOnFocus> 组件包装的。

清单 22. 字符串编码示例
<% request.setAttribute("msg", "abc \\ \" & < > \t \n \r 123"); %>
...
<js:helpOnFocus>
    <h:form>
        <h:inputText ... onfocus="alert('focus')">
            <f:attribute name="helpOnFocus" value="#{msg}"/>
        </h:inputText>
    </h:form>
</js:helpOnFocus>
...
<div id="helpOnFocus">
</div>
...

执行此页面时,<js:helpOnFocus> 组件遍历嵌套组件树,并设置 onfocusonblur 属性来显示和清除帮助消息。清单 23 显示了 JSF 组件呈现的 HTML。onfocus 属性包含 showHelpOnFocus() 调用和来自清单 22 中所示示例的 alert('focus') 表达式。编码字符串的 & 字符和 " 分隔符又被 <h:inputText> 组件回避,此组件编码自己的属性值。

清单 23. 编码后的字符串
<form ...>
...
<input ... onblur="clearHelpOnBlur()" 
           onfocus="showHelpOnFocus(&quot;abc \\ &amp;quot; &amp;amp; 
&amp;lt; &amp;gt; \t \n \r 123&quot;);alert('focus')" />
...
</form>
...
<div id="helpOnFocus">
</div>
...

当 Web 浏览器解析和解码清单 23 中的 HTML 片断时,从 onfocus 属性检索到的值为 showHelpOnFocus("abc \\ &quot; &amp; &lt; &gt; \t \n \r 123");alert('focus')。浏览器将此代码传递给它的 JavaScript 引擎,该引擎调用 showHelpOnFocus() 函数,将 abc \ &quot; &amp; &lt; &gt; [TAB] [NL] [CR] 123 插入到 Web 页面的 <div id="helpOnFocus"> 元素,而且浏览器显示 abc \ " & < > 123

在 JSF 页面中使用自定义组件

HelpOnFocus.jsp 页面(参见清单 24)是另一个使用 <js:helpOnFocus> 组件的示例。此 JSF 表单包含三个输入组件:文本字段、复选框和下拉列表。这些组件都使用 <f:attribute> 标记来指定帮助消息。

清单 24. HelpOnFocus.jsp 示例
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
<%@ taglib prefix="js" uri="/js.tld" %>

<f:view>
    ...
    <script type="text/javascript"
        src="<%=request.getContextPath()%>/HelpOnFocus.js">
    </script>
    ...
    <js:helpOnFocus>
        ...
        <h:inputText id="text" ...>
            <f:attribute name="helpOnFocus"
                    value="Enter the text you want to search for."/>
        </h:inputText>
        ...
        <h:selectBooleanCheckbox id="matchCase" ...>
            <f:attribute name="helpOnFocus"
                    value="Distinguish between lowercase and uppercase
                                                        characters."/>
        </h:selectBooleanCheckbox>
        ...
        <h:selectOneMenu id="language" ...>
            ...
            <f:attribute name="helpOnFocus"
                    value="Select the language of the searched text."/>
        </h:selectOneMenu>
        ...
        <f:verbatim>
            <div id="helpOnFocus">
            </div>
        </f:verbatim>
        ...
    </js:helpOnFocus>
    ...
</f:view>

清单 25 展示了 HelpOnFocus.jsp 页面产生的 HTML。

清单 25. HelpOnFocus.jsp 生成的 HTML
...
<script type="text/javascript" 
    src="/jsf12js/HelpOnFocus.js">
</script>
...
<input id="searchForm:text" type="text" ... onblur="clearHelpOnBlur()" 
    onfocus="showHelpOnFocus(&quot;Enter the text you want to search for.&quot;)"/>
...
<input id="searchForm:matchCase" type="checkbox" ... onblur="clearHelpOnBlur()" 
    onfocus="showHelpOnFocus(&quot;Distinguish between lowercase and uppercase
        \r\n                                                 characters.&quot;)" />
...
<select id="searchForm:language" ... onblur="clearHelpOnBlur()" 
    onfocus="showHelpOnFocus(&quot;Select the language of the searched text.&quot;)">
    ...
</select>
...
<div id="helpOnFocus">
</div>
...

结束语

本文展示如何开发可更新由 JSF 组件呈现的 HTML 的 JavaScript 事件处理程序。介绍了几种 Web 技术,包括:

  • 设置 className 属性以更改 class 属性的值
  • 使用 innerHTML 属性在 HTML 元素中插入一些内容
  • 借助 CSS 隐藏和显示 JSF 组件
  • 借助 JavaScript 实现客户端验证

还展示了如何使用自定义属性和包装器组件向现有 JSF 组件添加新功能。


下载

描述名字大小
本文的示例应用程序jsfcssjs_p2.zip30KB

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Web development, Java technology
ArticleID=311376
ArticleTitle=联合使用 CSS、JavaScript 和 JSF 精心打造 Ajax 应用程序,第 2 部分: 动态 JSF 表单
publish-date=06052008