改进 Calculator 示例
在本节中,将用 JSF 技术改进 Calculator 应用程序的外观并简化它。您将学习如何使用 CSS、设置国际化(I18N)消息和以其他方式改进应用程序的外观和感觉。还要改进默认的错误消息,以便于用户理解。
使用面板单元格
在前一节中,使用了大量 HTML 控制页面布局。可以使用 HTML 代码精确地控制页面的布局。但是,Web 应用程序的布局可能并不太重要。为了简化 Calculator 应用程序的 GUI,现在使用一个 <h:panelGrid>
在一个单元格中布置元素。
清单 12 演示如何修改输入表单代码来使用 <h:panelGrid>
:
清单 12. 修改表单来使用 <h:panelGrid>
<
h:form
id
="calcForm"
>
<
h4
>
Calculator
</
h4
>
<
h:panelGrid
columns
="3"
>

<%
...
-- First Number--
%>
<
h:outputLabel
value
="First Number"
for
="firstNumber"
/>
<
h:inputText
id
="firstNumber"
value
="#{calculator.firstNumber}"
required
="true"
/>
<
h:message
for
="firstNumber"
/>

<%
...
-- Second Number--
%>
<
h:outputLabel
value
="Second Number"
for
="secondNumber"
/>
<
h:inputText
id
="secondNumber"
value
="#{calculator.secondNumber}"
required
="true"
/>
<
h:message
for
="secondNumber"
/>
</
h:panelGrid
>
清单 13 给出对结果部分的修改:
清单 13. 修改结果部分
<
h:panelGroup
rendered
="#{calculator.result != 0}"
>
<
h4
>
Results
</
h4
>
<
h:panelGrid
columns
="1"
>
<
h:outputText
value
="First Number #{calculator.firstNumber}"
/>
<
h:outputText
value
="Second Number #{calculator.secondNumber}"
/>
<
h:outputText
value
="Result #{calculator.result}"
/>
</
h:panelGrid
>
</
h:panelGroup
>
这就减少了大约 20 行代码(大约三分之一),使代码更容易阅读了。而且,现在这个应用程序的 JSF “味道” 更强了,有些人(例如大多数开发人员)喜欢这样,有些人(Web 设计人员)不喜欢。您应该根据自己项目的需要权衡考虑应该怎么做。项目的情况各不相同,所以要判断是需要绝对控制(纯 HTML 布局),还是需要容易维护的应用程序(更具 JSF 风格)。
这里有一个需要注意的问题。<h:panelGrid>
只能包含组件,而 <h:form>
、<f:view>
和 <h:panelGroup>
可以包含 HTML 和组件。在这个表单中,第一个 <h:panelGrid>
有三列,当添加一个额外组件时,它会转入下一行。第二个 <h:panelGrid>
只包含一列,所以每个组件都会添加到下一行中。<h:panelGrid>
显示一个表格,所以输出几乎与以前相同。(对于用户,确实是相同的。)再次重申:不能在 <h:panelGrid>
中添加 HTML。它不会按照您的期望显示 HTML。它只接受组件。
用 CSS 修饰 GUI
如果您了解 HTML 和 CSS,就知道如何改进 Calculator 应用程序第一个版本的外观和感觉。<h:panelGrid>
也允许使用 CSS。可以导入一个 CSS 样式表,然后将它用于 <h:panelGrid>
。我们要让 <h:panelGrid>
显示一个边框以及交替的银色和白色行。
首先导入样式表,见清单 14:
清单 14. 导入样式表
<
head
>
<
title
>
Calculator Application
</
title
>
<
link
rel
="stylesheet"
type
="text/css"
href
="<%=request.getContextPath()%>/css/main.css"
/>
</
head
>
清单 15 是样式表:
清单 15. CSS 样式表
oddRow {
background-color: white;
}

evenRow {
background-color: silver;
}

formGrid {
border: solid #000 3px;
width: 400px;
}

