在IBM WebSphere Portal中实现可定制的打印
【IT168 技术文档】
本文介绍了一种在 IBM WebSphere Portal 中实现定制打印 Portal 页面的思路以及示例程序。通过该方法,Portal 用户不仅能够根据自己的需要选择想要打印的 Portlet,而且能够打印的页面布局。示例程序主要应用到的技术是:IBM WebSphere Portal 主题和外表,Dojo(一种 Javascript 库)以及 Javascript。因此在阅读本文之前,如果您对这些技术能有所了解,对于理解本文是很有帮助的。
目前,在IBM WebSphere Portal中打印Portlet页面,用户没有太多的选择余地-或者打印整个页面,或者利用浏览器的"部分选择"功能打印选择的页面元素。有时,用户只需要打印其中的一个或几个Portlet;有时,用户所要打印的Portlet不是相邻的,因此用浏览器自带的选择打印功能便无法满足需要。本文所描述的思路以及示例程序为这样一些个性化的需求提供了可能性。
程序演示
之所以将程序演示放在文章开头是为了能够让您对本文所要描述的对象以及最后程序实现的效果有个直观和感性的认识。
初始页面。
图 1. 程序演示-初始页面 
Portal页面上有四个Portlet:客户列表、客户满意度、客户支持问题以及客户投诉。右上角有一个“启动打印管理器”链接。
点击“启动打印管理器”链接,出现“打印管理器”。
图 2. 程序演示-打印管理器初始页面 
“打印管理器”左边列出的四个灰色方框分别代表页面上的四个Portlet。右上角列出六种“布局方式”,中间显示的表格称为“布局表格”,默认显示“一栏”布局方式。下面有三个控制按钮:打印、预览和取消。
选择T字型布局方式,并将三个代表Porltet的灰色方框拖放到“布局表格”中。
图 3. 程序演示-定制后的打印布局 
用户选择了某种“布局方式”后,相应表格形式便出现在“布局表格”中。之后,用户可以根据需要以“拖放”的方式将左边列出的代表Portlet的灰色方框放入“布局表格”中。
打印。
图 4. 程序演示-打印 
用户点击“打印”按钮,程序调用浏览器打印功能进行打印。预览。
图 5. 程序演示-预览 
用户也可以先预览再打印(在图3中,点击“预览”按钮)。点击“打印”按钮后,画面同图4。
示例代码的安装
建议再继续阅读本文之前,先安装本文附带的示例程序。这样,在您阅读具体实现代码的时候会有较为直观的认识。
安装环境:
本示例代码附带的皮肤和外表代码是在IBM WebSphere Portal 6.0.2进行开发和测试的,附带的Dojo版本为0.4.2,所以建议您也采用相同的环境进行安装。如果您有兴趣,也可以采用其他的版本的WebSphere Portal或Dojo进行安装,但有可能对示例代码需要进行适当的修改。另外,本示例代码在Firefox 2.0以及IE 6.0上已通过测试,同样,或许您需要自行进行修改才能在其他浏览器上运行。
安装步骤:
下载本文附带的示例程序压缩包PrintManager.zip,将其解压缩到任意目录下。压缩包中包含三个目录:
1)PrintletTheme - 主题目录。将其拷贝到 was_profile_root \installedApps\ cellname \wps.ear\wps.war\themes\html目录下。 was_profile_root 在Windows操作系统中默认是C:\IBM\WebSphere\profiles\wp_profile。
2)PrintletSkin - 外表目录。将其拷贝到 was_profile_root \installedApps\ cellname \wps.ear\wps.war\skins\html目录下。
3)nls - 主题中使用的Resource Bundle文件。将nls文件夹中的properties文件拷贝到 portal_server_root \shared\app\nls目录下。 portal_server_root 在Windows操作系统中默认是C:\Program Files\IBM\WebSphere\PortalServer。
注意:上文中提到的路径都只是示例路径,请根据自己的环境调整文件路径。
启用自动JSP重加载功能。具体请参阅 Portal Info Center 。该步骤主要是用于开发调试阶段。启用该功能后,您可以通过修改示例代码改进原有实现。
重新启动IBM WebSphere Portal服务器。
安装“主题”和“外表”。具体安装步骤如下:
用管理员帐号登录Portal后,进入“主题和外表”页面(启动>管理>门户网站用户界面>主题和外表)。
点击“添加新外表”,在“外表名和缺省的语言环境标题”输入PrintletSkin,在“外表的目录名”输入PrintletSkin。输入示范参见图6。
图 6. 添加新外表 
如果您想为其他语言设置特定的标题,点击“设置语言环境特定的标题”,最后点“确定”返回到之前的页面,新添加的外表“PrintletSkin”已经显示在“外表”列表中。
点击“添加新主题”,在“主题名和缺省语言环境标题”输入PrintletTheme,在“主题目录名”输入PrintletTheme。选择“PrintletSkin”到“此主题的外表”中。由于我们只选择了一个外表,所以PrintletSkin默认成为该主题的缺省外表(下面显示一行信息“主题的缺省外表:PrintletSkin”),否则您需要通过“设置为缺省值”按钮来设置主题的缺省外表。 输入示范参见图7。 图 7. 添加新主题 
如果您想为其他语言设置特定的标题,点击“设置语言环境特定的标题”,最后点“确定”返回到之前的页面,新添加的主题“PrintletTheme”已经显示在“主题”列表中。
将新“主题”和“外表”应用到您的Portal页面。应用“主题”和“外表”的步骤非常简单:进入一个Portal页面,在页面的标题上点击蓝色的倒三角(页面菜单),选择“编辑页面属性”之后,在“页面属性”页面上选择“主题”为“PrintletTheme”,见图8。
图 8. “页面属性”界面 
按“确定”按钮完成输入后,Portal页面上就会显示“启动打印管理器”链接,见图9。
图 9. 应用了新主题和外表的页面示例 
程序原理
本程序工作原理如下:
1)首先,通过新建Portal外表,在每个Portlet外部“套”上一个div。div上加上两个额外的属性pageprint、displaytext以及HTML标准属性id。pageprint用于标识该Portlet是一个可以被“打印管理器”管理的Portlet。displaytext用于给“打印管理器”中的“打印小部件”提供文本显示,它的值为Portlet的标题。id的值是Portlet的id值,它被用来关联“打印小部件”和真正的Portlet,以便取到实际要打印的内容。
2) 用Dojo实现一个“打印管理器”,它能够搜索到当前页面上所有具有pageprint标识的Portlet,将它们显示出来。并且提供六种布局格式供用户选择。
3)通过新建Portal主题,方便的在用户的Portal页面上增加一个“启动打印管理器”的链接,该链接用于调用2)中提到的“打印管理器”。
打印管理器
打印管理器由如下四个区域组成(见图10):
“打印小部件”选择区:该区域列出所有当前页面上的“打印小部件”,用户可以通过拖拽的方式将其放入打印布局定制区。
布局方式选择区:该区域列出了一栏、两栏、三栏、T字型、倒T字型以及“工”字型六种打印布局方式。用户可以根据自己的打印需要从中选择合适的布局方式。
打印布局定制区:该区域根据用户当前选定的打印布局方式显示一个相应的表格,然后用户可以从“打印小部件”选择区选取需要的“打印小部件”放入该表格。最终打印将根据该表格的内容打印出结果。
按钮区:该区域显示三个按钮。它们分别是“打印”、“预览”和“取消”。“打印”将直接打印定制好的结果,而“预览”会显示一个预览效果以便用户进行修改。
图 10. 打印管理器示意图 
什么叫“打印小部件”?
打印小部件表示页面上的一个可打印单元,通过打印管理器可以管理这些打印小部件。在本文中,打印小部件即指Portlet,但事实上通过“标记”,页面上的任何元素都可以成为打印小部件。
图11是一个真实“打印管理器”的屏幕截图,将它与图10进行对照可以更容易地理解“打印管理器”的工作方式。
图 11. 打印管理器屏幕截图 
开发新“外表” - 将Portlet标识为“打印小部件”
什么样的页面元素可以被“打印管理器”识别为“打印小部件”呢?正如“程序原理”部分中讲到的,所有的“打印小部件”都必须具有三个属性pageprint,displaytext和id。清单1中示范了这两种属性的用法。
清单 1. 打印小部件示例
<table id="mytable" pageprint="true" displaytext="示例表格"> ... </table> <img id="myimage" src="myimage.jpg" pageprint="true" displaytext="示例图片"/>
在一般的Web应用程序中,这些属性需要手工添加。如果您使用的是基于组件的Web框架,可以直接在组件的级别上添加这些属性。在IBM WebSphere Portal,通过创建“外表”就可以为每个应用了该“外表”的Portlet添加这些属性,也就是说,这些Portlet会自动标识为“打印小部件”。从另一个角度讲,在开发Portlet的阶段,开发者可以完全不用关心此事,而是将它推迟到部署阶段来完成。 简单来说,创建新“外表”只需要拷贝一个Portal中已有的“外表”目录(一般是IBM),更改目录名称,然后根据自己的需要修改里面的jsp文件,最后在Portal的管理界面添加就可以了。如何创建“外表”不是本文的重点,因此本文不做详细叙述,具体步骤请参考 Portal Info Center 。创建好“外表”目录后,打开Control.jsp,找到class为“wpsPortlet”的div标签,在其外面加上一个div标签,在该标签中上添加属性pageprint、displaytext和id。displaytext的值就是Portlet的标题,id就是Portlet的id。示例代码见清单2。
清单 2. Control.jsp示例
... <div pageprint="true" displaytext="<portal-skin:portletTitle/>" id="<portal-skin:portletID />"> <div class="wpsPortlet" ...> ... </div> </div> ...
开发新“主题” - 集成“打印管理器”到Portal页面
"主题"的开发,简单来说,也是拷贝一个已有的“主题”目录(一般是IBM),更改目录名,然后根据需要修改添加删除里面的程序文件,最后在Portal的管理界面添加即可。具体开发“主题”的步骤请参考 Portal Info Center 。新“主题”目录创建后,在主题目录下:
创建PrintManager.jspf,该文件包含“打印管理器”所有的页面元素。
创建js/PrintManager,拷贝Dojo库文件到该目录(Dojo库文件从Dojo的网站下载)并创建文件PrintManager.js。PringManager.js定义实现“打印管理器”的函数。
创建images/PrintManager,该目录包含六种布局方式的示意图片。
修改Default.jsp让它包含PrintManager.jspf - 找到id为“mainContent”的div标签,在它上面加入一个行include语句。示例代码见清单3。清单 3. Default.jsp示例
用Dojo实现“打印管理器”... <%@ include file="./PrintManager.jspf" %> <div id="mainContent"> <portal-core:screenRender/> ... </div> ...
在PrintManager.jspf中实现前述的打印管理器的HTML代码,启动打印管理器的链接以及预览的HTML代码,并将它们声明为相应的Dojo小部件。
清单 4. PrintManager.jspf代码示例
图12列出了PrintManager.js定义的函数名称。<script type="text/javascript"> var djConfig = { isDebug: false, debugContainerId: "debugConsole" }; </script> <script type="text/javascript" src="<portal-logic:urlFindInTheme file="./js/PrintManager/dojo.js"/>"></script> <script type="text/javascript" src="<portal-logic:urlFindInTheme file="./js/PrintManager/PrintManager.js"/>"></script> <style type="text/css"> .dojoDialog { background : #FFFFFF; border : 1px solid #999; -moz-border-radius : 5px; padding : 4px; } </style> <div style="width:100%;text-align:right;" zIndex="50"> <a href="javascript:pmConsole.show()"><portal-fmt:text key="launch" bundle="nls.PrintManager"/></a> </div> <div dojoType="dialog" id="PreviewDialog" bgColor="#eee" bgOpacity="0.5" toggle="fade" toggleDuration="250" style="display:none;width:100%" zIndex="100"> ... </div> <div dojoType="dialog" id="PrintManager" bgColor="#eee" bgOpacity="0.5" toggle="fade" toggleDuration="250" style="display:none"> ... </div> <div id="debugConsole" style="background-color:lightgray"></div>
图 12. PrintManager.js方法列表

下面,我们选择其中几个较为重要的函数进行讲解。findPrintlets - 搜索当前页面上的“打印小部件”,并构造返回一个数组。
清单 5. 函数findPrintlets代码
该函数利用正则表达式搜索页面中包含pageprint标签的HTML元素,进而获取其中的id以及displaytext标签的值,最后构造成对象放入数组返回。function findPrintlets(){ var bodyHTML = document.body.innerHTML; var re=/<.*pageprint\s*=[\'\"]+true[\'\"]+[^<>\/]*>/gim; var r=bodyHTML.match(re); var printletArray=new Array(); if(r!=null && r!=undefined){ for(i=0;i<r.length;i++){ var divPiece=r[i]; //Find id attribute var idRe=/id\s*\=[\'|\"]{0,1}(\w+)[\'|\"]{0,1}/; r[i].search(idRe); var idVal=RegExp.$1; RegExp.$1=""; //Find displaytext attribute var idxBegin=divPiece.indexOf("displaytext"); idxBegin+=13; dojo.debug(idxBegin); var idxEnd=divPiece.indexOf("\"",idxBegin); dojo.debug(idxEnd); var displayText=divPiece.substring(idxBegin,idxEnd); dojo.debug(displayText); //Construct Printlet object and push it into the array. var printlet= new Object(); printlet.displayText=displayText; printlet.idVal=idVal; printletArray.push(printlet); } } return printletArray; }
createDragSource - 创建Drag Source。
清单 6. 函数createDragSource代码
Drag Source是Dojo中的概念,它表示一个可以拖拽的对象。在“打印管理器”中,一个Drag Source就表示一个“打印小部件”,Drag Source对象通过属性printId保存“打印小部件”的id,因此可以根据它找到对应的打印小部件。function createDragSource(objId,objText,printId){ var newObj=document.createElement("div"); var style="height:40;width:80;background:lightgrey;border:1px dotted black; font-size:10;margin:4px;padding:4px;"; newObj.innerHTML="<div printId='"+printId+"' id='"+objId+"' style='"+style+"'>"+objText+"</div>"; document.getElementById("objSource").appendChild(newObj); new dojo.dnd.HtmlDragSource(byId(objId), [dragType]); }
traverseNodeTree - 遍历布局表格的DOM树,将真正的Portlet内容替换Drag Source的内容。
清单 7. 函数traverseNodeTree代码
该函数通过递归的方法查询子节点中是否包含属性printId,如果包含,就用该printId的值查找页面中具体的对象,对该对象做节点克隆并将其替换DOM树中原有节点。因此,该函数输入的是一个用户在打印管理器中打印布局定制区中表格的DOM树对象,返回的是一个已经将实际Porlet内容进行了替换的DOM树对象。function traverseNodeTree(currentNode){ if(currentNode.tagName != undefined){ var printId=currentNode.getAttribute("printId"); } if(printId!=null && printId!=undefined){ var srcHTMLObj=byId(printId).cloneNode(true); currentNode.parentNode.replaceChild(srcHTMLObj,currentNode); return currentNode; }else if(currentNode.hasChildNodes()){ var nodes=currentNode.childNodes; for(var i=0;i<nodes.length;i++){ var childNode=nodes[i]; traverseNodeTree(childNode); } } return currentNode; }
constructPrintContent - 根据用户定制的结果构造实际打印的内容。
清单 8. 函数constructPrintContent代码
该函数首先把打印布局定制区中的表格做一个克隆,将其边框改为0,然后调用traverseNoteTree函数把克隆的DOM树转换成包含实际打印内容的DOM树,最后通过属性innerHTML返回HTML内容。function constructPrintContent(){ // Do a clone dom tree of print layout table. var printLayoutCopy=byId("printLayout").cloneNode(true); var layoutTable=printLayoutCopy.getElementsByTagName("TABLE")[0]; layoutTable.border="0"; var printContent=traverseNodeTree(printLayoutCopy); dojo.debug(printContent.innerHTML); return printContent.innerHTML; }
doRealPrint - 实现真正的打印功能。
清单 9. 函数doRealPrint代码
该函数打开一个新的浏览器窗口,通过document.write将打印内容输出到新窗口中,继而调用window的print函数进行打印,最后关闭窗口,并把“打印管理器”重置。function doRealPrint(htmlContent){ var w = window.open('','page_printer','height=10,width=10'); window.focus(); w.document.write('<html>\n <head>\n'); var styleSheets = document.styleSheets; for (var i = 0; i <= styleSheets.length - 1; i++) if (styleSheets[i].href != null && styleSheets[i].href != '') w.document.write(' <link rel="stylesheet" type="text/css" href="' + styleSheets[i].href + '" />\n'); w.document.write('</head>\n'); w.document.write(htmlContent); w.document.write('<script language="JavaScript">'); w.document.write(' var links = document.getElementsByTagName("A");'); w.document.write(' for (var i = 0; i <= links.length - 1; i++) '); w.document.write('{ links[i].setAttribute("onclick","return false");'); w.document.write(' links[i].setAttribute("href","#"); }'); w.document.write(' var buttons = document.getElementsByTagName("INPUT");'); w.document.write(' for (var i = 0; i <= buttons.length - 1; i++) '); w.document.write('{ buttons[i].setAttribute("onclick","return false"); }'); w.document.write('<' + '/' + 'scr' + 'ipt>\n'); w.document.write('\n</html>\n'); w.document.close(); w.print(); w.close(); //reset Print Manager pmConsole.hide(); resetPM(); changeLayout(1); }
结束语
本文介绍了一种在IBM WebSphere Portal实现Portal页面定制打印的思路和方法,并讲解了示例程序。如果您在安装示例代码时碰到任何问题、对该程序有任何改进的想法或者发现了任何bug,欢迎发送邮件至shenrui@cn.ibm.com,与作者联系。