JavaServer Faces中的页面导航
10/29/2003
页面导航是Web编程的重要组成部分。程序越复杂管理用户页面调度就越困难。 JavaServer Faces (JSF) 通过把页面导航信息配置在配置文件中来解决这一问题,这使得JSF将来会成文Web开发的首选。你可以在这里现在例子.这些例子使用了JSF Early Access 4。请参考ONJava.com 网站上的"Introducing JavaServer Faces"里提到的“如何部署你的JSF应用”,如果你对JSF还不熟悉,这篇文章是你的首选。
所有的多页面Web程序都需要为用户管理页面之间的导航关系。如果你熟悉Servlet和JSP的Model 1和Model 2开发方式,你肯定了解Model 1方式中页面导航是多么复杂,因为用户进入下一个页面的链接全部都是硬编码,这导致Model 1方式不是和中等规模的应用开发。在Model 2这个问题有所缓解,因为导航信息是在Servlet控制其中定义的。然而,要改变链接就不得不重新编译Servlet。 JSF提供了在应用配置文件中定义页面导航的规则。
页面导航在JSF应用开发中至关重要。所有多页面的JSF都要求用户为用户进行页面导航,用户通过点击一个叫做UICommand的组件进入其他页面,用command_button标签或者command_hyperlink标签代表。点击这个组件将产生出javax.faces.event.ActionEvent的时间对象。通常,要捕获一个ActionEvent,你需要编写一个ActionListener。但是,JSF为页面导航提供了一个默认的 listener。因此你不需要亲自动手去编写。但是这个默认的action listener用户点击UICommand时进入哪个界面?答案是通过在应用程序配置文件中定义的页面导航规则。因为它是一个XML文件,你可以用任何文本编辑起来编写,这意味着它很容易修改。
这篇文章讲述了页面导航规则和如何应用它们。将会伴随一个简单的导航的例子。然后,讨论Action类在导航中扮演的角色。然后用一个比较复杂的例子来说明如何使现有条件的页面导航。开始吧!
导航规则
单击UICommand组件将会发送ActionEvent到一个默认的句柄,这个句柄会被自动注册到每一个组UICommand上。
每个在应用配置文件中定义的导航规则都有一个源页面和一个或多个目标页面。一个页面用它的树型标识符代表,每个可能的目标页面用导航条件(Naviation Case)代表。你需要为每一个页面指定一个导航规则,从这个页面上用户可以跳转到其他的页面。”
为了更好的理解,试想有一个叫login.jsp的页面,它包含一个登录的表单。当用户单击提交按钮登录的时候,程序将重定向到welcome.jsp(如多登录成功)或者返回到login.jsp(如果登录失败),这样,login.jsp就有两个导航条件,登录成功和登录失败。
导航规则在应用配置文件navigation-rule元素中定义,如下
<!ELEMENT navigation-rule (description*, display-name*, icon*, from-tree-id?, navigation-case*)>
其中的“*”号表示出现0次或0次以上,因此description, display-name, icon和navigation-case都是可选的且出现很多次。标记着“?”号表示元素可以出现0次获1次,navigation-rule最重要的两个子元素就是from-tree-id 和navigation-case。
from-tree-id子元素的值是源页面的树形标识符。要描述login.jsp的导航规则,如下所示
<from-tree-id>login.jsp</from-tree-id>
navigation-case子元素代表了一个可能的目标。navigation-rule元素可以没有或有多个navigation-case子元素,每个元素为from-tree-id处理定义了一个结果,结果可以从UICommand的action和actionRef属性中获取。
navigation-case元素描述如下:
<!ELEMENT navigation-case
(description*, display-name*, icon*, from-action-ref?, from-outcome?, to-tree-id)>
display-name和icon很少用到,from-action-ref, from-outcome和to-tree-id 的结束如下:
to-tree-id定义了这个导航条件的目标页面;from-outcome定义了处理from-tree-id的结果。它是从触发了ActionEvent事件的UICommand组件的action属性值获得的。
from-action-ref 元素和from-outcome一样,它对应UICommand组件的actionRef
一个简单的例子
下面的例子展示了一个简单的页面导航规则,这个程序由两个页面组成:a.jsp 和b.jsp。从a.jsp,,用户可以单击按钮到b.jsp 反之亦然,下面是它们的导航规则:
<navigation-rule>
<from-tree-id>/a.jsp</from-tree-id>
<navigation-case>
<to-tree-id>/b.jsp</to-tree-id>
</navigation-case>
</navigation-rule>
<navigation-rule>
<from-tree-id>/b.jsp</from-tree-id>
<navigation-case>
<to-tree-id>/a.jsp</to-tree-id>
</navigation-case>
</navigation-rule>
第一个navigation-rule为a.jsp定义了目标,它包含一个带有 to-tree-id子元素的navigation-case元素,没有 from-outcome和from-action-ref 子元素,不管什么结果都会显示b.jsp。第二个为b.jsp定义了目标,从b.jsp,用户肯定会跳转到a.jsp。
注意:如果有错误处理的话源页面还会被显示
a. jsp 和b.jsp 用到了下面的ValueBean:
package jsfArticle;
public class ValueBean {
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
这个bean的定义必须在应用配置文件中给出:
<managed-bean>
<managed-bean-name>ValueBean</managed-bean-name>
<managed-bean-class>jsfArticle.ValueBean</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
a. jsp 包含一个UIInput组件和一个强迫用户输入4个以上自负的校验器:
a.jsp
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<html>
<head>
<title>First Page</title>
</head>
<body>
<f:use_faces>
<h:form formName="myForm">
Please enter your user id (Your user id must be at least 4
characters long) :
<h:input_text valueRef="ValueBean.value">
<f:validate_length minimum="4"/>
</h:input_text>
<br/>
<h:command_button commandName="submit" label="submit"/>
<br/>
<hr/>
<h:output_errors/>
</h:form>
</f:use_faces>
</body>
</html>
b.jsp 页面包含一个UIOutput组件,用来显示在a.jsp 页面中的UIInput 组件中输入的字符,b.jsp 也有一个命令按钮用来返回a.jsp。
b.jsp
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<html>
<head>
<title>Second Page</title>
</head>
<body>
<f:use_faces>
<h:form formName="myForm">
The user id you have entered is <h:output_text
valueRef="ValueBean.value"/>
<br/>
<h:command_button commandName="back"
label="Back to Previous Page"/>
</h:form>
</f:use_faces>
</body>
</html>
你可以用这个URL来访问a.jsp(假设你的web container用8080端口而且你在本机上运行这个程序)
http://localhost:8080/jsfPageNav/faces/a.jsp
现在,输入并提交四个以上的字符组成的字符串
如果你单击b.jsp上的按钮就会回到a.jsp。
如果你输入了小于四个的字符串,a.jsp中的校验器就会添加一条消息到FacesContext实例中,取消导航。结果如下:
Action类
Action是一个执行任务的对象。在JSF中,定义为一个抽象类javax.faces.application.Action。
最重要的方法是invoke()这是一个抽象方法,返回值是String类
public abstract String invoke();
你在这个方法中编写代码,JSF实现会调用你的方法。
Action会通过command_button 或command_hyperlink标签的actionRef属性被UICommand 调用。为了说明如何使用这个类,请看下面的例子“有条件的页面导航”。
有条件的页面导航
这部分介绍一个例子来师范如何通过导航规则定义多个导航条件。这个例子包含两个页面: login.jsp 和welcome.jsp. login.jsp 页面包行两个UIInput 组件让用户输入用户名和密码,单击命令按钮登录。登录成功将会进入welcome.jsp 页面,否则用户会看到带有错误消息的login.jsp页面。
有条件的导航页面规则:
<navigation-rule>
<from-tree-id>/login.jsp</from-tree-id>
<navigation-case>
<from-outcome>success</from-outcome>
<to-tree-id>/welcome.jsp</to-tree-id>
</navigation-case>
<navigation-case>
<from-outcome>failed</from-outcome>
<to-tree-id>/login.jsp</to-tree-id>
</navigation-case>
</navigation-rule>
navigation-rule定义了login.jsp页面的可能目标, from-outcome元素值为success (登录成功), welcome.jsp 页面将会被显示,否则,如果from-outcome 元素的值是failed, login.jsp页面将被重新显示。welcome.jsp没有定义导航规则,你可以自己去做。
login.jsp
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<html>
<head>
<title>Login</title>
</head>
<body>
<f:use_faces>
<h:form formName="loginForm">
<h2>Please enter your user name and password</h2>
<br>User Name: <h:input_text
valueRef="LoginBean.userName"/>
<br>Password: <h:input_secret
valueRef="LoginBean.password"/>
<br><h:command_button commandName="login"
label="login"
actionRef="LoginBean.login"/>
<br>
<h:output_errors/>
</h:form>
</f:use_faces>
</body>
</html>
welcome.jsp
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<html>
<head>
<title>Welcome</title>
</head>
<body>
You logged in successfully.
</body>
</html>
login.jsp 中的command_button签有一个值为LoginBean.login的 actionRef:
<h:command_button commandName="login" label="login"
actionRef="LoginBean.login"/>
这意味着command button的输出结果来自LoginBean 的login属性,注意下面的LoginBean 的代码,我们将在后面进行讲解。
package jsfArticle;
import javax.faces.application.Action;
import javax.faces.application.Message;
import javax.faces.application.MessageImpl;
import javax.faces.context.FacesContext;
public class LoginBean {
private String userName;
private String password;
private Action login;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Action getLogin() {
if (login==null)
login = new LoginAction();
return login;
}
class LoginAction extends Action {
public String invoke() {
if ("anna".equals(userName) && "honolulu".equals(password))
return "success";
else {
Message loginErrorMessage =
new MessageImpl(1, "<hr>Login failed", null);
FacesContext.getCurrentInstance().addMessage(
null, loginErrorMessage);
return "failed";
}
}
}
}
因为一个bean的属性代表了一个action的输出结果,这个属性必须是javax.faces.application.Action类
private Action login;
getLogin返回login Action.
public Action getLogin() {
if (login==null)
login = new LoginAction();
return login;
}
Action类的实现必须提供invoke方法,根据导航规则,值是success或failed 这就是LoginAction 类中invoke方法的返回值。这样,正确的用户名和密码分别是anna和blackcombhonolulu。
例子用到的LoginBean必须在应用配置文件中注册:
注册LoginBean
<managed-bean>
<managed-bean-name>LoginBean</managed-bean-name>
<managed-bean-class>jsfArticle.LoginBean</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
现在可以在浏览器中输入
http://localhost:8080/JSFCh0jsfPageNav6/faces/login.jsp
你将看到类似下面的画面:
输入正确的用户名和密码,将看到welcome.jsp
输入错误的话将看到有错误信息的login.jsp
注意前面的例子你也可以使用from-action-ref元素来定义:
<navigation-rule>
<from-tree-id>/login.jsp</from-tree-id>
<navigation-case>
<from-action-ref>LoginBean.login</from-action-ref>
<from-outcome>success</from-outcome>
<to-tree-id>/welcome.jsp</to-tree-id>
</navigation-case>
<navigation-case>
<from-action-ref>LoginBean.login</from-action-ref>
<from-outcome>failed</from-outcome>
<to-tree-id>/login.jsp</to-tree-id>
</navigation-case>
</navigation-rule>
然而, 这不是必需的,因为action的值来自bean,它是唯一的。
使用from-action-ref元素
这个例子既使用了action-ref属性又使用了action 属性,它用到了login2.jsp, 和login.jsp很相似,只是login2.jsp 包含了一个到help.jsp页面的超链接。
注意: 你必须重新启动你的浏览器好让JSF创建一个新的LoginBean实例。
<navigation-rule>
<from-tree-id>/login2.jsp</from-tree-id>
<navigation-case>
<from-outcome>success</from-outcome>
<to-tree-id>/welcome.jsp</to-tree-id>
</navigation-case>
<navigation-case>
<from-outcome>failed</from-outcome>
<to-tree-id>/login2.jsp</to-tree-id>
</navigation-case>
<navigation-case>
<from-outcome>help</from-outcome>
<to-tree-id>/help.jsp</to-tree-id>
</navigation-case>
</navigation-rule>
login2.jsp
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<html>
<head>
<title>Login 2</title>
</head>
<body>
<f:use_faces>
<h:form formName="loginForm">
<h2>Please enter your user name and password</h2>
<br>User Name: <h:input_text
valueRef="LoginBean.userName"/>
<br>Password: <h:input_secret
valueRef="LoginBean.password"/>
<br><h:command_button commandName="login"
label="login"
actionRef="LoginBean.login"/>
<br><h:command_hyperlink commandName="forgottenPassword"
action="help"
label="Forgotten your password?"/>
<h:output_errors/>
</h:form>
</f:use_faces>
</body>
</html>
help.jsp
<html>
<head>
<title>Help</title>
</head>
<body>
If you have forgotten your password, please contact the admin
(admin@brainysoftware.com).
</body>
</html>
在浏览器输入http://localhost:8080/JSFCh0jsfPageNav6/faces/login2.jsp
如果你输入正确的用户名和密码,你将看到welcome.jsp页面。输入错误将返回login.jsp。如果点了下面的超链接,你将看到help.jsp
如果你打算用success作为command_hyperlink标签中action属性的值:
<h:command_hyperlink commandName="forgottenPassword"
action="success" label="Forgotten your password?"/ >
navigation-rule元素应当这样写(这次用到了from-action-ref元素):
<navigation-rule>
<from-tree-id>/login2.jsp</from-tree-id>
<navigation-case>
<from-action-ref>LoginBean.login</from-action-ref>
<from-outcome>success</from-outcome>
<to-tree-id>/welcome.jsp</to-tree-id>
</navigation-case>
<navigation-case>
<from-action-ref>LoginBean.login</from-action-ref>
<from-outcome>failed</from-outcome>
<to-tree-id>/login2.jsp</to-tree-id>
</navigation-case>
<navigation-case>
<from-outcome>success</from-outcome>
<to-tree-id>/help.jsp</to-tree-id>
</navigation-case>
</navigation-rule>
总结
这篇文章示范了页面导航的方法,这是JSF编程中重要的一方面,作者先阐述了页面导航规则,然后给出了很多例子加以说明。
Budi Kurniawan 是一位IT顾问,专攻网络和面向对象编程,讲授过微软和java技术。