JSF——事件处理

来源:互联网 发布:淘宝客服售后工作 编辑:程序博客网 时间:2024/06/02 19:28

事件处理
JSF
的事件模型提供一个近似的桌面GUI事件模式,让熟悉GUI设计的人员也能快速上手Web程序设计。
o 动作事件
o 实时事件
o 值变事件
o Phase 事件

动作事件

 JSF支持事件处理模型,虽然由于HTTP本身无状态(stateless)的特性,使得这个模型多少有些地方仍不太相同,但JSF所提供的事件处理模型已足以让一些传统GUI程序的设计人员,可以用类似的模型来开发程序。

  在 简单的导航 中,我们根据动作方法(action method)的结果来决定要导向的网页,一个按钮系结至一个方法,这样的作法实际上即使JSF所提供的简化的事件处理程序,在按钮上使用action系结至一个动作方法(action method),实际上JSF会为其自动产生一个「预设的ActionListener」来处理事件,并根据其传回值来决定导向的页面。

  如果您需要使用同一个方法来应付多种事件来源,并想要取得事件来源的相关讯息,您可以让处理事件的方法接收一个javax.faces.event.ActionEvent事件参数,例如:

  • UserBean.java

UserBean.java

package onlyfun.caterpillar;

 

 import javax.faces.event.ActionEvent;

 

 public class UserBean {

    private String name;

    private String password;

    private String errMessage;

    private String outcome;

 

    public void setName(String name) {

        this.name = name;

    }

 

    public String getName() {

        return name;

    }

 

    public void setPassword(String password) {

        this.password = password;

    }

 

    public String getPassword() {

        return password;

    }

 

    public void setErrMessage(String errMessage) {

        this.errMessage = errMessage;

    }

 

    public String getErrMessage() {

        return errMessage;

    }

 

    public void verify(ActionEvent e) {

        if(!name.equals("justin") ||

           !password.equals("123456")) {

            errMessage = "名称或密码错误" + e.getSource();

            outcome = "failure";

        }

        else {

            outcome = "success";

        }

    }

 

    public String outcome() {

        return outcome;

    }

 }

  在上例中,我们让verify方法接收一个ActionEvent对象,当使用者按下按钮,会自动产生ActionEvent对象代表事件来源,我们故意在错误讯息之后如上事件来源的字符串描述,这样就可以在显示错误讯息时一并显示事件来源描述。

  为了提供ActionEvent的存取能力,您的index.jsp可以改写如下:

  • index.jsp

index.jsp

<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>

 <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>

 <%@page contentType="text/html;charset=Big5"%>

 <html>

 <head>

 <title>第一个JSF程序</title>

 </head>

 <body>

    <f:view>

        <h:form>

            <h3>请输入您的名称</h3>

            <h:outputText value="#{user.errMessage}"/><p>

           名称: <h:inputText value="#{user.name}"/><p>

           密码: <h:inputSecret value="#{user.password}"/><p>

            <h:commandButton value="送出"

                            actionListener="#{user.verify}"

                            action="#{user.outcome}"/>

        </h:form>

    </f:view>

 </body>

 </html>

  主要改变的是按钮上使用了actionListener属性,这种方法可以使用一个ActionListenerJSF会先检查是否有指定的 actionListener,然后再检查是否指定了动作方法并产生预设的ActionListener,并根据其传回值导航页面。

  如果您要注册多个ActionListener,例如当使用者按下按钮时,顺便在记录文件中增加一些记录讯息,您可以实作javax.faces.event.ActionListener,例如:

LogHandler.java

package onlyfun.caterpillar;

 

 import javax.faces.event.ActionListener;

 ....

 

 public class LogHandler implements ActionListener {

     public void processAction(ActionEvent e) {

         // 处理Log

     }

 }

VerifyHandler.java

package onlyfun.caterpillar;

 

 import javax.faces.event.ActionListener;

 ....

 

 public class VerifyHandler implements ActionListener {

     public void processAction(ActionEvent e) {

         // 处理验证

     }

 }

  这么一来,您就可以使用<f:actionListener>卷标向组件注册事件,例如:

<h:commandButton value="送出" action="#{user.outcome}">

    <f:actionListener type="onlyfun.caterpillar.LogHandler"/>

    <f:actionListener type="onlyfun.caterpillar.VerifyHandler"/>

 </h:commandButton>

  <f:actionListener>会自动产生type所指定的对象,并呼叫组件的addActionListener()方法注册Listener

