自从 IBM Lotus Notes/Domino 8.5 为 Lotus Domino 程序开发人员提供 XPages Web 2.0 开发技术以来,这项技术已经使用的越来越广泛,深受 Lotus Domino Web 2.0 应用开发人员的喜爱。XPages 是一项全新的技术,与以往的发行版本相比,它提供了更加方便快捷、更加灵活、效率更加的 Lotus Domino 应用程序开发方式。如果读者希望对 XPages 有更加深入、全面细致的了解,建议阅读 Xpages揭秘,XPages全接触 等 XPages 相关技术文章。
作为基于 Lotus Domino/Notes 的 Web 2.0 解决方案,XPages 对 Ajax 有很好的支持,这体现在 Xpages 提供 ClientSide JavaScript 和 ServerSide JavaScript 供开发人员进行 Ajax 应用开发。不论是在客户端还是在服务器端,任意事件都支持 JavaScript,例如,在 XPage 中,我们可以使用 JavaScript 来计算某个 ComputedField 的值。另外,我们可以使用 JavaScript 获取和设置 XPage 中控件的属性以及值。本文接下来将会对 XPages 中的 JavaScript 技术进行一个细致的讲解。
在传统的 IBM Lotus Domino/Notes 应用中,我们使用 Formula 和 LotusScript 来进行逻辑判断、数值输入验证、数据转换处理等相关逻辑操作功能。而在 XPages 就用程序中,应用程序开发人员可以使用 JavaScript 来进行全部的业务逻辑处理,甚至使用 JavaScript 调用 Java 类来实现部分复杂的业务逻辑,XPages 中元素关系图如下:
图 1. XPages 元素关系图
客户端 JavaScript 脚本一般用来操作 XPage 页面上控件的值,例如,我们可以使用如下 JavaScript 语句来设置 XPage 页面中 ID 为 comp 的控件值。
清单 1. 客户端 JavaScript 设置控件值
var editID = '#{javascript:getClientId("comp")}'; document.getElementById(editID).value = "HelloWorld"; |
通过函数 getClientId("textExample") 来获取 XPage 控件的客户端 ID 值(如 view:_id1:sampleComp),然后就可以设置该控件值。读者从这里可以看出,XPages 中客户端 JavaScript 语法与平时普通 HTML 所使用的 JavaScript 脚本语法并无二样。
对于上述例子,采用服务器端 JavaScript 也可以实现同样的功能。
清单 2. 服务器端 JavaScript 设置控件值
var editComponent:javax.faces.component.UIComponent = getComponent("comp"); editComponent.setValue( "HelloWorld"); |
通过函数 getComponent 来获取 ID 为 comp 的控件,将其转化为 JSF 控件,并通过 setValue 函数来实现控件值的设置。
那么,同为 JavaScript 脚本语言,只是一个运行于服务器端,一个运行于客户端,它们究竟有什么的区别,共同点又在哪里呢?接下来本文将要为读者详细介绍 XPages 中的这两种 JavaScript 脚本。
客户端 JavaScript 功能介绍
在 XPages 中使用客户端 JavaScript 可以帮助完成一些前端的逻辑处理、用户输入验证以及信息提示的工作。在 JSP/HTML 程序开发中,开发人员喜欢用 JavaScript alert 函数实现信息提示,用 confirm 函数实现用户操作确认。在 XPages 中,也可以客户端 JavaScript 来实现同样的功能。
下面举例说明如何在 XPages 中使用客户端 JavaScript 来实现用户输入验证的功能。在此案例中,当用户点击“Save”按钮时,将会有一个操作确认框弹出让用户确认将保存操作,如果用户选择确定,则完成保存操作,否则,则取消保存操作。该验证过程采用客户端 JavaScript 实现。
清单 3.XPages 文件上传示例代码
<?xml version="1.0" encoding="UTF-8"?> <xp:view xmlns:xp="http://www.ibm.com/xsp/core"> <xp:this.data> <xp:dominoDocument var="AttachDoc" formName="Attachment"> </xp:dominoDocument> </xp:this.data> <xp:table style="width:100%"> <xp:tr> <xp:td style="width:300.0px"> <xp:label value="File to Upload:" id="label1" style="font-size:8pt"> </xp:label> <xp:fileUpload id="Attachments" style="width:216.0px" value="#{AttachDoc.Attachments}"> </xp:fileUpload> </xp:td> <xp:td> <xp:button value="Upload" id="button3"></xp:button> </xp:td> </xp:tr> </xp:table> </xp:view> |
图 2. 按钮客户端 JavaScript 验证代码添加指示()
在 Lotus Domino Designer 的 XPages Design 视图中,选择要进行操作确认的按钮,如图示 1 所示。在 Events 面板中选择 onclick 事件,表示用户点击该按钮进行操作时,将会有验证操作发生,如图中 2 所示。然后选择 Client 端验证,并选择在脚本编辑的模式下输入如下操作确认验证代码,如图中 3,4 所示。
清单 4. 客户端 JavaScript 操作验证代码片段
if(confirm('Are you sure you want to upload?')){ return true; } else { return false; } |
该段代码如果效果如下图所示,当用户“确定”操作时,将执行”upload”按钮所绑定的事件,若点击“取消”,则取消当前操作。
图 3. 客户端验证效果图
读到这里,有 WEB 开发经验的读者可能会意识到,XPages 中的客户端 JavaScript 的语法以及使用方式与传统的 JSP/HTML 开发中 JavaScript 的用法是类似的。对,确实是这样,XPages 客户端 JavaScript 与标准的 JavaScript 在语法上是保持一致的。事实上,XPages 还封装了 Dojo 包作为它的一个 Ajax 解决方案,对 Dojo 和 JavaScript 语法感兴趣的读者可以从本文的参考资料中获取相关的学习资料。
服务器端 JavaScript 功能介绍
XPages 使用服务器端 JavaScript 来完成业务逻辑操作、与 Domino 的交互操作、文档的创建、查看、编辑与删除等功能。服务器端 JavaScript 除了具有与普通 JavaScript 同样的语法规则外,还具有更多的内置函数,具体的内置函数库,读者可以在 Lotus Domino Designer 的帮助内容中获取到,在 Domino Design 的 Help 菜单下查看”Help Content”菜单项,即可查看详细的服务器端 JavaScript 内置函数库,如下图。
图 4. 查看服务器端 JavaScript 内置函数
服务器端 JavaScript 提供了一些与 Notes Formula 对应的功能函数,例如 @IsAvailable,@ @IsDocBeingSaved 以及 @IsNewDoc 等等,还提供了一些标准对象支持,例如 Array, Boolean, Date, Math, Number, Object, String 以及 RegExp 等等,此外,还有一些顶层操作函数例如 eval,isNaN 等。Lotus Domino Designer 中服务器端 JavaScript 的规范包含五个方面的内容:
具体的服务器端内置函数内容读者均可以在 Lotus Domino Design/Notes 所带的帮助文档中可以查看到。
图 5. 查看 Lotus Domino Design 帮助文档内容
此外,服务器端 JavaScript 与提供与 Java 代码的相互调用功能以及强大的 Domino 文档管理功能,读者可以在”XPages 开发实践:开发通用的 Tree View 定制控件”这篇文章中学习服务器端 JavaScript 如何与 Java 代码进行交互。接下来,将为大家讲述服务器端 JavaScript 如何进行 domino 文档的管理操作。
服务器端 JavaScript 与客户端 JavaScript 交互分析与实现
服务器端 JavaScript 在与 Domino 文档、数据库交互方面有着及其强大的功能及丰富的内置函数支持,而客户端 JavaScript 则在与用户交互、信息提示等有着更大的优势。那么,如何才能完成服务器端 JavaScript 和客户端 JavaScript 的交互使用,充分发挥两种脚本语言的优势和长项呢?
考虑这样一个简单的场景,我们要获取当前 Notes 用户的用户名,并用弹出框展示出来。分为两个部分,第一个部分为获取 Notes 用户的用户名,我们可以使用服务器端 JavaScript 脚本 context.
getUser()
.
getDistinguishedName()
来正确获取到。第二个部分为用弹出框向用户展示用户名称,这个操作也相当简单,通过客户端 JavaScript 函数 alert()
就可以实现。但是问题是,服务器端脚本获取到用户名以后,如何传递给客户端脚本进行展示呢? XPages 本身并不支持服务器端脚本与客户端脚本的直接性交互,这需要我们设计出一种可行的服务器端脚本与客户端脚本的交互模式来。
由于服务器端 JavaScript 脚本运行在 Domino 服务器端,而客户端 JavaScript 脚本运行在浏览器客户端,要实现服务器端 JavaScript 与客户端 JavaScript 进行交互,我们可以采用如下的设计。
在 XPage 页面中添加一个 id 为 convertHelperFunName 的 inputText 控件,用来存放服务器端将要调用的客户端 JavaScript 函数名。并在 XPage 页面中添加多个存放函数参数的输入控件,当然,把这些控件设为不可见。在 XPage 页面初始化时,取出控件 convertHelperFunName 中的值,通过 eval 函数实现对该函数的调用操作,具体代码如下。
清单 5.XPage 客户端代码
<xp:scriptBlock> <xp:this.value> function getCtrlValue(controlId) { var nameCtrl = XSP.getElementById(controlId); return nameCtrl.value; } function getFunctionName(){ return getCtrlValue("#{id:convertHelperFunName}"); } function getPara1(){ return getCtrlValue("#{id:convertHelperFunPara1}"); } function getPara2(){ return getCtrlValue("#{id:convertHelperFunPara2}"); } function getPara3(){ return getCtrlValue("#{id:convertHelperFunPara3}"); } function getPara4(){ return getCtrlValue("#{id:convertHelperFunPara4}"); } function getPara5(){ return getCtrlValue("#{id:convertHelperFunPara5}"); } function clearConvertFunction() { XSP.getElementById("#{id:convertHelperFunName}").value = ""; } </xp:this.value> </xp:scriptBlock> <xp:div id="cvtHelperParagraph"> <xp:inputText id="convertHelperFunName" style="Display:None"></xp:inputText> <xp:inputText id="convertHelperFunPara1" style="Display:None"></xp:inputText> <xp:inputText id="convertHelperFunPara2" style="Display:None"></xp:inputText> <xp:inputText id="convertHelperFunPara3" style="Display:None"></xp:inputText> <xp:inputText id="convertHelperFunPara4" style="Display:None"></xp:inputText> <xp:inputText id="convertHelperFunPara5" style="Display:None"></xp:inputText> <xp:text escape="false" id="cvtComputedField"> <xp:this.value> #{javascript:return "<script>initConvertHelper();</script>"} </xp:this.value> </xp:text> </xp:div> |
定义函数 getFunctionName 来获取存放在 convertHelperFunName 控件中的函数名称,并定义了多个函数来获取不同的函数参数。这里,我们采用客户端 JavaScript 内置的 XSP 对象来获取控件值,在 XPage 页面初始化完成之后,将会调用 initConvertHelper() 函数来实现对客户端函数的调用。函数 initConvertHelper 代码如下。
清单 6. 客户端函数调用实现代码
function initConvertHelper() { var funName = getFunctionName(); if(funName != null && funName != ""){ executeFunction(funName); clearConvertFunction(); } } function executeFunction(functionName) { if(functionName != null && functionName != ""){ eval(functionName+"()"); } } |
这样我们已经完成客户端 JavaScript 代码对服务器端 JavaScript 传递过来的函数进行调用的功能,这是一个“取”的操作。要完成整个服务器、客户端脚本的交互功能,还需要完成一个“存”的操作,即完成将服务器端要求客户端进行调用的函数的函数名放置在控件 convertHelperFunName 中,代码如下。
清单 7. 服务器端函数名称传递实现代码
function setFunctionName(functionName) { getComponent("convertHelperFunName").setValue(functionName); } function setFunctionPara1(para) { getComponent("convertHelperFunPara1").setValue(para); } function setFunctionPara2(para) { getComponent("convertHelperFunPara2").setValue(para); } function setFunctionPara3(para) { getComponent("convertHelperFunPara3").setValue(para); } function setFunctionPara4(para) { getComponent("convertHelperFunPara4").setValue(para); } function setFunctionPara5(para) { getComponent("convertHelperFunPara5").setValue(para); } |
这样,我们通过设置中转控件的方式,完成了服务器端脚本与客户端脚本的交互设计。下面将举例来论证和验证该交互模式。
基于上述交互技术实现一个类似传统 Notes Form 的强交互式应用
回到上一章获取 Notes 用户名的场景,我们新建一个 XPage 页面,在上面添加一个按钮,用户点击此按钮用来获取 Notes 用户名,并通过客户端脚本弹出框将用户名弹出显示,此 XPage 页面代码如下。
清单 8. 获取 Notes 用户名 XPage 源码
<?xml version="1.0" encoding="UTF-8"?> <xp:view xmlns:xp="http://www.ibm.com/xsp/core"> <xp:this.resources> <xp:script clientSide="false" src="/cvtHelperFuncSvr.jss"></xp:script> </xp:this.resources> <xp:button value="Click Me" id="button1"> <xp:eventHandler event="onclick" submit="true" refreshMode="complete"> <xp:this.action><![CDATA[ #{javascript:var name=context.getUser().getCommonName(); setFunctionName("Function_Prompt_Alert_FromFormula"); setFunctionPara1(name); }]]></xp:this.action> </xp:eventHandler></xp:button> <xp:scriptBlock> <xp:this.value> function refreshToGetDataFromMap(type){ if(type=='confirm') document.getElementById("#{id:refreshButtonToHandlerDataMap}").click(); else document.getElementById("#{id:refreshButtonToClosePopupDialog}").click(); } function getCtrlValue(controlId) { var nameCtrl = XSP.getElementById(controlId); return nameCtrl.value; } function getFunctionName(){ return getCtrlValue("#{id:convertHelperFunName}"); } function getPara1(){ return getCtrlValue("#{id:convertHelperFunPara1}"); } function getPara2(){ return getCtrlValue("#{id:convertHelperFunPara2}"); } function getPara3(){ return getCtrlValue("#{id:convertHelperFunPara3}"); } function getPara4(){ return getCtrlValue("#{id:convertHelperFunPara4}"); } function getPara5(){ return getCtrlValue("#{id:convertHelperFunPara5}"); } function clearConvertFunction() {XSP.getElementById("#{id:convertHelperFunName}").value = "";} </xp:this.value> </xp:scriptBlock> <xp:div id="cvtHelperParagraph"> <xp:inputText id="convertHelperFunName" style="Display:None"></xp:inputText> <xp:inputText id="convertHelperFunPara1" style="Display:None"></xp:inputText> <xp:inputText id="convertHelperFunPara2" style="Display:None"></xp:inputText> <xp:inputText id="convertHelperFunPara3" style="Display:None"></xp:inputText> <xp:inputText id="convertHelperFunPara4" style="Display:None"></xp:inputText> <xp:inputText id="convertHelperFunPara5" style="Display:None"></xp:inputText> <xp:inputText id="Prompt_Function_MiddleValue" style="Display:None"> </xp:inputText> <xp:inputText id="Prompt_Function_Exec" style="Display:None" value="false"> </xp:inputText> <xp:text escape="false" id="cvtComputedField"> <xp:this.value> #{javascript:return "<script>initConvertHelper();</script>"} </xp:this.value> </xp:text> </xp:div> </xp:view> |
客户端 JavaScript 函数 Function_Prompt_Alert_FromFormula
实现弹出用户名操作,该函数的唯一参数为弹出框上显示的提示信息。
清单 9.Function_Prompt_Alert_FromFormula 代码实例
function Function_Prompt_Alert_FromFormula(){ var para1 = getPara1(); alert(para1); } |
当用户点击“Click me”按钮时,该按钮绑定的服务器端脚本将调用 context.getUser().getCommonName()
语句来实现 Notes 用户名的获取,再通过本文所提供的服务器端 JavaScript 与客户端 JavaScript 交互解决方案来完成通过弹出框向用户展示用户名信息。示例运行效果如下图。
图 6. 服务器端 JavaScript 与客户端 JavaScript 交互示例运行效果
案例改进与论证
通过以上案例我们实现了服务器端 JavaScript 脚本向客户端 JavaScript 脚本传递消息进行展现的操作,信息的传递方向为服务器端向客户端。让我们考虑如下情况,用户点击 XPage 页面中的某个按钮,触发该按钮绑定的服务器端 JavaScript 脚本,该服务器端脚本要求用户输入相关信息后才能继续操作。该案例的消息传递流为服务器端 JavaScript 至客户端 JavaScript 至服务器端 JavaScript,那么如何才能实现这么复杂的服务器端 JavaScript 与客户端 JavaScript 的交互操作呢?
由前述案例我们已经完成了服务器端脚本至客户端脚本单向的交互操作,而本案例服务器端与客户端交互部分我们也可以用上述原理实现。但是有两点区别:
显然,该按钮绑定的事件脚本要执行两遍,第一遍跳转至用户输入,而第二遍则完成所有的操作。那么,我们该如何来实现这样的功能呢?其实这可以简单的在页面中放置一个隐藏的控件来实现,该控件初始值为 false, 当在执行事件脚本时,判断该控件值,则值为 true,则完成执行所有的事件脚本,如果该控件值为 false, 则将该控件值设为 true,然后跳转至用户输入功能模块。
清单 10. 事件脚本执行情况标识控件代码
<xp:inputText id="Prompt_Function_Exec" style="Display:None" value="false"> </xp:inputText> |
另外,我们还需要一个在 XPage 页面中添加一个隐藏的控件来存储用户从弹出框里输入的值,该值在第二次执行按钮事件脚本时,该会被直接使用。
清单 11. 用户输入值隐藏控件代码
<xp:inputText id="Prompt_Function_MiddleValue" style="Display:None"></xp:inputText> |
接下来,向大家展示完整的示例代码。
清单 12. 复杂交互示例 XPage 页面代码
<xp:panel themeId="Panel.actionsBar"> <xp:button dojoType="dijit.form.Button" id="AbT0" value="Click me to input name"> <xp:eventHandler event="onclick" refreshId="cvtHelperParagraph" refreshMode="full" submit="true"> <xp:this.action> <![CDATA[ #{javascript: try{ var n = "User Input Name: "; var name = Function_Prompt_FromFormula(this,"[OkCancelEdit]", "Enter Your Name", "Type your name in the box below.", @UserName()); @SetField("value", @List(n, name)); }catch(e){ print("exception occur"); } }]]> </xp:this.action> </xp:eventHandler> </xp:button> </xp:panel> <xp:panel style=""> <xp:inputText id="value" style="" value="#{currentDocument.value}"></xp:inputText> </xp:panel> |
从此段代码中可以看出,该 XPage 页面有一个按钮,当用户点击它时,将弹出一个输入框供用户进行名称的输入,当用户完成用户名输入以后,则在 value 控件里显示”User Input Name :”加上用户所输入的用户名。而 Function_Prompt_FromFormula
函数实现了动态判断是否需要弹出输入框,该函数代码如下。
清单 13. Function_Prompt_FromFormula 函数代码
function Function_Prompt_FromFormula(param0,param1,param2,param3,param4){ if(param1!=null&¶m1!=undefined) setFunctionPara1(param1); if(param2!=null&¶m2!=undefined) setFunctionPara2(param2); if(param3!=null&¶m3!=undefined) setFunctionPara3(param3); if(param4!=null&¶m4!=undefined) setFunctionPara4(param4); setFunctionPara5(param0.getParent().getId()); if(param1=="[Ok]") { setFunctionName("Function_Prompt_Alert_FromFormula"); }else{ var Prompt_Function_Exec:String=getComponent("Prompt_Function_Exec").getValue(); if(Prompt_Function_Exec.equals("false")){ getComponent("Prompt_Function_Exec").setValue("true"); }else{ getComponent("Prompt_Function_Exec").setValue("false"); setFunctionName(""); var returnValue=getComponent("Prompt_Function_MiddleValue").getValue(); return returnValue; } if(param1.equals("[OkCancelEdit]")){ setFunctionName("Function_Prompt_Prompt_FromFormula"); return; } } } |
当控件 Prompt_Function_Exec 的值为 true 时,直接返回 Prompt_Function_MiddleValue 的控件值,否则,则调用 setFunctionName("Function_Prompt_Prompt_FromFormula")
函数来实现用户名数据的输入操作。函数 setFunctionName 的实现原理我们在前一章的案例中已经介绍了,接下来,将为大家讲解一下客户端 JavaScript 函数 Function_Prompt_Prompt_FromFormula
的实现。
清单 14. 客户端函数 Function_Prompt_Prompt_FromFormula 实现代码
function Function_Prompt_Prompt_FromFormula(){ var para1 = getPara1(); var para2 = getPara2(); var para3 = getPara3(); var para4 = getPara4(); var para5 = getPara5(); var value=window.prompt(para3,""); var hiddenList=document.getElementsByTagName("input"); for(var i=0;i<hiddenList.length;i++){ var inputCompId=hiddenList[i].id; if(inputCompId!=""&&inputCompId.indexOf("Prompt_Function_MiddleValue")>-1){ hiddenList[i].value=value; break; } } var refreshButtonID=null; var buttonList=document.getElementsByTagName("button"); for(var i=0;i<buttonList.length;i++){ var inputCompId=buttonList[i].id; if(inputCompId!=""&&inputCompId.indexOf(para5)>-1){ refreshButtonID=inputCompId; break; } } setTimeout("document.getElementById('"+refreshButtonID+"').click()",1000); } |
当用户在 window.prompt 输入框内完成数据输入操作时,将该输入值存储在 Prompt_Function_MiddleValue 控件中,并再次触发事件绑定按钮,将该输入值传递给服务器端脚本,完成该按钮绑定的全部服务器端脚本操作。具体实现效果如下图所示。
图 7. 服务器端与客户端脚本复杂交互示例运行效果 ( 第一部分 )
图 8. 服务器端与客户端脚本复杂交互示例运行效果 ( 第二部分 )
图 9. 服务器端与客户端脚本复杂交互示例运行效果 ( 第三部分 )