|
目录
要学习本教程,您需要具备以下软件和资源。
| NetBeans IDE |
Web & Java EE 版本 6.1 或 6.0 |
| Java Development Kit (JDK) |
版本 6 或
版本 5 |
JavaServer Faces 组件/
Java EE 平台 |
1.1(带有 J2EE 1.4)
|
| GlassFish Application Server |
V2 |
| Travel 数据库 |
无版本要求 |
教程要求
开始学习本教程之前,您需要在计算机上安装以下软件:
- 带有 Web 和 Java EE 功能(包含在 "Web & Java EE" 下载以及“全部”下载中)的 NetBeans IDE 6.0 或 6.1。(下载)
- Visual Web 样例插件。此插件包括 Dynamic Faces 组件库 (0.2)。请按照安装 Visual Web 样例插件的“安装插件”一节中的说明执行操作。此外,还务必同时选择 Visual Web JSF 发行后样例和 Visual Web JSF 向后兼容性工具包的相应条目。
创建项目
- 从主菜单中,选择“文件”>“新建项目”。
- 在“新建项目”向导中,从“类别”列表中选择 "Web",从“项目”列表中选择“Web 应用程序”,然后单击“下一步”。
- 将项目命名为 AjaxChatRoom,然后为项目选择一个位置。
在“服务器”中选择 Sun Java System Application Server,在“Java EE 版本”中选择 "J2EE 1.4",并确保“将源代码级别设置为 1.4”和“设置为主项目”两个复选框处于选中状态。然后,单击“下一步”。
- 选择 "Visual Web JavaServer Faces",然后单击“完成”。
配置部署描述符
Dynamic Faces 技术要求 Web 应用程序的部署描述符中有一个初始化参数。
- 在“项目”窗口中,展开“Web 页”> "WEB-INF" 节点。
- 双击 "web.xml" 以在编辑器中将其打开。
在编辑器工具栏中,单击 "Servlet",然后单击“初始化参数”下面显示的“添加”按钮,如下图所示。
在“添加初始化参数”对话框中,在“参数名称”文本框中键入 javax.faces.LIFECYCLE_ID,在“参数值”文本框中键入 com.sun.faces.lifecycle.PARTIAL,然后单击“确定”。
编辑器中将显示新参数。
- 在编辑工具栏中单击 "XML",然后搜索
lifecycle 以查看原始 XML 中的更改。
- 按 Ctrl-S 组合键保存对 web.xml 文件所做的更改,然后关闭文件标签以关闭此文件。
在项目中添加 Dynamic Faces 组件库
在“项目”窗口中,右键单击“组件库”节点,然后选择“添加组件库”,如下所示。
在“添加组件库”对话框中,选择 "Dynamic Faces Components (0.2)",然后单击“添加组件库”以关闭此对话框。
“组件面板”窗口中将显示一个新的 "Dynamic Faces" 类别。
在应用程序 Bean 中添加代码
- 在“项目”窗口中,展开“源包”> "ajaxchatroom" 节点,然后双击 "ApplicationBean1.java" 以在源代码编辑器中打开该文件。
滚动至 ApplicationBean1.java 的底部,然后在最后一个右花括号前面添加以下代码。
| 代码样例 1:用于存储评论和生成匿名用户名的应用程序 Bean 代码 |
private int nextAnonIndex;
private final int MAX_ENTRIES = 100;
//list of String arrays, with each array of length 2
private List entryList = new LinkedList();
//array containing same contents as entryList
private String[][] entries;
public String[][] getEntries() {
synchronized(this.entryList) {
return this.entries;
}
}
public void addEntry(String username, String comment) {
if (comment == null || comment.length() < 1) {
return;
}
if (username == null) {
username = "anonymous";
}
synchronized(this.entryList) {
if (this.entryList.size() == MAX_ENTRIES) {
this.entryList.remove(0);
}
String[] entry = new String[]{username, comment};
this.entryList.add(entry);
this.entries =
(String[][])this.entryList.toArray(
new String[this.entryList.size()][entry.length]);
}
}
public synchronized String getNextAnonUsername() {
nextAnonIndex++;
return "anonymous" + nextAnonIndex;
} |
在以上代码中,您将每条评论及其关联的用户名存储为一个记录条目,即一个长度为 2 的 String[] 对象。条目将同时存储在 entryList 变量和 entries 变量中。当通过 addEntry 方法添加评论或者通过 getEntries 方法获取记录条目时,客户端代码必须获取对 this.entryList 的监视(即与之保持同步),这必然会影响到性能,但却保留了数据完整性。您限制了记录中的最大条目数,此数目由 MAX_ENTRIES 决定。
用户将能够通过 http://server-ip-address:8080/AjaxChatRoom?username=someuser 形式的 URL 访问应用程序。指定的用户名将随用户发送的任何评论一起显示。如果用户通过未指定用户名的 URL 访问应用程序,则会随用户评论显示一个匿名的用户名。在这种情况下,调用 getNextAnonUsername 方法将生成一个在应用程序中唯一的匿名用户名。
在源代码中单击鼠标右键,然后选择“修复导入”。
由于 List 类存在多个选项,因此将显示“修复导入”对话框。
- 单击“确定”以接受 java.util.List 作为 List 的全限定名称。
- 关闭该文件,在出现的对话框中,单击“保存”。
添加用于存储用户名的代码
- 在“项目”窗口中,双击“源包”> "ajaxchatroom" > "SessionBean1.java" 以在源代码编辑器中打开该文件。
滚动至 SessionBean1.java 的底部,然后在最后一个右花括号前面添加以下代码。
| 代码样例 2:用于存储用户名的会话 Bean 代码 |
private String username;
public synchronized String getUsername() {
return username;
}
public synchronized void setUsername(String username) {
this.username = username;
} |
您将在代码样例 5 中看到,当用户发送评论时,操作方法将从会话范围中检索用户名并随评论一起记录该用户名。因为与同一会话关联的多个线程可能访问 getter 和 setter 方法,所以会同步这些方法。
- 关闭该文件,在出现的对话框中,单击“保存”。
- 在“项目”窗口中,双击“源包”> "ajaxchatroom" > "Page1.java",然后打开该页的 Java 源代码。
在“导航”窗口中,双击 "prerender()" 以导航至该方法在源代码编辑器中的位置。
将下面以粗体显示的代码添加到 prerender 方法中:
| 代码样例 3:用于存储用户名的 Page1 代码 |
public void prerender() {
String username = (String)getExternalContext().getRequestParameterMap().get("username");
if (username != null) {
getSessionBean1().setUsername(username);
}
else if (getSessionBean1().getUsername() == null) {
getSessionBean1().setUsername(getApplicationBean1().getNextAnonUsername());
}
} |
如果在当前请求期间初次呈现该页,并且用户在 URL 中指定了用户名,则请求参数映射中将包含该用户名。在这种情况下,会将用户名存储在会话 Bean 中。如果请求参数映射中未包含用户名,则将存在以下两种可能性:初次呈现该页,但用户未在 URL 中指定用户名;或者,当前请求是 Ajax 请求。在前一种情况下,如果之前未在会话 Bean 中存储用户名,则将生成并存储一个匿名用户名。在后一种情况下,已在初次呈现该页时,在会话 Bean 中存储了一个用户名。
在 Page1.java 中添加 transcript 属性
滚动至 Page1.java 的底部,然后在最后一个右花括号前面添加以下代码。
代码样例 4:transcript 属性代码 |
public String getTranscript() {
String[][] entries =
getApplicationBean1().getEntries();
if (entries == null) {
return null;
}
StringBuffer sb = new StringBuffer();
for (int i = 0; i < entries.length; i++) {
String entryUsername = entries[i][0];
String comment = entries[i][1];
String color = "purple";
String username = getSessionBean1().getUsername();
if (username.equals(entryUsername)) {
color = "blue";
}
sb.append(
"<div><span style=\"font-weight:bold;color:");
sb.append(color);
sb.append("\">[");
sb.append(entryUsername);
sb.append("]</span> ");
sb.append(comment);
sb.append("</div>");
}
return sb.toString();
} |
此方法用于将记录内容作为一个 String 对象来提供,并且如果条目的用户名与 SessionBean1 中存储的用户名相同,则以蓝色显示该条目,否则以紫色显示。这相当于将用户自己的条目显示为蓝色,而将聊天室其他参与者的条目显示为紫色。
- (可选)单击鼠标右键,选择“格式化代码”,然后单击主工具栏中的“全部保存”按钮以保存所做的更改。
创建用户界面
- 返回至 Page1 的可视设计器。
- 将一个布局面板从组件面板的“布局”类别拖到可视设计器中。
在可视设计器中,选择该布局面板组件,然后将右下角的大小调整框向右下方拖动,直到该组件大约有 14 个方格宽、10 个方格高。
- 在“属性”窗口中,将布局面板组件的
id 属性更改为 transcriptPanel。
单击 style 属性的省略号 (...) 按钮,在 CSS 样式中附加以下规则,然后单击“确定”。
; overflow: auto; border: 2px solid black;
将 overflow 样式属性设置为 auto 可使聊天室记录在必要时显示滚动条。请注意,transcriptPanel 布局面板的 panelLayout 属性缺省情况下将被设置为“流式布局”。在下面的步骤 6 至步骤 10 中,我们将添加一个静态文本组件作为 transcriptPanel 布局面板的子组件,并将该静态文本的 text 属性设置为 Page1 的 transcript 属性。
- 将一个静态文本组件从组件面板的“基本”类别拖到 transcriptPanel 布局面板中。确保 transcriptPanel 的轮廓以蓝色突出显示,然后松开鼠标按钮,以使 transcriptPanel 组件包含静态文本组件。
在“属性”窗口中,将静态文本组件的 id 属性更改为 transcriptText,然后清除 escape 复选框,以将该属性设置为 false。
将 escape 属性设置为 false 可使浏览器对文本中嵌入的任何标记进行评估。这确保了记录条目以所需的格式显示,例如文本显示为蓝色或紫色。
- 在可视设计器中,右键单击 transcriptText 静态文本组件,然后选择“绑定到数据”。
- 在“绑定到数据”对话框中,单击“绑定到对象”标签。
选择 "Page1" > "transcript",然后单击“确定”。
- 将一个文本字段组件从组件面板的“基本”类别拖放到 transcriptPanel 组件的下方。
- 在“属性”窗口中,将文本字段组件的
id 属性更改为 comment。
- 将 comment 文本字段的
columns 属性更改为 60。
- 将一个按钮组件从组件面板的“基本”类别拖放到 comment 文本字段的右侧,键入
Send,然后按 Enter 键。
单击 style 属性的省略号 (...) 按钮,在 CSS 样式中附加以下规则,然后单击“确定”。
; width: 70px
在“属性”窗口中,将按钮组件的 id 属性更改为 send。
Page1 应该与下图所示类似。
在可视设计器中,双击 send 按钮组件。
IDE 将为 send 按钮创建一个操作方法,并在源代码编辑器中显示该方法。
将下面以粗体显示的代码添加到该方法中:
| 代码样例 5:按钮操作处理程序 |
public String send_action() {
String comment = (String)getComment().getText();
String username = getSessionBean1().getUsername();
getApplicationBean1().addEntry(username, comment);
return null;
} |
以上代码用于检索用户在 comment 文本字段中输入的任何文本,并将该文本提供给 addEntry 方法,从而将用户的评论添加到聊天室记录中。
配置用于发送评论和执行轮询的 Ajax 事务
使用 Dynamic Faces 组件库 (0.2) 中包括的 Ajax 事务组件,可以在设计时以可视方式配置 Ajax 功能,从而在可视设计器中通过不同的颜色编码边框来显示各种组件。您应至少指定当触发 Ajax 事务时将输入发送至服务器的组件,以及当客户端接收到 Ajax 响应时重新呈现的组件。在可视设计器中,将输入发送至服务器的组件以实线边框显示,重新呈现的组件以虚线边框显示。此外,您还必须编写一行 JavaScript 代码来触发 Ajax 事务。
在本节中,您将配置两个 Ajax 事务,一个用于发送评论,另一个用于轮询服务器。当用户单击 send 按钮或按 Enter 键时,将触发 commentTx Ajax 事务以做出响应。当触发 commentTx 时,comment 文本字段和 send 按钮会将它们的输入通过 Ajax 请求发送至服务器;当客户端接收到 Ajax 响应时,会重新呈现 transcriptText 静态文本。在装入页面时,将初次触发 pollTx Ajax 事务。随后,当客户端接收到与 pollTx Ajax 事务关联的 Ajax 响应时,会在短暂延迟后再次触发 pollTx。这样,您便可以不断地轮询服务器。当触发 pollTx 时,不会有任何组件通过 Ajax 请求发送其输入(因为您仅仅是获取记录数据,而不是修改记录数据);当客户端接收到 Ajax 响应时,会重新呈现 transcriptText 静态文本。这样,评论记录便可以在浏览器中不断更新。
- 返回至 Page1 的可视设计器。
在可视设计器工具栏中,单击“显示虚拟表单”按钮 。
Ajax 事务组件所提供的 Ajax 功能与虚拟表单为常规提交操作所提供的功能类似。单击“显示虚拟表单”按钮可同时显示虚拟表单以及为页面配置的 Ajax 事务。
展开组件面板中的 "Dynamic Faces" 类别,然后将一个 Ajax 事务组件从 "Dynamic Faces" 类别拖到可视设计器中。
在可视设计器的底部将出现一个 Ajax 事务图例,并显示与蓝色关联的 ajaxTransaction1 Ajax 事务。
在可视设计器中,同时选择 comment 文本字段和 send 按钮。然后,单击鼠标右键并选择 "Configure Ajax Transactions"。
将出现 "Configure Ajax Transactions" 对话框。在该对话框的顶部,将显示 "send" 和 "comment",用于指示您要为 send 按钮和 comment 文本字段配置 Ajax 事务。
- 在 "Configure Ajax Transactions" 对话框中,在 "Name" 字段内双击鼠标,键入
commentTx 以重命名 Ajax 事务,然后按 Enter 键。
将 "Send Input" 字段设置为 "Yes",然后单击“确定”关闭该对话框。
在可视设计器中,comment 文本字段和 send 按钮将以蓝色实线边框显示,用于指示当触发 commentTx Ajax 事务时,这些组件会将其输入通过 Ajax 请求发送至服务器。send 按钮必须将其输入发送至服务器,以便通知服务器已单击该按钮。在下面的添加 JavaScript 一节中,您将添加一行 JavaScript 代码,当用户单击 "Send" 按钮或按 Enter 键时,该行代码将触发 commentTx Ajax 事务以做出响应。
在设计器中选择 transcriptText 静态文本。单击鼠标右键,然后选择 "Configure Ajax Transactions"。
将出现 "Configure Ajax Transactions" 对话框。在该对话框的顶部,将显示 "transcriptText",用于指示您要为 transcriptText 静态文本配置 Ajax 事务。
- 在 "Configure Ajax Transactions" 对话框中,单击 "New" 配置新的 Ajax 事务。
- 选择红色作为与新的 Ajax 事务关联的颜色。
- 在新的 Ajax 事务的 "Name" 字段中双击鼠标,键入
pollTx,然后按 Enter 键。
将 commentTx 和 pollTx Ajax 事务的 "Re-Render" 字段设置为 "Yes",然后单击“确定”关闭该对话框。
transcriptText 静态文本将以蓝色虚线边框和红色虚线边框显示,用于指示当客户端接收到与 commentTx 或 pollTx Ajax 事务关联的 Ajax 响应时,将会重新呈现该组件。在下面的添加 JavaScript 一节中,您将添加通过触发 pollTx Ajax 事务来轮询服务器的 JavaScript 逻辑。
- 在“导航”窗口中,展开 "Page1" > "page1" > "html1" > "head1" 节点,然后选择 "commentTx" Ajax 事务。
在“属性”窗口中,将 commentTx Ajax 事务组件的 postReplace 属性设置为 customPostReplaceForCommentTx。
此步骤用于指定在接收到与 commentTx Ajax 事务关联的 Ajax 响应并重新呈现相应的组件(即 transcriptText 静态文本)后,客户端应调用 customPostReplaceForCommentTx JavaScript 函数。在下面的添加 JavaScript 一节中,您将看到此 JavaScript 函数。
- 在“导航”窗口中的 "Page1" > "page1" > "html1" > "head1" 节点下,选择 "pollTx" Ajax 事务。
在“属性”窗口中,将 pollTx Ajax 事务组件的 inputs 和 execute 属性均设置为 none。
此步骤用于显式配置 pollTx Ajax 事务,以便当触发 Ajax 事务时,将不会有任何组件通过 Ajax 请求发送其输入,或者在服务器上进行处理。
将 pollTx Ajax 事务的 postReplace 属性设置为 customPostReplaceForPollTx,将 replaceElement 属性设置为 customReplaceForPollTx。
此步骤用于指定当客户端接收到与 pollTx Ajax 事务关联的 Ajax 响应时,customReplaceForPollTx JavaScript 函数将实现定制的重新呈现逻辑。此外,还指定在重新呈现相应的组件(即 transcriptText 静态文本)后,客户端应调用 customPostReplaceForPollTx JavaScript 函数。在下面的添加 JavaScript 一节中,您将看到这些 JavaScript 函数。
设置主体组件和表单组件的 JavaScript 属性
- 在“导航”窗口中的 "Page1" > "page1" > "html1" 节点下,选择 "body1" 主体组件。
在“属性”窗口中,将 body1 主体组件的 onLoad 属性设置为 handleOnLoad(),将 onUnload 属性设置为 handleOnUnload()。
在下面的添加 JavaScript 一节中,您将看到这些 JavaScript 函数。
- 在“导航”窗口中的 "Page1" > "page1" > "html1" > "body1" 节点下,选择 "form1" 表单组件。
在“属性”窗口中,将 form1 表单组件的 onSubmit 属性设置为 return interceptFormSubmit()。
在下面的添加 JavaScript 一节中,您将看到 interceptFormSubmit JavaScript 函数。当用户单击 send 按钮或按 Enter 键时,此 JavaScript 函数将禁止执行常规的表单提交操作,但同时会触发 commentTx Ajax 事务。
添加 JavaScript
接下来,您将创建 ajaxchatroom.js JavaScript 文件。
在“项目”窗口中,展开“Web 页”节点,右键单击 "resources" 文件夹,然后选择“新建”>“其他”。
- 在“新建文件”向导中,从“类别”列表中选择“其他”,从“项目”列表中选择“空文件”,然后单击“下一步”。
- 将文件命名为
ajaxchatroom.js,然后单击“完成”。
将以下 JavaScript 代码添加到该文件中:
代码样例 6:axchatroom.js |
var pollDelay = 1000;
var continuePolling = false;
var mouseDownOnTranscript = false; //whether the user has performed a mousedown on the transcript (including any scrollbar)
//and not yet performed a mouseup
function customPostReplaceForCommentTx(element, markup) {
//scroll to the bottom of the transcript
var transcriptPanel = document.getElementById('form1:transcriptPanel');
transcriptPanel.scrollTop = transcriptPanel.scrollHeight;
//clear the text field
var commentTextField = document.getElementById('form1:comment');
commentTextField.value = '';
//place focus in the text field
commentTextField.focus();
}
function handleOnLoad() {
//handle mousedown on the transcript
var transcriptPanel = document.getElementById('form1:transcriptPanel');
transcriptPanel.onmousedown = handleMouseDown;
//handle mouseup anywhere on the page
document.onmouseup = handleMouseUp;
//turn autocomplete off for the text field
document.getElementById('form1:comment').setAttribute('autocomplete','off');
//start polling
continuePolling = true;
poll();
}
function handleOnUnload() {
//stop polling
continuePolling = false;
}
function poll() {
//fire the pollTx Ajax Transaction
DynaFaces.Tx.fire('pollTx');
}
function customReplaceForPollTx(element, markup) {
//provided that the user is not performing an operation such as selecting transcript text or scrolling the transcript,
//perform replacement (re-rendering) for this poll request,
//and scroll the transcript as appropriate after the replacement
if (!mouseDownOnTranscript) {
var transcriptPanel = document.getElementById('form1:transcriptPanel');
//scrollTop: distance between top of transcript and top of the portion currently visible
//scrollHeight: total height of transcript, including any portion not visible due to scrolling
//clientHeight: height of the visible portion of the transcript
//capture whether scrollbar exists before replacement.
//scrollbar exists if the scrollHeight exceeds the clientHeight
var scrollbarExistsBeforeReplacement = transcriptPanel.scrollHeight > transcriptPanel.clientHeight;
//capture whether the transcript is scrolled to the bottom before replacement
var scrolledToBottomBeforeReplacement = false;
if (scrollbarExistsBeforeReplacement) {
//transcript is scrolled to the bottom if the sum of scrollTop and clientHeight equals scrollHeight
if (transcriptPanel.scrollTop + transcriptPanel.clientHeight == transcriptPanel.scrollHeight) {
scrolledToBottomBeforeReplacement = true;
}
}
//capture the scrollTop before replacement
var scrollTopBeforeReplacement = transcriptPanel.scrollTop;
//invoke default replacement function to perform actual replacement of transcript content
DynaFaces.replace(element, markup);
//capture whether scrollbar exists after replacement
var scrollbarExistsAfterReplacement = transcriptPanel.scrollHeight > transcriptPanel.clientHeight;
//scroll to the bottom of the transcript if it was scrolled to the bottom before replacement
//or if the scrollbar did not exist before replacement and it now exists after replacement.
//otherwise, scroll the transcript to the same place it was before replacement
if (scrolledToBottomBeforeReplacement || (!scrollbarExistsBeforeReplacement && scrollbarExistsAfterReplacement)) {
transcriptPanel.scrollTop = transcriptPanel.scrollHeight; //scroll to the bottom of the transcript
}
else {
transcriptPanel.scrollTop = scrollTopBeforeReplacement; //scroll transcript to the place it was before replacement
}
}
}
function handleMouseDown() {
//if the mousedown occurs on the transcript's scrollbar,
//IE will not invoke the corresponding mouseup event handler when the mouse is released.
//in such a case, do not set mouseDownOnTranscript to true, since nothing will set it back to false.
//instead, just perform replacement in customReplaceForPollTx even though the user is scrolling the transcript,
//as this does not seem to cause a problem on IE anyway
if (document.all) {
var transcriptPanel = document.getElementById('form1:transcriptPanel');
var scrollBarExists = transcriptPanel.scrollHeight > transcriptPanel.clientHeight;
if (scrollBarExists) {
if (window.event.offsetX > transcriptPanel.clientWidth) {
//mousedown occurred on the scrollbar
return;
}
}
}
mouseDownOnTranscript = true;
}
function handleMouseUp() {
mouseDownOnTranscript = false;
}
function customPostReplaceForPollTx(element, markup) {
//send the next poll request
if (continuePolling) {
setTimeout(poll, pollDelay);
}
}
function interceptFormSubmit() {
//if the text field is not blank, fire commentTx
if (document.getElementById('form1:comment').value != '') {
DynaFaces.Tx.fire('commentTx');
}
//prevent conventional form submission
return false;
} |
由于您将 commentTx Ajax 事务的 postReplace 属性设置为 customPostReplaceForCommentTx,因此在接收到与 commentTx 关联的 Ajax 响应并重新呈现相应的组件(即 transcriptText 静态文本)后,客户端将调用 customPostReplaceForCommentTx 函数。Dynamic Faces 使用的 "replacement" 和 "re-rendering" 两词可互换,有时前者用得多一点,有时后者用得多一点。当用户通过单击 send 按钮或按 Enter 键来发送评论时,customPostReplaceForCommentTx 函数将滚动至记录的底部,清除 comment 文本字段,并将焦点置于 comment 文本字段内。
由于您将 page1 页面组件的 onLoad 属性设置为 handleOnLoad,因此客户端将在初次装入该页后调用 handleOnLoad 函数。此函数将 handleMouseDown 函数引用分配给 transcriptPanel.onmousedown 事件处理程序,将 handleMouseUp 函数引用分配给 document.onmouseup 事件处理程序。handleMouseDown 函数用于对 transcriptPanel 元素上的 mousedown 事件做出响应;而 handleMouseUp 函数用于对页面上任意位置的 mouseup 事件做出响应。这两个函数与 customReplaceForPollTx 函数一起使用,从而确保当用户在记录(包括任意滚动条)上按下鼠标,但尚未松开鼠标时,不会对轮询请求做出重新呈现响应。此时,如果用户正在选择某些记录文本(例如,为复制操作做准备),重新呈现记录将阻止该选择操作;同样,如果用户在 Firefox 上滚动记录,重新呈现记录将导致滚动条冻结。handleMouseUp 函数可以处理 document 对象(而不仅仅是 transcriptPanel 元素)上的 mouseup 事件,因为用户可能会在记录的滚动条上按下鼠标,将鼠标拖出记录区域,然后松开鼠标。在这种情况下,代码逻辑需要对 mouseup 事件做出响应,即使该事件并未发生在记录上。除了分配这些函数外,handleOnLoad 函数还对 comment 文本字段禁用浏览器的自动完成功能,并启动对服务器的轮询。
poll 函数用于触发 pollTx Ajax 事务,该事务可向服务器发送 Ajax 请求。对应的 Ajax 响应将触发 transcriptText 静态文本的重新呈现。
由于您将 pollTx Ajax 事务的 replaceElement 属性设置为 customReplaceForPollTx,因此当接收到与 pollTx 关联的 Ajax 响应时,客户端将调用 customReplaceForPollTx 函数,而不是缺省的替换函数(即 DynaFaces.replace)。通过提供 customReplaceForPollTx 定制替换函数,您可以基于用户是否在记录上执行某些操作(即选择记录文本或滚动记录)来有条件地调用缺省替换函数,以及在替换之前捕获滚动位置并在替换之后以编程方式相应地进行滚动。替换之前,您使用 transcriptPanel 元素的 scrollTop、scrollHeight 和 clientHeight 属性来确定滚动条是否存在,如果存在,还确定记录是否滚动至底部。然后,将记录内容的实际替换委托给缺省替换函数 (DynaFaces.replace)。替换之后,您将记录滚动至替换之前所在的位置,但在某些情况下例外。具体而言,如果在替换之前记录已滚动至底部,或者滚动条现在存在,但在替换之前不存在,则会始终将记录滚动至底部。在前一种情况下,如果不以编程方式将记录滚动至底部,则任何新的记录条目都不会出现在视图中。在后一种情况下,如果不以编程方式将记录滚动至底部,则记录会保留滚动至顶部,从而使用户必须手动将记录滚动至底部。
在 handleMouseDown 函数中,Internet Explorer 需要一些额外的逻辑。如果用户在记录的滚动条上按下鼠标,则当用户松开鼠标时,Internet Explorer 将不会调用分配给 document.onmouseup 的事件处理程序。因此,不能将 mouseDownOnTranscript 设置为 true,因为用户释放鼠标的操作不会调用 handleMouseUp;所以,mouseDownOnTranscript 将保留为 true。在这种情况下,customReplaceForPollTx 函数将首先重新呈现记录,直到一些后续的鼠标操作触发 handleMouseup 函数。一种更好的简便方法是从 handleMouseDown 函数返回,而不将 mouseDownOnTranscript 设置为 true。因此,customReplaceForPollTx 函数将在 Internet Explorer 上重新呈现记录,即使当用户正在滚动记录时也是如此。但是,这不会导致任何问题,因为在用户滚动记录时重新呈现记录只会使滚动条在 Firefox 上冻结,而不会在 Internet Explorer 上冻结。
由于您将 pollTx Ajax 事务的 postReplace 属性设置为 customPostReplaceForPollTx,因此在接收到与 pollTx 关联的 Ajax 响应并调用 customReplaceForPollTx 函数后,客户端将调用 customPostReplaceForPollTx 函数。如果 continuePolling 变量为 true(以便不卸载页面),customPostReplaceForPollTx 函数将在 pollDelay 变量指定的延迟后发送下一个轮询请求。
由于您将 form1 表单组件的 onSubmit 属性设置为 return interceptFormSubmit,因此当用户单击 send 按钮或按 Enter 键时,客户端将调用 interceptFormSubmit 函数。如果 comment 文本字段不为空,interceptFormSubmit 函数将触发 commentTx Ajax 事务(该事务发送 Ajax 请求)并返回 false,以禁止执行常规的表单提交操作。
- 展开组件面板中的“高级”类别,然后将一个脚本组件从“高级”类别拖到可视设计器中。
- 在“导航”窗口中的 "Page1" > "page1" > "html1" > "head1" 节点下,选择 "script1" 脚本组件。
在“属性”窗口中,单击 script1 脚本组件 url 属性的省略号 (...) 按钮。在出现的对话框中,选择 "resources" > "ajaxchatroom.js",以使 /resources/ajaxchatroom.js 显示在 "URL" 字段中,然后单击“确定”。
您已对 script1 脚本组件进行了配置,使其为您之前创建的 ajaxchatroom.js JavaScript 文件发送 <script> 标记。
部署项目
此 Web 应用程序已在 Firefox 和 Internet Explorer 7 上进行了测试。
在“项目”窗口中,右键单击项目节点,然后选择“运行”以生成、部署并启动 Web 应用程序。
您的浏览器将导航至 http://localhost:8080/AjaxChatRoom/。
在 comment 文本字段中输入一条评论,然后按 Enter 键或单击 send 按钮。
此条目将以蓝色显示在记录中,并且您的用户名为 anonymous1。
在浏览器的地址栏中,将 URL 更改为 http://localhost:8080/AjaxChatRoom/?username=jack。
此时,上一个条目将显示为紫色,因为您现在被识别为 jack,而不是 anonymous1。
- 在 comment 文本字段中输入其他评论,然后按 Enter 键或单击 send 按钮。新条目将以蓝色显示在记录中,并且您的用户名为
jack。
- 通过打开终端(或命令)窗口并执行以下某项操作来确定您的 IP 地址:
- 对于 Solaris/Linux/MAC。键入
ifconfig -a,然后按 Enter 键。
- 对于 Windows。键入
ipconfig,然后按 Enter 键。
打开第二个浏览器窗口或标签,然后键入 http://your-ip-address:8080/AjaxChatRoom?username=jill。
由 anonymous1 和 jack 发送的条目将显示为紫色。
在 comment 文本字段中输入其他评论,然后按 Enter 键或单击 send 按钮。
新条目将以蓝色显示在记录中,并且您的用户名为 jill。
切换回原始浏览器窗口或标签。
由 jill 发送的条目将显示为紫色。
- 继续
jack 与 jill 之间的对话,直到记录显示滚动条。
向上滚动至记录内容的开头,并保持按下鼠标,如同要重新读取对话的开头。等待几秒钟,然后松开鼠标。
此行为会因浏览器而稍有不同。在 Firefox 上,当鼠标处于按下状态时,记录不会对轮询请求做出重新呈现响应。在 Internet Explorer 上,当鼠标处于按下状态时,记录会继续重新呈现。这是因为在 Internet Explorer 上,handleMouseDown 函数会首先将 mouseDownOnTranscript 变量设置为 true。按照本节所述单独测试应用程序时,不会检测出行为上的这一细小差异。要观察行为上的不同之处,必须邀请另一个人与您聊天,在执行此步骤的同时发送评论。
选择记录中的一些文本,但在执行此操作时不松开鼠标。使用快捷键将该文本复制到剪贴板。然后,松开鼠标;请注意,在执行此操作后将取消选择文本。
按下鼠标时,记录将首先对轮询请求做出重新呈现响应。但是,松开鼠标后,后续轮询请求将导致重新呈现记录。重新呈现记录会使所做的任何选择丢失。通过以下方法,可以增强应用程序:在 customReplaceForPollTx 函数中添加逻辑,以捕获替换之前有关选择的信息,并在替换后重新建立选择。在这种情况下,当用户选择了一些文本时,您便可以通过取消选择这些文本来对在文档中任意位置的 mousedown 操作做出响应。为此,可以添加一个用于取消选择记录中所有文本的函数,并为 document.onmousedown 事件处理程序分配对该函数的引用。
另请参见
|
|