resultGrid {
border: solid #000 1px;
width: 200px;
}
清单 15 定义了 oddRow
和 evenRow
样式,让奇数行显示为白色,偶数行显示为银色。
现在将这些样式应用于 panelGrid
。清单 16 将它们添加到表单 panelGrid
:
清单 16. 在表单 panelGrid
中使用样式类
<
h:panelGrid
columns
="3"
rowClasses
="oddRow, evenRow"
styleClass
="formGrid"
>
...
</
h:panelGrid
>
清单 16 通过设置 rowClasses="oddRow, evenRow"
属性,将 oddRow
和 evenRow
样式应用于表单。styleClass="formGrid"
会在表格周围显示边框。结果 <h:panelGrid>
是相似的,它显示比较小的边框,见清单 17:
清单 17. 在结果 panelGrid
中使用样式类
<
h:panelGrid
columns
="1"
rowClasses
="oddRow, evenRow"
styleClass
="resultGrid"
>
...
</
h:panelGrid
>
图 6 显示 Calculator 应用程序现在的外观:
图 6. 使用一些样式之后的 Calculator
我只触及了 <panelGrid>
支持的样式的皮毛。更多信息请参见 参考资料 中的标记库 API 链接。
改进错误消息
如果您的用户是技术专家,那么这些错误消息倒是很合适。否则,用户很难理解它们的意思。可以以几种方式改进它们。可以先添加一个标签,见清单 18:
清单 18. 添加一个标签
<
h:inputText
id
="firstNumber"
label
="First Number"
...
/>
...
<
h:inputText
id
="secondNumber"
label
="Second Number"
...
/>
...
注意,在 h:inputText
字段中使用了 label="First Number"
属性。现在看到的错误文本像图 7 这样:
图 7. 带标签的消息
标签名不再是属性名,对用户更友好了。但是,既然错误消息总是出现在字段旁边,那么可能根本不需要标签。另外,错误消息非常长。可以用清单 19 中的代码缩短它们:
清单 19. 显示简短的消息而不是细节
<
h:outputLabel
value
="First Number"
for
="firstNumber"
/>
<
h:inputText
id
="firstNumber"
label
="First Number"
value
="#{calculator.firstNumber}"
required
="true"
/>
<
h:message
for
="firstNumber"
showSummary
="true"
showDetail
="false"
/>
注意,清单 19 将 h:message
组件的 showSummary
和 showDetail
属性设置为 showSummary="true" showDetail="false"
。对于转换和必需字段 firstNumber
和 secondNumber
,这会产生 “First Number: 'aaa' must be a number consisting of one or more digits.” 和 “Second Number: Validation Error: Value is required.” 这样的消息。但是,这仍然不够好。下面讨论一种更好的替代方法。
覆盖消息文本
JSF 1.2 添加了 requiredMessage
和 conversionMessage
,所以我们可以根据不同的情况覆盖消息,见清单 20:
清单 20. 使用 requiredMessage
和 converterMessge
覆盖消息
<%
...
-- First Number--
%>
<
h:outputLabel
value
="First Number"
for
="firstNumber"
/>
<
h:inputText
id
="firstNumber"
label
="First Number"
value
="#{calculator.firstNumber}"
required
="true"
requiredMessage
="required"
converterMessage
="not a valid number"
/>
<
h:message
for
="firstNumber"
/>

