八、高级 JSF2 HTML5 组件
在前一章中,我们为 HTML5 中引入的一些新输入元素构建了 JSF2 组件。在本章中,我们将继续构建 JSF2 组件,这些组件利用了一些新的非输入 HTML5 元素。
媒体组件
随着移动设备使用的增加,HTML 的一个弱点变得越来越明显,那就是缺乏实现网页媒体播放器的标准化方法。在 HTML5 之前,网页作者必须使用 object 或 embed 元素通过 Java 小程序或 Flash SWFs 显示嵌入的媒体内容。如果你是一个经验丰富的网页作者,你应该知道要确保你的网页在所有浏览器上都能正常工作需要经历的所有麻烦。随着移动设备的出现,这个问题变得更加严重。尽管在过去十年中进行了各种 web 标准化努力,但您仍然必须在 web 代码中编写一些变通方法和故障转移,以确保它们在最流行的 web 浏览器和移动设备上运行良好。其中一个流行的移动平台是苹果的 iOS。早在 2010 年 4 月,史蒂夫·乔布斯写了一封公开信 1 解释苹果对 Flash 的想法,基本上是说 iOS 设备将不支持 Flash,未来应该使用新的开放标准(如 HTML5)来创建图形丰富的应用和游戏。
HTML5 中引入的媒体元素
用于播放视频和音频。HTML5 引入了四个新元素:音频、视频、源和轨道。音频和视频元素定义了视频或音频剪辑应该如何播放,以及网页访问者可以使用的控件。源元素嵌套在音频和视频元素中,可以插入源元素以提供媒体备选方案,web 浏览器可以根据其支持的媒体类型和编解码器从中进行选择。
注你可以在 W3C 网站www.w3.org/TR/html5/embedded-content-0.html#media-elements上找到 HTML5 规范中关于媒体元素的规范细节。
音频和视频元素都实现了 HTML5 规范中指定的 HTMLMediaElement 接口。该接口定义了回放音频和视频剪辑的通用属性和方法(见表 8-1 )。可以使用音频和视频元素的属性 设置可写属性。可以通过 JavaScript 在 DOM 中访问只读属性和方法。
表 8-1 。媒体元素(音频和视频)的通用属性列表
video 元素包含一些附加属性,用于指定视频和海报(如果有)的尺寸,以便在视频回放开始之前显示。附加视频属性在表 8-2 中列出。
表 8-2 。仅适用于视频元素的附加属性列表
在清单 8-1 中,你会看到一个 HTML5 中引入的音频和视频元素的例子。该示例还展示了如何使用 JavaScript 控制媒体元素。
***清单 8-1。***html 5 中引入的音频和视频元素的基本示例,可在不使用 Java 小程序或 Flash SWFs 等插件的情况下播放音频和视频剪辑
<!DOCTYPE html>
<html FontName">http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8" />
<title>Media Element</title>
<style>
video, audio { display: block }
</style>
<script type="text/javascript">
// Obtaining the MediaElement via JavaScript and invoking methods
function togglePlay(source, elementId) {
var mediaElement = document.getElementById(elementId);
if (mediaElement.paused) {
mediaElement.play();
source.innerText = "Pause";
} else {
mediaElement.pause();
source.innerText = "Play";
}
}
</script>
</head>
<body>
<article>
<h2>Video Example</h2>
<video id="video-example"
controls="controls"
poster="media/poster.png"
src="media/trailer.mp4">
<!-- Provide fallback for browsers that doesn't support the video element -->
<p>Browser not support the HTML5 Video element.</p>
</video>
<button onclick="togglePlay(this, 'video-example');">Play</button>
</article>
<article>
<h2>Audio Example</h2>
<audio id="audio-example"
controls="controls"
src="media/04-Death_Becomes_Fur.mp4">
<!-- Provide fallback for browsers that doesn't support the audio element -->
<p>Browser not support the HTML5 Audio element.</p>
</audio>
<button onclick="togglePlay(this, 'audio-example');">Play</button>
</article>
</body>
</html>
不同的网络浏览器和设备支持不同的媒体格式。可以指定多个媒体源,而不是进行设备和浏览器检测并相应地更新 src 属性。浏览器可以根据其支持的格式和编解码器自由选择最合适的媒体源。指定源时,没有必要在音频或视频元素的 src 属性中指定媒体文件。最常支持的格式和编解码器是 WebM 格式(使用 VP8 视频编解码器和 Vorbis 音频编解码器)和 MP4 格式(使用 H.264 视频编解码器和 ACC 或 MP3 音频编解码器)。
清单 8-2 是一个为提供多个视频和音频源的例子,这些视频和音频源将根据访问页面的浏览器所支持的格式自动选择。
清单 8-2。 可提供多种媒体源,确保跨浏览器和设备的最佳播放效果
<video id="video-example" controls="controls" poster="media/poster.png">
<source src="media/trailer.mp4" type="video/mp4" />
<source src="media/trailer.webm" type="video/webm" />
<!-- Provide fallback for browsers that doesn't support the video element -->
<p>Browser not support the HTML5 Video element.</p>
</video>
<audio id="audio-example" controls="controls">
<source src="media/04-Death_Becomes_Fur.mp4" type="audio/mp4" />
<source src="media/04-Death_Becomes_Fur.oga" type="audio/ogg; codecs=vorbis" />
<!-- Provide fallback for browsers that doesn't support the audio element -->
<p>Browser not support the HTML5 Audio element.</p>
</audio>
使用 track 元素和 Web Video Text Tracks (WebVTT)格式的文件,可以将定时文本轨道(如字幕)添加到音频和视频元素中。关于这种格式的细节可以在 W3C 网站的 http://dev.w3.org/html5/webvtt/找到。通过指定轨道的种类(字幕、说明、元数据、章节和描述),轨道可以用于不同的目的。也可以通过指定音轨的语言来本地化音轨。将根据浏览器中的语言设置自动选择曲目。
清单 8-3 是一个提供多个字幕的例子,这些字幕由浏览器根据用户偏好的地区自动选择。如果用户的首选语言环境是丹麦语,浏览器将选择丹麦语字幕。在所有其他情况下,它将回落到英文字幕。
清单 8-3。 轨道元素可以用来指定多种语言的字幕;浏览器将根据其语言设置自动检测最合适的语言
<video id="video-example" controls="controls" poster="media/poster.png">
<source src="media/trailer.mp4" type="video/mp4" />
<source src="media/trailer.webm" type="video/webm" />
<track src="media/subtitles_en.vtt"
kind="subtitles" default="default"
label="English" srclang="en" />
<track src="media/subtitles_da.vtt"
kind="subtitles"
label="Dansk" srclang="da" />
<!-- Provide fallback for browsers that doesn't support the video element -->
<p>Browser not support the HTML5 Video element.</p>
</video>
提示 WebVTT 是取代 SRT 字幕格式的简单格式。WebVTT 文件包含一个提示列表,它指定了文本的时间。下面是在第 12 秒和第 16 秒之间以及第 18 秒和第 21 秒之间显示的两个文本的示例。
WEBVTT
00:12.000 --> 00:16.000
What brings you to the land of the gatekeepers?
00:18.000 --> 00:21.000
I'm searching for someone
如果浏览器不支持视频或音频元素,您可以通过在视频和音频元素中添加必要的代码来指定后备。例如,你可以插入一个旧的学校 Flash SWF ,如清单 8-4 中的所示。
清单 8-4。 浏览器不支持视频元素时回退到 Flash SWF 的例子
<video id="video-example" controls="controls" poster="media/poster.png" src="media/trailer.mp4">
<!-- Provide fallback for browsers that doesn't support the video element -->
<object width="300" height="150" data="video-player.swf"
type="application/x-shockwave-flash" title="Video Player ">
<param name="movie" value="media/trailer.mp4" />
<param name="height" value="300" />
<param name="width" value="150" />
<param name="menu" value="true" />
</object>
</video>
创建 JSF 媒体组件
对于 HTML5 媒体元素的 JSF 支持,我们将为音频和视频元素创建一个组件。这两个组件将具有 HTML5 规范中的 HTMLMediaElement 中定义的公共属性。视频组件将有三个额外的属性来指定视频的尺寸,并在回放之前显示海报。为了支持媒体源和音轨,我们将在组件的接口中包含两个集合:一个用于指定可用的媒体源,一个用于可用的文本音轨。
在这种情况下,我们面临着新的挑战。html 5 中的布尔属性并不是简单的表示为 attribute="true "和 attribute="false "。HTML5 规范声明
“元素上出现布尔属性代表真值,没有属性值代表假值。”
这意味着我们不能将 autoplay 和 output 之类的属性作为 autoplay="true "或 autoplay="false ",因为浏览器会评估两个版本,就好像 autoplay 为 true 一样。挑战在于,我们必须读取传递给组件的值,并评估它是否应该插入到定制组件的输出中。有两种方法可以做到这一点。您可以在标签下面包含条件性的<f:passThroughAttribute/>标签,也可以在复合组件根中操作输出的元素,复合组件根是组件的一种托管 bean。我们将展示这两种方法,但是首先我们将定义复合组件的接口。
在清单 8-5 中组件接口的定义中,也有一个组件的最小实现。我们添加到视频标签的属性只在客户端有意义。也就是说,它们不会在服务器端增加任何价值。我们不需要跟踪 HTML5 属性的状态。因此,我们可以使用 JSF 2.2 中添加的传递功能来输出必要的 HTML5 属性。在清单 8-1 的最小实现中,视频标签有两个属性。这些属性以 jsf:为前缀,这告诉 Facelets TagDecorator 这些属性不是传递属性,它们应该与组件的 id 和 value 属性相匹配。TagDecorator 负责将组件映射到一个已知的 JSF 组件。例如,在前一章中创建的输入组件都映射到了< h:inputText / >或 HtmlInputText 组件。TagDecorator 不熟悉 HTML5 < video / >元素,并将它映射到一个名为 PassThroughElement 的后备类。
清单 8-5。 复合组件接口,可用于视频和音频组件
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html FontName">http://www.w3.org/1999/xhtml"
xmlns:cc="http://xmlns.jcp.org/jsf/composite">
<cc:interface componentType="UIMediaComponent">
<cc:attribute name="value" type="java.lang.String"
shortDescription="URL of the video file to display" />
<cc:attribute name="crossorigin" type="java.lang.String" default="anonymous"
shortDescription="Specifying how to deal with cross origin requests.
anonymous (default) or use-credentials." />
<cc:attribute name="preload" type="java.lang.String" default="auto"
shortDescription="Preload the video file. none, metadata or auto" />
<cc:attribute name="autoplay" type="java.lang.Boolean" default="false"
shortDescription="Start playback as soon as the page has loaded" />
<cc:attribute name="mediagroup" type="java.lang.String" default=""
shortDescription="Media group for which the video file belong" />
<cc:attribute name="loop" type="java.lang.Boolean" default="false"
shortDescription="Restart the video once it reaches the end" />
<cc:attribute name="muted" type="java.lang.Boolean" default="false"
shortDescription="Mute the audio of the video" />
<cc:attribute name="controls" type="java.lang.Boolean" default="false"
shortDescription="Display user controls" />
<cc:attribute name="poster" type="java.lang.String"
shortDescription="URL of a poster (image) to display before playback" />
<cc:attribute name="width" type="java.lang.String"
shortDescription="Width of the video" />
<cc:attribute name="height" type="java.lang.String"
shortDescription="Height of the video" />
<cc:attribute name="sources" type="java.util.Collection"
shortDescription="Collection of alternative MediaSources" />
<cc:attribute name="tracks" type="java.util.Collection"
shortDescription="Collection of MediaTracks" />
</cc:interface>
<cc:implementation >
<div id="#{cc.clientId}">
<video jsf:id="media-player"
jsf:value="#{cc.attrs.value}">
</video>
</div>
</cc:implementation>
</html>
下面演示了在复合组件的输出中有条件地包含属性的两种方法。
方法一:
每个属性都可以通过在标签中嵌入标签直接添加到 Facelets 视图文件中,如清单 8-6 所示。
清单 8-6。 使用条件< f:passThroughAttribute / >元素
<cc:implementation >
<div id="#{cc.clientId}">
<video jsf:id="media-player"
jsf:value="#{cc.attrs.value}"
crossorigin="#{cc.attrs.crossorigin}"
preload="#{cc.attrs.preload}"
mediagroup="#{cc.attrs.mediagroup}"
src="#{cc.attrs.value}">
<c:if test="#{cc.attrs.autoplay}">
<f:passThroughAttribute name="autoplay" value="true" />
</c:if>
<c:if test="#{cc.attrs.loop}">
<f:passThroughAttribute name="loop" value="true" />
</c:if>
<c:if test="#{cc.attrs.muted}">
<f:passThroughAttribute name="muted" value="true" />
</c:if>
<c:if test="#{cc.attrs.controls}">
<f:passThroughAttribute name="controls" value="true" />
</c:if>
</video>
</div>
</cc:implementation>
这种方法的好处是您不需要在组件后面有一个 UIComponent 类,并且您可以快速地改变如何输出标签。使用这种方法的缺点是 Facelets 视图可能会被逻辑污染,而这些逻辑在其他地方会得到更好的管理。
方法二:实现复合组件根
第二种方法实现了一个复合组件根,它是位于组件后面的 UIComponent 类。在这个类中,你可以实现任何你能想到的逻辑。在清单 8-7 中,你可以看到如何指定位于复合组件后面的 UIComponent。复合组件背后的实际 UIComponent 可以在清单 8-8 中看到。
清单 8-7。 组件组件根被指定为复合组件接口中的 componentType
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html FontName">http://www.w3.org/1999/xhtml"
xmlns:cc="http://xmlns.jcp.org/jsf/composite"
xmlns:jsf="http://xmlns.jcp.org/jsf"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core">
<cc:interface componentType="UIMediaComponent">
<!-- OMITTED FOR READABILITY //-->
</cc:interface>
<cc:implementation >
<div id="#{cc.clientId}">
<video jsf:id="#{cc.elementId} "
jsf:value="#{cc.attrs.value}"
crossorigin="#{cc.attrs.crossorigin}"
preload="#{cc.attrs.preload}"
mediagroup="#{cc.attrs.mediagroup}"
src="#{cc.attrs.value}">
</video>
</div>
</cc:implementation>
</html>
清单 8-8。【UIMediaComponent.java】表示复合组件根,在清单 8-3 中指定为 componentType
package com.apress.projsf2html5.components.media;
import java.io.IOException;
import javax.el.ValueExpression;
import javax.faces.component.FacesComponent;
import javax.faces.component.UIComponent;
import javax.faces.component.UINamingContainer;
import javax.faces.context.FacesContext;
/**
* Composite component for the {@code <audio/>} and {@code <video/>} elements.
*/
@FacesComponent("UIMediaComponent")
public class UIMediaComponent extends UINamingContainer {
private static final String ELEMENT_ID = "media-player";
private static final String ATTRIBUTE_AUTOPLAY = "autoplay";
private static final String ATTRIBUTE_LOOP = "loop";
private static final String ATTRIBUTE_MUTED = "muted";
private static final String ATTRIBUTE_CONTROLS = "controls";
private static final String ATTRIBUTE_POSTER = "poster";
private static final String ATTRIBUTE_WIDTH = "width";
private static final String ATTRIBUTE_HEIGHT = "height";
public String getElementId() {
return ELEMENT_ID;
}
@Override
public void encodeBegin(FacesContext context) throws IOException {
super.encodeBegin(context);
UIComponent element = findMediaElement();
addAttributeIfTrue(element, ATTRIBUTE_AUTOPLAY);
addAttributeIfTrue(element, ATTRIBUTE_LOOP);
addAttributeIfTrue(element, ATTRIBUTE_MUTED);
addAttributeIfTrue(element, ATTRIBUTE_CONTROLS);
addAttributeIfNotNull(element, ATTRIBUTE_POSTER);
addAttributeIfNotNull(element, ATTRIBUTE_WIDTH);
addAttributeIfNotNull(element, ATTRIBUTE_HEIGHT);
}
private void addAttributeIfNotNull(UIComponent component, String attributeName) {
Object attributeValue = getAttribute(attributeName);
if (attributeValue != null) {
component.getPassThroughAttributes().put(attributeName, attributeValue);
}
}
private void addAttributeIfTrue(UIComponent component, String attributeName) {
if (isAttributeTrue(attributeName)) {
component.getPassThroughAttributes().put(attributeName, attributeName);
}
}
/**
* Finds the {@code <video/>} or {@code <audio/>} element in the
* composite component.
*
* @return {@link UIComponent} representing the {@code <video/>} or
* {@code <audio/>} element
* @throws IOException If {@link UIComponent} could not be found.
* There is no way to recover from this exception at run-time. Ensure that
* the ID of the element corresponds to ID specified in the
* {@link #ELEMENT_ID} constant
*/
private UIComponent findMediaElement() throws IOException {
UIComponent element = findComponent(getElementId());
if (element == null) {
throw new IOException("Media element with ID "
+ getElementId() + " could not be found");
}
return element;
}
/**
* Utility method for retrieving the attributes of a component. This method
* first checks if the attribute is an EL Expression followed by checking if
* it is a simple value.
*
* @param name Name of the attribute to retrieve
* @return The value of the attribute. If the contents of the attribute is
* an EL Expression, the expression will be executed and returned. If the
* contents of the attribute is a simple value, it will be returned as is.
* If the attribute cannot be found {@code null} is returned.
*/
private Object getAttribute(String name) {
ValueExpression ve = getValueExpression(name);
if (ve != null) {
// Attribute is a value expression
return ve.getValue(getFacesContext().getELContext());
} else if (getAttributes().containsKey(name)) {
// Attribute is a fixed value
return getAttributes().get(name);
} else {
// Attribute doesn't exist
return null;
}
}
/**
* Utility method that evaluates if the value in the given attribute is
* {@link Boolean.TRUE}.
*
* @param attributeName Name of the attribute to evaluate
* @return {@code true} if the value of the attribute evaluates to
* {@link Boolean.TRUE}, otherwise {@code false} is returned
*/
private boolean isAttributeTrue(String attributeName) {
boolean isBoolean = getAttribute(attributeName) instanceof java.lang.Boolean;
boolean isTrue = ((boolean) getAttribute(attributeName)) == Boolean.TRUE;
return isBoolean && isTrue;
}
}
这种方法的好处是,您可以轻松地对组件背后的逻辑进行单元测试。您还可以为多个组件重用复合组件根。例如,我们已经为音频和视频组件使用了前面的类。缺点是对于简单的组件来说,这可能是额外的工作,就代码行而言。此外,从复合组件输出的确切内容可能并不明显,因为输出现在是从 Facelets 视图和复合组件根生成和操作的。
支持源和轨道
除了视频和音频元素,HTML 5 还引入了可以嵌入视频和音频元素中的源和轨道元素,以支持多种媒体格式并提供文本轨道。我们已经在复合组件的接口中包含了可用属性的源和跟踪。在这些属性中,我们将期望对象的集合,因为每个视频和音频很可能有多个源和轨道。集合中的对象必须公开所需的属性来呈现源和轨道元素,所以我们创建了两个数据传输对象来保存关于源的细节,如清单 8-9 所示,以及关于轨道的细节,如清单 8-10 所示。
清单 8-9。MediaSource.java 是一个简单的数据传输对象,用于保存媒体源的信息
package com.apress.projsf2html5.components.media;
import java.util.Objects;
/**
* {@linkplain MediaSource Media source} used to provide the
* {@link UIMediaComponent} with alternative media files.
*/
public class MediaSource {
private String source;
private String type;
/**
* Creates a new instance of {@link MediaSource} with a blank source and
* mime type.
*/
public MediaSource() {
this("", "");
}
/**
* Creates a new instance of {@link MediaSource} with a preset source and
* MIME type.
*
* @param source URL of the media file to play
* @param type MIME type of the media file
*/
public MediaSource(String source, String type) {
this.source = source;
this.type = type;
}
/**
* Gets the URL of the media file to play.
*
* @return URL of the media file to play
*/
public String getSource() {
return source;
}
/**
* Sets the URL of the media file to play.
*
* @param source URL of the media file to play
*/
public void setSource(String source) {
this.source = source;
}
/**
* Gets the MIME type of the media file specified as the source.
*
* @return MIME type of the media file specified as the source
* @see <a href="http://www.iana.org/assignments/media-types/">IANA MIME
* types</a>
*/
public String getType() {
return type;
}
/**
* Sets the MIME type of the media file specified as the source.
*
* @param type MIME type of the media file specified as the source
* @see <a href="http://www.iana.org/assignments/media-types/">IANA MIME
* types</a>
*/
public void setType(String type) {
this.type = type;
}
}
***清单 8-10。***MediaTrack.java 是一个简单的数据传输对象,用于保存文本轨道的详细信息
package com.apress.projsf2html5.components.media;
import java.util.Locale;
/**
* {@linkplain MediaTrack Text track } used to provide the
* {@link UIMediaComponent} with localized text tracks for captioning, metadata,
* subtitles, etc.
*/
public class MediaTrack {
private String source;
private MediaTrackKind kind;
private boolean defaultTrack;
private String label;
private Locale locale;
/**
* Creates a new instance of {@link MediaTrack} with no details set.
*/
public MediaTrack() {
this("", null);
}
/**
* Creates a new instance of {@link MediaTrack} with the track and kind
* preset.
*
* @param source URL to the VTT text track
* @param kind Kind of text track
*/
public MediaTrack(String source, MediaTrackKind kind) {
this(source, kind, "", null, false);
}
/**
* Creates a new instance of {@link MediaTrack} with the track, kind, and
* label preset.
*
* @param source URL to the VTT text track
* @param kind Kind of text track
* @param label Label of the track (for display)
*/
public MediaTrack(String source, MediaTrackKind kind, String label) {
this(source, kind, label, null, false);
}
/**
* Creates a new instance of {@link MediaTrack} with the track, kind, and
* label preset.
*
* @param source URL to the VTT text track
* @param kind Kind of text track
* @param label Label of the track (for display)
* @param locale Locale of the VTT text track
*/
public MediaTrack(String source, MediaTrackKind kind, String label, Locale locale) {
this(source, kind, label, locale, false);
}
/**
* Creates a new instance of {@link MediaTrack} with the source, kind,
* label, {@link Locale} and default track preset.
*
* @param source URL to the VTT text track
* @param kind Kind of text track
* @param label Label of the track (for display)
* @param locale Locale of the VTT text track
* @param defaultTrack Is the track the default track?
*/
public MediaTrack(String source, MediaTrackKind kind, String label, Locale locale, boolean defaultTrack) {
this.source = source;
this.kind = kind;
this.defaultTrack = defaultTrack;
this.label = label;
this.locale = locale;
}
/**
* Determine if the {@link MediaTrack} is the default track to use if the
* browser could not match the appropriate track based on the
* {@link Locale}.
*
* @return {@link Boolean#TRUE} if this is the default track, otherwise
* {@link Boolean#FALSE}
*/
public boolean isDefaultTrack() {
return defaultTrack;
}
/**
* Sets the Default Track indicator of text track.
*
* @param defaultTrack {@link Boolean#TRUE} if this is the default track,
* otherwise {@link Boolean#FALSE}
*/
public void setDefaultTrack(boolean defaultTrack) {
this.defaultTrack = defaultTrack;
}
/**
* Gets the URL of the VTT text track.
*
* @return URL of the VTT text track
* @see <a href="http://dev.w3.org/html5/webvtt/">WebVTT: The Web Video Text
* Tracks Format</a>
*/
public String getSource() {
return source;
}
/**
* Sets the URL of the VTT text track.
*
* @param source URL of the VTT text track
* @see <a href="http://dev.w3.org/html5/webvtt/">WebVTT: The Web Video Text
* Tracks Format</a>
*/
public void setSource(String source) {
this.source = source;
}
/**
* Gets the kind of text track.
*
* @return Kind of text track
*/
public MediaTrackKind getKind() {
return kind;
}
/**
* Sets the kind of text track.
*
* @param kind Kind of text track
*/
public void setKind(MediaTrackKind kind) {
this.kind = kind;
}
/**
* Gets the label of the text track for display.
*
* @return Label of the text track for display.
*/
public String getLabel() {
return label;
}
/**
* Sets the label of the text track for display.
*
* @param label Label of the text track for display
*/
public void setLabel(String label) {
this.label = label;
}
/**
* Gets the {@link Locale} of the text track.
*
* @return {@link Locale} of the text track
*/
public Locale getLocale() {
return locale;
}
/**
* Sets the {@link Locale} of the text track.
*
* @param locale {@link Locale} of the text track
*/
public void setLocale(Locale locale) {
this.locale = locale;
}
}
sources 和 tracks 集合嵌入在 video 元素中,sources 和 tracks 属性被添加到组件中,如清单 8-11 所示。
清单 8-11。 支持复合组件中的源和轨道集合
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html FontName">http://www.w3.org/1999/xhtml"
xmlns:cc="http://xmlns.jcp.org/jsf/composite"
xmlns:jsf="http://xmlns.jcp.org/jsf"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
xmlns:f="http://xmlns.jcp.org/jsf/core">
<cc:interface componentType="UIMediaComponent">
<!-- OMITTED FOR READABILITY //-->
<cc:attribute name="sources" type="java.util.Collection"
shortDescription="Collection of alternative MediaSources. " />
<cc:attribute name="tracks" type="java.util.Collection"
shortDescription="Collection of MediaTracks. " />
</cc:interface>
<cc:implementation >
<div id="#{cc.clientId}">
<video jsf:id="#{cc.elementId} "
jsf:value="#{cc.attrs.value}"
crossorigin="#{cc.attrs.crossorigin}"
preload="#{cc.attrs.preload}"
mediagroup="#{cc.attrs.mediagroup}"
src="#{cc.attrs.value}">
<c:forEach items="#{cc.attrs.sources}" var="source" >
<source src="#{source.source}" type="#{source.type}" />
</c:forEach>
<c:forEach items="#{cc.attrs.tracks}" var="track" >
<track jsf:value="#{track.source}" src="#{track.source}">
<c:if test="#{track.kind != null}">
<f:passThroughAttribute name="kind" value="#{track.kind.toString()}" />
</c:if>
<c:if test="#{track.locale != null}">
<f:passThroughAttribute name="srclang"
value="#{track.locale.toString()}" />
</c:if>
<c:if test="#{track.defaultTrack}">
<f:passThroughAttribute name="defaultTrack" value="defaultTrack" />
</c:if>
</track>
</c:forEach>
</video>
</div>
</cc:implementation>
</html>
使用视频组件的示例
使用视频组件很简单。如果您想要向组件提供媒体源和文本轨道,您必须使用一个托管 bean 来包含您的集合;否则,组件可以与受管 bean 一起使用。清单 8-12 显示了一个例子,其中媒体源和文本轨道的集合是从后台 bean 中获取的。清单 8-13 中的后台 bean 将属性公开为可能来自数据库的集合。
清单 8-12。 使用视频组件
<h2>Video with a single media file</h2>
<projsfhtml5:video value="media/trailer.mp4"
autoplay="true"
controls="true" />
<h2>Video with multiple media sources and tracks</h2>
<projsfhtml5:video value="media/trailer.mp4"
controls="true"
sources="#{exampleVideoComponent.mediaSources}"
tracks="#{exampleVideoComponent.textTracks}"/>
清单 8-13。 为视频组件撑腰豆
package com.apress.projsf2html5.jsf;
import com.apress.projsf2html5.components.media.MediaSource;
import com.apress.projsf2html5.components.media.MediaTrack;
import com.apress.projsf2html5.components.media.MediaTrackKind;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Locale;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;
@Named(value = "exampleVideoComponent")
@RequestScoped
public class ExampleVideoComponent {
public Collection<MediaSource> getMediaSources() {
Collection<MediaSource> sources = new ArrayList<>();
sources.add(new MediaSource("media/trailer.mp4", "video/mp4"));
sources.add(new MediaSource("media/trailer.webm", "video/webm"));
return sources;
}
public Collection<MediaTrack> getTextTracks() {
Collection<MediaTrack> tracks = new ArrayList<>();
tracks.add(new MediaTrack("media/subtitles_da.vtt", MediaTrackKind.subtitles,
"Dansk", new Locale("da"), false));
tracks.add(new MediaTrack("media/subtitles_en.vtt", MediaTrackKind.subtitles,
"English", Locale.ENGLISH, true));
return tracks;
}
}
进度条组件
大多数 JSF UI 框架都带有进度条组件。在 HTML5 之前,这些框架使用小部件框架,如 JQueryUI。HTML5 引入了一个新元素来表示进度条,这就是元素。元素可以用来显示确定的和不确定的进度。不确定进度可用于在等待进程完成时显示等待指示器,而确定进度可用于显示任务的进度,其中任务的结束时间是已知的。图 8-1 显示了一个不确定和确定进度条的例子。
图 8-1 。进度指标示例
progress 元素有两个简单的属性。省略这些属性会创建一个不确定的进度条。通过使用两个属性,您可以指定进度条的最大值和进度条的当前值(即位置)。关于属性的详细信息可以在表 8-3 中看到。
表 8-3 。进度元素的属性列表
progress 元素相当简单,可以快速转换成 JSF 组件。为了使组件有用,我们将添加一个附加的“for”属性,该属性可以指向另一个组件,如视频或音频组件。进度条将根据视频或音频组件的播放自动更新其进度。
该组件有一个复合组件视图、一个 JavaScript 和一个 FacesComponent。复合视图显示在清单 8-14 的中。注意组件的 JavaScript 是如何被分离到它自己的 JavaScript 文件中并使用 outputScript 组件导入的。outputScript 确保 JavaScript 文件只被导入一次,不管组件在一个页面上被使用多少次。
清单 8-14。 复合进度组件 resources/projs html 5/Progress . XHTML
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html FontName">http://www.w3.org/1999/xhtml"
xmlns:cc="http://xmlns.jcp.org/jsf/composite"
xmlns:jsf="http://xmlns.jcp.org/jsf"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:h="http://xmlns.jcp.org/jsf/html">
<cc:interface componentType="UIProgress">
<cc:attribute name="value" type="java.lang.String"
shortDescription="How much of the task has been completed" />
<cc:attribute name="max" type="java.lang.String"
shortDescription="The maximum number of the progress bar indicating that
the task has completed" />
<cc:attribute name="for" type="java.lang.String"
shortDescription="ID of the media component for which the progress bar
should automatically update" />
</cc:interface>
<cc:implementation >
<h:outputScript name="progress.js" library="projsfhtml5/progress" target="head"/>
<div id="#{cc.clientId}">
<progress jsf:id="progress">
<c:if test="#{cc.attrs.value != null}">
<f:passThroughAttribute name="value" value="#{cc.attrs.value}" />
</c:if>
<c:if test="#{cc.attrs.max != null}">
<f:passThroughAttribute name="max" value="#{cc.attrs.max}" />
</c:if>
<cc:insertChildren />
</progress>
<c:if test="#{cc.attrs.for != null}">
<script type="text/javascript">
progressBar.init("#{cc.clientId}", "#{cc.forClientId}");
</script>
</c:if>
</div>
</cc:implementation>
</html>
该组件支持通过 for 属性自动更新进度条。自动更新是通过清单 8-15 中的 progress.js JavaScript 配置的,该清单包含一个带两个参数的 JavaScript 闭包。第一个参数是进度条的标识符,第二个参数是进度条应该自动更新的源组件的标识符。在下面的 JavaScript 中,只支持音频和视频组件作为更新进度条的来源。
清单 8-15。 支持复合组件的资源/projs html 5/progress/progress . js JavaScript
if (!window["progressBar"]) {
var progressBar = {};
}
progressBar.init = function init(componentId, forId) {
var media = document.getElementById(forId + "\:media-player");
var bar = document.getElementById(componentId + "\:progress");
// Add an event listener for the “timeupdate” event of the media player
media.addEventListener("timeupdate", function() {
var percent = Math.floor((100 / media.duration) * media.currentTime);
bar.value = percent;
});
}
清单 8-16 中复合组件后面的 FacesComponent 是计算 for 属性中指定的组件的客户端标识符所必需的。
***清单 8-16。***ui Progress faces 进度复合组件所使用的组件
package com.apress.projsf2html5.components.progress;
import java.io.IOException;
import javax.el.ValueExpression;
import javax.faces.component.FacesComponent;
import javax.faces.component.UIComponent;
import javax.faces.component.UINamingContainer;
/**
* Composite component for the {@code <progress/>} element.
*/
@FacesComponent("UIProgress")
public class UIProgress extends UINamingContainer {
private static final String ATTRIBUTE_FOR = "for";
private UIComponent forComponent;
/**
* Finds the component specified in the {@code for} attribute.
*
* @return {@link UIComponent} specified in the {@code for} attribute
* @throws IOException If a {@link UIComponent} with the name specified in
* the {@code for} attribute could not be found
*/
public UIComponent getForComponent() throws IOException {
if (getAttributes().containsKey(ATTRIBUTE_FOR)) {
String forAttribute = (String) getAttribute(ATTRIBUTE_FOR);
this.forComponent = findComponent(forAttribute);
if (this.forComponent == null) {
throw new IOException("Component with ID "
+ forAttribute + " could not be found");
}
} else {
throw new IOException("The for attribute was not set on the component");
}
return forComponent;
}
/**
* Gets the client id of the {@link #getForComponent()}
*
* @return Client id of the {@link #getForComponent()}
* @throws IOException If the component specified in the {@code for}
* attribute could not be found
*/
public String getForClientId() throws IOException {
UIComponent element = getForComponent();
return element.getClientId(getFacesContext());
}
/**
* Utility method for retrieving the attributes of a component. This method
* first checks if the attribute is an EL Expression followed by checking if
* it is a simple value.
*
* @param name Name of the attribute to retrieve
* @return The value of the attribute. If the contents of the attribute is
* an EL Expression, the expression will be executed and returned. If the
* contents of the attribute is a simple value, it will be returned as is.
* If the attribute cannot be found {@code null} is returned.
*/
private Object getAttribute(String name) {
ValueExpression ve = getValueExpression(name);
if (ve != null) {
// Attribute is a value expression
return ve.getValue(getFacesContext().getELContext());
} else if (getAttributes().containsKey(name)) {
// Attribute is a fixed value
return getAttributes().get(name);
} else {
// Attribute doesn't exist
return null;
}
}
}
注意for 属性只支持媒体组件。为了使组件可以投入生产,您应该实现对其他组件的支持,比如文件上传、表单完成和 Ajax 请求。
该组件的使用非常简单。通过不向组件提供任何属性,它可以用作不确定的进度条。通过提供 value 和 max 属性,它可以用作手动更新的确定性条。最后,还可以通过将 for 属性指向媒体组件来使用它。图 8-2 是使用清单 8-17 所示组件的截图。
图 8-2 。进度组件截图(版权 Blender Foundation | www.sintel.org
)
清单 8-17。 使用进度组件的例子
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html FontName">http://www.w3.org/1999/xhtml"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
xmlns:projsfhtml5="http://xmlns.jcp.org/jsf/composite/projsfhtml5">
<ui:composition template="/base.xhtml">
<ui:define name="title">
Progress Component
</ui:define>
<ui:define name="top">
Progress Component
</ui:define>
<ui:define name="content">
<h2>Progress bar (indeterminate)</h2>
<projsfhtml5:progress id="indeterminate" />
<h2>Progress bar (determinate)</h2>
<projsfhtml5:progress id="determinate" value="56" max="100"/>
<h2>Progress bar (using for)</h2>
<projsfhtml5:video id="video" value="media/trailer.mp4"
autoplay="true"
controls="true">
</projsfhtml5:video>
<projsfhtml5:progress id="video-progress"
value="0" max="100"
for=":video" />
</ui:define>
</ui:composition>
</html>
摘要
在这一章中,我们已经学习了创建高级 HTML5 组件。这些示例演示了如何用 FacesComponents 支持复合组件视图,您可以在其中实现高级逻辑,否则在 Facelets 视图中很难或不可能实现。应该清楚的是,创建 JSF 2.x 组件比早期版本的 JSF 要容易得多。然而,仍然需要大量的工作来创建可以重用和扩展用于多种目的的组件。作为一名组件开发人员,您将在指定接口和实现组件时面临选择。值得注意的是,如果不考虑页面作者将如何使用组件,简单地将 HTML5 元素直接转换为 JSF 组件可能没有多大用处。在开发视频和音频组件时,我们面临着像 HTML5 一样将轨道指定为嵌入式子元素的选择,但在大多数情况下,JSF 页面作者在托管 bean 的集合中已经有了轨道和源;因此,我们在接口中包含了音轨和源作为属性获取集合。
12010 年 4 月史蒂夫·乔布斯对 Flash 的思考【http://www.apple.com/hotnews/thoughts-on-flash/
九、JSF 组件库
在这一章中,你将简要学习如何利用 JSF 组件库来生成漂亮的 web 应用。将向您介绍两个最著名的 JSF 开源组件库,它们是 PrimeFaces 和 RichFaces。虽然深入这些框架的细节超出了本书的范围,因为这些框架实在太大,无法在一章中涵盖,但是我们将向您概述每个组件库附带的不同组件,以及如何使用这些库来创建漂亮的 JSF 2.2 web 应用。
素数面
PrimeFaces 是一个开源的 JSF 组件库,具有许多不同的功能。PrimeFaces 拥有丰富的组件集,内置基于标准 JSF 2.0 Ajax API 的 Ajax,以及使用 web sockets 的 Ajax 推送支持;最后,PrimeFaces 包括移动用户界面 renderkit,允许 JSF 开发者在移动设备上创建 JSF 应用。PrimeFaces 还包括一个皮肤框架,它有超过 35 个内置主题。
为了配置 PrimeFaces,您需要下载 prime faces jar(prime faces-XXX . jar)。有两种方法可以下载这个 jar:你可以从 http://primefaces.org/downloads.html 下载,或者如果你是一个 Maven 用户,你可以将它定义为一个 Maven 依赖项。依赖项的组 ID 是 org.primefaces,工件 ID 是 primefaces,如下所示。
<dependency>
<groupId>org.primefaces</groupId>
<artifactId>primefaces</artifactId>
<version>3.5</version>
</dependency>
除了前面的配置之外,您还需要将 PrimeFaces Maven 存储库添加到您的 Maven 配置的存储库列表中,以便 Maven 可以如下下载它。
<repository>
<id>prime-repo</id>
<name>Prime Repo</name>
<url>http://repository.primefaces.org</url>
</repository>
PrimeFaces 需要 Java 5+运行时和 JSF 2.x 实现作为强制依赖。为了支持 PrimeFaces 中的一些特性,你可以包含一些可选的库,如表 9-1 所示。
表 9-1 。可选的 PrimeFaces 库
|
图书馆
|
特征
|
| — | — |
| itext 版本 2.1.7 | 资料转存(PDF) |
| Apache 3.7 版 | 资料汇出(Excel) |
| 罗马 1.9 版 | feed reader(feed reader) |
| Commons 文件上传版本 1.2.1 Commons io 版本 1.4 | FileUpload(文件上载) |
组件概述
PrimeFaces 有 100 多个 UI 组件,可以用来创建丰富的 Web 2.0 应用。深入这些组件的细节超出了本书的范围。然而,我们将在表 9-2 中给出这些组件的简短列表供您参考,根据 PrimeFaces 文档按字母顺序排序。
表 9-2 。PrimeFaces 组件概述
|
成分
|
描述
|
| — | — |
| AccordionPanel
| AccordionPanel 是一个以堆叠格式显示内容的容器组件。 |
| AutoComplete
| AutoComplete 在输入时提供实时建议。 |
| BreadCrumb
| Breadcrumb 是一个导航组件,提供关于工作流中页面层次结构的上下文信息。 |
| Button
| Button 是标准 h:button 组件的扩展,具有皮肤功能。 |
| Calendar
| Calendar 是一个用于选择日期的输入组件,具有显示模式、分页、本地化、Ajax 选择等功能。 |
| Captcha
| Captcha 是一个基于 Recaptcha API 的表单验证组件。 |
| Carousel
| Carousel 是一个多用途组件,以幻灯片效果显示一组数据或一般内容。 |
| Chart
| 图表用于显示图形数据。有各种图表类型,如饼图、条形图、折线图等。 |
| Clock
| 时钟显示服务器或客户端的实时日期/时间。 |
| Color Picker
| ColorPicker 是一个带有调色板的输入组件。 |
| CommandButton
| CommandButton 是标准 CommandButton 的扩展版本,具有 Ajax 和主题功能。 |
| CommandLink
| CommandLink 用 Ajax 功能扩展了标准的 JSF commandLink。 |
| ConfirmDialog:
| ConfirmDialog 是对传统 JavaScript 确认框的替代。换肤、定制和避免弹出窗口拦截器是传统 JavaScript 确认的显著优势。 |
| ContextMenu
| ContextMenu 提供鼠标右键事件时显示的覆盖菜单。 |
| Dashboard
| Dashboard 提供了一个类似门户的布局,具有基于拖放的重新排序功能。 |
| DataExporter
| DataExporter 可以方便地将使用 PrimeFaces 数据表列出的数据导出为各种格式,如 excel、pdf、csv 和 xml。 |
| DataGrid
| DataGrid 在网格布局中显示数据集合。 |
| DataList
| DataList 在列表布局中以几种显示类型呈现数据集合。 |
| DataTable
| DataTable 是标准 DataTable 的增强版本,它为许多常见的用例提供了内置的解决方案,如分页、排序、选择、延迟加载、过滤。 |
| DefaultCommand
| 当按下回车键时使用哪个命令提交表单是 web 应用中的一个常见问题,而不仅仅是 JSF 特有的问题。浏览器倾向于表现不同,因为似乎没有标准,即使标准存在,IE 可能也不会关心它。有一些丑陋的解决办法,比如在你的应用中放置一个隐藏的按钮,并为每个表单编写 JavaScript。DefaultCommand 通过规范化命令(如按钮或链接)来解决这个问题,通过按回车键提交表单。 |
| Dialog
| 对话框是一个面板组件,可以覆盖页面上的其他元素。 |
| Drag&Drop
| PrimeFaces 的拖放工具包括两个组件:Draggable 和 Droppable。 |
| Dock
| Dock 组件模仿了众所周知的 Mac OS X 的 dock 接口。 |
| Editor
| 编辑器是一个输入组件,具有丰富的文本编辑功能。 |
| FeedReader
| FeedReader 用于显示提要中的内容。 |
| Fieldset
| Fieldset 是一个分组组件,是 html fieldset 的扩展。 |
| FileDownload
| 向客户机呈现动态二进制数据的传统方式是编写一个 servlet 或过滤器,并流式传输二进制数据。FileDownload 提供了一种更简单的方法。 |
| FileUpload
| FileUpload 超越了浏览器输入 type="file "功能,并提供了一个基于 html5 的丰富解决方案,可针对传统浏览器进行适度降级。 |
| Focus
| 焦点是一个实用组件,它使得管理 JSF 页面上的元素焦点变得很容易。 |
| Galleria
| Galleria 用于显示一组图像。 |
| GMap
| GMap 是与谷歌地图 API V3 集成的地图组件。 |
| GraphicImage
| GraphicImage 扩展了标准的 JSF 图形图像组件,能够像 inputstream 一样显示二进制数据。GraphicImage 的主要用途是使显示存储在数据库中的图像或动态图像更容易。传统的方法是用一个 servlet 来实现流;GraphicImage 不需要 servlet 就能完成所有繁重的工作。 |
| Growl
| Growl 基于 Mac 的 Growl 通知小工具,用于以叠加方式显示面部信息。 |
| HotKey
| 热键是一个通用的键绑定组件,可以将任意形式的键绑定到 JavaScript 事件处理程序或 Ajax 调用。 |
| IdleMonitor
| IdleMonitor 监视页面上的用户操作,并在用户再次空闲或活跃时通知回调。 |
| ImageCompare
| ImageCompare 提供了一个丰富的用户界面来比较两幅图像。 |
| ImageCropper
| ImageCropper 允许裁剪图像的某个区域。创建一个包含裁剪区域的新图像,并将其分配给服务器端的 CroppedImage 实例。 |
| ImageSwitch
| ImageSwitch 是一个简单的图片库组件。 |
| Inplace
| Inplace 提供简单的 Inplace 编辑和内嵌内容显示。Inplace 由两个成员组成:显示元素是初始的可点击标签,内联元素是显示元素切换时显示的隐藏内容。Inplace 提供简单的 inplace 编辑和内嵌内容显示。Inplace 由两个成员组成:显示元素是初始的可点击标签,内联元素是显示元素切换时显示的隐藏内容。 |
| InputMask
| 输入掩码强制输入符合定义的掩码模板。 |
| InputText
| InputText 是标准 inputText 的扩展,具有皮肤功能。 |
| InputTextarea
| InputTextarea 是标准 inputTextarea 的扩展,具有自动完成、自动调整大小、剩余字符计数器和主题化功能。 |
| Keyboard
| 键盘是使用虚拟键盘提供输入的输入组件。显著的特点是可定制的布局和皮肤功能。 |
| Layout
| 布局组件具有高度可定制的 borderLayout 模型,即使你不熟悉网页设计,也可以非常容易地创建复杂的布局。 |
| LightBox
| LightBox 是一个强大的覆盖图,可以显示图像、多媒体内容、自定义内容和外部 URL。 |
| Log
| 日志组件是一个可视化控制台,用于在 JSF 页面上显示日志。 |
| Media
| 媒体组件用于嵌入多媒体内容。 |
| MegaMenu
| MegaMenu 是一个水平导航组件,可以一起显示子菜单。 |
| Menu
| 菜单是一个导航组件,具有多种定制模式,如多层、iPod 风格的滑动和叠加。 |
| Menubar
| 菜单栏是一个水平导航组件。 |
| MenuButton
| MenuButton 在弹出菜单中显示不同的命令。 |
| Message
| Message 是标准 JSF 消息组件的预皮肤扩展版本。 |
| Messages
| Messages 是标准 JSF 消息组件的预皮肤扩展版本。 |
| Mindmap
| 思维导图是一个交互式工具,可以可视化思维导图数据,包括延迟加载、回调和动画。 |
| NotificationBar
| 通知栏显示多用途固定位置面板,用于通知。 |
| OrderList
| OrderList 用于对具有基于拖放的重新排序、过渡效果和 POJO 支持的集合进行排序。 |
| OutputLabel
| OutputLabel 是标准 outputLabel 组件的扩展。 |
| OutputPanel
| OutputPanel 是一个具有自动更新能力的面板组件。 |
| OverlayPanel
| OverlayPanel 是一个通用的面板组件,可以显示在其他内容之上。 |
| Panel
| Panel 是一个分组组件,具有内容切换、关闭和菜单集成功能。 |
| PanelGrid
| PanelGrid 是标准 PanelGrid 组件的扩展,增加了主题化和 colspan-rowspan 等特性。 |
| PanelMenu
| PanelMenu 是 accordionPanel 和 tree 组件的混合组件。 |
| Password
| 密码组件是标准 inputSecret 组件的扩展版本,具有主题集成和强度指示器。 |
| PhotoCam
| PhotoCam 用于通过网络摄像头拍摄照片,并将其发送到 JSF 后端模块。 |
| PickList
| 选项列表用于在两个不同的集合之间传输数据。 |
| Poll
| Poll 是一个 Ajax 组件,能够定期发送 Ajax 请求。 |
| Printer
| 打印机允许向打印机发送特定的 JSF 组件,而不是整个页面。 |
| ProgressBar
| ProgressBar 是一个进程状态指示器,既可以在客户端工作,也可以使用 Ajax 与服务器端交互。 |
| Rating
| 评级组件采用基于星级的评级系统。 |
| RemoteCommand
| RemoteCommand 提供了一种直接从 JavaScript 执行 JSF 支持 bean 方法的方法。 |
| ResetInput
| 当验证失败时,输入组件将它们的本地值保持在状态。ResetInput 用于从状态中清除缓存的值,以便组件从后备 bean 模型中检索它们的值。 |
| Resizable
| 可调整大小的组件用于使另一个 JSF 组件可调整大小。 |
| Ring
| 环是一个带有圆形动画的数据显示组件。 |
| Schedule
| Schedule 提供了一个 Outlook 日历,类似 iCal 的 JSF 组件来管理事件。 |
| SelectBooleanButton
| SelectBooleanButton 用于通过切换按钮选择二元决策。 |
| SelectBooleanCheckbox
| SelectBooleanCheckbox 是带有主题集成的标准复选框的扩展版本。 |
| SelectCheckboxMenu
| SelectCheckboxMenu 是一个多选组件,以叠加方式显示选项。 |
| SelectManyButton
| SelectManyButton 是一个使用按钮 UI 的多选组件。 |
| SelectManyCheckbox
| SelectManyCheckbox 是带有主题集成的标准 SelectManyCheckbox 的扩展版本。 |
| SelectManyMenu
| SelectManyMenu 是标准 SelectManyMenu 的扩展版本,具有主题集成。 |
| SelectOneButton
| SelectOneButton 是一个进行单项选择的输入组件。 |
| SelectOneListbox
| SelectOneListbox 是标准 SelectOneListbox 的扩展版本,具有主题集成。 |
| SelectOneMenu
| SelectOneMenu 是标准 SelectOneMenu 的扩展版本,具有主题集成。 |
| SelectOneRadio
| SelectOneRadio 是标准 SelectOneRadio 的扩展版本,具有主题集成。 |
| Separator
| 分隔符显示一条水平线来分隔内容。 |
| SlideMenu
| SlideMenu 用于显示带有滑动动画的嵌套子菜单。 |
| Slider
| 滑块用于为输入提供各种定制选项,如方向、显示模式和皮肤。 |
| Socket
| Socket 组件是在服务器和客户端之间创建通道的代理。 |
| Spacer
| Spacer 用于在元素之间放置空格。 |
| Spinner
| 微调器是一个输入组件,通过增量和减量按钮提供数字输入。 |
| SplitButton
| 默认情况下,SplitButton 显示一个命令,并在覆盖图中显示其他命令。 |
| Stack
| Stack 是一个导航组件,它模仿了 Mac OS X 中的 stacks 特性。 |
| TabMenu
| TabMenu 是一个导航组件,它将菜单项显示为选项卡。 |
| TabView
| TabView 是一个选项卡式面板组件,具有客户端选项卡、Ajax 动态内容加载和内容转换效果。 |
| TagCloud
| TagCloud 显示不同强度的标签集合。 |
| Terminal
| Terminal 是一个 Ajax 驱动的基于 web 的终端,它将桌面终端带到了 JSF。 |
| ThemeSwitcher
| ThemeSwitcher 支持动态切换 PrimeFaces 主题,无需刷新页面。 |
| Toolbar
| 工具栏是命令和其他内容的水平分组组件。 |
| Tooltip
| Tooltip 通过提供自定义效果、事件、html 内容和高级主题支持,超越了传统的 html 标题属性。 |
| Tree
| 树用于显示分层数据和创建站点导航。 |
| TreeTable
| TreeTable 用于以表格格式显示分层数据。 |
| Watermark
| 水印在输入字段上显示提示。 |
| Wizard
| Wizard 提供了一个 Ajax 增强的 UI,可以在单个页面中轻松实现工作流。向导由几个子选项卡组件组成,其中每个选项卡代表流程中的一个步骤。 |
在下一节中,我们将展示一个 PrimeFaces 应用示例,向您展示如何利用这个库来创建漂亮的 JSF 2.2 web 应用。
注为了获得完整的质数面示例文档,你可以查看来自
primefaces.org/documentation.html
的用户指南文档。您可以访问完整的 PrimeFaces 展示区,其中包括来自primefaces.org/showcase/ui/home.jsf
的几乎所有 PrimeFaces 组件的示例。
集成和定制 PrimeFaces
Country Navigator 应用是一个 PrimeFaces 应用,它允许用户在点击任何一个可用国家的旗帜后,获得一个可用国家的可用城市列表(带有一些信息),如图图 9-1 所示。
图 9-1 。县导航应用
如截图所示,应用页面主要由两个 UI 组件 : 组成
- Ring 组件,显示可用国家/地区的列表(德国、埃及和巴西)。
- DataTable 组件,该组件显示所选国家/地区的可用城市列表。
清单 9-1 显示了代表显示可用国家列表的 ring 组件的代码片段。
清单 9-1。 显示不同国家的环形组件
<p:ring id="custom" value="#{countryNavigator.countries}" var="country"
styleClass="image-ring" easing="swing">
<p:commandLink update=":form:detail">
<p:graphicImage valueimg/#{country.name}.gif"
styleClass="flagIcon" />
<f:setPropertyActionListener value="#{country}"
target="#{countryNavigator.activeCountry}" />
</p:commandLink>
</p:ring>
{ countryNavigator.countries }表达式表示可用国家的列表。当单击一个环项目,然后通过 p:commandLink 的操作,所选的 country #{country}将在 countryNavigator 的 activeCountry 属性中设置,并且“detail”面板(包含城市数据表)将重新呈现所选国家的新城市。清单 9-2 显示了“细节”面板部分。
清单 9-2。 【细节】面板部分
<p:outputPanel id="detail" styleClass="detailsPanel" layout="block">
<p:dataTable var="city" value="#{countryNavigator.activeCountry.cities}"
rendered="#{countryNavigator.activeCountry ne null}">
<f:facet name="header">
#{countryNavigator.activeCountry.name}
</f:facet>
<p:column headerText="City">
<h:outputText value="#{city.name}" />
</p:column>
<p:column headerText="Population">
<h:outputText value="#{city.population}" />
</p:column>
</p:dataTable>
</p:outputPanel>
当 countryNavigator 对象中存在可用的 activeCountry 时,将呈现“city”数据表,该表将显示所选国家的可用城市信息。清单 9-3 显示了完整的 CountryNavigator 托管 bean 代码。
清单 9-3。 CountryNavigator 托管 Bean
package com.jsfprohtml5.countrynavigator.model;
import java.util.List;
public class CountryNavigator {
private List<Country> countries;
private Country activeCountry;
public List<Country> getCountries() {
return countries;
}
public void setCountries(List<Country> countries) {
this.countries = countries;
}
public Country getActiveCountry() {
return activeCountry;
}
public void setActiveCountry(Country activeCountry) {
this.activeCountry = activeCountry;
}
}
Country managed bean 保存国家类的名称、人口和城市,如清单 9-4 所示。
清单 9-4。 国家托管豆
public class Country {
private String name;
private long population;
private List<City> cities;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getPopulation() {
return population;
}
public void setPopulation(long population) {
this.population = population;
}
public List<City> getCities() {
return cities;
}
public void setCities(List<City> cities) {
this.cities = cities;
}
}
城市管理 bean 保存城市类的名称和人口,如清单 9-5 中的所示。
清单 9-5。 城市管理豆
public class City {
private String name;
private long population;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getPopulation() {
return population;
}
public void setPopulation(long population) {
this.population = population;
}
}
清单 9-6 显示了 Country Navigator 应用页面的完整代码,它将 ring 组件与“details”面板部分合并在一起。
清单 9-6。 国家导航器应用页面代码
<html FontName">http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:p="http://primefaces.org/ui">
<h:head>
<title>Welcome to the Country Navigator</title>
<h:outputStylesheet library="css" name="countryNavigator.css" />
</h:head>
<h:body>
<h:form id="form">
<h2>Click on a country to get its details</h2>
<p:ring id="custom" value="#{countryNavigator.countries}" var="country"
styleClass="image-ring" easing="swing">
<p:commandLink update=":form:detail">
<p:graphicImage valueimg/#{country.name}.gif"
styleClass="flagIcon" />
<f:setPropertyActionListener value="#{country}"
target="#{countryNavigator.activeCountry}" />
</p:commandLink>
</p:ring>
<p:outputPanel id="detail" styleClass="detailsPanel" layout="block">
<p:dataTable var="city" value="#{countryNavigator.activeCountry.cities}"
rendered="#{countryNavigator.activeCountry ne null}">
<f:facet name="header">
#{countryNavigator.activeCountry.name}
</f:facet>
<p:column headerText="City">
<h:outputText value="#{city.name}" />
</p:column>
<p:column headerText="Population">
<h:outputText value="#{city.population}" />
</p:column>
</p:dataTable>
</p:outputPanel>
</h:form>
</h:body>
</html>
为了填充国家和城市数据,创建并初始化 CountryNavigator 托管 bean 实例,如清单 9-7 中的 Faces 配置所示(注意,为了节省空间,省略了一些行)。
清单 9-7。 面孔配置文件
<?xml version='1.0' encoding='UTF-8'?>
<faces-config ...>
<managed-bean>
<managed-bean-name>countryNavigator</managed-bean-name>
<managed-bean-class>com.jsfprohtml5.countrynavigator.model.CountryNavigator</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
<managed-property>
<property-name>countries</property-name>
<list-entries>
<value>#{egypt}</value>
<value-class>com.jsfprohtml5.countrynavigator.model.Country</value-class>
<value>#{germany}</value>
<value-class>com.jsfprohtml5.countrynavigator.model.Country</value-class>
<value>#{brazil}</value>
<value-class>com.jsfprohtml5.countrynavigator.model.Country</value-class>
</list-entries>
</managed-property>
</managed-bean>
<!-- Egypt -->
<managed-bean>
<managed-bean-name>egypt</managed-bean-name>
<managed-bean-class>com.jsfprohtml5.countrynavigator.model.Country</managed-bean-class>
<managed-bean-scope>none</managed-bean-scope>
<managed-property>
<property-name>name</property-name>
<value>Egypt</value>
</managed-property>
<managed-property>
<property-name>population</property-name>
<value>82000000</value>
</managed-property>
<managed-property>
<property-name>cities</property-name>
<list-entries>
<value>#{cairo}</value>
<value-class>com.jsfprohtml5.countrynavigator.model.City</value-class>
<value>#{alexandria}</value>
<value-class>com.jsfprohtml5.countrynavigator.model.City</value-class>
<value>#{aswan}</value>
<value-class>com.jsfprohtml5.countrynavigator.model.City</value-class>
</list-entries>
</managed-property>
</managed-bean>
<managed-bean>
<managed-bean-name>cairo</managed-bean-name>
<managed-bean-class>com.jsfprohtml5.countrynavigator.model.City</managed-bean-class>
<managed-bean-scope>none</managed-bean-scope>
<managed-property>
<property-name>name</property-name>
<value>Cairo</value>
</managed-property>
<managed-property>
<property-name>population</property-name>
<value>8000000</value>
</managed-property>
</managed-bean>
<!-- Other configuration data is not shown ... -->
</faces-config>
注意 CountryNavigator 应用是一个 Maven web 应用:您可以从图书资源中下载,然后使用 mvn install 命令构建应用。最后,您可以在 GlassFish 版上部署最终的 country navigator-1.0-snapshot . war 来检查它是如何工作的。为了正确运行 Maven 命令,您必须确保 JAVA_HOME 指向安装在您的操作系统中的 Java 7 目录。
RichFaces
RichFaces 是一个开源的 JSF 组件库,拥有许多不同的功能。RichFaces 包括两个主要的标签库:
- a4j 标记库,它提供了 Ajax 功能和通用工具。
- 丰富的标记库,它提供了一组与 Ajax 完全集成的自包含的丰富组件。
为了配置 RichFaces,您需要首先理解它的依赖关系 jars。RichFaces 依赖于四个主要的 jar,它们代表 RichFaces 核心和组件的 API 和实现,如下所示:
- richfaces-core-api.jar
- richfaces 核心 impl.jar
- rich faces-组件-api.jar
- richfaces-components-ui.jar
RichFaces jars 具有以下强制性依赖项:
- Java Server Faces 2.x 实现:javax . Faces . jar(2 . 1 . 5 或更高版本)或 myfaces-impl . jar(2 . 1 . 5 或更高版本)
- 谷歌番石榴:Guava . jar(10 . 0 . 1 版本)。
- CSS 剖析器:cssparser.jar(版本 0.9.5)。
- CSS 的简单 API:sac . jar(1.3 版)
以及运行某些功能可能需要的以下可选 jar:
- 客户端验证(JSR-303 API 和实现)的 Bean 验证(JSR-303)集成:验证-api.jar(版本 1.0.0.GA)和 hibernate-validator.jar(版本 4.2.0.Final 或更高)。
- 推送传输库—Atmosphere(无依赖):atmosphere-runtime.jar(版本 1.0.10)(所选兼容模块 atmosphere-compat-*。罐子可能是必要的)。
- 推送 JMS 集成 (JMS API 和实现):JMS . jar(1.1 版)和 hornetq-JMS . jar(2 . 2 . 7 . final 或更高版本)
- 推送 CDI 集成 (CDI API 及实现):cdi-api.jar(版本 1.0-SP4)和 javax.inject.jar(版本 1)和 jsr-250-api.jar(版本 1.0)和 weld-servlet.jar(版本 1.1.4.Final)。
- 扩展缓存(EhCache): ehcache.jar(版本 1.6.0)。
注意要知道,前面提到的一些依赖关系是 Java EE 6 规范的一部分,所以如果你在一个像 GlassFish 这样的 Java EE 6 应用服务器上工作(不仅仅是一个 servlet 容器),那么就没有必要添加这些依赖关系。
有两种方式下载 RichFaces 依赖项:你可以直接从www.jboss.org/richfaces/download.html下载,或者如果你是 Maven 用户,你可以使用 RichFaces Maven 原型(RichFaces 需要 Maven 3.0.3 或更高版本)。使用名为 richfaces-archetype-simpleapp 的 Maven 原型,您可以生成 richfaces 应用项目的基本结构和需求。
为了运行 RichFaces Maven 原型,需要将 JBoss 存储库添加到 Maven 配置中。在${ Maven _ Installation _ Dir }/conf/settings . XML 文件中的元素下添加一个概要文件,如清单 9-8 所示。
清单 9-8。 将 JBoss 资源库添加到 Maven 配置中
<profiles>
...
<profile>
<id>jboss-public-repository</id>
<repositories>
<repository>
<id>jboss-public-repository-group</id>
<name>JBoss Public Maven Repository Group</name>
<url>https://repository.jboss.org/nexus/content/groups/public/</url>
<layout>default</layout>
<releases>
<enabled>true</enabled>
<updatePolicy>never</updatePolicy>
</releases>
<snapshots>
<enabled>true</enabled>
<updatePolicy>never</updatePolicy>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>jboss-public-repository-group</id>
<name>JBoss Public Maven Repository Group</name>
<url>https://repository.jboss.org/nexus/content/groups/public/</url>
<layout>default</layout>
<releases>
<enabled>true</enabled>
<updatePolicy>never</updatePolicy>
</releases>
<snapshots>
<enabled>true</enabled>
<updatePolicy>never</updatePolicy>
</snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>
除了创建 jboss-public-repository 概要文件之外,还需要将其添加到元素中来激活,如清单 9-9 所示。
清单 9-9。 激活 jboss-public-repository 概要
<activeProfiles>
<activeProfile>jboss-public-repository</activeProfile>
</activeProfiles>
在创建并激活 jboss-public-repository 之后,可以使用 rich faces-architect-simple app 原型生成项目。为此,为您的项目创建一个新目录,然后在该目录中运行以下 Maven 命令:
mvn archetype:generate -DarchetypeGroupId=org.richfaces.archetypes -DarchetypeArtifactId=richfaces-archetype-simpleapp -DarchetypeVersion=4.3.2.Final -DgroupId=com.jsfprohtml5.richfacesapp -DartifactId=richFacesApp
可以使用-DgroupId 参数来定义应用管理的 beans 的包,而可以使用-DartifactId 来定义项目的名称。
前面的 richfaces-archetype-simpleapp 命令生成一个新的 richfaces 项目,其结构如下。
richFacesApp
├── pom.xml
├── readme.txt
└── src
└── main
├── java
│ └── com
│ └── jsfprohtml5
│ └── richfacesapp
│ └── RichBean.java
└── webapp
├── index.xhtml
├── templates
│ └── template.xhtml
└── WEB-INF
├── faces-config.xml
└── web.xml
这就是创建 RichFaces 应用所需的全部内容;现在,您可以使用普通的 mvn install 命令构建生成的 Maven 项目。
组件概述
RichFaces 有 40 多个 UI 组件,可以用来创建丰富的 Web 2.0 应用。深入这些组件的细节超出了本书的范围。但是,我们将在表 9-3 中给出一些组件的简短列表,供您参考,根据 RichFaces 文档按字母顺序排序(注意,所有这些组件都是 rich 标签库的一部分)。
表 9-3 。RichFaces 组件概述
|
成分
|
描述
|
| — | — |
| Accordion
| 是一系列相互堆叠的面板,每个面板都折叠起来,只显示面板的标题。当单击面板的标题时,它会展开以显示面板的内容。单击不同的标题将折叠前一个面板并展开所选面板。包含在一个组件中的每个面板都是一个组件。 |
| Autocomplete
| 组件是一个内置 Ajax 的自动完成输入框。 |
| Calendar
| 组件允许用户通过内嵌或弹出日历输入日期和时间。 |
| CollapsiblePanel
| 组件是一个可折叠的面板,当标题栏被激活时,它显示或隐藏内容。它是< rich:togglePanel >组件的简化版本。 |
| ContextMenu
| 组件用于创建一个层次化的上下文菜单,该菜单在 onmouseover、onclick 等事件上被激活。该组件可以应用于页面上的任何元素。 |
| DataGrid
| 组件是用于在网格中排列数据对象的。网格中的值可以从数据模型中动态更新,Ajax 更新可以局限于特定的行。该组件支持页眉、页脚和标题方面。 |
| DataScroller
| 组件用于在多页表格或网格中导航。 |
| DataTable
| 组件用来呈现一个高度可定制的表格,包括表格的标题。它与和组件一起工作,列出数据模型的内容。 |
| DragIndicator
| 组件定义了一个在拖放操作中鼠标光标下显示的图形元素。 |
| DragSource
| 可以将组件添加到一个组件中,以表明它能够被用户拖动。然后可以将拖动的项目放入一个兼容的放置区域,使用组件指定。 |
| DropDownMenu
| 组件用于创建一个下拉的层次菜单。它可以与组件一起使用,在应用的工具栏中创建菜单。 |
| DropTarget
| 可以将组件添加到一个组件中,以便该组件可以接受拖动的项目。被拖动的项目必须用与< rich:dragSource >组件兼容的拖放类型来定义。 |
| Editor
| 组件用于在页面上创建所见即所得编辑器。 |
| ExtendedDataTable
| 组件建立在组件的功能之上,增加了一些特性,比如表体的滚动(水平和垂直)、垂直滚动的 Ajax 加载、冻结列、行选择和列的重新排列。它还支持所有基本的表特性,比如使用组件进行排序、过滤和分页。 |
| FileUpload
| 组件允许用户上传文件到服务器。它的特点是多次上传,进度条,对文件类型的限制,以及对上传文件大小的限制。 |
| Focus
| 组件允许用户操作页面上组件的焦点。它适用于任何输入字段。 |
| HashParam
| 组件允许将客户端参数分组到一个散列图中。然后可以将哈希映射传递给任何 RichFaces 组件的客户端 JavaScript API 函数。 |
| HotKey
| 组件允许 one 为页面或特定元素注册热键,并为这些键定义客户端处理功能。 |
| InplaceInput
| 组件允许在文本块中内联输入信息,提高文本的可读性。 |
| InplaceSelect
| 除了组件使用下拉选择框而不是常规的文本字段来输入文本之外,组件与组件相似。 |
| InputNumberSlider
| 组件提供了一个改变数值的滑块。 |
| InputNumberSpinner
| 组件是一个单行输入字段,带有增加和减少数值的按钮。可以使用键盘上相应的方向键或通过在字段中键入来更改该值。 |
| jQuery
| 组件将样式和定制行为应用于 JSF 对象和常规 DOM(文档对象模型)对象。它使用 jQuery JavaScript 框架向 web 应用添加功能。 |
| List
| 组件呈现一个项目列表。该列表可以是按数字排序的列表、无序的项目符号列表或数据定义列表。该组件使用数据模型来管理列表项,该模型可以动态更新。 |
| Message
| 组件呈现为组件添加的单个 FacesMessage 消息实例。可以定制消息的外观,并且可以使用工具提示来获得关于消息的更多信息。 |
| Messages
| 组件的工作方式类似于组件,但是它可以显示为当前视图添加的所有验证消息,而不仅仅是一条消息。 |
| Notify
| 组件用于高级用户交互,使用通知框向用户提供关于应用中正在发生的事情的即时反馈。每次呈现该组件时,都会在浏览器屏幕的选定角落显示一个浮动通知框。 |
| NotifyMessage
| 组件构建在之上;区别在于用法。组件显示与给定组件相关联的 FacesMessages,类似于:为堆栈中的第一个 FacesMessage 显示一个通知,该通知以编程方式或在组件的转换/验证期间出现。消息的严重性决定了生成的通知的颜色和图标。 |
| NotifyMessages
| 组件与组件相同,但是每个可用消息生成一个通知。与拥有相同的属性集。 |
| NotifyStack
| 由、、< rich:notifyMessage >和< rich:notifyMessages >发出的通知默认显示在屏幕的右上角。rich:notifyStack >可以用来定义消息将出现在哪里,并处理它们的堆叠。 |
| OrderingList
| 是对列表中的项目进行排序的组件(客户端)。 |
| Panel
| 组件是一个带有可选标题的边框面板。 |
| PanelMenu
| 组件与和一起使用来创建一个扩展的层次菜单。组件的外观可以高度定制,层次结构可以扩展到任意数量的子层。 |
| PickList
| 是用于从列表中选择项目的组件。此外,它允许对所选项目进行排序(客户端)。 |
| Placeholder
| 组件允许用户为输入组件使用类似于 HTML5 占位符属性的功能。 |
| PopupPanel
| 组件提供了一个弹出面板或窗口,出现在应用的其余部分的前面。组件既可以作为模态窗口,在活动时阻止与应用其他部分的交互,也可以作为非模态窗口。它可以定位在屏幕上,由用户拖动到新的位置,并调整大小。 |
| ProgressBar
| 组件显示一个进度条,向用户指示进程的状态。它既可以通过 Ajax 更新,也可以在客户端更新,外观和感觉可以完全定制。 |
| Select
| 组件提供了一个下拉列表框,用于从多个选项中选择一个值。组件可以被配置为一个组合框,它将接受键入的输入。 |
| TabPanel
| 组件提供了一组选项卡式面板,用于一次显示一个面板的内容。标签可以高度定制和主题化。一个容器中的每个标签都是一个组件。 |
| TogglePanel
| 组件用作其他可切换组件的基础,即< rich:accordion >组件和< rich:tabPanel >组件。它提供了一个抽象的可切换组件,没有任何关联的标记。同样地,rich:togglePanel >组件可以被定制为在 accordion 组件和 tab panel 组件都不合适时提供可切换组件。 |
| Toolbar
| 组件是一个水平工具栏。任何 JavaServer Faces (JSF)组件都可以添加到工具栏中。 |
| Tooltip
| 组件提供了一个信息工具提示。工具提示可以附加到任何控件,当鼠标光标悬停在控件上时显示。 |
| Tree
| 组件提供了一个分层的树形控件。每个组件通常由子组件组成。 |
在下一节中,我们将展示一个 RichFaces 应用示例,向您展示如何利用这个库。
注为了获得 RichFaces 的完整文档,可以查看来自
www.jboss.org/richfaces/docs
的文档。您可以访问完整的 RichFaces 展示,其中包括来自showcase.richfaces.org
的几乎所有 RichFaces 组件的示例。
集成和定制 RichFaces
RightCountry 应用是一个 RichFaces 应用,它允许用户将一个可用地点列表拖动到相应的国家,如图 9-2 所示。
图 9-2 。正确的国家应用
该应用不允许用户将一个地方拖放到该地方不属于的国家。清单 9-10 显示了代表拖放源面板(在左边)的代码片段。
清单 9-10。 【下拉源面板代码(在左边)
<rich:panel styleClass="dropSourcePanel">
<f:facet name="header">
<h:outputText value="Places" />
</f:facet>
<h:dataTable id="places" columns="1"
value="#{rightCountry.places}"
var="place" footerClass="footerClass">
<h:column>
<a4j:outputPanel styleClass="placesContainer"
layout="block">
<rich:dragSource type="#{place.country}"
dragValue="#{place}"
dragIndicator="ind"/>
<h:outputText value="#{place.name}"></h:outputText>
</a4j:outputPanel>
</h:column>
</h:dataTable>
</rich:panel>
“拖放源”面板包含以下主要组件:
- “地点”数据表,列出了要显示的不同地点。
- dragSource,它允许数据表中的位置是可拖动的;在这个例子中,我们主要使用了 dragSource 的两个属性;dragValue,表示在拖放事件完成后要发送到拖放区的数据;dragIndicator,表示在拖动操作期间用作拖动指针的 drag indicator 组件的组件 ID。
除了拥有单个拖放源面板之外,我们还有三个拖放目标面板,它们将从拖放源面板接收项目;每个放置目标代表放置源项目的相应国家(埃及、德国和巴西)。清单 9-11 显示了第一个拖放目标面板(它将接收与埃及相关的拖放源项目)。
清单 9-11。 首先放下目标面板
<rich:panel styleClass="dropTargetPanel">
<f:facet name="header">
<h:outputText value="Egypt" />
</f:facet>
<rich:dropTarget acceptedTypes="Egypt" dropValue="Egypt"
dropListener="#{rightCountry.processDrop}"
render="places, egyptPlaces, germanyPlaces, brazilPlaces"/>
<h:dataTable id="egyptPlaces" columns="1"
value="#{rightCountry.egyptPlaces}"
var="place" footerClass="footerClass">
<h:column>
<h:outputText value="#{place.name}"></h:outputText>
</h:column>
</h:dataTable>
</rich:panel>
如前面的代码片段所示,拖放目标面板包含以下组件:
- “egyptPlaces”数据表,显示属于埃及的地方。
- dragTarget,它定义了可放置的区域;在示例中,我们使用了 dragTarget 的四个属性;acceptedTypes,定义可拖放区可接受的元素类型(如果与 dragSource 类型匹配,则可拖放区将接受 dragSource 项);dropValue,表示 drop 事件完成后要处理的数据;dropListener,与表示操作监听器方法的 MethodExpression 绑定,该方法将在 drop 操作完成后被通知;最后是 render 属性,它定义了将参与请求处理生命周期的“呈现”部分的组件的 id。
在#{rightCountry.processDrop}方法表达式(将在拖放完成后执行)中,从 places 列表(与拖动源数据表绑定)中删除所选的 place 对象,并将其放入 egyptPlaces 列表中,以便用新的 place 对象更新“egyptPlaces”数据表,如清单 9-12 所示。
***清单 9-12。***right country 托管 Bean 的 processDrop 方法
public void processDrop(DropEvent event) {
Place place = (Place) event.getDragValue();
String dropValue = (String) event.getDropValue();
switch (dropValue) {
case "Egypt":
egyptPlaces.add(place);
places.remove(place);
break;
//...
}
if (places.size() == 0) {
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage("Congratulations! You are done."));
initialize();
}
}
您可能会注意到,在使用完 drag source 面板中的所有元素(拖放到相应的合适目标)之后,会创建一条 Faces 消息,向用户显示“恭喜!你完了。”并使用 initialize()方法重置页面信息;该 Faces 消息将由显示,如列表 9-13 所示。
与埃及下降目标一样,德国和巴西的其他下降目标也是如此。清单 9-13 显示了右国申请页面的完整代码。
清单 9-13。 右国申请页面
<html FontName">http://www.w3.org/1999/xhtml"
xmlns:rich="http://richfaces.org/rich"
xmlns:a4j="http://richfaces.org/a4j"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html">
<h:head>
<title>Welcome to the Right Country application</title>
<h:outputStylesheet library="css" name="rightCountry.css" />
</h:head>
<h:body>
<rich:dragIndicator id="ind" acceptClass="accept" rejectClass="reject"
draggingClass="default">
Drag the place to the right country
</rich:dragIndicator>
<h:form id="form">
<h2>Drag the places to the right country</h2>
<h:panelGrid columnClasses="column" columns="4"
styleClass="containerPanel">
<rich:panel styleClass="dropSourcePanel">
<f:facet name="header">
<h:outputText value="Places" />
</f:facet>
<h:dataTable id="places" columns="1"
value="#{rightCountry.places}"
var="place" footerClass="footerClass">
<h:column>
<a4j:outputPanel styleClass="placesContainer"
layout="block">
<rich:dragSource type="#{place.country}"
dragValue="#{place}"
dragIndicator="ind"/>
<h:outputText value="#{place.name}"></h:outputText>
</a4j:outputPanel>
</h:column>
</h:dataTable>
</rich:panel>
<rich:panel styleClass="dropTargetPanel">
<f:facet name="header">
<h:outputText value="Egypt" />
</f:facet>
<rich:dropTarget acceptedTypes="Egypt" dropValue="Egypt"
dropListener="#{rightCountry.processDrop}"
render="places, egyptPlaces, germanyPlaces, brazilPlaces"/>
<h:dataTable id="egyptPlaces" columns="1"
value="#{rightCountry.egyptPlaces}"
var="place" footerClass="footerClass">
<h:column>
<h:outputText value="#{place.name}"></h:outputText>
</h:column>
</h:dataTable>
</rich:panel>
<rich:panel styleClass="dropTargetPanel">
<f:facet name="header">
<h:outputText value="Germany" />
</f:facet>
<rich:dropTarget acceptedTypes="Germany" dropValue="Germany"
dropListener="#{rightCountry.processDrop}"
render="places, egyptPlaces, germanyPlaces, brazilPlaces"/>
<h:dataTable id="germanyPlaces" columns="1"
value="#{rightCountry.germanyPlaces}"
var="place" footerClass="footerClass">
<h:column>
<h:outputText value="#{place.name}"></h:outputText>
</h:column>
</h:dataTable>
</rich:panel>
<rich:panel styleClass="dropTargetPanel">
<f:facet name="header">
<h:outputText value="Brazil" />
</f:facet>
<rich:dropTarget acceptedTypes="Brazil" dropValue="Brazil"
dropListener="#{rightCountry.processDrop}"
render="places, egyptPlaces, germanyPlaces, brazilPlaces"/>
<h:dataTable id="brazilPlaces" columns="1"
value="#{rightCountry.brazilPlaces}"
var="place" footerClass="footerClass">
<h:column>
<h:outputText value="#{place.name}"></h:outputText>
</h:column>
</h:dataTable>
</rich:panel>
</h:panelGrid>
<rich:notifyMessages stayTime="2000" nonblocking="true" />
</h:form>
</h:body>
</html>
RightCountry 托管 bean 包含四个列表,这四个列表与拖动源和四个放置目标绑定在一起。清单 9-14 显示了完整的 RightCountry 托管 bean 代码。
清单 9-14。 RightCountry 托管豆
package com.jsfprohtml5.rightcountry.model;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import org.richfaces.event.DropEvent;
public class RightCountry implements Serializable {
private List<Place> places;
private List<Place> egyptPlaces;
private List<Place> germanyPlaces;
private List<Place> brazilPlaces;
public RightCountry() {
initialize();
}
public List<Place> getPlaces() {
return places;
}
public void setPlaces(List<Place> places) {
this.places = places;
}
public List<Place> getEgyptPlaces() {
return egyptPlaces;
}
public void setEgyptPlaces(List<Place> egyptPlaces) {
this.egyptPlaces = egyptPlaces;
}
public List<Place> getGermanyPlaces() {
return germanyPlaces;
}
public void setGermanyPlaces(List<Place> germanyPlaces) {
this.germanyPlaces = germanyPlaces;
}
public List<Place> getBrazilPlaces() {
return brazilPlaces;
}
public void setBrazilPlaces(List<Place> brazilPlaces) {
this.brazilPlaces = brazilPlaces;
}
public void processDrop(DropEvent event) {
Place place = (Place) event.getDragValue();
String dropValue = (String) event.getDropValue();
switch (dropValue) {
case "Egypt":
egyptPlaces.add(place);
places.remove(place);
break;
case "Germany":
germanyPlaces.add(place);
places.remove(place);
break;
case "Brazil":
brazilPlaces.add(place);
places.remove(place);
break;
}
if (places.size() == 0) {
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage("Congratulations! You are done."));
initialize();
}
}
private void initialize () {
egyptPlaces = new ArrayList<>();
germanyPlaces = new ArrayList<>();
brazilPlaces = new ArrayList<>();
places = new ArrayList<>();
places.add(new Place("The Great Pyramids of Giza", "Egypt"));
places.add(new Place("Western Pomerania Lagoon Area National Park", "Germany"));
places.add(new Place("Catete Palace", "Brazil"));
places.add(new Place("Saxon Switzerland National Park", "Germany"));
places.add(new Place("Luxor Temple", "Egypt"));
places.add(new Place("Mariano Procópio Museum", "Brazil"));
places.add(new Place("Bavarian Forest National Park", "Germany"));
places.add(new Place("Museu do Índio", "Brazil"));
places.add(new Place("Cairo Tower", "Egypt"));
}
}
最后,清单 9-15 展示了 RightCountry 应用的 CSS 样式类。
***清单 9-15。***right country 应用的 CSS 样式类
.column {
width: 25%;
vertical-align: top;
}
.dropTargetPanel {
width: 90%;
}
.dropSourcePanel {
width: 133px;
}
.containerPanel {
width: 100%;
}
.placesContainer {
width: 100px;
border: 1px solid gray;
padding: 2px
}
.footerClass {
text-align: center;
padding-top: 5px;
}
.default {
padding-left:30px;
background-position: 5px;
background-repeat: no-repeat;
}
.accept {
background-position: 5px;
background-repeat: no-repeat;
border:2px solid green
}
.reject {
border:2px solid red;
background-position: 5px;
background-repeat: no-repeat;
}
注意像 CountryNavigator 应用一样,RightCountry 应用是一个 Maven web 应用,你可以在www.apress.com/9781430250104从图书网站下载;它可以像 CountryNavigator 应用一样进行构建和部署。
摘要
在这一章中,向您介绍了两个最流行的开源 JSF 组件库(PrimeFaces 和 RichFaces)。尽管深入研究这些框架的细节超出了本书的范围,但是我们开发了两个应用(一个用于 PrimeFaces,另一个用于 RichFaces ),以便向您展示如何利用这些组件库在 JSF 2.2 世界中生成漂亮的 web 应用。