版权声明:本文章为学习日记网站(http://www.learndiary.com )版权所有,以<a href="http://creativecommons.org/licenses/by/2.0/">Creative Commons License</a>方式授权。欢迎转载,但请注明文章原始出处(http://www.learndiary.com/disDiaryContentAction.do?searchDiaryID=&goalID=1255&naviStr=a10a2167 )。
提纲: 一、背境; 二、需求分析: 1、学习日记网站的主要功能页面结构; 2、需要进行动态导航的主要模块; 3、需求的提出: 1)、层次导航的需求; 2)、同一列表中帖子间的导航(即上一条,下一条类似的导航); 3)、父帖与子帖列表的双向导航; 三、具体实现过程: 1、层次导航的实现过程: 1)列出导航功能需求列表,看都有哪些导航路径; 2)分析定位特定类型的页面所需的参数; 3)确定层次导航的实现方法; 4)进行层次导航系统的设计: (1)、进行页面节点导航封装字符串格式的设计; (2)、页面完整导航字符串的设计; (3)、将封装的导航字符串还原为显示在页面上的URL地址导航条; (4)、在页面上显示层次导航的URL字符串; 2、同一列表中帖子间的导航(即上一条,下一条类似的导航)的实现过程: 1)列出导航需求列表; 2)分析实现上一条、下一条导航所需的参数; 3)确定实现上一条、下一条导航的实现方法; 4)进行上一条、下一条导航的设计: (1)、根据层次导航的导航字符串确定上一条、下一条导航所在的层次导航位置; (2)、确定在哪几个Struts的Action中需要处理上一条、下一条导航; (3)、在页面上显示上一条、下一条导航的URL字符串; 3、父帖与子帖列表的双向导航的实现过程: 1)列出导航需求列表; 2)分析实现双向导航所需的参数; 3)确定实现方法; 4)进行双向导航的设计: (1)、确定在哪几个Struts的Action中需要处理双向导航; (2)、在页面上显示双向导航的URL字符串; 四、总结: 1、心得; 2、优点; 3、缺点; 4、愿望; 五、联系方式; 六、附件(附学习日记V0.9.0.4和学习日记系统ArgoUML建模(学习日记V0.9.0.4反向工程类图) )。
关键词:学习日记 Struts 动态导航 学习日记动态导航技术(简称:LDDN技术 ) 学习日记开发小组(简称:LDDG )
××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
<font size="+2"><b>正文</b></font>
一、背境: 学习日记(http://www.learndiary.com )网站是采用Struts框架的开源项目learndiary(http://develop.learndiary.com )的实际运行示范站点。目前由学习日记开发小组进行开发,致力于以JAVA技术构建一个普遍适用的开源网络学习交流平台。为了改善用户的浏览体验,故提出导航系统的改善计划。已经包含了这个导航系统的的最新版本为learndiaryV0.9.0.4。本文所提到的程序均可以在本站的下载菜单中获取:http://www.learndiary.com/download/download.html 。本文希望起到一个抛砖引玉的作用,引发一场关于java技术构建的web系统的导航系统设计的讨论。
二、基本思路: 1、学习日记网站的主要功能页面结构: 核心为:目标列表-》目标内容及评论-》目标下的日记列表-》日记内容及评论 (示意图:http://www.learndiary.com/indexAction.do?pageNum=1&naviStr=a10 (显示学友提出的所有学习目标列表 ) 2)、检索:http://www.learndiary.com/toSearchAction.do?naviStr=a10ac0 (检索本站的所有学习目标和学习日记 ) 3)、您的目标:http://www.learndiary.com/processGoalAction.do?currentGoalState=1&pageNum=1&naviStr=a10a60 (学友自己的学习目标归类:包括:进行中的目标、退出的目标、已完成的目标 )
3、需求的提出: 1)、层次导航的需求: 我们想建成一个导航系统,它可以跟踪用户在系统中浏览页面的过程,每浏览到一个新的页面,就在导航条中加入这个页面的URL,于是,当用户浏览到别的页面,可以点击返回系统导航条中的前面浏览过的页面;当浏览一个页面是与导航条中已经存在的页面类型一致时,就截去导航条中此类型页面后面的导航URL。 征对这个需求,我大概看了一下动网的实现方式和phpWind论坛的实现方式,他们用的导航基本上是静态的,也就是说,一种页面上的导航条是固定的,例如:显示一条帖子的内容,他们的方式是:论坛首页-》论坛版块-》主题内容。 而我们的需求是,例如:显示一篇日记的内容: 在所有目标模块中,导航条为:所有目标>>日记列表>>日记内容;(图例: ) 在您的目标中,导航条为:所有目标>>我进行中的目标>>本目标我的日记列表>>日记内容;(图例: ) 当我点击查看“解决:jsp页面中文显示问题”这篇日记中的下一条链接时,我们需要打开检索结果列表的下一篇日记:“问题:jsp中文显示,<c:set>的值可否是对象?me ”:(图例: ),您可以看到页面右边中上有两个链接分别是:“查看所有日记”和“查看我的日记”,点击“查看所有日记”链接出现的是页面(图例: );后面这两个页面的右中上部又都有到日记所在的目标中的链接:“查看目标内容”。 以上总结了学习日记需要实现的导航系统的三个方面的需求,征对这个需求,我看了一些论坛,均没有现存的东西可以参考,于是,我们决定探索一种能够实现上面动态导航需求的方法。因为我们实现的导航系统是动态变化的,故把这种实现的方法称之为“学习日记动态导航技术”(简称:LDDN技术 )。下面把我们的具体设计过程总结一下。
三、具体实现过程: 具体的设计分析过程我已经在进行的过程中记录在日记中了,所以,如果你要详细的了解我的分析设计过程的话,可以去看这两篇日记: 第一篇日记:“提高学习日记导航能力的思路”(http://www.learndiary.com/disDiaryContentAction.do?searchDiaryID=&goalID=1128&naviStr=a10a2506ah1167 ),主要记录的是我在对层次导航部分的分析设计过程; 第二篇日记:“分析学习日记横向导航及开几个窗口的思路”(http://www.learndiary.com/disDiaryContentAction.do?searchDiaryID=&goalID=1167&naviStr=a10a2506ah1128 ),主要记录的是我在对“上一条”和“下一条”导航部分的分析与设计过程。 当然,细节总是太烦琐和令人不愉快的,我把大概的设计过程总结如下:
1、层次导航的实现过程: 1)列出导航功能需求列表,看都有哪些导航路径,例如: (5)所有目标列表->相关日记列表->显示日记内容->编辑评论; (14)所有目标列表->进行中的目标列表->您的日记列表->显示日记内容->编辑评论; (21)所有目标列表->检索页面->搜索结果日记列表->相关日记列表->显示日记内容->撰写评论; 全部列表请见我上面提到的第一篇日记中的“一、学习日记导航路径列举:”(您可以在页面中搜索位置,前后的双引号除外,下同 )。
2)分析定位特定类型的页面所需的参数(这样,就可以根据页面类型和参数唯一的确定一个页面了 ): 经过分析,唯一定位一个页面所需要的参数变为: 1》页面类型; 2》参数ID; 其中参数ID分为下面几种情况: 1》列表:需要列表的parentID; 2》单个条目:需要它的ID; 3》用户的进行、完成等目标列表:什么都不需要,用户ID在Session中; 4》用户的进行、完成等目标的用户日记列表;目标ID,所需的用户ID在Session中; 5》搜索列表:什么都不需要,因为搜索条件字符串已保留在全局Session中了; 具体的分析过程请见我上面提到的第一篇日记中的“(2 )、如何唯一的定位一个特定的页面呢? ”
3)确定层次导航的实现方法: 当点击一个新的页面时,就把定位这个页面所需的参数加入到层次导航的参数链表中(链表的一个节点储存的是一个新的页面的定位参数 ),这时链表变长;当点击一个在链表中已经存在这种类型的页面(例如:显示一篇日记的内容的页面 )时,就把在导航链表中这个节点及后面的节点删除,再加上这个点击的页面的定位参数。 实现这种层次导航的需求有两种方法: (1)、为每条导航路径在session中设置一个层次导航所需的属性,在这条路径上导航链表的增长和缩短信息就由这个属性来维持; (2)、把这个链表封装成字符串,这个字符串在访问不同的页面时,会根据上面的思路不断增长和缩短。把这个封装的字符串连同用户新请求的页面的定位参数节点字符串一起传给请求这个页面前的Struts的action,形成新的页面上导航条所需的编码字符串,然后把这个编码字符串保存在request中,供请求的页面中的下一个链接使用;并同时由action把这个形成的编码导航字符串解码处理成用户请求的页面上需要的导航字符串。 经过分析,我决定采用第(2 )种方法来实现层次导航。 具体的分析过程请见我上面提到的第一篇日记中的“我知道有2种方法可以解决这个问题 ”。
4 )进行层次导航系统的设计: (1)、进行页面节点导航封装字符串格式的设计: 分隔符(用'a'作分隔符 )+页面节点类型(用一位字符代表,封装成一个页面类型常量类,用字符1-9,英文字符b-z和A-Z表示 )+页面参数ID(如显示目标的日记列表所需的目标ID,显示日记内容所需的日记ID )。如显示一篇日记的页面类型常量为字符:‘h’,那么,显示“解决:jsp页面中文显示问题”这篇日记内容(ID为292 )这一页面的封装字符串为:ah292; (2)、页面完整导航字符串的设计: 在下面的讨论中会用到的相关源文件: 负责封装学习日记所需导航的页面的类型常量(/WEB-INF/src/com/learndiary/website/PageTypeConsts.java ); 负责导航字符串封装的方法(/WEB-INF/src/com/learndiary/website/util/Pager.java中的public static String encodeNaviStr(String naviStr, char toPageType, String parameter) );
当用户提出一个新的页面请求,把当前页面中的完整的导航封装字符串和新的页面的类型和新请求页面的参数ID传给处理新页面显示前的Struts中的Action中,由Action调用一个方法,负责把这些参数组装成下一新页面所需的导航字符串。如:当前正显示的页面是“系统导航:所有目标>>我进行中的目标>>目标:一起学习Struts(MVC)我的日记列表”中的“目标:一起学习Struts(MVC)我的日记列表”(图例: ),你可以看到当点击“日记:解决:jsp页面中文显示问题”这篇日记中的“我要评论”的URL中的导航字符串为:“naviStr=a10a60a0167ah292”(图例中左下方红圈中 ); 当点击当前页面中的回到上级导航节点的链接时,如:“系统导航:所有目标>>我进行中的目标>>目标:一起学习Struts(MVC)我的日记列表”中的“我进行中的目标”,这时显示“我进行中的目标”页面的类型代码是‘6’,参数ID是“0”,那么以新页面的节点字符串(a60 )的类型代码‘6’搜索已有导航字符串,已经存在相同的类型代码,于是,就把导航字符串中这个节点和后面的所有节点删除得到字符串“a10”,再加上新页面的节点字符串为“a60”,得到显示“我进行中的目标”页面的导航字符串为“a10a60”,见例图:(使它在页面上显示导航条为“系统导航:所有目标>>我进行中的目标>>目标:一起学习Struts(MVC)我的日记列表>>日记:解决:jsp页面中文显示问题”(见图例: )(路径1) 在检索模块中,导航条为:所有目标>>检索>>检索日记列表>>日记内容;(图例: ) (路径3) 在上一条、下一条的导航中,层次导航条除了上一条目和下一条目内容的改变,其余是不会变的;而且,要得到当前条目的前后条目的ID,在不同的层次导航中是不同的。例如:在上面的路径1中,得到日记列表的查询条件是本目标下的所有日记;在路径2中,得到日记列表的查询条件是检索页面的条件组合;在路径3中,得到日记列表的查询条件是本目标下的用户的所有日记。而且,在学习日记的设计中,这三种情况的排序方式是分开的,可以由用户在浏览时自选的。 为了得到不同层次导航下的上一条、下一条URL导航字符串,我在页面导航URL字符串产生工具类Pager((/WEB-INF/src/com/learndiary/website/util/Pager.java)中用了3个重载的、用于产生上一条、下一条导航URL字符串的方法来征对不同的三种情况(与前面说的3种路径不是一一对应的),分别是: 1>、(包括对这一路径下的目标列表中目标的浏览和目标的日记列表的浏览,和检索目标列表中目标的浏览,和检索日记列表中日记的浏览):public static String getPreNextNaviStr( char toPageType, String url, HttpServletRequest request, String naviStr, int currentID, String condition,int orderType, int direction) throws Exception。输入参数是:toPageType-请求的页面类型,url-请求的页面的“/***Action.do”路径,request-请求对象,naviStr-当前页面的导航封装字符串,currentID-当前条目的ID,condition-查询的where子句,orderType-排序类型,direction-排序方向。为了分离数据库访问的代码,在这个方法中调用了一个直接访问数据库的类(/WEB-INF/src/com/learndiary/website/db/PageDB.java)中的方法(public int[] getAdjacentIDs(String tableName, int currentID, String condition, int orderType, int direction) throws Exception )来得到含有前一条、当前、后一条帖子ID的数组; 2>、(包括对检索日记列表的日记所在目标、日记所在目标下的日记列表的浏览):public static String getPreNextNaviStr(String url, HttpServletRequest request, String naviStr, int searchDiaryID, String condition,int orderType, int direction) throws Exception。输入参数是:url-请求的页面的“/***Action.do”路径,request-请求对象,naviStr-当前页面的导航封装字符串,searchDiaryID-搜索日记列表中当前日记的ID,condition-查询的where子句,orderType-排序类型,direction-排序方向。为了分离数据库访问的代码,在这个方法中调用了一个直接访问数据库的类(/WEB-INF/src/com/learndiary/website/db/PageDB.java)中的方法(public int[] getAdjacentIDs(String tableName, int currentID, String condition, int orderType, int direction) throws Exception )来得到含有前一条、当前、后一条帖子ID的数组; 3>、(包括对进行中的目标、退出的目标、完成的目标列表中目标的浏览):public static String getPreNextNaviStr(String url, HttpServletRequest request, String naviStr, int userID, int currentID, int myGoalTypeFlag, int orderType, int direction) throws Exception。输入参数是:url-请求的页面的“/***Action.do”路径,request-请求对象,naviStr-当前页面的导航封装字符串,userID-当前用户ID,currentID-当前条目的ID,myGoalTypeFlag-用户目标的类型(进行、退出、或者完成),orderType-排序类型,direction-排序方向。为了分离数据库访问的代码,在这个方法中调用了一个直接访问数据库的类(/WEB-INF/src/com/learndiary/website/db/PageDB.java)中的方法(public int[] getAdjacentIDs(int userID,int currentID, int myGoalTypeFlag, int orderType, int direction) throws Exception)来得到含有前一条、当前、后一条帖子ID的数组; 现在的问题是怎么样来区别上面1>、2>、3>中列出的各种情况,并调用对应的“getPreNextNaviStr”方法来产生正确的上一条、下一条的URL导航字符串呢? 我的答案是根据当前页面的封装导航字符串来确定,我通过分析上面1>、2>、3>中列出的各种情况的导航字符串的特征码,然后在程序中通过检索特征码来确定当前页面的层次导航位置(对应于上面不同的几种情况),这是一个烦琐的过程,这里仅列举一二: 例如:在检索目标的列表中的目标页面中的封装导航串一定会含有“ae”两个字符,且帖子类型为“目标”;在检索日记的列表中的日记页面中的封装导航串一定会含有“ad”两个字符,且帖子类型为“日记”。
(2)、确定在哪几个Struts的Action中需要处理上一条、下一条导航: 1)、首先,需要导航的地方有三种情况: 1>、目标内容 2>、日记内容 3>、目标的日记列表 2)、 1>、1)的1>和2>需要放在DisGoalContentAction.java(/WEB-INF/src/com/learndiary/website/action/disgoal/DisGoalContentAction.java)中处理; 2>、1)的3>需要分在几个地方处理,分别是: 1>检索目标的日记列表,所有目标的日记列表,进行、完成、退出的日记列表:在DiaryAction.java(/WEB-INF/src/com/learndiary/website/action/disdiary/DiaryAction.java)中处理; 2>进行、完成、退出的我的日记列表:在MyDiaryAction.java(/WEB-INF/src/com/learndiary/website/action/mydiaries/MyDiaryAction.java)中处理;
(3)、在页面上显示上一条、下一条导航的URL字符串; 在需要上一条、下一条导航的页面上,把从request中获得的属性“preNextNavigation”显示在页面的右上部和右下部就行了。 上一条、下一条导航完整的设计分析过程见我上面提到的第二篇日记:“分析学习日记横向导航及开几个窗口的思路”(http://www.learndiary.com/disDiaryContentAction.do?searchDiaryID=&goalID=1167&naviStr=a10a2506ah1128 )。
3、父帖与子帖列表的双向导航的实现过程:
1)列出导航需求列表: (1)“目标<──>目标下的所有日记列表”的双向导航; (2)“目标<──>目标下我的日记列表”的双向导航;
2)分析实现双向导航所需的参数: 只需要目标的ID或日记列表的目标ID和导航封装字符串;
3)确定实现方法: 直接在Action中得到当前目标的ID或日记列表的目标ID和导航封装字符串,附在“***Action.do?”的后面就行了。
4)进行双向导航的设计: (1)、确定在哪几个Struts的Action中需要处理双向导航: 1>、在“目标──>目标下的所有日记列表”和“目标──>目标下我的日记列表”的导航,需要放在DisGoalContentAction.java(/WEB-INF/src/com/learndiary/website/action/disgoal/DisGoalContentAction.java)中处理; 2>、在“目标下的所有日记列表──>目标”的导航,需要放在DiaryAction.java(/WEB-INF/src/com/learndiary/website/action/disdiary/DiaryAction.java)中处理; 3>、在“目标下的我的日记列表──>目标”的导航,需要放在在MyDiaryAction.java(/WEB-INF/src/com/learndiary/website/action/mydiaries/MyDiaryAction.java)中处理; (2)、在页面上显示双向导航的URL字符串: 在需要“目标──>目标下的所有日记列表”和“目标──>目标下我的日记列表”的导航的页面上,把从request中获得的属性“jumpToViewDiaries”显示在页面的右上部和右下部;在“目标下的所有日记列表──>目标”和“目标下的我的日记列表──>目标”的导航中,把从request中获得的属性“jumpToViewGoal”显示在页面的右上部和右下部就行了。
四、总结: 1、心得: 1)、编码前的分析设计是非常重要的,这一步工作做好了,编码就很容易了(但我还做得不够,如下面的第3)条心得。可是,也许是我的水平有限,有些应该放在设计时的工作不到编码的时候就是想不到,还望大家给予指点); 2)、Struts框架把程序的逻辑实现代码和页面显示部分能比较好的分离,有利于功能模块的新增和程序的后期维护; 3)、在进行类和类的方法的设计时(如前面提到的Pager类和PageDB类),没有先进行完整的高层设计,是采用边编码边设计的方式,致使类的设计不够面向对象,给后期的理解和维护会造成困难; 4)、如果这种导航设计思路真的有用,有必要把它进行精心的设计,做成插件的形式,这样可以把它方便的应用于需要这种动态导航的各种java的web程序中;
2、这种动态导航的优点: 1)、能够极大的提高用户的浏览体验,使网站的导航更符合逻辑和人们的思维习惯; 2)、能够无限的进行需要导航的页面的增加和减少,后期的维护代码少量增加就行了;
3、这种动态导航的缺点: 1)、实现过程较为复杂,牵涉的页面和逻辑代码较多,权衡实现的代价和收到的效果,真的值得吗?; 2)、动态导航能被用户的使用习惯接受吗?这是一个未知数; 3)、还有什么缺点呢?暂时还没有想出来,大家帮我们想一想吧。
4、愿望: 1)、希望朋友们能够对学习日记开发小组提出的这种动态导航技术展开充分的讨论,论证这种导航方式的可行性; 2)、希望与软件相关(尤其是java )、学习相关(尤其是计算机软件自学 )、开源相关(尤其是java开源 )的网站结为友站关系并交换网站链接,共谋发展。我们的链接代码(请直接复制代码,我们的新logo正在酝酿中 ):图片链接:“<a href="http://www.learndiary.com" target=_blank><img border=0 src="http://www.learndiary.com" target=_blank>学习日记</a>”。如果,你愿意与我们交换链接,请把你的链接代码发至:“mdx-xx@tom.com”。
五、联系方式: 1、在学习日记网站对这篇文章进行评论(需要注册并登录):http://www.learndiary.com/disDiaryContentAction.do?searchDiaryID=&goalID=1255&naviStr=a10a2167 2、在学习日记网站留言(不需登录,但留言内容只有本站管理员才能查看与回复):http://www.learndiary.com/toWriteMessageAction.do?typeID=4&goalID=0&naviStr=a10af0 3、电子邮件:mdx-xx@tom.com 4、QQ:81251712
六、附件 1、学习日记V0.9.0.4,简介:http://www.learndiary.com/disDiaryContentAction.do?goalID=1245&naviStr=a10a21 ; 下载地址:http://www.learndiary.com/download/learndiaryV0.9.0.4.war ; 2、学习日记系统ArgoUML建模(学习日记V0.9.0.4反向工程类图),下载地址:http://www.learndiary.com/download/learndiaryV0.9.0.4.zargo ; 用ArgoUML V0.20.alpha2打开,ArgoUML下载地址:http://argouml-downloads.tigris.org/nonav/argouml-0.20.ALPHA_2/ArgoUML-0.20.ALPHA_2.zip )。
-全文完 作者:学习日记开发小组(http://www.learndiary.com ,http://develop.learndiary.com )
|