<%
...
-- Second Number--
%>
<
h:outputLabel
value
="Second Number"
for
="secondNumber"
/>
<
h:inputText
id
="secondNumber"
label
="Second Number"
value
="#{calculator.secondNumber}"
required
="true"
requiredMessage
="required"
converterMessage
="not a valid number"
/>
<
h:message
for
="secondNumber"
/>
注意,清单 20 中的 h:inputText
添加了 requiredMessage="required" converterMessage="not a valid number"
。现在看起来不错了,而且消息在 <h:panelGrid>
的上下文中是有意义的:它们出现在字段的旁边,所以用户知道它们应用于哪个上下文(见图 8):
图 8. 更短的消息
这种方法的问题是,需要在每个 inputText
字段中添加 requiredMessage
和 converterMessage
。对于这个简单的示例,这倒不是问题。但是对于真正的应用程序,就会在维护方面造成大问题,肯定会破坏 DRY(don't repeat yourself)原则。
以全局方式修改消息
为了以全局方式修改消息,需要在 faces-config.xml 文件中定义一个资源束并用它重新定义默认的消息,见清单 21:
清单 21. 在 faces-config.xml 中配置消息
<?
xml version="1.0" encoding="UTF-8"
?>

<
faces-config
xmlns
="http://java.sun.com/xml/ns/javaee"
xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation
="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd"
version
="1.2"
>

<
application
>
<
message-bundle
>
messages
</
message-bundle
>
</
application
>
...
message.properties 文件包含清单 22 所示的条目:
清单 22. 消息资源束(messages.properties)
javax.faces.component.UIInput.REQUIRED_detail=required
javax.faces.converter.IntegerConverter.INTEGER_detail=not a valid number
现在已经以全局方式修改了必需字段检验或整数转换失败时显示的消息。
注意:如果使用 Eclipse JEE,那么一定要将 src/main/resources/messages.properties 添加为源文件夹。
本节中所做的改进增加了应用程序的 GUI 逻辑,所以在下一节中将添加一个 CalculatorController
类,它会注入 Calculator
类。
添加控制器
现在要重构这个应用程序,不要把纯 Java Calculator
对象绑定到 JSF,以避免 JSF 和 POJO 之间有紧密联系。这需要创建一个控制器类并把纯模型对象注入这个控制器类。这个控制器类将能够感知 JSF,但是模型类不了解 JSF 的任何情况。
本节讨论:
- 使用 JSF 的依赖性注入容器
- 处理 JSF
facesContext
- 添加
FacesMessage
- 使用
h:messages
- 将组件绑定到控制器
下面依次执行每个步骤。然后,我会回过头来详细解释每个步骤。
在 Calculator
中添加一个 divide()
方法
首先,在 Calculator
中添加一个 divide()
方法(见清单 23),以便从 “被零除” 异常中恢复并添加一个 FacesMessage
向用户显示消息:
清单 23. 在 Calculator
POJO 中添加一个 divide()
方法
package
com.arcmind.jsfquickstart.model;


/** */
/**
* Calculator. Simple POJO.
*
* @author Rick Hightower
*/

public
class
Calculator
...
{


/** *//** First number used in operation. */
private int firstNumber = 0;


/** *//** Result of operation on first number and second number. */
private int result = 0;

...


/** *//** Divide the two numbers. */

public void divide() ...{
this.result = this.firstNumber / this.secondNumber;
}


/** *//** Clear the results. */

public void clear () ...{
result = 0;
}

...

}
创建控制器类
接下来,添加一个称为 CalculatorController
的新类,它接收 Calculator
POJO。
有三个 JSF 组件绑定到 CalculatorController
。它能够感知 JSF。它还通过将 FacesMessages
放在 FacesContext
中,从异常中恢复。
绑定到 CalculatorController
的三个 JSF 组件是:
resultsPanel
,这是一个 UIPanel
firstNumberInput
,这是一个 UIInput
secondNumberInput
,这是一个 UInput
图 9 显示 Calculator 应用程序如何处理错误:
图 9. “被零除” 异常
图 10 显示应用程序如何报告状态:
图 10. 显示状态消息的结果
CalculatorController
(见清单 24)避免了 JSF 相关代码混入 POJO 中。它能够感知 JSF,与组件绑定,并将错误和状态消息放到 facesContext
中。
清单 24. 感知 JSF 的 CalculatorController
package
com.arcmind.jsfquickstart.controller;

import
javax.faces.application.FacesMessage;
import
javax.faces.component.UIInput;
import
javax.faces.component.UIPanel;
import
javax.faces.context.FacesContext;
import
com.arcmind.jsfquickstart.model.Calculator;


public
class
CalculatorController
...
{

private Calculator calculator;
private UIPanel resultsPanel;
private UIInput firstNumberInput;
private UIInput secondNumberInput;


public String add() ...{
FacesContext facesContext = FacesContext.getCurrentInstance();


try ...{
calculator.add();
resultsPanel.setRendered(true);
facesContext.addMessage(null, new FacesMessage(
FacesMessage.SEVERITY_INFO, "Added successfully", null));


} catch (Exception ex) ...{
resultsPanel.setRendered(false);
facesContext.addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_ERROR, ex.getMessage(), null));
}
return null;
}


