在Web应用开发中,我们经常会遇到这种场景:需要在页面的某一部分显示一个需要独立刷新的内容,这一部分内容的更新不影响整个页面其他部分的内容。这个需求显然我们可以用HTML的iframe来完成。
然而在这里,我要介绍另一种实现方式:AJAX Updater!(下文简称Updater)
与iframe相比,Updater至少有如下好处:
-
Updater的刷新不会引起浏览器显式的刷新动作,使局部内容的更新在不经意间完成。
-
Updater的内容与整个页面融合成同一棵JSF组件树。
-
Updater的内容与整个页面的其他部分可以当作同一个页面,可以方便的执行JavaScript。
-
Updater既可以独立刷新,又可以作为整个页面的一部分在同一次服务端请求/响应内完成客户/服务器的交互,而不像iframe要发起多次服务端请求/响应,影响速度。
一个最简单的Updater的例子是啥样?写一个<a:updater>的Tag,给它一些属性,足够了!若你想稍稍复杂点,可以再写一个按钮,按一下这个按钮,可以手动刷新这个Updater。
index.jsp:
-----------
<%@ page pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %>
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
<%@ taglib prefix="a" uri="http://www.apusic.com/jsf/ajax" %>
<%@ taglib prefix="w" uri="http://www.apusic.com/jsf/widget" %>
<f:view renderKitId="AJAX">
<w:head>
<link rel="stylesheet" type="text/css" href="/articles/updater/html_single/updater.css"/>
</w:head>
<w:page title="AjaxUpdater Demo">
<a:updater styleClass="myUpdater" jsvar="updater" renderId="global" url="/simple.jsp"/>
<w:button value="更新" onclick="updater.update(false);"/>
</w:page>
</f:view>
看看,这个例子足够简单,但却完整的演示了一个Updater的基本用法,下面是运行结果:
这个例子中引用的外部文件simple.jsp可以是任意的文件,本例中的文件显示当前的时间:
simple.jsp:
-----------
<%@ page pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %>
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
<f:view renderKitId="AJAX">
来自于外部文件:<br><br>
<h:outputText style="font:normal 18px arial;" value="#{UpdaterBean.time}"></h:outputText>
</f:view>
UpdaterBean是一个ManagedBean,time属性简单的返回当前的时间:
UpdaterBean.java:
-----------------
@ManagedBean(scope=ManagedBeanScope.SESSION)
public class UpdaterBean {
public String getTime() {
return new Date().toString();
}
... ...
}
在这个例子中,我们采用binding的方式,把Updater绑定到ManagedBean的一个类型为AjaxUpdater的属性,这样,我们可以通过服务器端编程的方式,对Updater有更多的控制。
看代码:
index2.jsp
-----------
<%@ page pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %>
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
<%@ taglib prefix="a" uri="http://www.apusic.com/jsf/ajax" %>
<%@ taglib prefix="w" uri="http://www.apusic.com/jsf/widget" %>
<f:view renderKitId="AJAX">
<w:head>
<w:stylesheet src="/updater.css"/>
</w:head>
<w:page title="AjaxUpdater Demo">
<h:form>
<a:updater styleClass="myUpdater" jsvar="updater" renderId="global" binding="#{UpdaterBean.contentUpdater}">
内部内容:<br><br>
<h:outputText value="#{UpdaterBean.time}"></h:outputText>
</a:updater>
<h:selectBooleanCheckbox id="loadSel" value="#{UpdaterBean.loadUrl}"/>
<h:outputLabel value="装载外部文件" for="loadSel"/>
<w:button value="开始自动更新(间隔2秒)" onclick="return update(this);"/>
<w:button value="手动更新(服务器端编程)" action="#{UpdaterBean.updateAction}"/>
<w:button value="手动更新(客户端编程)" onclick="updater.update(false);return false;"/>
<a:timer jsvar="atimer" sendForm="true" period="2" start="false" action="#{UpdaterBean.updateAction}"/>
</h:form>
<script type="text/javascript">
function update(btn) {
if (atimer.scheduled && !atimer.cancelled) {
atimer.cancel();
btn.setText("开始自动更新(间隔2秒)");
} else {
atimer.schedule();
btn.setText("停止自动更新");
}
return false;
}
</script>
</w:page>
</f:view>
这个例子指出了updater的内容既可以是直接写在<updater>标签内部的,也可以是通过“url”属性或通过服务器端编程的方式指定的外部文件,在这个例子中演示了<updater>标签内部的内容,同时也演示了内容来自于外部文件的情况,不过,外部文件内容我们没有像上一个例子中在JSP文件中给出updater的url属性,而是完全通过编程的方式在ManagedBean中指定。内部和外部内容的切换,也通过编程的方式来控制,通过选择或不选择复选框“装载外部文件”,我们可以选择显示内部内容还是外部文件的内容。
为了定时刷新updater的内容,本例中我们采用了Timer组件,Timer组件提供定时器的功能。这个定时器我们设定它每隔两秒钟动作一次,并且默认不自动启动,而是通过我们按一个"开始自动更新(间隔2秒)"按钮,手动启动。
另外,我们提供了两种手动更新updater的方式,一种是通过服务器端编程的方式来实现的更新,一种是通过客户端JavaScript编程的方式来实现的更新。注意,客户端手动更新的情况下,复选框“装载外部文件”不起作用,因为客户端JavaScript对象OM.ajax.Updater的update(once)方法向服务器端发送更新的AJAX请求时并不包含外部form中的元素作为参数,对具体细节感兴趣的读者请看OperaMasks源码的ajax.js文件中关于OM.ajax.Updater的定义。
关于Updater的服务器端控制,全部在ManagedBean UpdaterBean中完成,下面是UpdaterBean.java的代码:
package demo;
import java.util.Date;
import javax.faces.context.FacesContext;
import org.operamasks.faces.annotation.ManagedBean;
import org.operamasks.faces.annotation.ManagedBeanScope;
import org.operamasks.faces.component.ajax.AjaxUpdater;
@ManagedBean(scope=ManagedBeanScope.SESSION)
public class UpdaterBean {
public String getTime() {
return new Date().toString();
}
private AjaxUpdater contentUpdater;
public AjaxUpdater getContentUpdater() {
return this.contentUpdater;
}
public void setContentUpdater(AjaxUpdater contentUpdater) {
this.contentUpdater = contentUpdater;
}
public void updateAction() {
FacesContext context = FacesContext.getCurrentInstance();
if (context != null) {
if (isLoadUrl()) {
loadContent("/simple.jsp");
} else {
contentUpdater.unload();
}
}
}
private void loadContent(String uri) {
contentUpdater.unload();
String viewId = contentUpdater.getSubviewId();
if (viewId == null || !viewId.equals(uri)) {
contentUpdater.load(uri);
}
}
private boolean loadUrl = true;
public boolean isLoadUrl() {
return loadUrl;
}
public void setLoadUrl(boolean load) {
this.loadUrl = load;
}
}
<updater>标签的众多属性中,renderId属性是有必要单独说一说的。这个属性有类别的意思,也就是说不同的Updater的renderId可以相同,renderId相同的那些Updater被认为是一类的。这个属性如果不提供,系统会默认以Updater的clientId为它的renderId。
在用户通过JavaScript对某个Updater进行客户端操纵的时候,或者在提交Updater内的一个Form表单时,又或是Updater内的一个Timer组件动作时,系统会自动的把这个Updater的renderId作为请求参数,发到服务器端,而在服务器端OperaMasks引擎对Updater进行处理的时候,只有renderId与请求的参数中renderId的值一样的那些Updater,才会得到处理。在效果上,等于你更新某一个Updater,所有与这个Updater有着相同renderId的Updater,都会得到更新。
比如在<updater>标签被处理的时候,如果当前请求/响应是AJAX请求/响应,并且请求参数中renderId的值不为空,那么,对于<updater>的url属性的处理是受到renderId属性的影响的。只有renderId属性值等于请求参数中的renderId的值的那些Updater,才会按照它们的url属性值重新装载。而renderId属性值不匹配的Updater将被忽略。
而在引擎渲染页面的时候,renderId同样起着很重要的作用。同样,如果当前请求/响应是AJAX请求/响应,并且请求参数中renderId的值不为空,那么如果当前页面中存在Updater,并且这个Updater的renderId等于请求参数中renderId的值,那么只有这样的Updater才得到AJAX渲染,Updater之外的页面其他部分以及其他renderId不匹配的Updater并不渲染,这体现了Updater的渲染独立性。
Updater中显示的内容不但可被独立渲染,而且可以独立导航,就好像iframe一样。看下面的例子:
partialNav.jsp
--------------
<%@ page pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %>
<%@ taglib prefix="f" uri="http://java.sun.com/jsf/core" %>
<%@ taglib prefix="h" uri="http://java.sun.com/jsf/html" %>
<%@ taglib prefix="a" uri="http://www.apusic.com/jsf/ajax" %>
<%@ taglib prefix="w" uri="http://www.apusic.com/jsf/widget" %>
<f:view renderKitId="AJAX">
<w:head>
<w:stylesheet src="/updater.css"/>
</w:head>
<w:page title="AjaxUpdater Demo">
<a:updater style="border:1px solid #800000;width:300px;height:215px;overflow:auto;" url="/greeting.jsp">
</a:updater>
<p style="font:normal 14px arial">Updater外部的任意内容。。。
</w:page>
</f:view>
在这个例子中Updater中包含的内容其实就是OperaMasks网站提供的示例helloduke,运行这个页面,你可以体会Updater的独立导航的含义。
Updater包含的内容greeting.jsp页面很简单,代码如下:
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<f:view>
<h:form>
<h2>Hello, my name is Duke. What is yours?</h2>
<h:graphicImage url="duke.gif"></h:graphicImage>
<h:outputText value="#{userBean.result}" />
<br>
<h:inputText value="#{userBean.name}"></h:inputText>
<h:commandButton value="sayHello" action="#{userBean.sayHello}" />
</h:form>
</f:view>
greeting.jsp中用到的ManagedBean userBean代码如下:
package helloduke;
import org.operamasks.faces.annotation.ManagedBean;
import org.operamasks.faces.annotation.ManagedBeanScope;
@ManagedBean(name = "userBean", scope = ManagedBeanScope.SESSION)
public class UserBean {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getResult() {
if (this.name == null || "".equals(name.trim()))
return "Please input your name.";
else
return "Hello " + name;
}
public Object sayHello() {
if ("duke".equalsIgnoreCase(name))
return "/sameName.jsp";
return null;
}
}
导航到的页面sameName.jsp的代码如下:
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<f:view>
<h2>Hi, we have the same name "Duke"!</h2>
<h:form>
<h:graphicImage url="duke.gif"></h:graphicImage>
<h:commandLink value="return" action="greeting.jsp"/>
<%-- <a href="/articles/updater/html_single/greeting.jsp">return</a> --%>
</h:form>
</f:view>
下面是运行结果:
运行这个例子,你会发现,导航被限制在Updater内部,对外部没有任何影响。
不过有一点要注意的是,在sameName.jsp中,我们没有用注释<%-- --%>内的语句,而是用的JSF方式的导航,这是因为Updater目前对注释内那种传统的链接方式的导航方法,支持得还不好,期待以后的版本会有所改进。
Updater与周围环境的关系是既分离又融合,从行为上看,它是分离的,可以独立渲染,独立导航;从程序控制方面看,它又是融合的,Updater与外围的页面构成同一棵JSF组件树、同一个页面,因此为你控制它与外界的交互带来方便。
本文浅显的讲述了Updater的一些基本使用方法,相信Updater也在不断发展和改进中,更加深入的理解和更好的使用,甚至于更好的改进,期待感兴趣的你加入OperaMasks开源社区去进一步探索。
本文的例子updaterDemo已打成标准war包,可以下载后直接放到应用服务器上运行。
<a:updater styleClass="myUpdater" jsvar="updater" renderId="global" url="/simple.jsp"/>
<w:button value="更新" onclick="updater.update(false);"/> 
