WDK下实现Ajax
最近想在WDK中实现Ajax的效果,在Google上查了一下可用的方案基本上就是利用JQuery或者prototype这些第三方的脚本库来实现,但是这种方案的主要弊端在于我必需自己来组装需要发送给服务器的参数以及需要在服务器端写单独的Servlet来处理请求,这无疑增加了大量的手工代码。
为了能够以简单的方式来处理这个问题,我跟踪了一下WDK自带的Tree控件的Ajax的实现。通过查看代码发现它是在tree.js中自己组装了一个Html,然后写到动态创建的iframe中,再调用iframe.submit将组装的html提交给了treeExpandNode.jsp页中,再在这个页里得到Tree控件后调用Tree控件的相应方法触发了组件的服务器端的事件,然后再通过调用这个iframe.parent的回调方法再进行处理(即采用了iframe方式的Ajax实现),其实现方式还是蛮复杂的,由于要引入特定的页面进行相应处理,所以也不太适合大量的页面的开发需求。
在跟踪Tree控件的过程中,我发现在包含了tree控件的页面中,引入了inlinerequest.js这个文件,这个文件的名称引起了我的注意,所以随即通过查找发现login组件的loginex.jsp页面采用了inlinerequest中提供的相应类和函数。通过浏览代码发现这个inlinerequest.js文件的确是用来处理Ajax请求的,并且支持JSON、XML、Html(还可以调用页面中包含的js代码)、PLAIN、JavaScript几种方式,并在服务器端可以通过getFormRequest().isInlineRequest()来确定是否为Ajax的内联请求,具体使用方法可以参见loginex.jsp所在的login组件。
上面的方法从运行的效率上来讲已经基本上达到了一种平衡,即:不需要单独的编写Servlet和WebService来接受请求,但是一次Post的数据量是所有的input域的信息,而不是需要什么发什么。这种平衡所带来的性能问题,对于WDK这种相对“庞大”的框架来讲已经可以算是微乎其微了。
上面的方式来实现Ajax的效果已经相对完美了,但是还有一个影响开发效率的问题:服务器端只是返回客户端所需要的数据,页面内容还需要在客户端通过JS来处理,但对于WDK生成的比较复杂的Html代码来实现复杂的Ajax效果,恐怕对于开发和调试来讲都是异常困难的事。
通过上面的分析可以知道inlinerequest的请求是可以返回html的,即request.reponseText返回是html代码,那其实是可以通过解析返回的html代码来得到我们需要的某段代码,然后替换掉当前页面的代码来实现“伪Ajax”的效果的。按照这个思路,我做了以下尝试:
1、动态创建iframe来加载html,其代码如下:
{
if (iframe==null)
{
iframe = document.createElement("IFRAME");
var strUniqueIFrameId = WDK_TREENODE_EXPAND_ID + new Date().getTime();
iframe.id = strUniqueIFrameId;
iframe.name = strUniqueIFrameId;
iframe.src = g_virtualRoot + "/wdk/blank.htm";
with (iframe.style) {
position = "absolute";
visibility = "hidden";
overflow = "hidden";
zIndex = 0;
left = 0;
top = 0;
width = 0;
height = 0;
}
document.body.appendChild(iframe);
iframe.setAttribute("expansionForm", "");
}
iframe.setAttribute("expansionForm", html);
if (g_clientInfo.isBrowser(ClientInfo.SAFARI) == true) {
setTimeout("getExpandedContents('" + iframe.id + "');", 100);
} else {
getExpandedContents(iframe.id);
}
var doc = (iframe.contentDocument) ? iframe.contentDocument : window.frames[strUniqueIFrameId].document;
var obj=iframe.document.getElementById(name);
var result=obj.innerHTML;
return result;
}
function getExpandedContents(strUniqueIFrameId) {
var iframe = document.getElementById(strUniqueIFrameId);
var doc = (iframe.contentDocument) ? iframe.contentDocument : window.frames[strUniqueIFrameId].document;
if (typeof doc == "undefined") {
setTimeout("getExpandedContents('" + strUniqueIFrameId + "');", 10);
} else {
doc.write(iframe.getAttribute("expansionForm"));
doc.close();
}
}
但是这种方式在doc.write(iframe.getAttribute("expansionForm"))时发现有在页面中的全局变量所调用方法会引发错误(例如页面中有个wdk变量,页面中直接调用了wdk这个变量的相应函数)。
2、考虑直接将html代码生成xml的document对象来处理,代码如下:
if (window.ActiveXObject) {
//for IE
xmlDoc=new ActiveXObject("Microsoft.XMLDOM");
xmlDoc.async="false";
xmlDoc.loadXML(xmlData);
return xmlDoc;
} else if (document.implementation && document.implementation.createDocument) {
//for Mozila
parser=new DOMParser();
xmlDoc=parser.parseFromString(xmlData,"text/xml");
return xmlDoc;
}
}
但是这种方式由于WDK生成的html代码太不规范,导致无法生成xml的document对象,这种方法也是失败的。
3、考虑直接将html代码生成html的document对象来处理,代码如下:
{
var dd=document;
var doc = document.implementation.createDocument ('http://www.w3.org/1999/xhtml', 'html', null);
doc.documentElement.innerHTML = html;
return doc;
}
但是这种方式在IE下的document.implementation中没有createDocument方法,只有firefox才支持这个方法,所以也是失败的。
以下三种尝试均以失败告终,在山穷水尽的时候想起在.net中的updatePanel的实现的效果其实和这个是类似的,所以查看了updatePanel的相关原理,得到了第四种方式:
4、类似.net的updatePanel的方法,其完整的jsp页面代码如下:
<%@ page contentType="text/html; charset=UTF-8"%>
<%@ page errorPage="/wdk/errorhandler.jsp"%>
<%@ taglib uri="/WEB-INF/tlds/dmform_1_0.tld" prefix="dmf"%>
<%@ taglib uri="/WEB-INF/tlds/dmformext_1_0.tld" prefix="dmfx"%>
<%@ page import="com.documentum.web.form.Form"%>
<%@ page import="com.only.custom.myworkflow.MyWorkflow"%>
<%@ page import="com.documentum.web.common.ClientInfoService"%>
<%@ page import="com.documentum.web.common.ClientInfo"%>
<%@ page import="com.only.custom.test.treeTest"%>
<html>
<dmf:head>
<dmf:webform />
<title></title>
<script type="text/javascript">
function onShowOptions()
{
var prefs = InlineRequestEngine.getPreferences(InlineRequestType.HTML);
prefs.setCallback("onShowOptionsCallBack");
prefs.setErrorCallback("onShowOptionsErrorCallBack");
if (window.suspendTestEvents)
{
window.suspendTestEvents();
}
postInlineServerEvent(null, prefs, null, null, "onShowOptions", null, null);
}
// 这里存放隐藏字段的id 和 value
var hiddenFields = null;
var updatePanelContent = null;
function onShowOptionsCallBack (data)
{
//process
hiddenFields = [];
updatePanelContent = "";
var root = document.createElement("span");
root.innerHTML = data;
parseNode(root,"t123");
for (var i = 0; i < hiddenFields.length / 2; i++) {
var hideObjs=document.getElementsByName(hiddenFields[i * 2]);
if (hideObjs.length>0)
{
hideObjs[0].value = hiddenFields[i * 2 + 1];
}
}
document.getElementById("t123").innerHTML = updatePanelContent;
root=null;
releaseEventPostingLock();
if (window.resumeTestEvents)
{
window.resumeTestEvents();
}
}
function onShowOptionsErrorCallBack (data)
{
alert("error:"+data);
releaseEventPostingLock();
}
// 递归分析节点
function parseNode(node,name) {
if (node.nodeType == 3){
return;
}
if (node.tagName.toLowerCase() == "input" && node.type.toLowerCase() == "hidden") {
hiddenFields.push(node.name);
hiddenFields.push(node.value);
return;
}
if (node.id == name) {
updatePanelContent = node.innerHTML;
return;
}
for (var i = 0; i < node.childNodes.length; i++) {
parseNode(node.childNodes[i],name);
}
}
</script>
</dmf:head>
<body class='contentBackground' marginheight='0' marginwidth='0'
topmargin='0' bottommargin='0' leftmargin='0' rightmargin='0'>
<dmf:form>
<dmf:tree name="mytree" onexpand="onExpand" onclick="onClick"
nodeheight="50" cachechildren="true" />
<dmf:textarea name="mytxt" />
<div id="t123">
<dmf:text name="ajaxText" />
</div>
<dmf:button id='tt' name='ttd' runatclient='true'
onclick='onShowOptions'></dmf:button>
</dmf:form>
</body>
</html>
import java.text.SimpleDateFormat;
import java.util.Date;
import com.documentum.web.common.ArgumentList;
import com.documentum.web.form.control.Button;
import com.documentum.web.form.control.Text;
import com.documentum.web.form.control.TextArea;
import com.documentum.web.form.control.Tree;
import com.documentum.web.form.control.TreeNode;
import com.documentum.web.formext.component.Component;
public class treeTest extends Component
{
public void onShowOptions(Button showOptions, ArgumentList arg)
{
Text ttxt = (Text) this.getControl("ajaxText", Text.class);
String ss = ttxt.getValue();
System.out.println(ss);
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
ttxt.setValue(df.format(new Date()));
ss = ttxt.getValue();
System.out.println(ss);
if (getFormRequest().isInlineRequest())
{
//System.out.println(ss);
}
}
public void onInit(ArgumentList args)
{
Tree tree = (Tree) this.getControl("mytree", Tree.class);
TreeNode root = tree.createRootNode("root",
"icons/browsertree/role.gif", "根结点");
root.setNodeState(TreeNode.STATE_EXPANDED);
TreeNode node1 = tree.createNode("childA",
"icons/browsertree/role.gif", "子结点A", tree.getRootNode());
node1.setNodeState(TreeNode.STATE_COLLAPSEDNODATA);
TreeNode node2 = tree.createNode("childB",
"icons/browsertree/role.gif", "子结点B", tree.getRootNode());
node2.setNodeState(TreeNode.STATE_COLLAPSEDNODATA);
root.expandNode();
}
public void onExpand(Tree tree, ArgumentList args)
{
// Retrieve node to be expanded
TreeNode node = tree.getNodeWithId(tree.getFocusNode());
if (node != null)
{
// Retrieve data if node is collapsed with no data
int nNodeState = node.getNodeState();
if (nNodeState == TreeNode.STATE_NO_CHILDREN)
{
// Get the data
//node.getData();
TreeNode node1 = tree
.createNode("childAA", null, "子结点AA", node);
node1.setNodeState(TreeNode.STATE_NO_CHILDREN);
}
// Expand the node
node.expandNode();
// Fire event
if (isRunAtClient() == false)
{
fireEvent(Tree.EVENT_ONEXPAND, args);
}
}
}
public void onClick(Tree tree, ArgumentList args)
{
// Retrieve node to be expanded
TreeNode node = tree.getNodeWithId(tree.getFocusNode());
if (node != null)
{
// Retrieve data if node is collapsed with no data
TextArea txt = (TextArea) this.getControl("mytxt", TextArea.class);
txt.setValue(node.getLabel());
// Fire event
if (isRunAtClient() == false)
{
fireEvent(Tree.EVENT_ONCLICK, args);
}
}
}
}
第4种方法通过在页面中将需要实现ajax的地方包含在一个div中,然后截取内联调用时返回的Html中的特定id的Div中的Html以及隐藏域,并更新原页面中的相应div就可以了。其最关键的地方在于使用了var root = document.createElement("span"); root.innerHTML = data;的方式来得到Html字符串的Dom对象,这使得递归解析变成了可能。
当然,如上所述,第4种方法实现的就是“伪Ajax”,其运行效率是最低的,但是对于开发的工作量来说确是最小的,在应对复杂的页面实现Ajax效果时(例如表格的更新、增、删行等),这种方式还是有一定的可取之处的。