锁定老帖子 主题:Portal展现机制研究
该帖已经被评为精华帖
|
|
---|---|
作者 | 正文 |
发表时间:2004-09-02
buaawhl 写道 和BlueDavy一样,我也有一个开发Portal的计划,用fastm做Portal layout,用fastm做portlet template。
很久以前,我就说过,fastm在Portal layout展现这样的复杂页面逻辑方面,具有任何其他模板技术都无可比拟的优势。由于fastm的逻辑和template完全分离, view层可以达到和Model层一样的OO程度。:-) 不说了,还是先做出来再说。 非常之期待,很希望看到一个符合OO的View层,其实Turbine算是一个,不过实现的有些复杂化了。 |
|
返回顶楼 | |
发表时间:2004-09-02
buaawhl说了很多相关的Portal的Layout层的实现,这里我说一下Jetspeed关于这部分的实现,Jetspeed对于一个页面的显示过程是这样的,首先从用户布局文件信息中获取当前用户请求的页面以及页面中的Pane,根据Pane中使用的Layout进行Layout的解析,在Layout类中执行当前的Screen类,Screen则根据布局文件中的信息获取所有的当前Screen中显示的Portlet,将Portlet作为标签注入VM的context中,页面调用直接显示,所以我觉得buaawhl所说的velocity不能用做Layout是不对,反而我觉得velocity用做Layout是很好的做法。
贴一下Jetspeed中对于Layout部分的做法,由于Jetspeed是基于Turbine的扩展,所以其实它的View层是比较复杂的,是按照这样的顺序来解析的: Page-->Layout-->Screen-->Controller-->Portlet 在Jetspeed中Layout其实只是负责了控制页面中Screen的摆放为止而已,而且只能一个,Controller才是真正的控制页面布局的,控制了Portlet的存放 Layout类: PortletSet set = controller.getPortlets();; // normalize the constraints and calculate max num of rows needed Enumeration en = set.getPortlets();; int row = 0; int col = 0; while (en.hasMoreElements();); { Portlet p = (Portlet); en.nextElement();; PortletSet.Constraints constraints = p.getPortletConfig();.getConstraints();; if ( (constraints != null); && (constraints.getColumn(); != null); && (constraints.getRow(); != null);); { col = constraints.getColumn();.intValue();; if (col > colNum); { constraints.setColumn(new Integer(col % colNum););; } row = constraints.getRow();.intValue();; if (row > rowNum); { rowNum = row; } } } row = (int); Math.ceil(set.size(); / colNum);; if (row > rowNum); { rowNum = row; } Log.debug("Controller calculated setSize " + set.size(); + " row " + row + " colNum: " + colNum + " rowNum: " + rowNum);; // initialize the result position table and the work list List[] table = new List[colNum]; List filler = Collections.nCopies(rowNum + 1, null);; for (int i = 0; i < colNum; i++); { table[i] = new ArrayList();; table[i].addAll(filler);; } List work = new ArrayList();; //position the constrained elements and keep a reference to the //others for (int i = 0; i < set.size();; i++); { Portlet p = set.getPortletAt(i);; PortletSet.Constraints constraints = p.getPortletConfig();.getConstraints();; if ( (constraints != null); && (constraints.getColumn(); != null); && (constraints.getRow(); != null); && (constraints.getColumn();.intValue(); < colNum);); { row = constraints.getRow();.intValue();; col = constraints.getColumn();.intValue();; table[col].set(row, p);; } else { work.add(p);; } } //insert the unconstrained elements in the table Iterator i = work.iterator();; for (row = 0; row < rowNum; row++); { for (col = 0; i.hasNext(); && (col < colNum);; col++); { if (table[col].get(row); == null); { table[col].set(row, i.next(););; } } } // now cleanup any remaining null elements for (int j = 0; j < table.length; j++); { i = table[j].iterator();; while (i.hasNext();); { Object obj = i.next();; if (obj == null); { i.remove();; } } } if (Log.getLogger();.isDebugEnabled();); { dumpColumns(table);; } context.put("portlets", table);; 这是它的一部分代码,其实原理比较明显,获取当前Screen中所有的Portlet,以及它们所在的行列,最后在页面上的显示方式则为: <table border="0" cellspacing="0" width="100%" cellpadding="0"> <tr> #foreach ( $column in $portlets ); #if ($!sizes.size(); >= $velocityCount); #set ($idx = $velocityCount - 1 ); #set ($width = $!sizes.elementAt($idx);); #end #if ($!col_classes.size(); >= $velocityCount); #set ($col_class = $!col_classes.elementAt($idx);); #else #set ($col_class = "$!{skin.ControllerStyleClass}"); #end <td #if ($width); width="$width" #end valign="top"> <table width="100%" cellspacing="2" cellpadding="0" border="0"> #foreach ( $portlet in $column ); <tr> <td width="100%">$!portlet.getContent($data);</td> </tr> #end </table> </td> #end </tr> </table> 可见其页面的调用方式比较简单,而且也算是一个View层的OO的实现 当然,就如buaawhl所说的,这样的layout确实只能由开发人员来搞定,象用户只能是选择我要用哪种Layout |
|
返回顶楼 | |
发表时间:2004-09-02
说完上面jetspeed的Layout层实现方法,再说说我现在的View层解析框架对于Layout层的实现方法
Layout类示例: Iterator it=psmlService.getChildNodes(userId,name);; while(it.hasNext(););{ Psml node=(Psml);it.next();; // TODO:获取模板的方法Layout和Portlet并不相同 String template=null; if(node.getLayerName();.trim();.equals("layout"););{ template=paramsService.getTemplate(node.getModuleName();,userId);; } else{ template=paramsService.getPortlet(params,node.getModuleName();,userId);; } template=node.getLayerName();+"/"+template; String tagKey="L"+node.getColumn();+node.getRow();; context.put(tagKey,new BaseViewlayerTag(context,node.getModuleName();,template,node.getLayerName();+"."+node.getRegistry();.getClassname();););; } 实现方法为根据当前请求的Layout名以及用户ID从用户布局文件信息中获取相应的该布局中子模块的信息,进而作为标签反回至页面中 引用 在velocity, freemarker里面直接调用这些复杂java class的各种方法比较别扭,而且得不偿失。 页面调用的不会是这些复杂的java class的各种方法,就算你是用jsp去实现也是一样,而用velocity这种实现调用的也仅仅是标签而已,同样的tag lib概念,在以上代码的执行下结合如下页面模板完成此Layout层,页面模板文件如下: <html> <title>Welcome to webblog</title> <table width="768" border="1" cellspacing="0" cellpading="0"> <tr> <td colspan="2">$L00</td> </tr> <tr> <td>$L01</td> <td>$L11</td> </tr> <tr> <td colspan="2">$L02</td> </tr> </table> </html> 可以看到,页面的调用非常简单,当然这是目前这个框架的大概样子,还需要改进,这个也就基本做到了View层的Layout可以随意的由用户定制 目前的改进在于把Layout抽出一个更简单的概念,而不应该象我上面的那个页面模板文件一样,只需要支持那种几行几列的就行,而不需要支持在其中的一行中又分为几列,那其实可以采用嵌套layout来实现,那样的话layout完全可以做到由用户定制。 在重构后页面Layout的代码应该就类似如此: <table> #foreach $row in $rows <tr> #foreach $viewTag in $row.Columns $viewTag #end </tr> #end </table> 这样的话Layout完全可以由用户自定义 |
|
返回顶楼 | |
发表时间:2004-09-02
BlueDavy 写道 可以看到,页面的调用非常简单,当然这是目前这个框架的大概样子,还需要改进,这个也就基本做到了View层的Layout可以随意的由用户定制 目前的改进在于把Layout抽出一个更简单的概念,而不应该象我上面的那个页面模板文件一样,只需要支持那种几行几列的就行,而不需要支持在其中的一行中又分为几列,那其实可以采用嵌套layout来实现,那样的话layout完全可以做到由用户定制。 在重构后页面Layout的代码应该就类似如此: 这样的话Layout完全可以由用户自定义 谢谢BlueDavy的代码和思路。 BlueDavy提到了嵌套layout。我是否可以这样理解? 整个Layout分成两个层 (1) Page <table> #foreach $row in $rows <tr> #foreach $viewTag in $row.Columns $viewTag #end </tr> #end </table> 其中的viewTag表示 Column (Pane)。对吗? (2 )Pane (column) 即 viewTag <td> .... </td>对吗?如果对,那么里面的...是如何写的? 多谢。 |
|
返回顶楼 | |
发表时间:2004-09-03
在View层这层的抽象上,我现在做的是抽出了Layout和Portlet两层,当然Skin层也是需要的
Layout层只是负责页面的格局,也就是说控制此Page分为多少行多少列,并且负责引入属于自己层次的所有子节点,并将其作为标签传递至Layout层,以供调用 Portlet层则只是负责页面中一个小的应用模块的交互或者显示 我这里贴出我现在View层框架一个简单的example,相信应该能回答部分buaawhl的问题吧 这是一个用户的布局文件的信息,是存在数据库中的,为了方便说明,这里我把它写成文件形式: <page name="FirstPage" ref="ThreeRowLayout"> <layout name="TopNavLayout" ref="TwoColumnLayout" column="0" row="0"> <portlet name="TopNavLeftPortlet" ref="NavPortlet" column="0" row="0"/> <portlet name="TopNavRightPortlet" ref="NavPortlet" column="1" row="0"/> </layout> <portlet name="MainScreenPortlet" ref="FirstNewPortlet" column="0" row="1"/> <portlet name="BottomPortlet" ref="BottomNavPortlet" column="0" row="2"/> </page> 其中上面的ref=""表明此处所引用的模块对应了View层模块注册文件的信息,这个在我的PPT的Domain Model中有描述,View层模块注册文件示例如下: <views> <view name="ThreeRowLayout" classname="spike.framework.view.layout.DefaultLayout" id="GUID--This" template="DefaultLayout.vm"> <params> <param name="row">3</param> <param name="column">0</param> </params> </view> <view name="NavPortlet" classname="spike.framework.view.portlet.DefaultNavPortlet" id="GUID--This" template="DefaultNavPortlet.vm"/> </views> 上面的文件大概示例也就只写这些,其他都是类似的,就没写了 然后就是此用户FirstPage这个页面是如何生成的呢?这就是View层框架要做的了,上面所列举的文件只是用于说明此View层框架的Domain Model以及其基本的使用方法 View层解析框架首先得到解析的请求,首先调用参数解析服务获取当前请求的Page的ModuleName,比如现在请求的是FirstPage,根据ModuleName获取相应的所引用的View层模块名,也就是上面文件中的ref=的那一部分,调用View层类加载服务加载执行此View层模块所对应的class,执行完毕后调用参数解析服务获取当前请求的Page所对应的Template,如请求的参数中不含有则取View层注册模块中的相应所引用的Template 然后讲一下如上面的DefaultLayout所执行的过程: 首先根据用户名以及模块名从用户布局文件中获取其第一级子节点的信息,比如对于上面布局文件Page引用的ThreeRowLayout,它获取的第一级子节点信息为:TopNavLayout、MainScreenPortlet、BottomPortlet,从View层注册模块信息中获取此ThreeRowLayout的row以及column,并将上面的第一级的子节点传入标签类中 标签类则根据节点信息再次调用View层解析框架入口进行解析,它的步骤类似于Page的解析过程,只是更为简单,它直接就根据modulename获取View层类,then解析对应的模板 这样也就完成了整个View层的解析 希望我解释的清楚吧....... |
|
返回顶楼 | |
发表时间:2004-09-03
多谢BlueDavy的详细解答。
我还是给出我的理解,请指正,看对不对。 Template分三层。 (1) ThreeRowLayout -- DefaultLayout.vm (2) TwoColumnLayout -- xxx.vm (3) NavPortlet -- DefaultNavPortlet.vm 这些层次的包含关系可以任意制定,非常灵活。对吗? 用{view Class, template}的成对组合定义,代替了JSP的嵌套Tag Lib。view Class中不用输出HTML, 所有的HTML都在template中定义,而且template中的代码逻辑也可以减少到最少程度。对吗? 另外,能否给出DefaultLayout.vm, xxx.vm, DefaultNavPortlet.vm的例子?(你前面给出的vm是DefaultLayout.vm吧?) 是否要求过多了?:-) sorry. 我想做的Portal,现在还有几个地方没有思考透彻,正在攻关阶段。到现在,还没有贡献自己的一点思路,惭愧。 下面我也给出我的关于fastm layout的大概思路。 由于fastm template 是被动的 DOM结构的资源,template内部就是分层次的,所以不需要从物理上把文件分开来分层次。 fastm layout template的内部层次是这样划分的。 (1) Page。 包括header, body, footer. (2) Body。包括Pane. (3) Pane。包括Window。 (4) Window 包括Control, 和 Content。 Control就是最大化/最小化/移动/编辑/删除等操作。 Content就是Portlet的具体输出。 <!-- BEGIN DYNAMIC: header --> <.... html text here ....> <!-- END DYNAMIC: header --> <!-- BEGIN DYNAMIC: body --> <.... html text here ....> <!-- BEGIN DYNAMIC: pane --> <.... html text here ....> <!-- BEGIN DYNAMIC: window --> <.... html text here ....> <!-- BEGIN DYNAMIC: control --> <a href="{base-url}?portlet={portlet_id}&action=max">maxisize</a> <a href="{base-url}?portlet={portlet_id}&action=min">minimize</a> <a href="{base-url}?portlet={portlet_id}&action=back">move up</a> <a href="{base-url}?portlet={portlet_id}&action=forth">move down</a> <!-- END DYNAMIC: control --> <.... html text here ....> <.... html text here ....> <!-- BEGIN DYNAMIC: content --> <.... html text here ....> <!-- END DYNAMIC: content --> <.... html text here ....> <!-- END DYNAMIC: window --> <.... html text here ....> <!-- END DYNAMIC: pane --> <.... html text here ....> <.... html text here ....> <!-- END DYNAMIC: body --> <!-- BEGIN DYNAMIC: footer --> <.... html text here ....> <!-- END DYNAMIC: footer --> 上面给出的Portal Layout是一个HTML文件。其中的<...html text...>可以被换成任何文本。比如,<table>或<div>,<a href="">或<img>等。用户可以直接在浏览器中看到更改后的HTML文件,观察效果。 对于每个Portlet来说,也都有自己的template。我自己提供的Portlet,采用fastm作为template。下面也给出一个例子。 <a href="{base-url}?portlet={portlet_id}&......"> <form action="{base-url}?portlet={portlet_id}&......"> <....> </form> 目前,我正在考虑Portlet输出内容中的URL问题(form action或者link的url)。 很多Portal干脆就把url的target设一个值,到时候就会弹出一个新的页面,不会影响原来的页面。(my.sun.com就是这样,提供的布局控制功能非常简单。大家可以访问一下。) 我想学习liferay,真正地把应用程序集成到Portal里面。比如,一个portlet的form提交之后,返回的结果还是回到这个portlet。其他的portlet不受影响。 但这里,URL重写是个很大的问题,是我到现在还没想透的问题之一。 现有的Portlet规范没有关于Portlet的URL参数名的定义。 如果我的Portlet里面包含了这样的特殊URL,那么这个Porlet就失去通用性了。 |
|
返回顶楼 | |
发表时间:2004-09-06
引用 用{view Class, template}的成对组合定义,代替了JSP的嵌套Tag Lib。view Class中不用输出HTML, 所有的HTML都在template中定义,而且template中的代码逻辑也可以减少到最少程度。对吗? 恩,分析的很清楚哦,其实我的意思就是{view Class,template}组成了页面的OO思想,一直没表达清楚,多谢buaawhl提出这个 引用 另外,能否给出DefaultLayout.vm, xxx.vm, DefaultNavPortlet.vm的例子?(你前面给出的vm是DefaultLayout.vm吧?) ^_^,有DefaultLayout.vm已经说明了Layout层了,至于DefaultNavPortlet.vm这种模板就不用写出了吧,这就是一个具体的业务模块来着,比如显示一个投票页面呀,什么的 引用 fastm layout template的内部层次是这样划分的。 (1) Page。 包括header, body, footer. (2) Body。包括Pane. (3) Pane。包括Window。 (4) Window 包括Control, 和 Content。 Control就是最大化/最小化/移动/编辑/删除等操作。 Content就是Portlet的具体输出。 恩,很同意buaawhl这样的划分方法,其实这和Turbine就很类似,不过我觉得不应该完全限死层次之间的嵌套关系,否则会失去一定的灵活性,不过限制嵌套关系另外一个好处就是使得系统的层次关系更加的清晰,这是我目前有些矛盾的地方 不过我不是很同意在一个页面上写上所有的控制,看buaawhl提供的页面示例好像有这个概念,在我的理解上我是这么做的 比如page包括header, body, footer 那么我的page页面就只会是这个样子: <table> <tr><td> $header </td></tr> <tr><td> $body </td></tr> <tr><td> $footer </td></tr> </table> 然后header中所引用的页面又是一个单独的页面,这样使得系统的层次以及页面的调整更加的灵活,从这里也就一定程度上体现出如果把这个层次关系定死的坏处,所以我使用的是$viewTag 你所说的Portlet的URL确实是个大问题来着,由于Portlet规范中未说明,目前实现的各厂商在这个方面处理就有很大的不同,确实是个麻烦的事 |
|
返回顶楼 | |
发表时间:2004-09-09
buaawhl、flyisland、BlueDavy你们好!
请教一个问题: 如果实现portlet使用view class + template方式的话,如果用户设置样式如字体、颜色时,这些设置应该放置在view class里吧?能否举个例子代码看看? 如果单抽出一个skin层做这件事,是否做一个样式层封装了大部分通用的样式设置?如表格风格、字体风格等等,再把它们加载到template上? 还有layout能做到用户自由设置么,而不是系统预制才行? 现在比较混乱,希望各位能指点迷津! |
|
返回顶楼 | |
发表时间:2004-09-09
引用 Domain Model层则由用户布局信息、View层模块注册信息共同构成
业务逻辑中包含view层信息? 那我要更改一个layout,是否也要更改Domain Model? 用户布局信息、View层模块注册信息应该基于角色的基础上来处理比较好。 不同的角色拥有不同的界面信息(layout, view, porlet),这也更方便的加入权限的控制。 |
|
返回顶楼 | |
发表时间:2004-09-09
intolong 写道 请教一个问题: 如果实现portlet使用view class + template方式的话,如果用户设置样式如字体、颜色时,这些设置应该放置在view class里吧?能否举个例子代码看看? 如果单抽出一个skin层做这件事,是否做一个样式层封装了大部分通用的样式设置?如表格风格、字体风格等等,再把它们加载到template上? 还有layout能做到用户自由设置么,而不是系统预制才行? 现在比较混乱,希望各位能指点迷津! 抽象出skin层是很重要的,这也是个性化定制的关键所在。在你的view注册信息中指定skin模板,通过相应的skin服务来加载。具体如何做,还没有想好。呵呵 简单点也可以直接同制定不同的css方案来处理。 layout可以由用户自由设置,可以自定义layout模板和portlet的位置显示。 |
|
返回顶楼 | |