JSF事件处理 (4)

 1.JSF Gossip: 动作事件

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

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

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

  • 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
<%@ 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属性,这种方法可以使用一个ActionListener,JSF会先检查是否有指定的 actionListener,然后再检查是否指定了动作方法并产生预设的ActionListener,并根据其传回值导航页面。

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

 package onlyfun.caterpillar;

 import javax.faces.event.ActionListener;
 ....

 public class LogHandler implements ActionListener {
     public void processAction(ActionEvent e) {
         // 处理Log
     }
 }
 

 
 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。

2.JSF Gossip: 即时事件

所谓的即时事件(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会先被排入伫列,然后必须再通过验证、更新模式值阶段,之后才处理伫列中的事件。

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

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

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

一个例子如下:

  • 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.name与user.password。

基于范例的完整起见,我们列出这个程式Bean物件及faces-config.xml:

  • 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
<?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
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
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就请自行设计了,程式的画面如下:




3.JSF Gossip: 值变事件

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

 <h:selectOneMenu value="#{user.locale}"
                  οnchange="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() 方法,例如:

 package onlyfun.caterpillar;
 ....
 public class SomeListener implements ValueChangeListener {
    public void processValueChange(ValueChangeEvent event) {
        ....
    }
    ....
 }

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

 <h:selectOneMenu value="#{user.locale}"
                  οnchange="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"
οnchange="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>

4.JSF Gossip: 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每个阶段的流程变化。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值