public String multiply() ...{
FacesContext facesContext = FacesContext.getCurrentInstance();


try ...{
calculator.multiply();
resultsPanel.setRendered(true);
facesContext.addMessage(null, new FacesMessage(
FacesMessage.SEVERITY_INFO, "Multiplied successfully", null));


} catch (Exception ex) ...{
resultsPanel.setRendered(false);
facesContext.addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_ERROR, ex.getMessage(), null));
}
return null;
}


public String divide() ...{
FacesContext facesContext = FacesContext.getCurrentInstance();


try ...{
calculator.divide();
resultsPanel.setRendered(true);
facesContext.addMessage(null, new FacesMessage(
FacesMessage.SEVERITY_INFO, "Divided successfully", null));


} catch (Exception ex) ...{
resultsPanel.setRendered(false);

if (ex instanceof ArithmeticException) ...{
secondNumberInput.setValue(Integer.valueOf(1));
}
facesContext.addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_ERROR, ex.getMessage(), null));
}
return null;
}


public String clear() ...{
FacesContext facesContext = FacesContext.getCurrentInstance();


try ...{
calculator.clear();
resultsPanel.setRendered(false);
facesContext.addMessage(null, new FacesMessage(
FacesMessage.SEVERITY_INFO, "Results cleared", null));


} catch (Exception ex) ...{
resultsPanel.setRendered(false);
facesContext.addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_ERROR, ex.getMessage(), null));
}
return null;
}


public String getFirstNumberStyleClass() ...{

if (firstNumberInput.isValid()) ...{
return "labelClass";

} else ...{
return "errorClass";
}
}
//remove simple props
更新 calculator.jsp
接下来,更新 calculator.jsp(见清单 25),让它显示错误消息并绑定到 calculatorController
,而不是直接绑定到 Calculator
POJO:
清单 25. 更新后的 calculator.jsp
<?
xml version="1.0" encoding="ISO-8859-1"
?>

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

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

<!
DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
>
<
html
xmlns
="http://www.w3.org/1999/xhtml"
>
<
head
>
<
title
>
Calculator Application
</
title
>
<
link
rel
="stylesheet"
type
="text/css"
href
="<%=request.getContextPath()%>/css/main.css"
/>
</
head
>
<
body
>
<
f:view
>
<
h:form
id
="calcForm"
>
<
h4
>
Calculator 3
</
h4
>
<
h:messages
infoClass
="infoClass"
errorClass
="errorClass"
layout
="table"
globalOnly
="true"
/>
<
h:panelGrid
columns
="3"
rowClasses
="oddRow, evenRow"
styleClass
="formGrid"
>

<%
...
-- First Number--
%>
<
h:outputLabel
value
="First Number"
for
="firstNumber"
styleClass
="#{calculatorController.firstNumberStyleClass}"
/>
<
h:inputText
id
="firstNumber"
label
="First Number"
value
="#{calculatorController.calculator.firstNumber}"
required
="true"
binding
="#{calculatorController.firstNumberInput}"
/>
<
h:message
for
="firstNumber"
errorClass
="errorClass"
/>


<%
...
-- Second Number--
%>
<
h:outputLabel
id
="snl"
value
="Second Number"
for
="secondNumber"
styleClass
="#{calculatorController.secondNumberStyleClass}"
/>
<
h:inputText
id
="secondNumber"
label
="Second Number"
value
="#{calculatorController.calculator.secondNumber}"
required
="true"
binding
="#{calculatorController.secondNumberInput}"
/>
<
h:message
for
="secondNumber"
errorClass
="errorClass"
/>
</
h:panelGrid
>
<
div
>
<
h:commandButton
action
="#{calculatorController.add}"
value
="Add"
/>
<
h:commandButton
action
="#{calculatorController.multiply}"
value
="Multiply"
/>
<
h:commandButton
action
="#{calculatorController.divide}"
value
="Divide"
/>
<
h:commandButton
action
="#{calculatorController.clear}"
value
="Clear"
immediate
="true"
/>
</
div
>
</
h:form
>


