技术开发 频道

借助 Ajax 自动保存 JSF 表单,第 1 部分: 利用 XMLHttpRequest 提交

  使用 JSF 侦听程序处理 Ajax 请求

  到目前为止,您已经学会了如何用 Ajax 将表单数据提交给 JSF 页面。现在,让我们看一下如何在服务器端处理 Ajax 请求。我们先来简单介绍 JSF 请求处理生命周期,这是理解本文所附的 示例代码 所必需的。JSF 规范包括了对请求处理生命周期的完整描述,在开发自已的 JSF 应用程序时,您会发现该规范非常有用。

  理解 JSF 请求处理生命周期

  JSF 框架处理一个典型的、发布表单数据的请求要经过六个阶段:

  1.恢复视图

  2.应用请求值

  3.处理验证

  4.更新模型值

  5.调用应用程序

  6.呈现响应

  首先,框架需要恢复表单页面的组件树。根据 javax.faces.STATE_SAVING_METHOD 配置参数的值,组件树可能会从请求参数中解除序列化,也可能会从 HttpSession 对象获取。

  然后,JSF 框架递归式地遍历组件树,更新组件状态。例如,一个实现 EditableValueHolder 的输入组件的 submittedValue 属性将会设置为相应的请求参数。如果组件的 immediate 属性为 true,那么 JSF 框架还会转换并验证所提交的值,设置组件的 value 属性。如果 immediate 属性为 false,那么这个转换和验证就会在 JSF 请求处理生命周期的下一阶段执行。

  前三个阶段(恢复视图、应用请求值和处理验证)完成后,组件树就包含了所提交的表单数据,而且数据也已由 JSF 框架进行了解码、转换和验证。就目前而言,应用程序应保存 JSF 组件的值以便能在日后恢复 Web 表单的数据。

  一个能自动保存表单数据的 Ajax 请求处理生命周期必须在验证阶段过后终止。否则,JSF 框架将进入下一阶段:更新绑定到 JSF 组件的 JavaBean 属性的模型值。在用户单击按钮发生常规表单提交的情况下,JSF 框架还将调用与这个命令按钮相关的动作方法。最后一个阶段是呈现 HTML 响应。若表单是自动保存的,请求处理生命周期的这后三个阶段(更新模型值、调用应用程序和呈现响应)是不必要的。

  实现 PhaseListener 接口

  表单自动保存不应妨碍应用程序的功能。这意味着当利用 Ajax 为自动保存的目的而提交表单时,不应设置任何 JavaBean 属性,也不应调用任何动作方法。此外,在自动保存后也不应生成任何 HTML 响应,因为浏览器不需要任何刷新。因此,应用程序需要控制 JSF 请求处理生命周期,这可以通过实现 PhaseListener 很轻松地完成。在本文所附的示例应用程序中,JSF 侦听程序名为 AutoSaveListener 并在 faces-config.xml 文件中配置(见清单 11)。

  清单 11. 配置 JSF 阶段侦听程序

<faces-config>

    
<lifecycle>
        
<phase-listener>autosave.AutoSaveListener</phase-listener>
    
</lifecycle>
    ...

</faces-config>

  正如前面所说明的,验证阶段过后,对自动保存请求的处理必须终止以便 JSF 框架不会更新 JavaBean 数据模型,该模型不应受到自动保存的影响。因此,只有在验证阶段以后,侦听程序才开始接收通知,其 ID 通过 getPhaseId() 方法返回。如果请求被标记为 Ajax-Request 报头,那么侦听程序的 afterPhase() 方法就会调用 FacesContext 对象的 responseComplete() 方法,告知 JSF 框架,它应该停止处理请求。 (见清单 12)。

  清单 12. 实现 JSF 阶段侦听程序

package autosave;

import javax.faces.component.EditableValueHolder;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;

import java.util.Iterator;
import java.util.Map;

public class AutoSaveListener implements PhaseListener {

    
public PhaseId getPhaseId() {
        return PhaseId.PROCESS_VALIDATIONS;
    }
    
    
public void beforePhase(PhaseEvent e) {
    }

    
public void afterPhase(PhaseEvent e) {
        System.out.println();
        System.out.print(e.getPhaseId());
        System.out.print(
" - ");
        FacesContext ctx
= e.getFacesContext();
        Map headers
= ctx.getExternalContext().getRequestHeaderMap();
        
if ("Auto-Save".equals(headers.get("Ajax-Request"))) {
            System.out.println(
"Auto-Save");
            ctx.responseComplete();
        }
else
            System.out.println(
"Submit");
        printTree(ctx.getViewRoot(),
0);
    }
    ...
}

  打印组件树

  除了在合适的时候停止请求处理外,侦听程序会通过称为 printTree() 的递归方法打印组件树。此方法会输出组件成员、呈现程序类型、惟一 ID 及每个组件验证后的值(见清单 13)。

  清单 13. 打印 JSF 组件树

public class AutoSaveListener implements PhaseListener {
    ...
    
public void printTree(UIComponent comp, int level) {
        
if (comp == null)
            return;
        
Object value = null;
        
if (comp instanceof EditableValueHolder)
            value
= ((EditableValueHolder) comp).getValue();

        
for (int i = 0; i < level; i++)
            System.out.print(
"    ");
        System.out.print(comp.getFamily());
        System.out.print(
" - ");
        System.out.print(comp.getRendererType());
        System.out.print(
" - [");
        System.out.print(comp.getId());
        System.out.print(
"]");
        
if (value != null) {
            System.out.print(
" - ");
            
if (value instanceof Object[]) {
                
Object array[] = (Object[]) value;
                
for (int i = 0; i < array.length; i++) {
                    System.out.print(
array[i]);
                    System.out.print(
" ");
                }
            }
else
                System.out.print(value);
        }
        System.out.println();

        Iterator children
= comp.getChildren().iterator();
        
while (children.hasNext()) {
            UIComponent child
= (UIComponent) children.next();
            printTree(child, level
+ 1);
        }
    }

}

  清单 14 显示了所打印的组件树。

  清单 14. 打印出的组件树

PROCESS_VALIDATIONS 3 - Auto-Save
javax.faces.ViewRoot
- null - [null]
    javax.faces.Form
- javax.faces.Form - [supportForm]
        javax.faces.Output
- javax.faces.Text - [_id0]
        javax.faces.Message
- javax.faces.Message - [_id1]
        javax.faces.Input
- javax.faces.Text - [name] - John Smith
        ...
        javax.faces.Output
- javax.faces.Text - [_id11]
        javax.faces.Message
- javax.faces.Message - [_id12]
        javax.faces.SelectOne
- javax.faces.Radio - [platform] -
                                                           Windows
            javax.faces.SelectItem
- null - [_id13]
            javax.faces.SelectItem
- null - [_id14]
            javax.faces.SelectItem
- null - [_id15]
        ...
        javax.faces.Output
- javax.faces.Text - [_id26]
        javax.faces.Message
- javax.faces.Message - [_id27]
        javax.faces.Input
- javax.faces.Textarea - [problem] -
                                                     Unable
to ...
        javax.faces.Command
- javax.faces.Button - [submit]

  通过获取能实现 EditableValueHolder 接口的输入组件的值,printTree() 方法会从组件树实际获得已提交的表单数据。在本系列的第 2 部分,您将获得更多有关处理组件树和控制请求处理生命周期的 JSF 技巧。

0
相关文章