开发人员:Java 使用 Oracle ADF Faces 富客户端和活动数据服务构建 Google Talk 客户端 了解如何构建与 Google Talk 完全集成的富 Java 客户端。 作者:Lucas Jellema  2008 年 3 月发布 正如甲骨文全球大会 2007 所明确指出的,将 Web 2.0 的概念和趋势集成到企业应用程序中已经成为应用程序开发的主要趋势。那么如何将极具吸引力、视觉丰富的用户界面与社会书签和即时消息处理 (IM) 等流行的互联网趋势组合到一起形成极具吸引力和富有成效的应用程序呢?Oracle 融合应用产品在这方面提供了良好的范例。集成到应用程序的用户界面中的通信是这种新型应用程序所倡导的协作中的一个重要元素。电子邮件、IP 电话和短消息服务都是您可以考虑的通信渠道,而 IM(聊天)无疑是其中功能最强大的一个。 本教程将说明如何开发与一种主要的 IM 服务 Google Talk 完全集成的富 Web 应用程序。开源的 Smack 项目提供了一个 Java API 库来钩入可扩展消息处理和在线协议 (XMPP) 这一 IM 通信协议。使用 Smack,您可以非常快速地创建一个通过 Google Talk 或任何其他遵循 XMPP 协议的 IM 服务进行交流的 Java 应用程序。接下来,您将用 Oracle ADF Faces 富客户端(本文档编写期间为 Oracle JDeveloper 11g 技术预览版的一部分)创建用户界面,尤其是在收到消息后允许即时 Web 客户端更新(从服务器到浏览器的推送)的活动数据服务。最后,使用 Oracle ADF Faces 富客户端拖放功能快速将数据记录拖放到聊天消息中。 此处提供本教程各部分的完整的项目文件和示例代码。 准备工作 第 1 部分:创建通过 Google Talk 进行交流的 Java 应用程序 如何设置 Smack 并连接到 Google Talk 以进行编程聊天(读取和发送消息) 启动 Oracle JDeveloper 11g。创建一个新的应用程序,将其命名为 GoogleTalk(该名称仅用于举例,也可以叫的名称)。再创建一个新项目,命名为 GoogleTalkJavaClient。 从 Application Template 菜单中选择 No Template [All Technologies]。从在 Project 节点上单击鼠标右键显示的菜单中,或者通过 Tools 菜单,打开 Project Properties 对话框。转至 Libraries and Classpath 节点。 将 smack.jar 和 smackx.jar 文件归档添加到项目中:单击 Add JAR/Directory 按钮。浏览 Smack 库和文件,选择 smack.jar 和 smackx.jar 并单击 Select 按钮。 JAR 文件将添加到项目中。 单击 OK 按钮。 现在,我们将创建一个可以通过 Google Talk 发送 IM 消息的类。在程序包 otn.adf.googletalk 中创建一个 MessageSender 类。通知 Oracle JDeveloper 添加一个 main 方法。 package otn.adf.googletalk;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Message;
public class MessageSender {
private static String username = "YOUR_GOOGLE_TALK_USERNAME";
private static String password = "YOUR_GOOGLE_TALK_PASSWORD";
ConnectionConfiguration connConfig;
XMPPConnection connection;
public MessageSender() throws XMPPException {
connConfig = new ConnectionConfiguration("talk.google.com", 5222, "gmail.com");
connection = new XMPPConnection(connConfig);
connection.connect();
connection.login(username, password);
}
public void sendMessage(String to, String message ) {
Message msg = new Message(to, Message.Type.chat);
msg.setBody(message);
connection.sendPacket(msg);
}
public void disconnect() {
connection.disconnect();
}
public static void main(String[] args) throws XMPPException {
MessageSender messageSender = new MessageSender();
messageSender.sendMessage("youraccount@gmail.com",
"Hello You. This is my first message sent programmatically using Smack API and Google Talk.");
messageSender.disconnect();
}
}
确保将您自己的 Google Talk 帐户名和口令放到第 10 行和第 11 行。并将您的帐户或您朋友的帐户放到第 36 行,作为该“Hello, world of instant messaging”的目的地。 MessageSender 类的构造器创建一个到 Google Talk 服务器的连接并登录(第 17–21 行)。创建 MessageSender 实例后,将调用 sendMessage 方法,包含目的地(将消息发送给谁)以及消息本身。最后,在 disconnect 方法中关闭连接。 运行该应用程序后,您很快就会收到您的应用程序通过 Google Talk 服务器发送给您的第一条 Google Talk 消息: ![]() ![]() 在线 即时消息处理的一个有用功能是在线 — 指明我的哪些朋友和联系人在线。Google 支持在线,Smack API 亦然。 首先可以通过执行以下代码告诉大家自己在线: // tell the world (or at least our friends) that we are around
Presence presence = new Presence(Presence.Type.available);
presence.setMode(Presence.Mode.chat);
connection.sendPacket(presence);
注:有多种在线模式可供选择:在线、聊天、离开、xa(长时间离开)和 dnd(请勿打扰)。 对我们来说更有趣的是,我们的程序甚至可以了解我们的所有联系人(用 IM 术语叫做“花名册”)或特定联系人是否在线。使用以下代码可以所有联系人的名单: Roster roster = connection.getRoster();
Collection<RosterEntry> iter = roster.getEntries();
for (RosterEntry entry : iter) {
System.out.println(entry.getName() + " (" + entry.getUser() + ")");
}
接收消息 如果只能发送消息,这样的谈话将是很乏味的。接下来,我们将向“应用程序”中添加“听”的功能。我们将创建一个 MessageListener 类,它与 MessageSender 及其相似。事实上,要创建它,可以首先将 MessageSender.java 文件另存为 MessageListener.java。然后使用查找和替换功能将所有 MessageSender 替换为 MessageListener。 然后,通过将 implements PacketListener 添加到类规范中,让该类实施 PacketListener 接口。该类要求我们做的只是实施方法 processPacket,如下所示: public void processPacket(Packet packet) {
Message message = (Message)packet;
System.out.println("Message (from: "+message.getFrom()+"): "+message.getBody());
}
将我们的类注册为使用该连接传递的消息的监听器后,无论何时通过 Google Talk 服务器将消息发送到我们的连接时都会调用该方法。通过在 MessageListener() 构造器的结尾添加以下行将我们的类注册为我们帐户的聊天消息的监听器: // to listen for incoming messages on this connection
PacketFilter filter = new MessageTypeFilter(Message.Type.chat);
connection.addPacketListener((PacketListener)this, filter);
注:我们需要将以下导入语句添加到我们的类;但是当我们添加了上述代码后,Oracle JDeveloper 基本上会为我们执行该操作。 import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.filter.MessageTypeFilter;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.packet.Packet;
现在我们就可以接收消息了。如果按照如下所示更改 MessageReceiver 类的 main 方法,我们将接收到自己的消息: public static void main(String[] args) throws XMPPException,
InterruptedException {
MessageListener messageListener = new MessageListener();
messageListener.sendMessage("youraccount@gmail.com",
"Hello You. This is my second message sent programmatically using Smack API and Google Talk.");
// listen for 3 seconds
Thread.sleep(3000);
messageListener.disconnect();
}
当然,较之与自己交谈,聆听朋友发送给来的聊天信息要好得多。如果目前没人在线,Google Talk 提供了一系列您可以与之交谈的“朋友”。这些所谓的聊天机器人是程序编好的 Google Talk 帐户,可以提供语言翻译。这些聊天机器人具有类似 en2zh@bot.talk.google.com 和 nl2en@bot.talk.google.com 之类的帐户,由源语言和目标语言的两字母缩写组成。(请参见 http://googletalk.blogspot.com/2007/12/merry-christmas-god-jul-and.html 查看 Google Talk 翻译聊天机器人的通告)。 我们只需将聊天消息发送给 Google 聊天机器人,该机器人将发送给我们一个返回消息 — 非常适合快速找到简单单词或短语的翻译。我们还可以再次更改 main 方法,让它用法语 1 到 10 计数(当然,对于此操作, 您 并不需要翻译聊天机器人): MessageListener messageListener = new MessageListener();
String[] englishCounting = new String [] {"one", "two", "three", "four", "five",
"six", "seven", "eight", "nine", "ten"};
for (int i=0;i<englishCounting.length; i++) {
messageListener.sendMessage("en2fr@bot.talk.google.com", englishCounting[i]);
// add the slight pause in order to increase the chances of
// receiving the replies in the right order
Thread.sleep(500);
}
messageListener.disconnect();
这次运行应用程序的结果用法语计数: ![]() 注:Google Talk 聊天机器人在其服务器上的许多不同线程上运行。这意味着只要发往聊天机器人的消息之间有些许的延迟,回复就可能会按不同的顺序返回,因为处理五个消息的线程的响应速度有可能会比处理两个消息的线程快。如果我们等待大约 500ms 就可以发送下一条消息,则可能按正常顺序收到回复。 第 2 部分:构建 Oracle ADF Faces 富客户端 现在我们已经拥有了既可以发送聊天消息又可以进行接收的基本 Java 应用程序,现在要做的是了解如何创建可以执行相同任务的 Web 应用程序。我们将使用 Oracle ADF Faces Rich 在 Oracle ADF 数据绑定层上创建用户界面以将 Web 层连接到提供内容和操作访问的数据服务。 我们将向 MessageSender 添加一点额外的功能:一个名为 roster 的属性,即“聊天好友”的集合,我们添加到 Google Talk 中的朋友。 首先,创建一个名为 TalkMate 的新类,这是一个具有两个属性的 bean:toName 和 displayName。 package otn.adf.googletalk;
public class TalkMate {
String toName;
String displayName;
public TalkMate() {
}
public void setToName(String toName) {
this.toName = toName;
}
public String getToName() {
return toName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public String getDisplayName() {
return displayName;
}
}
将以下专有属性添加到 MessageSender 类: private Collection<TalkMate> roster = new ArrayList();
并为该属性创建 accessor 方法(getter 和 setter。 然后,将以下代码行添加到 MessageSender() 构造器方法: Roster friendsRoster = connection.getRoster();
Collection<RosterEntry> rosterIter = friendsRoster.getEntries();
for (RosterEntry entry : rosterIter) {
TalkMate friend = new TalkMate();
friend.setDisplayName(entry.getName()!=null?entry.getName():entry.getUser());
friend.setToName( entry.getUser());
this.roster.add(friend);
} // rosterIter
// also add your own account to chat with yourself for testing purposes
TalkMate veryBestFriend = new TalkMate();
veryBestFriend.setDisplayName("Your Own Display Name");
veryBestFriend.setToName( "YOUR_OWN_GOOGLE_TALK_ACCOUNTNAME");
this.roster.add(veryBestFriend);
我们将创建一个 MessageReceiver bean,用于以友好的方式向可能的用户公开聊天聆听功能。首先创建以下 bean: package otn.adf.googletalk;
import java.util.Date;
public class ChatMessage {
private Date timestamp;
private String from;
private String body;
public ChatMessage() {
}
public ChatMessage(String from, String body) {
this.timestamp = new Date();
this.from = from;
this.body = body;
}
public void setTimestamp(Date timestamp) {
this.timestamp = timestamp;
}
public Date getTimestamp() {
return timestamp;
}
public void setFrom(String from) {
this.from = from;
}
public String getFrom() {
return from;
}
public void setBody(String body) {
this.body = body;
}
public String getBody() {
return body;
}
}
接下来,创建 MessageReceiver 类,如下所示: package otn.adf.googletalk;
import java.util.ArrayList;
import java.util.List;
public class MessageReceiver {
private List<ChatMessage> messages = new ArrayList();
public MessageReceiver() {
try {
MessageListener messageListener = new MessageListener();
messageListener.setReceiver(this);
} catch (Exception e) {
}
}
public void processMessage(String from, String body) {
ChatMessage message = new ChatMessage(from, body);
messages.add(0, message);
for (ChatMessageListener listener:messageListeners) {
listener.processChatMessage(message);
}
}
public void setMessages(List<ChatMessage> messages) {
this.messages = messages;
}
public List<ChatMessage> getMessages() {
return messages;
}
}
我们需要对 MessageListener 类进行些许更改以使它们协同工作。将专有属性 receiver 添加到 MessageListener 类,并为其创建 getter 和 setter: MessageReceiver receiver;
接下来,在方法 processPacket() 专添加以下行: if (this.receiver != null) {
receiver.processMessage(message.getFrom(), message.getBody());
}
这会使 MessageListener 调用 MessageReceiver 上的 processMessage,而 MessageReceiver 又会将新的 ChatMessage 添加到 MessageReceiver bean 的消息集合中。 要在 Oracle ADF Data Control Palette 中公开 MessageSender 和 MessageReceiver bean,我们需要将这两个 bean 公布为数据控件。我们首先关注 MessageSender:通过在 bean 节点上单击鼠标右键显示的菜单,创建并发布一个数据控件: ![]() 在 Oracle JDeveloper 完成该数据控件的创建后,我们将在 Data Control Palette 中发现 MessageSender,它可供任何 Oracle ADF 客户端使用: ![]() 按照与 MessageSender 类似的方式,通过鼠标右键菜单发布 MessageReceiver 的数据控件。 现在,在 Google Talk 应用程序中创建一个新项目,将其命名为 GoogleTalkWebClient。该项目将包含我们的 Web 应用程序、JavaServer Faces (JSF) 页面和相关的配置文件。 创建 Web 项目后,在导航器中选择 GoogleTalkWebClient 节点,转至 New Gallery 并选择创建一个 JSF 页面。 将该页面命名为 ChatClient.jspx(该名称仅用于举例,也可以叫的名称): ![]() 单击 OK,将创建页面。 在该页面中,我们希望有两个部分:一部分用于编写和发送聊天消息,一部分用于列出传入的消息。在本文中,我们不会浪费太多的时间来介绍如何同多个收信人进行聊天 — 我们会把这个留给读者来做(如果作者企图这样脱离主题,您一定会很气愤)。 在一个页面中创建两个部分的工作可以由 Oracle ADF 11g Faces Rich Panel Splitter 组件出色完成。将该组件从 Component Palette 中拖出并放到页面上。这将为我们提供一个具有两个部分的面板,用于发送和接收消息。将该面板分离器的宽度设置为 100%。 将 sendMessage() 拖到第一个 facet。将其作为 ADF 参数表单放下。 ![]() 在随后显示的对话框中设置有意义的提示: ![]() 将显示下一个对话框,允许我们设置 sendMessage() 操作的输入参数的默认值。单击 OK 接受默认设置。 Oracle JDeveloper 将在该页面中创建两个 inputText 元素和一个命令按钮,并创建一个 PageDefinition 文件,该文件具有一个 variables 迭代器,包含 sendMessage() 操作的两个变量、两个 attributeValues 元素和一个 operationBinding 元素。 ![]() 更改 messageBody 元素的维度 — 设置 rows = 15,columns = 60。 删除 message_to inputText 元素。将其替换为从中可以选择联系人的所有联系人列表。 将 displayName 属性拖到页面的 MessageSender 数据控件的 roster 集合中,在 Message inputText 前将其作为 ADF 单选放下。 ![]() 将显示 Edit List Binding 对话框。选择 variables iterator 作为 Base Data Source。选择 MessageSender.root.roster 作为 List Data Source。将 Data Value sendMessage_to 映射到 List Attribute toName 并选择 displayName 作为 Display Attribute。然后单击 OK。 ![]() 现在运行 ChatClient 页面,则可以将聊天消息发送给 Google Talk 朋友。 接下来,将 ChatClient 扩展为还可以接收发送给我们的聊天消息。将 MessageReceiver 数据控件上的 Messages 集合拖到 ChatClient 页面的 Panel Splitter 的第二个 facet。将该集合作为 ADF 只读表放下。 ![]() 在显示的 Edit Table Columns 对话框中,仅保留 from 和 body 列。 将 Messages 集合上的 Execute 操作拖到页面,在前面创建的 Messages 表下将其作为命令按钮放下。 将按钮上的文本由 Execute 更改为 Refresh。 运行该页面后,您可以使用页面左侧发送聊天消息,而在右侧您可以通过单击 Refresh 按钮,接收来自所有 IM 联系人的回复。 下图描述了该小型应用程序的体系结构: ![]() 该应用程序中的业务服务由 MessageSender 和 MessageReceiver 两个类构成,它们借助 Smack 库与 Google Talk 服务器进行通信。这两个类都已发布为 ADF 数据控件。JSF 页面已经绑定了这些数据控件:一个操作绑定(针对 MessageSender 数据控件上的 SendMessage 操作)和一个表绑定(针对 MessageReceiver 数据控件上的 Messages 集合)。ADF 集合具有大量与它们相关的操作,其中一个是 Execute,它将刷新集合;该操作与 Refresh 按钮绑定。 注意,如果我们在两个按钮上都将 partialSubmit 设置为 true 并将 Refresh 按钮的 ID 值添加到 Messages 表的 partialTriggers 属性中,就可以使页面操作更加顺畅一些: <af:commandButton actionListener="#{bindings.Execute.execute}"
text="Refresh"
disabled="#{!bindings.Execute.enabled}"
partialSubmit="true" id="refreshReceived"/>
<af:table value="#{bindings.messages.collectionModel}" var="row"
rows="#{bindings.messages.rangeSize}"
emptyText="#{bindings.messages.viewable ? 'No rows yet.' : 'Access Denied.'}"
fetchSize="#{bindings.messages.rangeSize}"
partialTriggers="refreshReceived">
我已给自己发送了一条消息,并收到了试图吸引我注意的大儿子的大量消息: ![]() 随意对该页面进行进一步的修饰,可以立即开始,但是可以进行一些整理。例如,将显示聊天消息正文的 Body 列的 noWrap 属性设置为 false 将显示整个消息正文,根据需要可能显示多行。使用 PanelBox 容器是将某些内容框架和标题添加到页面中的各种组件的简单方式。 第 3 部分:使用 Oracle ADF 活动数据服务功能“激活”Web 客户端 现在,我们已经拥有了通过 Google Talk 进行交流的 Web 客户端。发送消息、获取消息 … 您还希望获得哪些功能?是的,不太理想的一件事是,我们需要单击 Refresh 按钮才能查看新接收的消息。我们希望聊天客户端自动报告获取的消息。这就是我们要使用 Oracle ADF 活动数据服务 (ADS) 添加到 Web 应用程序的功能。 Oracle 融合技术系列包括 ADS,它允许您使用 Oracle ADF 模型层或管理 bean 将特定的 Oracle ADF Faces 组件绑定到活动的数据源。组件包括表、树和所有类型的图形和图表,它们都支持 ADS,这意味着当底层数据源发生更改时,它们可以接近实时的速度进行更新。 要使用 ADS,您需要具有一个在数据变化时发布事件的数据存储,还需要创建响应这些事件的业务服务以及相关的数据控件来表示这些服务。利用我们组件的 ADS 功能的另一种方法是,使用管理 bean 作为值属性并让该 bean 实施 ActiveDataModel 接口。 这也是我们接下来要做的:基于一个新的管理 bean 创建 Messages 表。该新的管理 bean 实施 ActiveDataModel 并将其自身注册到新的 ChatMessage 事件的 MessageReceiver 类中。 首先,对业务服务进行一些修改。 创建接口 ChatMessageListener: package otn.adf.googletalk;
public interface ChatMessageListener {
public void processChatMessage(ChatMessage message);
}
然后将一些更改应用到 MessageReceiver 类。 插入以下行,这些行将添加一个专用的 ChatMessageListeners 列表和一个允许外部人员注册监听器的方法: private List<ChatMessageListener> messageListeners = new ArrayList();
public void registerChatMessageListener(ChatMessageListener listener) {
messageListeners.add(listener);
}
将这些行添加到 processMessage 方法: for (ChatMessageListener listener:messageListeners) {
listener.processChatMessage(message);
}
该操作负责无论任何时候收到一个新的 ChatMessage 时都调用每个注册的监听器。 ![]() 现在,在 GoogleTalkWebClient 项目中创建一个新类:GoogleTalkActiveListener。该类实施 ChatMessageListener 接口并在 MessageReceiver 中注册。它还对 ADF CollectionModel 进行扩展,以使其充当表组件的数据源。它需要实施 getRowData()、getRowCount()、getRowIndex()、getRowKey()、setRowKey() 等方法。 但是,真正有意思的部分是,该类还实施 ActiveDataModel 接口。这向表组件表明它可以在该类中注册为 ActiveDataUpdateEvents 的 ActiveDataListener。无论任何时候当我们的 GoogleTalkActiveListener 类收到一个新消息时,它都会向所有监听器(本例中只有表)发送一个新的 ActiveDataUpdateEvent。该表使用服务器到客户端的推送来刷新浏览器中显示的消息列表。 我们要执行的步骤如下所示: - 创建类 GoogleTalkActiveListener。
下面列出了一些关键的部分;项目文件下载的 /Part3/ 目录中包含完整的清单。 在构造器中,我们得到了绑定到当前页面的 MessageReceiver 实例。然后,将该新的 GoogleTalkActiveListener 实例作为 ChatMessageListener 注册到 MessageReceiver 中。 public GoogleTalkActiveListener() {
DCBindingContainer bc =
(DCBindingContainer)FacesContext.getCurrentInstance().getApplication().evaluateExpressionGet
(FacesContext.getCurrentInstance(),
"#{bindings}",
Object.class);
MessageReceiver messageReceiver =
(MessageReceiver)bc.findDataControl("MessageReceiver").getDataProvider();
messageReceiver.registerChatMessageListener(this);
…
无论任何时候收到 GoogleTalkChatMessage 时,都会通过 MessageReceiver 调用 processChatMessage()。而 processChatMessage() 又会为新消息创建一个包含单个 ActiveDateEntry 的 ActiveDataUpdateEvent。该事件将发往所有注册的 ActiveDataListeners: public synchronized void processChatMessage(ChatMessage message) {
Map values = new HashMap();
values.put("body", message.getBody());
values.put("from", message.getFrom());
ActiveDataEntry newEntry = new ChatActiveInsertDataEntry(values, "" + ++rowCount);
List<ActiveDataEntry> updateList = new ArrayList();
updateList.add(newEntry);
ActiveDataUpdateEvent event =
new ChatActiveDataUpdateEvent(this, rowCount, updateList);
for (ActiveDataListener listener : adsListeners) {
try {
listener.dataChanged(event);
} catch (Throwable e) {
}
} //for
}
- 配置管理的 bean googleTalkActiveListener。
<managed-bean>
<managed-bean-name>googleTalkActiveListener</managed-bean-name>
<managed-bean-class>googletalkwebclient.GoogleTalkActiveListener</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
- 更改 af:table 组件中的值属性,让其引用管理 bean 而不是表绑定。
<af:table value="#{googleTalkActiveListener}" var="message">
- 将 Refresh 按钮从 ChatClient 页面中删除。不仅是刷新操作(表也不再使用表绑定),由于有了 ADS 框架,我们也不再需要刷新选项。
下面我们来看运行中的 ADS:我已经点击了页面左侧的聊天消息。我发送消息不到一秒钟的时间,Google Talk 客户端 和 屏幕右侧支持 ADS 的消息表就都收到了该消息。由于有了服务器到客户端的推送,无需用户参与,聊天消息就显示在浏览器中。 ![]() 第 4 部分:利用拖放功能将数据记录拖放到聊天会话中 在这最后一部分,我们将了解如何将到现在为止创建的聊天功能集成到应用程序中的一个面向数据的实际页面中。我们将了解如何利用 Oracle ADF 11g Faces Rich 中提供的拖放功能允许用户将页面中显示的记录拖放到聊天组件中,从而将记录的要点添加到可以发给同事或朋友的聊天消息。 实现这一十分高级的功能的步骤相当的简单,只是在第 3 步需要编写一点代码: - 提供要显示的数据。
- 构建页面布局,包括数据表。
- 添加拖放功能。
提供要显示的数据 首先,为该应用程序提供一些数据。为了省掉访问数据库的麻烦,我们使用一些 bean 来提供某些数据。您会发现这些类的源以及本教程的资源。 LibraryManager 类扩展 ArrayList。它创建大量 Book Bean 实例,并使它们可供潜在的客户使用。将 LibraryManager 类配置为管理 bean: <managed-bean>
<managed-bean-name>libraryManager</managed-bean-name>
<managed-bean-class>otn.adf.googletalk.model.LibraryManager</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
构建页面布局(包括数据表) ChatClient.jspx 页面将进行一些重新修饰。步骤如下: - 将 panelSplitter 组件添加到 af:form 节点。将该 panelSplitter 的方向设置为水平。将其 SplitterPosition 设置为 400,将其 Inline Style 设置为 width:100%,height:800px。
- 将现有的 panelSplitter 拖放到新添加的 panelSplitter 的第一个 facet。将其方向设置为垂直,将其 SplitterPosition 设置为 320。
- 如果尚未执行此操作,将 Messages 表中 Body 列的 noWrap 属性设置为 false,以允许多行显示收到的消息。
- 将一个面板框组件从 Component Palette 拖放到新 PanelSplitter 中的第二个 facet。将文本设置为 Books。
- 将一个表组件从 Component Palette 拖放到新 PanelSplitter 中的第二个 facet。将该表绑定到 #{libraryManager} bean。将元素类型设置为类 otn.adf.googletalk.model.Book,这将帮助 Oracle JDeveloper 填充列的列表。按照显示的顺序重新排列这些列;删除 keywords 列。将 Thumbnail 列的组件类型更改为 af:image。单击 OK 将表组件添加到页面上。
接下来,将 Thumbnail af:image 组件的 Inline Style 设置为 width:60px。 ![]() 该页面的结构窗口现在应类似如下所示: ![]() 此时,您可以运行该页面,您将看到一个表,在页面左侧的集成聊天功能的右侧提供一些书的详细信息。但是,拖放集成尚未提供。 添加拖放功能 要向 Oracle ADF 11g Faces Rich 基础架构通知表中的记录是可拖放的(拖到源),我们需要向该表中添加一个 Collection Drag Source 元素: ![]() 在该元素中指定 modelName 属性: <af:collectionDragSource actions="COPY"
modelName="libraryModel"/>
同样地,我们需要指定该拖放操作的拖放目标。我们的拖放目标是用于输入聊天消息的 Message inputText 元素。我们需要将 DropTarget 元素添加到该 inputText 中: ![]() 我们将指定 af:dataFlavor 接受类 org.apache.myfaces.trinidad.model.RowKeySet 的实例,该类是表中记录的拖放操作的数据载荷,并将 discriminant 属性设置为 libraryModel。注:要使 inputText 元素在部分页面呈现 (PPR) 中可刷新,我们需要为其指定一个 ID — 任何一个唯一的 ID 值都可以。 由于我们需要对消息文本进行一些处理,因此我们将使用一个管理 bean 属性来关联 inputText 的值而非 pageDefinition 的变量的值;将值属性设置为 #{googleTalkActiveListener.messageBody}。最后,我们需要指定如何处理拖放事件。为此,将 dropTarget 元素上的 dropListener 属性设置为 #{googleTalkActiveListener.handleDrop}。 <af:inputText id="msgbody"
value="#{googleTalkActiveListener.messageBody}"
label="Message" columns="60"
rows="10">
<f:validator binding="#{bindings.message.validator}"/>
<af:dropTarget actions="COPY"
dropListener="#{googleTalkActiveListener.handleDrop}">
<af:dataFlavor flavorClass="org.apache.myfaces.trinidad.model.RowKeySet"
discriminant="libraryModel"/>
</af:dropTarget>
</af:inputText>
我们现在已经将对两个尚不存在的元素的引用添加到 googleTalkActiveListener bean。因此,让拖放功能起作用的最后一步在 GoogleTalkActiveListener 类中进行。首先,添加属性 messageBody 和 messageAddition(都是字符串类型)以及 handlingDrop(布尔类型)。生成属性 messageBody 的 accessor。 无论任何时候当 msgBody 元素检测到一个 drop 事件时,handleDrop 方法都会受到调用。它接收类 DropEvent 的实例并返回一个 DnDAction 实例。在本例中,处理 drop 事件意味着找出哪本书被放到聊天消息区域并将此书的描述添加到当前的聊天消息中。 public DnDAction handleDrop(DropEvent dropEvent) {
Transferable dropTransferable = dropEvent.getTransferable();
DataFlavor<RowKeySet> rowKeySetFlavor =
DataFlavor.getDataFlavor(RowKeySet.class);
RowKeySet tableDrop = dropTransferable.getData(rowKeySetFlavor);
if (tableDrop != null) {
// get the data for the dropped rows
CollectionModel dragModel =
dropTransferable.getData(CollectionModel.class);
StringBuilder rowOutput = new StringBuilder();
if (dragModel != null) {
for (Object currKey : tableDrop) {
dragModel.setRowKey(currKey);
Object rowValue = dragModel.getRowData();
rowOutput.append(rowValue);
}
}
messageAddition= rowOutput.toString();
handlingDrop=true;
return DnDAction.COPY;
} else {
return DnDAction.NONE;
}
}
通过 drop 事件,我们可以获取 Transferable 对象。我们为希望在该特殊的 drop 事件中出现的类构建了一个 DataFlavor 实例。通过 Transferable 对象,我们可以获取 RowKeySet,它包含放到该事件中的表集合中的 Book 记录的 rowkey。在 for 循环中,我们将提取每个 rowValue(Book 实例)并将它们(它们的 toString() 方法的结果)附加到 StringBuilder。在本例中,我们已经在 Book 类中实施了 toString() 方法,如下所示: @Override
public String toString() {
return this.getTitle()+" by "+this.getAuthor()+", published in "+this.getYear()+" by "+this.getPublisher();
}
然后,将所有放下的记录的结果存储在 messageAddition 属性中,将 handlingDrop 属性设置为 true。 在 setMessageBody() 方法(在 applyRequestValues 阶段调用)中,完成 handleDrop 方法后,我们将从客户端发来的消息正文的当前值与从放下的记录提取的其他内容相结合: public void setMessageBody(String messageBody) {
if (handlingDrop) {
handlingDrop = false;>
this.messageBody= messageBody +" "+ messageAddition;
} else {
this.messageBody = messageBody;
}
}
当您现在运行 ChatClient 页面时,您可以拖动任何书籍记录并将它放到 Message 区域(不能放到任何别的地方)。 ![]() 当您放下记录时,书的详细信息将添加到消息中,好像您自己输入了这些信息一样: ![]() 当然,放下一本或多本书籍后,您可以再次发送聊天消息,就好像您自己输入了书的详细信息一样。 ![]() ![]() 恭喜,您已经构建了一个“活动的”富客户端应用程序! Lucas Jellema 是 AMIS(位于荷兰 Nieuwegein)的技术经理,同时还是 Oracle ACE 总监(Oracle 融合中间件)。Lucas 发表了多篇网络日志,撰写了多本书籍,并且经常在国际会议和研讨会上发表演讲。他主要从事 Oracle SOA 套件和 ADF 技术方面的教学和咨询。 |