实时事件

 所谓的实时事件(Immediate Events),是指JSF视图组件在取得请求中该取得的值之后,即立即处理指定的事件,而不再进行后续的转换器处理、验证器处理、更新模型值等流程。

  在JSF的事件模型中会有所谓实时事件,导因于Web应用程序的先天特性不同于GUI程序,所以JSF的事件模式与GUI程序的事件模式仍有相当程度 的不同,一个最基本的问题正因为HTTP无状态的特性,使得Web应用程序天生就无法直接唤起伺服端的特定对象。

  所有的对象唤起都是在伺服端执行的,至于该唤起什么对象,则是依一个基本的流程:

  • 回复画面(Restore View

  依客户端传来的session数据或伺服端上的session数据,回复JSF画面组件。

  • 套用请求值(Apply Request Values

  JSF画面组件各自获得请求中的值属于自己的值,包括旧的值与新的值。

  • 执行验证(Process Validations

  转换为对象并进行验证。

  • 更新模型值(Update Model Values

  更新Bean或相关的模型值。

  • 唤起应用程序(Invoke Application

  执行应用程序相关逻辑。

  • 绘制回应画面(Render Response

  对先前的请求处理完之后,产生画面以响应客户端执行结果。

  对于动作事件(Action Event)来说,组件的动作事件是在套用请求值阶段就生成ActionEvent对象了,但相关的事件处理并不是马上进行,ActionEvent会先被排入队列,然后必须再通过验证、更新模式值阶段,之后才处理队列中的事件。

  这样的流程对于按下按钮然后执行后端的应用程序来说不成问题,但有些事件并不需要这样的流程,例如只影响画面的事件。

  举个例子来说,在窗体中可能有使用者名称、密码等字段,并提供有一个地区选项按钮,使用者可以在不填下按钮的情况下,就按下地区选项按钮,如果依照正 常的流程,则会进行验证、更新模型值、唤起应用程序等流程,但显然的,使用者名称与密码是空白的,这会引起不必要的错误。

  您可以设定组件的事件在套用请求值之后立即被处理,并跳过后续的阶段,直接进行画面绘制以响应请求,对于JSFinputcommand组件,都有一个immediate属性可以设定,只要将其设定为true,则指定的事件就成为立即事件。

  一个例子如下:

  • index.jsp

index.jsp

<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>

 <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>

 <%@page contentType="text/html;charset=UTF8"%>

 

 <f:view locale="#{user.locale}">

 <f:loadBundle basename="messages" var="msgs"/>

 

 <html>

 <head>

 <title><h:outputText value="#{msgs.titleText}"/></title>

 </head>

 <body>

 

    <h:form>

        <h3><h:outputText value="#{msgs.hintText}"/></h3>

        <h:outputText value="#{msgs.nameText}"/>:

                <h:inputText value="#{user.name}"/><p>

        <h:outputText value="#{msgs.passText}"/>:

                <h:inputSecret value="#{user.password}"/><p>

        <h:commandButton value="#{msgs.commandText}"

                        action="#{user.verify}"/>

        <h:commandButton value="#{msgs.Text}"

                      immediate="true"

                      actionListener="#{user.changeLocale}"/>

   </h:form>

 

 </body>

 </html>

       

 </f:view>

  这是一个可以让使用者决定使用语系的示范,最后一个commandButton组件被设定了immediate属性,当按下这个按钮后,JSF套用请 求值之后会立即处理指定的actionListener,而不再进行验证、更新模型值,简单的说,就这个程序来说,您在输入字段与密码字段中填入的值,不 会影响您的user.nameuser.password

  基于范例的完整起见,我们列出这个程序Bean对象及faces-config.xml

  • UserBean.java

UserBean.java

package onlyfun.caterpillar;

 

 import javax.faces.event.ActionEvent;

 

 public class UserBean {

    private String locale = "en";

    private String name;

    private String password;

    private String errMessage;

 

    public void changeLocale(ActionEvent e) {

        if(locale.equals("en"))

            locale = "zh_TW";

        else

            locale = "en";

    }

 

    public String getLocale() {

        if (locale == null) {

            locale = "en";

        }

        return locale;

    }

 

    public void setName(String name) {

        this.name = name;

    }

 

    public String getName() {

        return name;

    }

 

    public void setPassword(String password) {

        this.password = password;

    }

 

    public String getPassword() {

        return password;

    }

 

    public void setErrMessage(String errMessage) {

        this.errMessage = errMessage;

    }

 

    public String getErrMessage() {

        return errMessage;

    }

 

    public String verify() {

        if(!name.equals("justin") ||

           !password.equals("123456")) {

            errMessage = "名称或密码错误";

            return "failure";

        }

        else {

            return "success";

        }

    }

 }

  • faces-config.xml

faces-config.xml

<?xml version="1.0"?>

 <!DOCTYPE faces-config PUBLIC

 "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"

 "http://java.sun.com/dtd/web-facesconfig_1_0.dtd">

 

 <faces-config>

    <navigation-rule>

        <from-view-id>/pages/index.jsp</from-view-id>

        <navigation-case>

            <from-outcome>success</from-outcome>

            <to-view-id>/pages/welcome.jsp</to-view-id>

        </navigation-case>

        <navigation-case>

            <from-outcome>failure</from-outcome>

            <to-view-id>/pages/index.jsp</to-view-id>

        </navigation-case>

    </navigation-rule>

 

    <managed-bean>

        <managed-bean-name>user</managed-bean-name>

        <managed-bean-class>

            onlyfun.caterpillar.UserBean

        </managed-bean-class>

        <managed-bean-scope>session</managed-bean-scope>

    </managed-bean>

 </faces-config>

  讯息资源文件的内容则是如下:

  • messages_en.properties

messages_en.properties

titleText=JSF Demo

 hintText=Please input your name and password

 nameText=name

 passText=password

 commandText=Submit

 Text=/u4e2d/u6587

  Text中设定的是「中文」转换为Java Unicode Escape格式的结果,另一个讯息资源文件的内容则是英文讯息的翻译而已,其转换为Java Unicode Escape格式结果如下:

  • messages_zh_TW.properties

messages_zh_TW.properties

titleText=JSF/u793a/u7bc4

 hintText=/u8acb/u8f38/u5165/u540d/u7a31/u8207/u5bc6/u78bc

 nameText=/u540d/u7a31

 passText=/u5bc6/u78bc

 commandText=/u9001/u51fa

 Text=English

  welcome.jsp就请自行设计了,程序的画面如下:

 

值变事件

如果使用者改变了JSF输入组件的值后送出窗体,就会发生值变事件(Value Change Event),这会丢出一个javax.faces.event.ValueChangeEvent对象,如果您想要处理这个事件,有两种方式,一是直接设定JSF输入组件的valueChangeListener属性,例如:

<h:selectOneMenu value="#{user.locale}"

                  onchange="this.form.submit();"

                  valueChangeListener="#{user.changeLocale}">

 

     <f:selectItem itemValue="zh_TW" itemLabel="Chinese"/>

     <f:selectItem itemValue="en" itemLabel="English"/>

 </h:selectOneMenu>

  为了仿真GUI中选择了选单项目之后就立即发生反应,我们在onchange属性中使用了JavaScript,其作用是在选项项目发生改变之后,立 即送出窗体,而不用按下提交按钮;而valueChangeListener属性所绑定的user.changeLocale方法必须接受 ValueChangeEvent对象,例如:

UserBean.java

package onlyfun.caterpillar;

 

 import javax.faces.event.ValueChangeEvent;

 

 public class UserBean {

    private String locale = "en";

    private String name;

    private String password;

    private String errMessage;

 

    public void changeLocale(ValueChangeEvent event) {

        if(locale.equals("en"))

            locale = "zh_TW";

        else

            locale = "en";

    }

 

    public void setLocale(String locale) {

        this.locale = locale;

    }

 

    public String getLocale() {

        if (locale == null) {

            locale = "en";

        }

        return locale;

    }

 

    public void setName(String name) {

        this.name = name;

    }

 

    public String getName() {

        return name;

    }

 

    public void setPassword(String password) {

        this.password = password;

    }

 

    public String getPassword() {

        return password;

    }

 

    public void setErrMessage(String errMessage) {

        this.errMessage = errMessage;

    }

 

    public String getErrMessage() {

        return errMessage;

    }

 

    public String verify() {

        if(!name.equals("justin") ||

           !password.equals("123456")) {

            errMessage = "名称或密码错误";

            return "failure";

        }

        else {

            return "success";

        }

    }

 }

  另一个方法是实作javax.faces.event.ValueChangeListener接口,并定义其processValueChange()方法,例如:

SomeListener.java

package onlyfun.caterpillar;

 ....

 public class SomeListener implements ValueChangeListener {

    public void processValueChange(ValueChangeEvent event) {

        ....

    }

    ....

 }

 

 

  然后在JSF页面上使用<f:valueChangeListener>卷标,并设定其type属性,例如:

 

{code:borderStyle=solid}

 <h:selectOneMenu value="#{user.locale}"

                  onchange="this.form.submit();">

     <f:valueChangeListener

              type="onlyfun.caterpillar.SomeListener"/>

     <f:selectItem itemValue="zh_TW" itemLabel="Chinese"/>

     <f:selectItem itemValue="en" itemLabel="English"/>

 </h:selectOneMenu>

  下面这个页面是对 立即事件 中的范例程序作一个修改,将语言选项改以下拉式选单的选择方式呈现,这必须配合上面提供的UserBean类别来使用:

index.jsp

<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>

 <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>

 <%@page contentType="text/html;charset=UTF8"%>

 

 <f:view locale="#{user.locale}">

 <f:loadBundle basename="messages" var="msgs"/>

 

 <html>

 <head>

 <title><h:outputText value="#{msgs.titleText}"/></title>

 </head>

 <body>

 

    <h:form>

        <h:selectOneMenu value="#{user.locale}"

                  immediate="true"

                  onchange="this.form.submit();"

                  valueChangeListener="#{user.changeLocale}">

 

            <f:selectItem itemValue="zh_TW"

                          itemLabel="Chinese"/>

            <f:selectItem itemValue="en"

                          itemLabel="English"/>

        </h:selectOneMenu>

 

        <h3><h:outputText value="#{msgs.hintText}"/></h3>

        <h:outputText value="#{msgs.nameText}"/>:

                <h:inputText value="#{user.name}"/><p>

        <h:outputText value="#{msgs.passText}"/>:

                <h:inputSecret value="#{user.password}"/><p>

        <h:commandButton value="#{msgs.commandText}"

                        action="#{user.verify}"/>

    </h:form>

 

 </body>

 </html>

       

 </f:view>

 

Phase 事件

实时事件 中我们提到,JSF的请求执行到响应,完整的过程会经过六个阶段:

  • 回复画面(Restore View

  依客户端传来的session数据或伺服端上的session数据,回复JSF画面组件。

  • 套用请求值(Apply Request Values

  JSF画面组件各自获得请求中的值属于自己的值,包括旧的值与新的值。

  • 执行验证(Process Validations

  转换为对象并进行验证。

  • 更新模型值(Update Model Values

  更新Bean或相关的模型值。

  • 唤起应用程序(Invoke Application

  执行应用程序相关逻辑。

  • 绘制回应画面(Render Response

  对先前的请求处理完之后,产生画面以响应客户端执行结果。

  在每个阶段的前后会引发javax.faces.event.PhaseEvent,如果您想尝试在每个阶段的前后捕捉这个事件,以进行一些处理,则 可以实作javax.faces.event.PhaseListener,并向javax.faces.lifecycle.Lifecycle登记这 个Listener,以有适当的时候通知事件的发生。

  PhaseListener有三个必须实作的方法getPhaseId()beforePhase()afterPhase(),其中getPhaseId()传回一个PhaseId对象,代表Listener想要被通知的时机,可以设定的时机有:

  • PhaseId.RESTORE_VIEW
  • PhaseId.APPLY_REQUEST_VALUES
  • PhaseId.PROCESS_VALIDATIONS
  • PhaseId.UPDATE_MODEL_VALUES
  • PhaseId.INVOKE_APPLICATION
  • PhaseId.RENDER_RESPONSE
  • PhaseId.ANY_PHASE

  其中PhaseId.ANY_PHASE指的是任何的阶段转换时,就进行通知;您可以在beforePhase()afterPhase()中撰写阶段前后撰写分别想要处理的动作,例如下面这个简单的类别会列出每个阶段的名称:

ShowPhaseListener.java

package onlyfun.caterpillar;

 

 import javax.faces.event.PhaseEvent;

 import javax.faces.event.PhaseId;

 import javax.faces.event.PhaseListener;

 

 public class ShowPhaseListener implements PhaseListener {

 

    public void beforePhase(PhaseEvent event) {

        String phaseName = event.getPhaseId().toString();

        System.out.println("Before " + phaseName);

    }

 

    public void afterPhase(PhaseEvent event) {

        String phaseName = event.getPhaseId().toString();

        System.out.println("After " + phaseName);

    }

 

    public PhaseId getPhaseId() {

        return PhaseId.ANY_PHASE;

    }

 }

  撰写好PhaseListener后,我们可以在faces-config.xml中向Lifecycle进行注册:

faces-config.xml

<?xml version="1.0"?>

 <!DOCTYPE faces-config PUBLIC

 "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"

 "http://java.sun.com/dtd/web-facesconfig_1_0.dtd">

 

 <faces-config>

    <lifecycle>

        <phase-listener>

            onlyfun.caterpillar.ShowPhaseListener

        </phase-listener>

    </lifecycle>

    ......

 </faces-config>

  您可以使用这个简单的类别,看看在请求任一个JSF画面时所显示的内容,藉此了解JSF每个阶段的流程变化。

 

 
原创粉丝点击