每个 JSF 组件都有一个默认事件,当在组件标记内部嵌入 <f:ajax> 标记时,该事件将触发 Ajax 调用。对于菜单,该事件为 change 事件。这意味着我可以删除 中的 <f:ajax> 的 event 属性。<f:ajax> 的 execute 属性的默认值是 @this,这表示围绕在 <f:ajax> 周围的组件。在本例中,该组件为菜单,因此还可以删除 execute 属性。
通过对 <f:ajax> 使用默认属性值,我可以将 简化为清单 8:
清单 8. 简化后的 Ajax 缩放菜单<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" <h:selectOneMenu value="#{cc.parent.attrs.location.zoomIndex}"> <f:ajax render="map"/> <f:selectItems value="#{places.zoomLevelItems}"/> </h:selectOneMenu> <m:map...>
这演示了使用 JSF 2 向组件添加 Ajax 有多么容易。当然,前面的例子非常简单:我仅仅是在用户选择某个缩放级别时重新绘制了地图而不是整个页面。验证表单中的各个字段等操作要更加复杂一些,因此接下来我将讨论这些用例。
验证当用户移出某个字段后对字段进行验证并提供即时的反馈,这始终是一个好的做法。例如,在图 7 中,我使用了 Ajax 对名称字段进行了验证:
图 7. Ajax 验证该名称字段的标记如清单 9 所示:
清单 9. 名称字段<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" <h:panelGrid columns="2"> #{cc.attrs.namePrompt} <h:panelGroup> <h:inputText value="#{cc.attrs.managedBean.name}" valueChangeListener="#{cc.attrs.managedBean.validateName}"> <f:ajax event="blur" render="nameError"/> </h:inputText> <h:outputText value="#{cc.attrs.managedBean.nameError}"/> </h:panelGroup> ... </h:panelGrid>
我再一次使用了 <f:ajax>,只不过这一次没有执行输入的默认事件 —change,因此我将 blur 指定为触发 Ajax 调用的事件。当用户移出名称字段时,JSF 将对服务器发出 Ajax 调用并在生命周期的执行部分运行 name 输入组件。这意味着 JSF 将在生命周期的 Process Validations 阶段调用 name 输入的值修改监听程序(在 中指定)。清单 10 展示了这个值修改监听程序:
清单 10. validateName() 方法package com.clarity import javax.faces.context.FacesContext import javax.faces.bean.ManagedBean import javax.faces.bean.SessionScoped import javax.faces.event.ValueChangeEvent import javax.faces.component.UIInput @ManagedBean() @SessionScoped public class User { private String name, password, nameError; ... public void validateName(ValueChangeEvent e) { UIInput nameInput = e.getComponent() String name = nameInput.getValue() if (name.contains("_")) nameError = "Name cannot contain underscores" else if (name.equals("")) nameError = "Name cannot be blank" else nameError = "" } ... }
这个修改值的监听程序(user 托管 bean 的 validateName() 方法)将验证名称字段并更新 user 托管 bean 的 nameError 属性。
返回 Ajax 调用后,借助 中的 <f:ajax> 标记的 render 属性,JSF 呈现 nameError 输出。该输出显示了 user 托管 bean 的 nameError 属性。
多字段验证在前面的小节中,我展示了如何对单一字段执行 Ajax 验证。但是,有些情况下,需要同时对多个字段进行验证。比如,图 8 展示了 places 应用程序同时验证名称和密码字段:
图 8. 验证多个字段我在用户提交表单时同时验证了名称和密码字段,因此对这个例子不需要用到 Ajax。相反,我将使用 JSF 2 的新事件系统,如清单 11 所示:
清单 11. 使用 <f:event><h:form prependId="false"> <f:event type="postValidate" listener="#{cc.attrs.managedBean.validate}"/> ... </h:form> <div> <h:messages layout="table"/> </div>
在 中,我使用了 <f:event>— 类似于 <f:ajax> ,它是 JSF 2 中新增的内容。<f:event> 标记在另一方面还类似于 <f:ajax>:使用起来很简单。
将一个 <f:event> 标记放到组件标记的内部,当该组件发生指定的事件(使用 type 属性指定)时,JSF 将调用一个使用 listener 属性指定的方法。因此,<f:event> 标记在 中的含义就是:对表单进行验证后,对用户传递给这个复合组件的托管 bean 调用 validate() 方法。该方法如清单 12 所示:
清单 12. validate() 方法package com.clarity import javax.faces.context.FacesContext import javax.faces.bean.ManagedBean import javax.faces.bean.SessionScoped import javax.faces.event.ValueChangeEvent import javax.faces.component.UIInput @ManagedBean() @SessionScoped public class User { private final String VALID_NAME = "Hiro"; private final String VALID_PASSWORD = "jsf"; ... public void validate(ComponentSystemEvent e) { UIForm form = e.getComponent() UIInput nameInput = form.findComponent("name") UIInput pwdInput = form.findComponent("password") if ( ! (nameInput.getValue().equals(VALID_NAME) && pwdInput.getValue().equals(VALID_PASSWORD))) { FacesContext fc = FacesContext.getCurrentInstance() fc.addMessage(form.getClientId(), new FacesMessage("Name and password are invalid. Please try again.")) fc.renderResponse() } } ... }