(2)加载用户定制页面的第二种方案
其实在前文(一)已经提到了,把页面、模块、布局都抽象成组件类,就如同Jsp标签一样
这种标签类负责输出每个模块的内容,当然包含了业务数据在里面,具体参考一下几个类
大家会发现:在每个组件类中都强制实现toString方法,其实就是使用FreeMarker的功能,每个组件类都有属性对应ftl模板,并且又有数据属性
所以使用FreeMarker的process方法,传入ftl模板和数据既可以把布局、模板都输出来
这才是为什么设计布局、模块表结构时,为什么要记录其对应的ftl模板路径
当然:要注意一点,因为所有ftl模板是已经定制好的,放在某个固定的目录,可以在系统启动之后先一次加载完所有模板
那么每个组件类只要传入(模板所在路径+数据)即可得到页面
(相信使用过FreeMarker来模拟Hibernate或者MyBatis来做动态SQL的同学都明白我的意思)
所有组件的父类:BaseTag
/** * */ package com.yli.cshop.bean; /** * * 把页面/布局/模块都抽象成一个个组件,就好比Jsp标签一样<br> * BaseTag 则是所有组件的父类,强制子类重写toString方法 * * @author yli * */ public abstract class BaseTag { /** * 重写toString()方法 */ public abstract String toString(); }
负责输出页面的组件类:Page
package com.yli.cshop.bean; import java.util.List; /** * 页面可以认为是所有组件的父容器<br> * 它可以包含有固定部分和动态布局的部分<br> * * @author yli * */ public class Page extends BaseTag { /** * 页面ID-系统生成的ID */ private long pageId; /** * 页面名称-用户可自由添加页面,定义页面名称 */ private String pageName; /** * 页面序号-用户可以自由拖动页面排序,记录该序号 */ private int sortNo; /** * 当前页面包含的布局列表<br> * 布局又包含了模块,所以组成了完整的页面 */ private List<Layout> layoutList; public long getPageId() { return pageId; } public void setPageId(long pageId) { this.pageId = pageId; } public String getPageName() { return pageName; } public void setPageName(String pageName) { this.pageName = pageName; } public List<Layout> getLayoutList() { return layoutList; } public void setLayoutList(List<Layout> layoutList) { this.layoutList = layoutList; } public int getSortNo() { return sortNo; } public void setSortNo(int sortNo) { this.sortNo = sortNo; } public String generateHtml() { return null; } @Override public String toString() { if (null != layoutList) { StringBuffer buffer = new StringBuffer(); for (Layout layout : layoutList) { buffer.append(layout.toString()); } return buffer.toString(); } return null; } }
负责输出布局的组件类:Layout
package com.yli.cshop.bean; import java.util.List; import com.yli.cshop.util.FreeMakerParser; /** * 布局可以认为是模块的父容器<br> * 用户可将模块自由添加进来,并对模块排序<br> * 布局其实也可以看做模块,只是它不负责具体的业务内容而已<br> * * @author yli * */ public class Layout extends BaseTag { /** * 布局ID-系统生成的ID */ private long layoutId; /** * 所属页面ID-建立页面与布局的关系 */ private long pageId; /** * 布局开始html标签-对应一个FreeMarker模板地址 */ private String beginTemplate; /** * 布局结束html标签-对应一个FreeMarker模板地址 * */ private String endTemplate; /** * 布局序号-用户可在页面添加多个布局,并自由排序,记录该序号 */ private int sortNo; /** * 当前布局包含的模块列表 */ private List<BaseModule> moduleList; public long getLayoutId() { return layoutId; } public void setLayoutId(long layoutId) { this.layoutId = layoutId; } public long getPageId() { return pageId; } public void setPageId(long pageId) { this.pageId = pageId; } public String getBeginTemplate() { return beginTemplate; } public void setBeginTemplate(String beginTemplate) { this.beginTemplate = beginTemplate; } public String getEndTemplate() { return endTemplate; } public void setEndTemplate(String endTemplate) { this.endTemplate = endTemplate; } public int getSortNo() { return sortNo; } public void setSortNo(int sortNo) { this.sortNo = sortNo; } public List<BaseModule> getModuleList() { return moduleList; } public void setModuleList(List<BaseModule> moduleList) { this.moduleList = moduleList; } @Override public String toString() { StringBuffer buffer = new StringBuffer(); String beginHtml = FreeMakerParser.process(beginTemplate, null); String endHtml = FreeMakerParser.process(endTemplate, null); buffer.append(beginHtml); for (BaseModule module : moduleList) { buffer.append(module.toString()); } buffer.append(endHtml); return buffer.toString(); } }
特别注意:负责输出模块的组件类:BaseModule
package com.yli.cshop.bean; import com.yli.cshop.util.FreeMakerParser; /** * 虽然模块和布局一样,负责输出一段html标签<br> * 但是模块是负责各自业务模块的,比如有[公告信息模块][轮播广告模块]<br> * 意味着每个模块还会关联各自的业务内容,即关联到业务表<br> * 但是每个业务模块要[保存/更新/删除]的业务内容:所对应的的业务类、方法肯定不一样<br> * 所以把模块抽象出来的同时,把每个模块对应的业务类和业务内容定义成两个属性<br> * * @author yli * */ public class BaseModule extends BaseTag { /** * 模板ID-系统生成的ID */ private long moduleId; /** * 布局的ID-建立布局与模块的关系 */ private long layoutId; /** * 模块名称-用户可以自定义模块名称 */ private String moduleName; /** * 模块序号-用户可自由拖动模块,记录该模块在当前布局的序号 */ private int sortNo; /** * 模块对应的FreeMarker模板路径-因为每个模块"长的样子肯定不一样" */ private String moduleTemplate; /** * 模块包含的业务数据-不同的业务模块对应的数据和数据类型不一样 */ private Object moduleContent; /** * 模块对应的业务处理类-此处记录业务Bean名称即可 */ private String moduleServiceBean; public long getModuleId() { return moduleId; } public void setModuleId(long moduleId) { this.moduleId = moduleId; } public long getLayoutId() { return layoutId; } public void setLayoutId(long layoutId) { this.layoutId = layoutId; } public String getModuleName() { return moduleName; } public void setModuleName(String moduleName) { this.moduleName = moduleName; } public int getSortNo() { return sortNo; } public void setSortNo(int sortNo) { this.sortNo = sortNo; } public String getModuleTemplate() { return moduleTemplate; } public void setModuleTemplate(String moduleTemplate) { this.moduleTemplate = moduleTemplate; } public Object getModuleContent() { return moduleContent; } public void setModuleContent(Object moduleContent) { this.moduleContent = moduleContent; } public String getModuleServiceBean() { return moduleServiceBean; } public void setModuleServiceBean(String moduleServiceBean) { this.moduleServiceBean = moduleServiceBean; } @Override public String toString() { StringBuffer buffer = new StringBuffer(); String moduleInfo = FreeMakerParser.process(moduleTemplate, moduleContent); buffer.append(moduleInfo); return buffer.toString(); } }
所有组件类已经写好了,那么就准备布局、模块对应的FreeMarker模板文件即可,如下所示
我为页面、布局和每种模块都定制了一个ftl页面,放在classpath目录的conf目录下
为了做演示,在MySql数据库针对页面、布局和模块分别建表,并插入演示的数据,如下所示
页面:
页面与布局关系
布局与模块关系
模块与业务表关系,以公告资讯为例
现在数据都准备好了,就可以输出页面了,此处举例说明如何实现
package com.yli.cshop.service.impl; import java.util.HashMap; import java.util.List; import java.util.Map; import com.yli.cshop.bean.BaseModule; import com.yli.cshop.bean.Layout; import com.yli.cshop.bean.Page; import com.yli.cshop.service.BaseModuleService; import com.yli.cshop.service.PageService; import com.yli.sample.base.ServiceImplBase; public class PageServiceImpl extends ServiceImplBase implements PageService{ public Map<String, BaseModuleService> moduleServiceCache; public void setModuleServiceCache( Map<String, BaseModuleService> moduleServiceCache) { this.moduleServiceCache = moduleServiceCache; } public String loadPage(){ Map<String, Object> param = new HashMap<String, Object>(); param.put("pageId", 100001); // 根据页面ID查询Page对象 Page page = sopDalClient.queryForObject("com.yli.cshop.layout.query_page", param, Page.class); // 根据页面ID查询:该页面包含的布局列表,根据布局序号升序排序 List<Layout> layoutList = sopDalClient.queryForList("com.yli.cshop.layout.query_layout_list", param, Layout.class); if(null != layoutList && !layoutList.isEmpty()) { // 设置布局列表到页面对象中 page.setLayoutList(layoutList); // 根据布局ID查询:每个布局包含的模块列表,根据模块所在布局序号升序排序 for(Layout layout : layoutList) { param.put("layoutId", layout.getLayoutId()); List<BaseModule> moduleList = sopDalClient.queryForList("com.yli.cshop.layout.query_module_list", param, BaseModule.class); if(null != moduleList && !moduleList.isEmpty()) { // 设置模块列表到页面对象中 layout.setModuleList(moduleList); for(BaseModule module : moduleList) { // 查询每个模块应该包含的业务内容,此处处理比较复杂 BaseModuleService baseModuleService = moduleServiceCache.get(module.getModuleServiceBean()); Object moduleContent = baseModuleService.getModuleContent(module.getModuleId()); // 将模块的业务内容设置到模块实例中 module.setModuleContent(moduleContent); } } } } // toString() 其实就是使用FreeMarker的process方法达到[数据]+[模板]=[输出]的目的 // 这个pageContent就是整个页面的内容了,只要显示在前端即可 // 不管你用Jstl来读取,还是ajax来读取,都可以 String pageContent = page.toString(); System.out.println(pageContent); // 如果觉得组件类依赖了FreeMarker,被侵入,则也可以抽象出输出页面的工厂类 // page 就只作为保存[数据]和[模板]的功能来设计 // String pageContent = PageFactory.generatePage(page); return pageContent; } }
最后就是效果图了,很简答的一个
相关推荐
在探讨JAVA WEB JSF的设计参考方案以及页面布局策略时,我们深入分析了使用JSF进行高效页面设计的方法,尤其关注了两种主流布局方案:利用JSP的`include`功能和集成Tiles框架。这两种方法旨在解决Web应用中页面设计...
EasyUI提供了如"layout"这样的组件,可以快速构建复杂的页面布局,包括北(north)、南(south)、东(east)、西(west)和中心(center)五个区域,方便地实现页面的分栏和伸缩。例如,开发者可以通过以下代码创建...
在传统页面布局中,我们通常会利用相对定位和负外边距(margin)来实现。首先,将body和container元素设置为相对定位,然后将footer设置为绝对定位,并将其底部与容器底部对齐。这种方法适用于内容较少的情况。 ```...
优秀的页面布局是吸引用户、提升网站或应用可用性的重要因素。本文将深入探讨“页面设计”这一主题,特别关注国内主流网页和应用的设计布局,如QQ邮箱、谷歌页面以及360页面。 首先,页面布局是指在网页或应用中对...
2. 布局能力动态化:Tangram方案可以动态生成页面布局,提供了多种布局方式,如瀑布流布局、吸顶布局、固定布局等。 3. 组件业务化:Tangram方案将组件作为独立的业务单元,每个组件都可以独立开发、测试和维护。 4....
动态化方案涉及到的技术能够根据用户的浏览习惯、购买行为等数据动态调整页面布局和内容展示。 2. 组件化布局: 组件化布局是把页面划分为多个独立的、可复用的组件,每个组件都负责页面的一部分功能或显示内容。...
在网页设计领域,CSS3(层叠样式表第三版)是一种强大的工具,它极大地扩展了网页的视觉表现力,同时也提供了更加灵活的页面布局解决方案。本资源“CSS3自适应浏览器页面框架布局代码”旨在帮助开发者创建适应各种...
flexible.js是淘宝提出的解决方案的核心部分,它的作用是动态调整HTML的`<meta name="viewport">`标签以及页面的根元素`<html>`的`font-size`属性,从而实现基于设备宽度的自适应布局。将flexible.js的代码复制到...
在网页设计中,页面布局与滚动条是两个关键的元素,它们共同决定了用户在浏览网站时的体验。页面布局有滚动条,意味着网页的内容超过了单屏显示的范围,需要通过滚动来查看全部信息。这样的设计既有可能是由于内容...
3. **自适应性**:根据不同屏幕尺寸自动调整页面布局。 #### 方案实施 为实现上述目标,本文推荐使用**REM与VW布局相结合**的方式: 1. **REM布局**:基于`html`元素的`font-size`定义元素尺寸,能够实现页面元素...
### 响应式Web页面布局 #### 一、响应式设计意味着什么? 响应式设计(Responsive Web Design,简称RWD)是一种使网站能够根据用户所使用的设备屏幕尺寸自动调整其布局的技术。它允许设计师和开发者创建出能够在...
在JavaScript中实现动态UI布局是..."UIGrid"可能是一个自定义的解决方案,通过结合JavaScript和CSS Grid来高效地管理页面元素的布局。通过这种方式,我们可以创建出适应性强、交互丰富的用户界面,满足各种需求和场景。
- **Fancy布局**:使用fancyhdr宏包实现更加复杂和美观的页面布局。 - **书本样式的布局**:设计适合书籍出版的页面布局,包括调整页面方向、边距等。 #### 六、处理浮动对象 - **浮动对象的放置**:使用`\begin{...
通过快速原型工具,可以快速构建出不同版本的页面布局,进行用户测试,找出最佳设计方案。页面布局.rplib可能是某种快速原型工具保存的库文件,包含了多种预设的布局模板,方便产品经理在设计过程中直接引用和修改。...
这两个文件是jQuery Layout插件的组成部分,它提供了一个强大的、可自定义的页面布局解决方案。通过这个插件,开发者可以轻松地创建多区域、响应式的页面布局。`jquery.layout.js`是未压缩的版本,适合调试和学习,...
本资料包提供了40种不同的XHTML页面布局方案,旨在帮助开发者掌握多样化的网页设计技巧。 首先,我们要理解XHTML的基本结构,它由一系列的元素(tags)组成,这些元素通过嵌套和组合来构建网页的结构。例如,`...
在传统的Java Web开发中,我们通常会使用JSP(JavaServer Pages)配合JSTL(JavaServer Pages Standard Tag Library)或者其他模板引擎如FreeMarker或Thymeleaf来实现页面布局。然而,这些方法往往需要一定的学习...
当我们谈论“用CSS实现的控制页面布局”时,我们主要是指如何利用CSS技术来组织和定位页面上的不同元素,使其按照我们的设计意图进行展示。下面我们将深入探讨CSS布局的一些核心概念和技巧。 1. **盒模型**:CSS的...
这里我们主要探讨基于Eclipse开发环境的实现方案。 首先,自定义ViewGroup是基础。你需要创建一个继承自LinearLayout或RelativeLayout的自定义布局类,重写其onMeasure()和onLayout()方法。在onMeasure()中,你需要...
3. **JavaScript解决方案**:如果浏览器支持不理想,或者需要更复杂的动态调整,可以使用JavaScript来实现。例如,`jQuery同等高`(`sameHeight`)插件是一个常见的选择,它遍历元素,设置所有元素的高度为最大高度...