<
h:panelGroup
binding
="#{calculatorController.resultsPanel}"
rendered
="false"
>
<
h4
>
Results
</
h4
>
<
h:panelGrid
columns
="1"
rowClasses
="oddRow, evenRow"
styleClass
="resultGrid"
>
<
h:outputText
value
="First Number #{calculatorController.calculator.firstNumber}"
/>
<
h:outputText
value
="Second Number #{calculatorController.calculator.secondNumber}"
/>
<
h:outputText
value
="Result #{calculatorController.calculator.result}"
/>
</
h:panelGrid
>
</
h:panelGroup
>
</
f:view
>

</
body
>
</
html
>
在 faces-config.xml 中映射控制器
接下来,需要在 faces-config.xml 中映射新的控制器并在其中注入 calculator
,见清单 26:
清单 26. 更新后的 faces-config.xml
<?
xml version="1.0" encoding="UTF-8"
?>

<
faces-config
xmlns
="http://java.sun.com/xml/ns/javaee"
xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation
="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd"
version
="1.2"
>

<
application
>
<
message-bundle
>
messages
</
message-bundle
>
</
application
>

<
managed-bean
>
<
managed-bean-name
>
calculatorController
</
managed-bean-name
>
<
managed-bean-class
>
com.arcmind.jsfquickstart.controller.CalculatorController
</
managed-bean-class
>
<
managed-bean-scope
>
request
</
managed-bean-scope
>
<
managed-property
>
<
property-name
>
calculator
</
property-name
>
<
value
>
#{calculator}
</
value
>
</
managed-property
>
</
managed-bean
>
<
managed-bean
>
<
managed-bean-name
>
calculator
</
managed-bean-name
>
<
managed-bean-class
>
com.arcmind.jsfquickstart.model.Calculator
</
managed-bean-class
>
<
managed-bean-scope
>
none
</
managed-bean-scope
>
</
managed-bean
>

</
faces-config
>
既然已经修改了整个应用程序,现在就讨论一下细节。
用 JSF 进行依赖性注入
JSF 支持依赖性注入。可以将 bean 注入其他 bean 的属性。因为要将 calculator
bean 注入 calculatorController
,所以可以把它放到 none
范围中。none
意味着在创建它时不把它放到范围中。清单 27 给出 faces-config.xml 的部分代码,这些代码注入托管 calculator
bean,并使用 none
范围:
清单 27. 托管的 calculator
,none
范围
<
managed-bean
>
<
managed-bean-name
>
calculator
</
managed-bean-name
>
<
managed-bean-class
>
com.arcmind.jsfquickstart.model.Calculator
</
managed-bean-class
>
<
managed-bean-scope
>
none
</
managed-bean-scope
>
</
managed-bean
>
calculatorController
在 request
范围下映射。将 calculator
注入 calculatorController
的方法是使用 <managed-property>
并传递表达式 #{calculator}
。这会创建一个 Calculator
对象并使用 CalculatorController
的 setCalculator
方法把它注入 CalculatorController
,见清单 28:
清单 28. 托管的 calculatorController
,request
范围,用 managed-property
注入
<
managed-bean
>
<
managed-bean-name
>
calculatorController
</
managed-bean-name
>
<
managed-bean-class
>
com.arcmind.jsfquickstart.controller.CalculatorController
</
managed-bean-class
>
<
managed-bean-scope
>
request
</
managed-bean-scope
>
<
managed-property
>
<
property-name
>
calculator
</
property-name
>
<
value
>
#{calculator}
</
value
>
</
managed-property
>
</
managed-bean
>
CalculatorController 要使用 calculator
,所以注入了 calculator
。这样就可以使用 calculator
并让它与 JSF 相互隔离,这是良好的模型对象应该具备的性质。JSF 相关代码只出现在 CalculatorController
中。这种良好的关注点隔离会使代码的可测试性和可重用性更好。
CalculatorController
的 JSF 绑定组件
根据设计,CalculatorController
了解 JSF 的许多情况。CalculatorController
绑定三个 JSF 组件,其中之一是 resultsPanel
,它代表显示计算器结果的面板,见清单 29:
清单 29. CalculatorController
的 resultsPanel
private
UIPanel resultsPanel;

...


public
UIPanel getResultsPanel()
...
{
return resultsPanel;
}


public
void
setResultsPanel(UIPanel resultPanel)
...
{
this.resultsPanel = resultPanel;
}
resultsPanel
通过 JSF 绑定到 CalculatorController
,见清单 30 中的 binding
属性:
清单 30. 把组件绑定到控制器
<
h:panelGroup
binding
="#{calculatorController.resultsPanel}"
rendered
="false"
>
<
h4
>
Results
</
h4
>
<
h:panelGrid
columns
="1"
rowClasses
="oddRow, evenRow"
styleClass
="resultGrid"
>
<
h:outputText
value
="First Number #{calculatorController.calculator.firstNumber}"
/>
<
h:outputText
value
="Second Number #{calculatorController.calculator.secondNumber}"
/>
<
h:outputText
value
="Result #{calculatorController.calculator.result}"
/>
</
h:panelGrid
>
/h:panelGroup>
在清单 30 中,binding="#{calculatorController.resultsPanel}"
通过绑定关联 resultsPanel
组件。实际上,JSF 会看到这个表达式,在装载页面时,它通过调用 calculateController.setResultsPanel
方法注入 resultsPanel
组件。这个方便的机制让我们能够以程序方式操作组件的状态,不需要在组件树中移动。
实际上,JSF 所做的操作是调用 calculateController.getResultsPanel
。如果这个调用返回一个组件,JSF 视图就会使用这个组件。如果 calculateController.getResultsPanel
返回 null
,JSF 就会创建 resultPanel
组件,然后用基于绑定表达式的新组件调用 calculateController.setResultPanel
。
清单 31 演示 CalculateController
的 add()
方法如何使用这种技术:
清单 31. CalculateController
的 add()
方法,关闭 resultsPanel
public
String add()
...
{

...

try ...{
calculator.add();
resultsPanel.setRendered(true);
...


} catch (Exception ex) ...{
...
resultsPanel.setRendered(false);
}
return null;
}
在清单 31 中,如果对 calculator.add
方法的调用成功,CalculateController.add
方法会调用 resultsPanel.setRendered(true)
,这会打开结果面板。如果调用失败,那么 CalculateController.add
调用 resultsPanel.setRendered(false)
,这个面板不再显示。
现在稍微停一下。请注意:因为 JSF 是一个组件模型,而且组件是有状态的,所以组件会记住它们的状态。不需要像前面的 Calculator 示例那样包含逻辑。告诉组件不要显示其本身,它就不会再显示了。如果告诉组件禁用其本身,那么只要视图是活动的,每次装载视图时这个组件都会被禁用。与 Model 2 框架相比,JSF 更接近传统的 GUI 应用程序。需要编写的代码更少,就可以更快地开发 Web 应用程序。JSF 使Web 应用程序 真正体现了应用程序 的思想。
需要 Ajax 支持吗?不必担心!可以进行部分页面显示
如果使用 JBoss Ajax4Jsf 等框架(参见 参考资料),那么只需对 JSF Calculator 应用程序做很少几处修改,就可以支持部分页面显示。(JSF 2.0 将提供相同的支持。目前,可以对 JSF 1.1 和 JSF 1.2 使用 Ajax4JSF。)从用户的角度来看,应用程序看起来像 applet 或 Flex 应用程序。不需要编写任何 JavaScript!
CalculatorController
处理消息
JSF 提供一种向用户显示状态消息的机制。CalculateController
使用 FacesContext
将消息添加到 FacesContext
中,这样就可以用 <h:messages>
标记向用户显示这些消息。
JSF 在 ThreadLocal
变量中存储一个 FacesContext
,可以通过调用 FacesContext.getCurrentInstance()
方法访问它。add()
方法使用当前的 FacesContext
添加消息,这些消息可供当前请求使用,见清单 32:
清单 32. CalculateController
的 add()
方法添加 JSF 消息
public
String add()
...
{

FacesContext facesContext = FacesContext.getCurrentInstance();


try ...{
calculator.add();
facesContext.addMessage(null, new FacesMessage(
FacesMessage.SEVERITY_INFO, "Added successfully", null));
...


} catch (Exception ex) ...{
facesContext.addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_ERROR, ex.getMessage(), null));
//Log the exception as well.
...
}
return null;
}
在清单 32 中,如果添加操作成功,就在 facesContext
中添加一个严重性级别为 INFO
的 FacesMessage
;如果添加操作抛出异常,就添加一个严重性级别为 ERROR
的 FacesMessage
。
用 <h:messages>
标记向用户显示消息,见清单 33:
清单 33. 向最终用户显示错误和状态消息
<
h:messages
infoClass
="infoClass"
errorClass
="errorClass"
layout
="table"
globalOnly
="true"
/>
如果将 globalOnly
属性设置为 true
,就只显示不与特定组件连接的消息,比如在清单 32 中添加的消息。注意,状态消息和错误消息使用不同的样式。
CalculatorController
纠正 “被零除” 异常
因为我们正在使用一个组件模型,所以可以根据显示逻辑修改组件的值并进行初始化。当新的除法方法抛出 “被零除” 异常时,可以通过将 secondNumberInput
值设置为 1 来恢复。
首先,需要将 secondNumberInput
绑定到 CalculatorController
类,见清单 34:
清单 34. 绑定输入组件:binding="#{calculatorController.resultsPanel}"
<
h:inputText
id
="secondNumber"
label
="Second Number"
value
="#{calculatorController.calculator.secondNumber}"
required
="true"
binding
="#{calculatorController.secondNumberInput}"
/>
接下来,使用 secondNumberInput
组件。如果遇到 “被零除” 异常,就将 secondNumberInput
值设置为 1,见清单 35:
清单 35. 新的 divide()
方法
public
String divide()
...
{

FacesContext facesContext = FacesContext.getCurrentInstance();


try ...{
calculator.divide();
facesContext.addMessage(null, new FacesMessage(
FacesMessage.SEVERITY_INFO, "Divided successfully", null));
resultsPanel.setRendered(true);


} catch (Exception ex) ...{

if (ex instanceof ArithmeticException) ...{
secondNumberInput.setValue(Integer.valueOf(1));
}
facesContext.addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_ERROR, ex.getMessage(), null));
}
return null;
}
一定要认识到 JSF 更接近传统的 GUI 组件模型,而不是 Model 2 的特殊版本。如果您一直牢记 JSF 是一个组件模型,就会发现许多可能性。在清单 35 中,可以设置 secondNumberInput
的值,这是因为它是一个对象,而不是 JSP 中的 HTML 代码。您可以操作它,它会记住它的值。它是有状态的。
处理属性
大多数 JSF 属性接受表达式,所以如果在发生错误时希望将字段标签变成红色的,那么很容易实现,见清单 36:
清单 36. 将标签变成红色
<%
...
-- First Number--
%>
<
h:outputLabel
value
="First Number"
for
="firstNumber"
styleClass
="#{calculatorController.firstNumberStyleClass}"
/>
...
注意,styleClass
属性被设置为表达式 #{calculatorController.firstNumberStyleClass}
,这与清单 37 中的方法绑定:
清单 37. 如果发生错误,就返回红色的样式类
public
String getFirstNumberStyleClass()
...
{

if (firstNumberInput.isValid()) ...{
return "labelClass";

} else ...{
return "errorClass";
}
}
清单 37 检查
firstNumbedInput
组件的输入是否有效,然后根据检查的结果修改返回的
styleClass
。