├── .gitignore ├── README.md ├── component.json ├── fis-conf.js ├── package.json ├── page ├── be │ ├── spring.vm │ └── variables.vm ├── doc │ ├── _standard.md │ ├── binding.vm │ ├── layout.vm │ ├── rewrite.vm │ ├── standard.vm │ └── widget.vm ├── examples │ ├── data.js │ ├── data.vm │ ├── form.js │ ├── form.vm │ ├── pagination.vm │ ├── partial.vm │ ├── partial │ │ └── form.vm │ └── spa │ │ ├── page1.vm │ │ ├── page2.vm │ │ ├── page3.vm │ │ └── spa.js ├── html │ └── index.html ├── index.vm ├── jsp │ ├── binding.jsp │ ├── index.jsp │ ├── layout.jsp │ ├── rewrite.jsp │ └── widget.jsp ├── layout │ ├── 2columns-with-left-sidebar.jsp │ ├── 2columns-with-left-sidebar.vm │ ├── 2columns-with-right-sidebar.jsp │ ├── 2columns-with-right-sidebar.vm │ ├── 3columns.jsp │ ├── 3columns.vm │ ├── ajax.vm │ ├── frame.jsp │ ├── frame.vm │ ├── front.jsp │ ├── front.vm │ ├── smart.vm │ └── spa.vm └── velocity │ ├── index.vm │ ├── jello.vm │ ├── tools.vm │ └── variables.vm ├── server.conf ├── static ├── favicon.ico ├── js │ ├── README.md │ ├── esl.js │ ├── mod-amd.js │ └── require.js ├── libs │ ├── README.md │ ├── _alert.tmpl │ ├── _confirm.tmpl │ ├── _modal.tmpl │ ├── alert.js │ ├── confirm.js │ ├── modal.js │ ├── scrollspy.js │ └── validator.js └── scss │ ├── _callout.scss │ ├── _highlight.scss │ ├── _modal.scss │ ├── global.scss │ └── normalize.css ├── test ├── data │ ├── ajax.jsp │ ├── data.json │ └── saveform.json ├── delay.jsp ├── mock.jsp ├── page.json ├── page.jsp └── page │ ├── doc │ ├── layout.json │ └── standard.json │ ├── examples.json │ ├── examples │ ├── data.json │ ├── pagination.json │ └── partial.json │ └── index.json └── widget ├── footer ├── footer.jsp └── footer.vm ├── header ├── header.jsp └── header.vm └── sidebarmenus ├── sidebarmenus.js ├── sidebarmenus.jsp ├── sidebarmenus.scss └── sidebarmenus.vm /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | /.idea 4 | 5 | /components 6 | 7 | /node_modules 8 | 9 | /output 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | jello-demo 2 | ========== 3 | 4 | Jello demo & doc, you can [preview this online](http://oak.baidu.com/jello-demo/). 5 | 6 | ## 如何使用 7 | 8 | 1. 安装 jello 9 | 10 | ``` 11 | npm install -g jello 12 | ``` 13 | 2. 安装插件 14 | 15 | ``` 16 | npm install -g fis-parser-marked 17 | npm install -g fis-parser-utc 18 | npm install -g fis-parser-sass 19 | npm install -g fis-packager-depscombine 20 | npm install -g fis-postprocessor-amd 21 | ``` 22 | 3. git clone 下来此仓库 23 | 24 | ``` 25 | git clone https://github.com/2betop/jello-demo.git 26 | ``` 27 | 4. 进入 jello-demo 目录后 安装 components 28 | 29 | ``` 30 | cd jello-demo 31 | jello install 32 | ``` 33 | 5. 进入当前目录后发布代码 34 | 35 | ``` 36 | jello release 37 | jello server start 38 | ``` 39 | 6. 自动打开浏览器预览页面 40 | -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "protocol": "github", 3 | "gihub": { 4 | "author": "fis-components" 5 | }, 6 | "dependencies": [ 7 | "jquery@^1.9.1", 8 | "bootstrap@3", 9 | "compass-mixins", 10 | "font-awesome", 11 | "jquery-ui", 12 | "jquery-validation" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /fis-conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | // -------------------------------- 4 | // 支持 amd 设置 5 | // -------------------------------- 6 | fis.config.set('modules.postprocessor.vm', 'amd'); 7 | fis.config.set('modules.postprocessor.js', 'amd'); 8 | fis.config.set('modules.postprocessor.jsp', 'amd'); 9 | fis.config.set('settings.postprocessor.amd', { 10 | 11 | packages: [ 12 | 13 | // 用来存放 libs 库 14 | { 15 | name: 'libs', 16 | location: 'static/libs/', 17 | main: 'index' 18 | } 19 | ] 20 | }); 21 | 22 | // -------------------------------- 23 | // sass/scss 配置 24 | // -------------------------------- 25 | 26 | fis.config.set('modules.parser.sass', 'node-sass'); 27 | fis.config.set('modules.parser.scss', 'node-sass'); 28 | // 设置 sass 的 include_paths 便于组件引入 29 | fis.config.set('settings.parser.node-sass.include_paths', [ 30 | './static/scss', 31 | './components/compass-mixins' 32 | ]); 33 | 34 | // 使用 depscombine 是因为,在配置 pack 的时候,命中的文件其依赖也会打包进来。 35 | fis.config.set('modules.packager', 'depscombine'); 36 | 37 | fis.config.set('pack', { 38 | 39 | // css 40 | 'pkg/frame.css': ['page/layout/frame.vm'], // 因为依赖会被打包,所以这个规则会把 frame.vm 依赖的 css 打包在一起。 41 | 42 | // js 43 | // 依赖也会自动打包进来。 44 | 'pkg/boot.js': ['static/js/require.js', 'components/jquery/jquery.js', 'components/bootstrap/js/bootstrap.js'], 45 | 'pkg/app.js': ['page/examples/form.js'] 46 | }); 47 | 48 | 49 | fis.config.set('roadmap.path', [ 50 | 51 | { 52 | reg: /^\/components\/.*\.(?:less|md)$/i, 53 | release: false 54 | }, 55 | 56 | { 57 | reg: 'doc/**.md', 58 | release: false 59 | }, 60 | 61 | { 62 | reg: /^\/static\/libs\/(.*\.js)$/i, 63 | isMod: true, 64 | release: '${statics}/${namespace}/libs/$1' 65 | } 66 | ].concat(fis.config.get('roadmap.path', []))); 67 | 68 | // markdown 支持,因为需要写文档,不用 markdown 真是不习惯 69 | // npm install -g fis-parser-marked 70 | // use the `fis-parser-marked` plugin to parse *.md file 71 | fis.config.set('modules.parser.md', 'marked'); 72 | // *.md will be released as *.html 73 | fis.config.set('roadmap.ext.md', 'html'); 74 | 75 | // js 模板支持 76 | fis.config.set('modules.parser.tmpl', 'utc'); 77 | // fis.config.set('roadmap.ext.tmpl', 'js'); 78 | // 79 | 80 | // 当通过 jello server start --webapp jello-demo 81 | // 设置其他 context path 的时候用。 82 | fis.config.set('roadmap.domain', '/jello-demo'); 83 | 84 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jello-demo", 3 | "version": "0.1.0", 4 | "description": "jello-demo", 5 | "keywords": [ 6 | "jello-demo" 7 | ], 8 | "license": "MIT" 9 | } 10 | -------------------------------------------------------------------------------- /page/be/spring.vm: -------------------------------------------------------------------------------- 1 | #set($pageTitle="spring 整合") 2 | #extends("/page/layout/2columns-with-left-sidebar.vm") 3 | 4 | #block("sidebar") 5 | #widget("/widget/sidebarmenus/sidebarmenus.vm" "var:menus=$menus.get(4).children") 6 | #end 7 | 8 | #block("content") 9 | 10 |

后端一般都是使用 spring 来开发,所以这里给出 spring 集成方式,其他运行模式请参考。关于 spring 整合的 demo 可以看这里

11 | 12 |

对于后端来说,只需关心前端输出的模板文件、静态资源和 map json文件。

13 | 14 |

默认的输出路径是:

15 | 16 | 21 | 22 |

为了让 velocity 能正常渲染模板,需要设置模板目录,以及将 fis 提供的自定义 diretives 启动。 23 | 配置内容如下:

24 | 25 |
<bean id="velocityConfigurer" class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
26 |     <property name="resourceLoaderPath" value="/WEB-INF/views/"/>
27 |     <property name= "velocityProperties">
28 |         <props>
29 |             <prop key="input.encoding">utf-8</prop>
30 |             <prop key="output.encoding">utf-8</prop>
31 |             <!--启用 fis 提供的自定义 diretives 启动-->
32 |             <prop key="userdirective">com.baidu.fis.velocity.directive.Html, com.baidu.fis.velocity.directive.Head, com.baidu.fis.velocity.directive.Body, com.baidu.fis.velocity.directive.Require, com.baidu.fis.velocity.directive.Script, com.baidu.fis.velocity.directive.Style, com.baidu.fis.velocity.directive.Uri, com.baidu.fis.velocity.directive.Widget, com.baidu.fis.velocity.directive.Block, com.baidu.fis.velocity.directive.Extends</prop>
33 |         </props>
34 |     </property>
35 | </bean>
36 | 
37 | 38 |

为了让 fis 自定义的 directive 能够正常读取 map.json 文件,需要添加一个 bean 初始化一下。

39 | 40 |
<!--初始 fis 配置-->
41 | <bean id="fisInit" class="com.baidu.fis.velocity.spring.FisBean" />
42 | 
43 | 44 |

默认 map json 文件是从 /WEB-INF/config 文件夹下读取的,如果有修改存放地址,则需要添加一个 fis.properties 文件到 /WEB-INF/ 目录。 45 | 内容如下:

46 | 47 |
# 相对与 WEB-APP 根目录。
48 | mapDir = /velocity/config
49 | 
50 | 51 |

fis 框架代码可以在此下载。所有代码开源在 github 上。

52 | 53 | 54 |

View Resolver 推荐配置

55 | 56 |
<bean id="viewResolver" class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
57 |         <property name="cache" value="true"/>
58 |         <property name="prefix" value=""/>
59 |         <property name="suffix" value=".vm"/>
60 |         <property name="cacheUnresolved" value="false" />
61 |         <property name="exposeSpringMacroHelpers" value="true"/>
62 |         <property name="contentType" value="text/html;charset=UTF-8" />
63 |         <property name="requestContextAttribute" value="request" />
64 |         <property name="exposeSessionAttributes" value="true" />
65 |         <property name="attributesMap">
66 |             <map>
67 |                 <entry key="esc"><bean class="org.apache.velocity.tools.generic.EscapeTool"/></entry>
68 |                 <entry key="render"><bean class="org.apache.velocity.tools.generic.RenderTool" /></entry>
69 |                 <entry key="link"><bean class="org.apache.velocity.tools.generic.LinkTool" /></entry>
70 |                 <entry key="context"><bean class="org.apache.velocity.tools.generic.ContextTool"/></entry>
71 | 
72 |                 <entry key="jello"><bean class="com.baidu.fis.velocity.tools.JelloTool" /> </entry>
73 |             </map>
74 |         </property>
75 |     </bean>
76 | 
77 | 78 |
79 |

注意

80 |

cacheUnresolved一定要设置成false,否则会影响前端分开部署。

81 |

另外这里只启用了部分 velocity tools, 其他 tools 请根据自己需求配置。

82 |
83 | 84 | #end## end of body 85 | 86 | ## 需要依赖一下自己,否则该 vm 中依赖没法自动加载进来。 87 | #require("./spring.vm") 88 | #end 89 | -------------------------------------------------------------------------------- /page/be/variables.vm: -------------------------------------------------------------------------------- 1 | #set($pageTitle="Spring 全局变量") 2 | #extends("/page/layout/2columns-with-left-sidebar.vm") 3 | 4 | #block("sidebar") 5 | #widget("/widget/sidebarmenus/sidebarmenus.vm" "var:menus=$menus.get(4).children") 6 | #end 7 | 8 | #block("content") 9 | 10 |

在 jello 模拟环境下,是有很些全局变量可以使用,整入到后端后不一定有,如何让 spring 开发这些变量?

11 | 12 |

${esc.d}request

13 |

在 spring 的 xml 配置中, viewResolver下设置<property name="requestContextAttribute" value="request" />

14 | 15 |

${esc.d}session

16 |

在 spring 的 xml 配置中, viewResolver下设置<property name="exposeSessionAttributes" value="true" />

17 | 18 |

${esc.d}rc

19 | 20 |

在 spring 的 xml 配置中, viewResolver下设置<property name="exposeSpringMacroHelpers" value="true"/>

21 | 22 |

设置了这个后,$!{esc.d}rc 指向一个 RequestContext(名为springBindRequestContext)对象,通过他可以处理表单和验证错误信息,另外利用$!{esc.d}{rc.getMessage("user.name")}读取/WEB-INF/classes/messages.properties本地化信息

23 | 24 |

批量加全局变量

25 |

在 controller 往 ModelMap 可以添加变量,但是如何让所有的 controller 都加上某些全局变量?

26 | 27 |

可以让所有的 controller 都基于某个基类, 然后在基类上,设置 ModelAttribute 变量即可。

28 | 29 |
public abstract class Base {
30 |     @Autowired
31 |     @Qualifier("appConfig")
32 |     protected AppConfig appConfig;
33 | 
34 |     @ModelAttribute("previewServer")
35 |     public String getPreviewServer() {
36 |         return appConfig.getImagePreivewUrl();
37 |     }
38 | }
39 | 
40 | 41 |

以上的例子,给所有 vm 都添加了 ${esc.d}previewServer 变量。

42 | 43 | #end## end of body 44 | 45 | ## 需要依赖一下自己,否则该 vm 中依赖没法自动加载进来。 46 | #require("./variables.vm") 47 | #end 48 | -------------------------------------------------------------------------------- /page/doc/_standard.md: -------------------------------------------------------------------------------- 1 | 在 jello 中开发项目需要遵循一定的目录规范。 2 | 3 | 一个完整的项目包括两部分 4 | - 前端部分:包含图片、样式、脚本、模板等一列前端资源。 5 | - 后端部分:各类 java 资源 jar 配置项文件等。 6 | 7 | jello 的开发模式是让前端同学主要负责前端部分,编写 velocity 模板文件、js、css等,脱离后端环境直接在 jello 的环境里面运行。 8 | 后端同学主要负责后端数据的获取和页面渲染逻辑,合并前端编译产出,完成整个项目的开发。 9 | 10 | 这样的好处是只要制定好数据格式便可以并行开发。 11 | 12 | 13 | ## 前端目录 14 | 15 | ``` 16 | ├── fis-conf.js 17 | ├── page 18 | │   ├── doc 19 | │   ├── examples 20 | │   ├── index.vm 21 | │   └── layout 22 | ├── server.conf 23 | ├── static 24 | │   ├── favicon.ico 25 | │   ├── js 26 | │   ├── libs 27 | │   └── scss 28 | ├── test 29 | │   ├── page 30 | │   ├── page.json 31 | │   └── page.jsp 32 | └── widget 33 | ├── footer 34 | ├── header 35 | └── sidebarmenus 36 | ``` 37 | 38 | ### 说明 39 | 40 | * `fis-conf.js` 用来设置 fis 编译配置。 41 | * `page` 用来存放各类页面级模板文件(.vm), 可以直接在jello 环境下预览。 42 | - `layout` 骨架 vm 43 | * `static` 用来存放各类静态资源,如:JS、CSS、image、swf... 44 | * `test` 用来存放各类测试数据 45 | * `widget` 用来存放页面小片段,方便其他页面引用。 46 | 47 | ## 产出目录 48 | 49 | ``` 50 | ├── WEB-INF 51 | │   ├── config 52 | │   │   └── map.json 53 | │   ├── views 54 | │   │   ├── page 55 | │   │   └── widget 56 | ├── static 57 | └── test 58 | ``` 59 | 60 | 将 jello 前端模块编译产出目录如上 61 | 62 | * `WEB-INF/config/map.json` 静态资源表,便于后端定位静态资源。 63 | * `WEB-INF/views` velocity 模板文件 64 | * `static` 静态资源,如果静态资源部署到了cdn 服务器上,后端环境则不需要此文件夹 65 | * `test` 用于本地环境调试,后端程序无需关注。 66 | 67 | 68 | -------------------------------------------------------------------------------- /page/doc/binding.vm: -------------------------------------------------------------------------------- 1 | #set($pageTitle="数据绑定") 2 | #set($title="数据绑定") 3 | 4 | #extends("/page/layout/2columns-with-left-sidebar.vm") 5 | 6 | #block("sidebar") 7 | #widget("/widget/sidebarmenus/sidebarmenus.vm" "var:menus=$menus.get(0).children") 8 | #end 9 | 10 | #block("content") 11 |

jello 前端项目,只负责模板文件的编写,不负责数据的获取,但是为了让页面能够正常预览,需要提供一种机制,让模板自动关联数据,这样只要前期制定了数据格式,前端完全是可以后端独立开发的。

12 | 13 |

那么,我们其实只用关心数据格式,而不用关心数据的真实性。我们完全可以通过 json 文件指定静态数据即可。

14 | 15 |

如何指定?

16 | 17 |

page 目录下的每个模板页面都会自动绑定上 test 目录下同名的 json 数据。

18 | 19 |

test/page/index.json

20 | 21 |
{
22 |     "title": "This is title.",
23 |     "subtitle": "This is subtitle."
24 | }
25 | 
26 | 27 |

page/index.vm

28 |
<h1>$title</h1>
29 | <h2>$subtitle</h2>
30 | 
31 | 32 |

那么页面预览输出的结果是

33 | 34 |
<h1>This is title.</h1>
35 | <h2>This is subtitle.</h2>
36 | 
37 | 38 |

如何解决公用数据问题?

39 |

页面多了,自然有公用的数据,如果你的数据都是通过 ajax 页面获取,那还好,通过 rewrite 转发就能每次获取同样的数据,如果是模板数据,则需要每次都不听的拷贝。

40 | 41 |

这样确实太麻烦了,如是,我们提供了这么一种机制。如果预览的页面地址是 /page/A/B/C.vm, 这个文件的 json 绑定,可以是多个,顺序为:

42 | 43 | 49 | 50 |

多个文件的 json 值会叠加进来。

51 | 52 |

如何动态绑定数据

53 | 54 |

json 绑定的是静态数据,如果需要绑定动态的数据,可以通过 jsp 方式绑定,绑定规则是一样的,且绑定时机在 json 绑定之后,可以针对动态数据,在 jsp 中补充。

55 | 56 |
<%
57 | request.setAttribute("title", "Welcome to jello.");
58 | %>
59 | 60 | 61 | 62 |
63 |

注意

64 |

65 | 在 jsp 中,任何输出都是没有意义的,你可以输出,但是输出没有任何作用,只能给 context 添加数据。 66 |

67 |
68 | #end 69 | 70 | ## 需要依赖一下自己,否则该 vm 中依赖没法自动加载进来。 71 | #require("./binding.vm") 72 | #end 73 | -------------------------------------------------------------------------------- /page/doc/layout.vm: -------------------------------------------------------------------------------- 1 | #set($layout = "#uri('/page/layout/2columns-with-left-sidebar.vm')") 2 | #set($param = $request.getParameter("layout")) 3 | 4 | #if ($param.equals("right")) 5 | #set($layout = "#uri('/page/layout/2columns-with-right-sidebar.vm')") 6 | #end 7 | 8 | #if ($param.equals("both")) 9 | #set($layout = "#uri('page/layout/3columns.vm')") 10 | #end 11 | 12 | #extends($layout) 13 | 14 | #block("sidebarSecondary") 15 | ## 注意只里面的 with:$sidebar 意思是将当前 sidebar 变量对象中的所有属性作为 widget 里面的局部变量。 16 | #widget("/widget/sidebarmenus/sidebarmenus.vm" "with:$sidebar") 17 | #end 18 | 19 | #block("sidebar") 20 | #widget("/widget/sidebarmenus/sidebarmenus.vm" "var:menus=$menus.get(0).children") 21 | #end 22 | 23 | #block("content") 24 |
25 |

模板继承思想来源于 smarty, 可以用来更好的复用代码。

26 | 27 |
28 |

Smarty 模板继承文档

29 |

如果之前没有接触过模板继承,请先一定要看下这个说明。

30 |
31 | 32 | 33 |

示例

34 | 35 |
36 |

先点击示例看效果

37 |

请切换下面这三个按钮看效果,同样的内容在不同的骨架下的效果。

38 | 43 |
44 | 45 |

没错,模板继承可以很方便的来定义具有容器功能的模板。子模板可以选择直接填充此类骨架类模板。

46 | 47 |

说明

48 | 49 |

layout.vm

50 |
<!DOCTYPE html>
 51 |   #html()
 52 | 
 53 |   #head()
 54 |     <meta charset="utf-8"/>
 55 |     <meta content="" name="description">
 56 |     <meta http-equiv="X-UA-Compatible" content="IE=edge">
 57 |     <title>Demo</title>
 58 |   #end 
 59 | 
 60 |   #body()
 61 |     <div id="wrapper">
 62 |       #block("body_content")
 63 |           This is body.
 64 |       #end
 65 |     </div>
 66 |   #end
 67 | #end 
 68 | 
69 | 70 |

以上示例通过${esc.h}block() 定义了一个区域body_content,那么在子模板中,就可以像以下方式填充它

71 | 72 |
#extends("layout.vm")
 73 |     #block("body_content")
 74 |     <h1>Hello Demo</h1>
 75 |     #end
 76 | #end
 77 | 
78 | 79 |
80 |

说明

81 |

以上示例,只需关心${esc.h}block()${esc.h}extends()标签,其他标签可以忽略

82 |
83 | 84 |

覆盖模式

85 | 86 |

默认,在子模板中同名 ${esc.h}block() 将会覆盖掉父模板中的同名 ${esc.h}block(),有时候,我们希望是扩充,而不是生硬的覆盖。

87 | 88 |

新版的 jello 扩充了一个新的 directive ${esc.h}parent() 通过它可以用来引用被覆盖的block

89 | 90 |
91 |
${esc.h}extends("layout.vm")
 92 |     ${esc.h}block("body_content")
 93 |     ${esc.h}parent()
 94 |     <h1>Hello Demo</h1>
 95 |     ${esc.h}end
 96 | ${esc.h}end
 97 | 
98 |
99 | 100 | 这样 body_content 的输出结果就是这样的。 101 | 102 |
This is body.
103 | 
104 | <h1>Hello Demo</h1>
105 |
106 | 107 |

局部变量

108 | 109 |

请直接查看 widget 中的局部变量用法,用法一样。传送门

110 |
111 | #end 112 | 113 | ## 需要依赖一下自己,否则该 vm 中依赖没法自动加载进来。 114 | #require("./layout.vm") 115 | #end 116 | -------------------------------------------------------------------------------- /page/doc/rewrite.vm: -------------------------------------------------------------------------------- 1 | #set($pageTitle="页面预览") 2 | #set($title="页面模拟") 3 | 4 | #extends("/page/layout/2columns-with-left-sidebar.vm") 5 | 6 | #block("sidebar") 7 | #widget("/widget/sidebarmenus/sidebarmenus.vm" "var:menus=$menus.get(0).children") 8 | #end 9 | 10 | #block("content") 11 |

在 page 目录下面的所有 vm 模板文件,只会有两类,一种是骨架模板,而另一种是入口模板。在 jello 环境下,所有入口模板都是可以直接预览的。他的预览规则为 /page/{{vm 路径}}

12 | 13 |

比如: page 目录下面的 index.vm 文件,则可以通过 /page/index(.vm 可以省略)路径预览。

14 | 15 |
16 |

namespace 项目

17 |

如果当前项目设置了 namespace,则预览地址得加上 namespace 作为前缀。

18 |

假如 namespacehome,预览地址则为:/home/page/index

19 |
20 | 21 | ##
22 | ##

预览 HTML/JSP

23 | ##

jello 除了能预览 vm 页面,一些简单的 html、jsp 页面,也可以像预览 vm 页面一样预览,不同的是,不会自动关联上模拟数据

24 | ##

示例

25 | ##

简单 html 页面简单的 jsp 页面

26 | ##
27 | 28 |

页面转发

29 |

这里有个限制就是路径总是以 /page 打头, 且模板路径必须与访问路径一致。这肯定是不能接受的。

30 | 31 |

针对这个问题,我们的解决方法是,提供rewrite 机制。

32 | 33 |

在根目录下面新建一个 server.conf 文件,通过配置此文件来实现页面跳转。

34 | 35 |
# 首页
36 | rewrite \/$ /page/index
37 | 
38 | #用户查看页面
39 | rewrite \/user/view/(\d*)$ /page/user/view?id=$1
40 | 
41 | # home 页面跳转首页
42 | redirect \/home$ /page/index
43 | 
44 | 45 |

server.conf 中支持两种指令。

46 | 50 | 51 |

语法说明

52 |

{{指定}} {{匹配正则}} {{目标地址}}

53 |

server.conf 中,不是 rewrite 或者 redirect 打头的都被认为是注释。目标地址支持 $数字 来替换捕获结果。

54 | 55 |

jsp 页面转发

56 | 57 |

在 test 目录下面的 .jsp 文件,也是可以在直接在 jello 环境下预览的,如 test 目录下面的 file.jsp 文件可以通过 /test/file.jsp 路径预览。

58 | 59 |

如果再配合rewrite, 在 jello 环境下就可以模拟任何后端输出。

60 | 61 |
rewrite \/testpage$ /test/mock.jsp
62 | 
63 | 64 |
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
65 | <%
66 |     response.getWriter().write("hello world");
67 | %>
68 | 
69 | 70 |

示例

71 | 72 |

json 页面模拟

73 | 74 |

关于json 页面,其实可以用 jsp 页面模拟,如示例,也可以直接在预览 json 文件,只要此文件放在 test 目录下面。如示例。注意查看 jello-demo 项目下面的 test 目录和 server.conf 文件内容。

75 | #end 76 | 77 | ## 需要依赖一下自己,否则该 vm 中依赖没法自动加载进来。 78 | #require("./rewrite.vm") 79 | #end 80 | -------------------------------------------------------------------------------- /page/doc/standard.vm: -------------------------------------------------------------------------------- 1 | #extends("/page/layout/3columns.vm") 2 | 3 | #block("sidebar") 4 | #widget("/widget/sidebarmenus/sidebarmenus.vm" "var:menus=$menus.get(0).children") 5 | #end 6 | 7 | #block("sidebarSecondary") 8 | ## 注意只里面的 with:$sidebar 意思是将当前 sidebar 变量对象中的所有属性作为 widget 里面的局部变量。 9 | #widget("/widget/sidebarmenus/sidebarmenus.vm" "with:$sidebar") 10 | #end 11 | 12 | #block("content") 13 | ## 内嵌 markdown 文件,这是编译期做得事情。 14 |
15 | 16 |
17 | #end 18 | 19 | ## 需要依赖一下自己,否则该 vm 中依赖没法自动加载进来。 20 | #require("./standard.vm") 21 | #end 22 | -------------------------------------------------------------------------------- /page/doc/widget.vm: -------------------------------------------------------------------------------- 1 | #set($pageTitle="Widget") 2 | #set($title="Widget") 3 | 4 | #extends("/page/layout/2columns-with-left-sidebar.vm") 5 | 6 | #block("sidebar") 7 | #widget("/widget/sidebarmenus/sidebarmenus.vm" "var:menus=$menus.get(0).children") 8 | #end 9 | 10 | #block("content") 11 |

页面中比较常用的小的部分,可以抽离成widget,比如:登陆框、头部菜单、边栏菜单等等

12 | 13 |

抽离成小模板后,可以通过这样的方式引入页面的任意位置。

14 | 15 |
16 |
\#widget("widget/sidebarmenus/sidebarmenus.vm")
17 |
18 | 19 |

局部变量

20 | 21 |

除了模板复用外,有时候变量也需要复用,在通过 \#widget() 引入模板的时候,可以通过以下两种方式指定变量

22 | 23 | 27 | 28 |
#widget("widget/sidebarmenus/sidebarmenus.vm" "with:${esc.d}sidebar" "var:literal=字面量" "var:variable=${esc.d}sidebar")
29 | 
30 | 31 |
32 |

注意

33 |

对于复杂的数据格式,通过var可能不能直接创建,但是可以先借助\#set()先设置后,然后再设置给var

34 | 35 |
#set($numbers = [1, 2, 3]);
36 | 
37 | ${esc.h}${esc.h} 这样在 sidebarmenus.vm 模板里面,就可以使用 list 变量,而这个变量的值为数组 [1, 2, 3]
38 | #widget("widget/sidebarmenus/sidebarmenus.vm" "var:list=${esc.d}numbers")
39 | 
40 |
41 | 42 |

复杂用法

43 |

有时候 widget 是有填充需求的,实际上widget中也可以定义区块\#block(), 引用的时候使用\#extends()替代\#widget(),效果相同,但是具备划分区域,使用者具有填充功能。

44 | #end 45 | 46 | ## 需要依赖一下自己,否则该 vm 中依赖没法自动加载进来。 47 | #require("./widget.vm") 48 | 49 | #end 50 | -------------------------------------------------------------------------------- /page/examples/data.js: -------------------------------------------------------------------------------- 1 | require('bootstrap'); 2 | var $ = require('jquery'); 3 | var alert = require('libs/alert'); 4 | 5 | var app = module.exports = function(opt) { 6 | 7 | // from velocity data 8 | var vm = opt.vm; 9 | $(vm.btn).on('click', function() { 10 | alert('
' + JSON.stringify(vm.data, null, 4) + '
'); 11 | }); 12 | 13 | 14 | var json = opt.json; 15 | $(json.btn).click(function() { 16 | $(json.btn).button('loading'); 17 | 18 | $.ajax(json.remote) 19 | .then(function(response) { 20 | alert('

返回结果为:

' + JSON.stringify(response, null, 4) + '
'); 21 | }) 22 | .fail(function() { 23 | alert('加载失败', 'danger'); 24 | }) 25 | .always(function() { 26 | $(json.btn).button('reset'); 27 | }); 28 | }); 29 | 30 | 31 | var jsp = opt.jsp; 32 | $(jsp.btn).click(function() { 33 | $(jsp.btn).button('loading'); 34 | 35 | $.ajax(jsp.remote) 36 | .then(function(response) { 37 | alert('

返回结果为:

' + JSON.stringify(response, null, 4) + '
'); 38 | }) 39 | .fail(function() { 40 | alert('加载失败', 'danger'); 41 | }) 42 | .always(function() { 43 | $(jsp.btn).button('reset'); 44 | }); 45 | }); 46 | 47 | }; -------------------------------------------------------------------------------- /page/examples/data.vm: -------------------------------------------------------------------------------- 1 | #extends("/page/layout/2columns-with-left-sidebar.vm") 2 | 3 | #block("sidebar") 4 | #widget("/widget/sidebarmenus/sidebarmenus.vm" "var:menus=$menus.get(1).children") 5 | #end 6 | 7 | #block("content") 8 | 9 |

Js 数据来源,主要有两种

10 | 14 | 15 |

关于数据模拟,请查看此处文档说明

16 | 17 |

示例

18 | 19 | 20 |
21 |

页面模板数据

22 |

23 | 24 |

25 |
26 | 27 |
28 |

ajax 异步数据 - 通过 json 文件模拟

29 |

30 | 31 |

32 |
33 | 34 |
35 |

ajax 异步数据 - 通过 jsp 文件模拟

36 |

37 | 38 |

39 |
40 | 41 | 42 | #script() 43 | require(['./data'], function(app) { 44 | app({ 45 | vm: { 46 | btn: '#fromVm', 47 | 48 | // \$user 变量数据来源与 /test/page/examples/data.json 模拟。 49 | data: $jello.jsonEncode($user) 50 | }, 51 | 52 | json: { 53 | btn: '#fromJson', 54 | remote: '$!{request.contextPath}/json' 55 | }, 56 | 57 | jsp: { 58 | btn: '#fromJsp', 59 | remote: '$!{request.contextPath}/jspdata' 60 | } 61 | }); 62 | }); 63 | #end 64 | 65 | 66 | #end## end of body 67 | 68 | ## 需要依赖一下自己,否则该 vm 中依赖没法自动加载进来。 69 | #require("./data.vm") 70 | #end 71 | -------------------------------------------------------------------------------- /page/examples/form.js: -------------------------------------------------------------------------------- 1 | // 因为设置了 amd.packager 所以,可以直接通过 libs 引用 /widget/libs 下面的库 2 | require('libs/validator'); 3 | var $ = require('jquery'); 4 | 5 | module.exports = function(opt) { 6 | var theform = $(opt.selector); 7 | var btn = theform.find(':submit'); 8 | var action = theform.attr('action'); 9 | 10 | theform.validate({ 11 | submitHandler: function() { 12 | // 已经通过了验证 13 | btn.button('loading'); 14 | 15 | $ 16 | .ajax(action, { 17 | method: 'POST', 18 | data: theform.serialize() 19 | }) 20 | .then(function(response) { 21 | alert(response.message); 22 | btn.button('reset'); 23 | }); 24 | } 25 | }); 26 | }; -------------------------------------------------------------------------------- /page/examples/form.vm: -------------------------------------------------------------------------------- 1 | #set($pageTitle="基本表单验证") 2 | #extends("/page/layout/2columns-with-left-sidebar.vm") 3 | 4 | #block("sidebar") 5 | #widget("/widget/sidebarmenus/sidebarmenus.vm" "var:menus=$menus.get(1).children") 6 | #end 7 | 8 | #block("content") 9 | 10 |

此 demo 演示了,如何快捷方便的使用第三方库。

11 | 12 |
13 |
14 | 15 | 16 |
17 | 18 |
19 | 20 | 21 |
22 | 23 |
24 | 25 | 26 |
27 | 28 |
29 | 30 | 31 |
32 | 33 | 34 |
35 | 36 | #script() 37 | require(['./form'], function(app) { 38 | app({ 39 | selector: '#nodeForm' 40 | }); 41 | }); 42 | #end 43 | 44 | 45 | #end## end of body 46 | 47 | ## 需要依赖一下自己,否则该 vm 中依赖没法自动加载进来。 48 | #require("./form.vm") 49 | #end 50 | -------------------------------------------------------------------------------- /page/examples/pagination.vm: -------------------------------------------------------------------------------- 1 | #set($pageTitle="普通列表页面") 2 | #set($title="普通列表页面") 3 | #extends("/page/layout/2columns-with-left-sidebar.vm") 4 | 5 | #block("sidebar") 6 | #widget("/widget/sidebarmenus/sidebarmenus.vm" "var:menus=$menus.get(1).children") 7 | #end 8 | 9 | #block("content") 10 | 11 | 26 | 27 |
28 |

查询结果

29 | 30 | 46 | 47 | #if ($result.isEmpty()) 48 |

没有结果

49 | #else 50 | 55 | #end 56 |
57 | 58 | #end## end of body 59 | 60 | ## 需要依赖一下自己,否则该 vm 中依赖没法自动加载进来。 61 | #require("./pagination.vm") 62 | #end 63 | -------------------------------------------------------------------------------- /page/examples/partial.vm: -------------------------------------------------------------------------------- 1 | #extends("/page/layout/2columns-with-left-sidebar.vm") 2 | 3 | #block("sidebar") 4 | #widget("/widget/sidebarmenus/sidebarmenus.vm" "var:menus=$menus.get(1).children") 5 | #end 6 | 7 | #block("content") 8 | 9 |

当用 ajax 去请求一个页面的时候,往往只需要 html 片段、css、JS 部分。

10 | 11 |

示例

12 | 13 | 14 |
15 |

ajax 页面

16 |

17 | dialog打开 18 | 浏览器中打开 19 |

20 |
21 | 22 | 23 | #script() 24 | require(['jquery', 'libs/modal'], function($, modal) { 25 | $('#remoteBtn').on('click', function() { 26 | modal($('#remoteBtn').attr('href')); 27 | return false; 28 | }); 29 | }); 30 | #end 31 | 32 | 33 | #end## end of body 34 | 35 | ## 需要依赖一下自己,否则该 vm 中依赖没法自动加载进来。 36 | #require("./partial.vm") 37 | #end 38 | -------------------------------------------------------------------------------- /page/examples/partial/form.vm: -------------------------------------------------------------------------------- 1 | ## 如果页面是来自与 ajax 则使用 ajax 骨架 2 | ## 否则使用2栏布局 3 | #set($layout = "#uri('/page/layout/2columns-with-left-sidebar.vm')") 4 | #set($pageTitle="正常打开,会输出所有内容") 5 | #if ($request.getHeader("X-Requested-With").equals("XMLHttpRequest")) 6 | #set($layout = "#uri('/page/layout/ajax.vm')") 7 | #set($pageTitle="在 ajax 请求中只会显示一部分") 8 | #end 9 | 10 | #extends($layout) 11 | 12 | 13 | #block("content") 14 |
15 |
16 | 17 | 18 |
19 | 20 |
21 | 22 | 23 |
24 | 25 |
26 | 27 | 28 |
29 | 30 |
31 | 32 | 33 |
34 | 35 | 36 |
37 | #end## end of body 38 | 39 | 40 | #script() 41 | /* fis async 用 `fis async` 告诉编译器,这个 require 以异步方式加载,而不是当程序入口*/ 42 | require(['../form'], function(app) { 43 | app({ 44 | selector: '#nodeForm' 45 | }); 46 | }); 47 | #end##endscript 48 | 49 | #require("./form.vm") 50 | #end 51 | -------------------------------------------------------------------------------- /page/examples/spa/page1.vm: -------------------------------------------------------------------------------- 1 | #set($title = "单页示例") 2 | #set($pageTitle = "单页示例") 3 | #extends("/page/layout/smart.vm") 4 | 5 | #block("sidebar") 6 | #widget("/widget/sidebarmenus/sidebarmenus.vm" "var:menus=$menus.get(1).children") 7 | #end 8 | 9 | #block("content") 10 |
11 | 页面1 12 | 页面2 13 | 页面3 14 |
15 | 16 |

单页内容一

17 | 18 |
19 |

注意此处内容切换是不会刷新页面的。

20 |

请打开网络面板观察请求。

21 |
22 | #end 23 | 24 | #script() 25 | // fis async 26 | require(['./spa'], function(app) { 27 | app('#pager'); 28 | }); 29 | #end 30 | 31 | ## 需要依赖一下自己,否则该 vm 中依赖没法自动加载进来。 32 | #require("./page1.vm") 33 | #end 34 | -------------------------------------------------------------------------------- /page/examples/spa/page2.vm: -------------------------------------------------------------------------------- 1 | #set($title = "单页示例") 2 | #set($pageTitle = "单页示例") 3 | #extends("/page/layout/smart.vm") 4 | 5 | #block("sidebar") 6 | #widget("/widget/sidebarmenus/sidebarmenus.vm" "var:menus=$menus.get(1).children") 7 | #end 8 | 9 | #block("content") 10 |
11 | 页面1 12 | 页面2 13 | 页面3 14 |
15 | 16 |

单页内容二

17 | 18 |
19 |

注意此处内容切换是不会刷新页面的。

20 |

请打开网络面板观察请求。

21 |
22 | #end 23 | 24 | #script() 25 | // fis async 26 | require(['./spa'], function(app) { 27 | app('#pager'); 28 | }); 29 | #end 30 | 31 | ## 需要依赖一下自己,否则该 vm 中依赖没法自动加载进来。 32 | #require("./page2.vm") 33 | #end 34 | -------------------------------------------------------------------------------- /page/examples/spa/page3.vm: -------------------------------------------------------------------------------- 1 | #set($title = "单页示例") 2 | #set($pageTitle = "单页示例") 3 | #extends("/page/layout/smart.vm") 4 | 5 | #block("sidebar") 6 | #widget("/widget/sidebarmenus/sidebarmenus.vm" "var:menus=$menus.get(1).children") 7 | #end 8 | 9 | #block("content") 10 |
11 | 页面1 12 | 页面2 13 | 页面3 14 |
15 | 16 |

单页内容三

17 | 18 |
19 |

注意此处内容切换是不会刷新页面的。

20 |

请打开网络面板观察请求。

21 |
22 | #end 23 | 24 | #script() 25 | // fis async 26 | require(['./spa'], function(app) { 27 | app('#pager'); 28 | }); 29 | #end 30 | 31 | ## 需要依赖一下自己,否则该 vm 中依赖没法自动加载进来。 32 | #require("./page3.vm") 33 | #end 34 | -------------------------------------------------------------------------------- /page/examples/spa/spa.js: -------------------------------------------------------------------------------- 1 | var $ = require('jquery'); 2 | 3 | var app = module.exports = function(nav) { 4 | var $nav = $(nav); 5 | var $container = $('#content'); 6 | 7 | $nav.off('click.spa').on('click.spa', 'a', function() { 8 | var $this = $(this); 9 | var href = $this.attr('href'); 10 | 11 | $ 12 | .ajax(href) 13 | .then(function(response) { 14 | // 我不确认是否都支持。 15 | history.replaceState && history.replaceState({}, document.title, href); 16 | $container.html(response); 17 | }); 18 | return false; 19 | }); 20 | }; -------------------------------------------------------------------------------- /page/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 简单的 html 页面 6 | 7 | 8 |

简单的 html 页面

9 |

除了可以预览 vm 页面外,还可以预览简单 html 页面,简单的活动页面可以用 html.

10 | 11 | -------------------------------------------------------------------------------- /page/index.vm: -------------------------------------------------------------------------------- 1 | #extends("/page/layout/front.vm") 2 | 3 | 4 | #block("content") 5 | 6 | 7 |
8 |
9 |

Jello ['dʒeləu]

10 |

针对服务端为 JAVA + Velocity/jsp 的前端集成解决方案。

11 |

为优化前端开发效率而生,提供前后端开发分离、自动性能优化、模块化开发机制等功能。

12 |

查看更多 »

13 |
14 |
15 | 16 |
17 |
18 |
19 |

前后端分离

20 |

基于 velocity/jsp 模板,实现数据与页面分离

21 |

开发分离,制定数据格式便能脱离后端独立开发。基于 jello 轻松模拟各类数据。

22 |
23 |
24 |

自动性能优化

25 |

基于配置,自动完成资源压缩,合并,优化工作。

26 |

收集 css 和 js,统一于页头和页底输出,优化性能。

27 |
28 |
29 |

模块化开发

30 |

提供 html、css、js 模块化机制,包括 widget 组件化与 js amd 加载机制,让内容更好的拆分与复用。

31 |

可以随意拆分,无需考虑性能问题。

32 |
33 |
34 | 35 |
36 |
37 |

模板继承 & widget 机制

38 |

提供更好的骨架 (layout) 机制。

39 |

公用部分抽离成 widget 便于多页面间复用。

40 |
41 |
42 |

简化环境安装

43 |

内嵌 j2ee 开发服务器,你无需再折腾 j2ee 环境搭建。直接通过 jello server start 就能开起服务,预览页面。

44 |
45 |
46 |

轻松接入 spring

47 |

提供标准 maven 示例程序,拷贝几条配置便能工作。

48 |
49 |
50 |
51 | 52 | #end## end of body 53 | 54 | ## 需要依赖一下自己,否则该 vm 中依赖没法自动加载进来。 55 | #require("./index.vm") 56 | #end 57 | -------------------------------------------------------------------------------- /page/jsp/binding.jsp: -------------------------------------------------------------------------------- 1 | <%@ page contentType="text/html;charset=utf-8" %> 2 | <%@ taglib uri="/fis" prefix="fis"%> 3 | <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |

jello 前端项目,只负责模板文件的编写,不负责数据的获取,但是为了让页面能够正常预览,需要提供一种机制,让模板自动关联数据,这样只要前期制定了数据格式,前端完全是可以后端独立开发的。

16 | 17 |

那么,我们其实只用关心数据格式,而不用关心数据的真实性。我们完全可以通过 json 文件指定静态数据即可。

18 | 19 |

如何指定?

20 | 21 |

page 目录下的每个模板页面都会自动绑定上 test 目录下同名的 json 数据。

22 | 23 |

test/page/index.json

24 | 25 |
{
26 |     "title": "This is title.",
27 |     "subtitle": "This is subtitle."
28 | }
29 | 
30 | 31 |

page/index.jsp

32 |
<h1>\${title}</h1>
33 | <h2>\${subtitle}</h2>
34 | 
35 | 36 |

那么页面预览输出的结果是

37 | 38 |
<h1>This is title.</h1>
39 | <h2>This is subtitle.</h2>
40 | 
41 | 42 |

如何解决公用数据问题?

43 |

页面多了,自然有公用的数据,如果你的数据都是通过 ajax 页面获取,那还好,通过 rewrite 转发就能每次获取同样的数据,如果是模板数据,则需要每次都不听的拷贝。

44 | 45 |

这样确实太麻烦了,如是,我们提供了这么一种机制。如果预览的页面地址是 /page/A/B/C.jsp, 这个文件的 json 绑定,可以是多个,顺序为:

46 | 47 |
    48 |
  • /test/page.json
  • 49 |
  • /test/page/A.json
  • 50 |
  • /test/page/A/B.json
  • 51 |
  • /test/page/A/B/C.json
  • 52 |
53 | 54 |

多个文件的 json 值会叠加进来。

55 | 56 |

如何动态绑定数据

57 | 58 |

json 绑定的是静态数据,如果需要绑定动态的数据,可以通过 jsp 方式绑定,绑定规则是一样的,且绑定时机在 json 绑定之后,可以针对动态数据,在 jsp 中补充。

59 | 60 |
<%
61 | request.setAttribute("title", "Welcome to jello.");
62 | %>
63 | 64 | 65 | 66 |
67 |

注意

68 |

69 | 在 jsp 中,任何输出都是没有意义的,你可以输出,但是输出没有任何作用,只能给 context 添加数据。 70 |

71 |
72 | 73 |
74 | 75 | 76 |
77 | -------------------------------------------------------------------------------- /page/jsp/index.jsp: -------------------------------------------------------------------------------- 1 | <%@ page contentType="text/html;charset=utf-8" %> 2 | <%@ taglib uri="/fis" prefix="fis"%> 3 | <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

新版的 Jello 已经完全采用 jsp 作为模板来开发。完全兼容之前云龙老大开发的 jsp 版本

15 | 16 |

请先阅读 jello 中目录规范,虽然文中的提及的模板是velocity,但是同时适用与 jsp

17 | 18 | jello 为了打通前后端,采用 jsp tag 的方式扩充了自定义的标签,使用前,请先在头部加入。 19 | 20 |
<%@ taglib uri="/fis" prefix="fis"%>
21 | 22 |
23 |

注意

24 |

虽然这里 prefix 值可以修改,但是千万别改,因为还有一系列编译工具,只识别 fis 打头的便签。

25 |
26 | 27 |

扩展标签

28 | 29 |

fis:require

30 |

用来引入资源,不管是 JS 资源还是 CSS 资源,都统一通过 <fis:require id="xxxx" /> 引入。

31 | 32 |
<%-- 引入 js --%>
 33 | <fis:require id="/static/js/dialog.js" />
 34 | 
 35 | <%-- 引入 css --%>
 36 | <fis:require id="./dialog.css" />
 37 | 
 38 | <%-- 引入 jsp 目录是把目标 jsp 所有依赖的 js 和 css 都引入。 --%>
 39 | <fis:require id="/page/index.jsp" />
40 | 41 |

路径说明:如果是本项目内的资源,请使用绝对路径或者相对路径。如果是跨项目引用。请使用 fis 资源 id。

42 | 43 |
44 |

FIS 资源

45 |

46 | FIS 资源 ID 写法是 [namespace:]文件绝对路径。当项目设置了 namespace 的时候:common:static/js/jquery.js、当项目没有设置 namespace 的时候:static/js/query.js 47 |

48 |

为什么要用 FIS 资源 ID 因为,我们的静态资源很可能是部署在 CDN 上 或者 文件名加了 md5 截,所以我们需要用 FIS 资源 ID标识资源,这样我们可以通过查找 jello release 出来的 map.json 表,查找出最终的存放路径。

49 |
50 | 51 |

除了单纯的引入资源之外,有时候,需要控制资源是否在低版本的 IE 中加载。如何控制?请参考以下代码。

52 | 53 |
<fis:require id="/static/scss/ie.scss" prefix="<!--[if lt IE 8]>" affix="<![endif]-->" />
54 | 55 |

fis:script & fis:style

56 |

引入 js 或者 css, 通过它组织的 js 和 css 可以自动完成性能优化。

57 | 58 |
<%-- 内联 js --%>
 59 | <fis:script>
 60 | console.log('hello world');
 61 | </fis:script>
 62 | 
 63 | <%-- 等价与 fis:require --%>
 64 | <fis:script src="/static/lib/dialog.js"></fis:script>
 65 | 
 66 | <%-- 引入外部资源 --%>
 67 | <fis:script src="//code.jquery.com/jquery-1.11.0.min.js"></fis:script>
 68 | 
 69 | <%-- 内联 js --%>
 70 | <fis:style>
 71 | .body {
 72 | }
 73 | </fis:style>
 74 | 
 75 | <%-- 等价与 fis:require --%>
 76 | <fis:style href="/static/lib/dialog.css"></fis:style>
 77 | 
 78 | <%-- 引入外部资源 --%>
 79 | <fis:style href="//domain.com/xxx.scss"></fis:style>
80 | 81 | 82 |

fis:html

83 |

用它来代替 html 标签。可以设置任意属性,设置的属性,将会作用 html 标签。

84 | 85 |

framework 属性,用来指定前端框架。

86 | 87 |
<fis:html framework="/static/js/mod.js" class="page-hello-world">
 88 | 
 89 | </fis:html>
90 | 91 |

fis:head & fis:body

92 |

用来代替 head 和 body 标签的。作用其实很简单。fis:head 在 head 的底部插入了。<!--FIS_STYLE_PLACEHOLDER-->, 而 fis:body 给 body 底部插入了 <!--FIS_FRAMEWORK_PLACEHOLDER--> <!--FIS_FRAMEWORK_CONFIG--> <!--FIS_SCRIPT_PLACEHOLDER-->

93 | 94 |

也就是说,如果你自己来控制在对应的位置插入这些标签,你是可以不用写 fis:head 和 fis:body 标签的。

95 | 96 |

fis:uri

97 | 把 FIS 资源 ID 转化成实际路径,可以跨模块。与 fis:uri 的区别是,这个只会简单的输出路径,而不会引入此文件。 98 | 99 |
<fis:uri name="ns:static/xxxx/xxx.js" />
100 | 101 |

fis:widget

102 |

引入一个模板碎片文件,支持指定局部变量。更多信息请查看此处说明

103 | 104 |

fis:extends & fis:block & fis:parent

105 | 106 |

请查看模板继承说明

107 | 108 |
109 | 110 | 111 |
112 | -------------------------------------------------------------------------------- /page/jsp/layout.jsp: -------------------------------------------------------------------------------- 1 | <%@ page contentType="text/html;charset=utf-8" %><%@ page import="java.util.*" %><%@ taglib uri="/fis" prefix="fis"%><%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><% 2 | 3 | String layout = request.getParameter("layout"); 4 | String parent = "page/layout/2columns-with-left-sidebar.jsp"; 5 | 6 | if (layout != null) { 7 | if (layout.equals("right")) { 8 | parent = "page/layout/2columns-with-right-sidebar.jsp"; 9 | } else if (layout.equals("both")) { 10 | parent = "page/layout/3columns.jsp"; 11 | } 12 | } else { 13 | layout = "left"; 14 | } 15 | 16 | request.setAttribute("layout", layout); 17 | request.setAttribute("parent", parent); 18 | %> 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | <%--with 表示把整个对象里面的成员展开,作为局部变量。--%> 29 | 30 | 31 | 32 | 33 |

模板继承思想来源于 smarty, 可以用来更好的复用代码。

34 | 35 |
36 |

Smarty 模板继承文档

37 |

如果之前没有接触过模板继承,请先一定要看下这个说明。

38 |
39 | 40 | 41 |

示例

42 | 43 |
44 |

先点击示例看效果

45 |

请切换下面这三个按钮看效果,同样的内容在不同的骨架下的效果。

46 | 51 |
52 | 53 |

没错,模板继承可以很方便的来定义具有容器功能的模板。子模板可以选择直接填充此类骨架类模板。

54 | 55 |

说明

56 | 57 |

layout.jsp

58 | 59 |
<!doctype html>
60 | <html>
61 | <head>
62 |   <meta charset="utf-8">
63 |   <title><fis:block name="title">My Site</fis:block></title>
64 | 
65 |   <fis:block name="head">
66 |   <link rel="stylesheet" href="main.css">
67 |   </fis:block>
68 | </head>
69 | <body>
70 |   <fis:block name="content"></fis:block>
71 | </body>
72 | </html>
73 | 74 |

index.jsp

75 |
<fis:extends name="layout.jsp">
76 |     <fis:block name="title">My Page</fis:block>
77 | 
78 |     <fis:block name="head">
79 |          <fis:parent />
80 |          <link rel="stylesheet" href="custom.css">
81 |     </fis:block>
82 | 
83 |     <fis:block name="content">
84 |     <p>This is just an awesome page.</p>
85 |     </fis:block>
86 | </fis:extends>
87 | 88 |

简单点描述就是,父模板挖坑,子模块填坑。特别注意下 fis:parent 的作用,就是把父模板中对应的 block 给拿过来。

89 | 90 |

局部变量

91 | 92 |

请直接查看 widget 中的局部变量用法,用法一样。传送门

93 | 94 |
95 | 96 | 97 |
98 | -------------------------------------------------------------------------------- /page/jsp/rewrite.jsp: -------------------------------------------------------------------------------- 1 | <%@ page contentType="text/html;charset=utf-8" %> 2 | <%@ taglib uri="/fis" prefix="fis"%> 3 | <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

在 page 目录下面的所有 jsp 模板文件,只会有两类,一种是骨架模板,而另一种是入口模板。在 jello 环境下,所有入口模板都是可以直接预览的。他的预览规则为 /page/{{jsp 路径}}

15 | 16 |

比如: page 目录下面的 index.jsp 文件,则可以通过 /page/index(.jsp 可以省略)路径预览。

17 | 18 |
19 |

namespace 项目

20 |

如果当前项目设置了 namespace,则预览地址得加上 namespace 作为前缀。

21 |

假如 namespacehome,预览地址则为:/home/page/index

22 |
23 | 24 |

页面转发

25 |

这里有个限制就是路径总是以 /page 打头, 且模板路径必须与访问路径一致。这肯定是不能接受的。

26 | 27 |

针对这个问题,我们的解决方法是,提供rewrite 机制。

28 | 29 |

在根目录下面新建一个 server.conf 文件,通过配置此文件来实现页面跳转。

30 | 31 |
# 首页
32 |     rewrite \/$ /page/index
33 | 
34 |     #用户查看页面
35 |     rewrite \/user/view/(\d*)$ /page/user/view?id=$1
36 | 
37 |     # home 页面跳转首页
38 |     redirect \/home$ /page/index
39 |     
40 | 41 |

server.conf 中支持两种指令。

42 |
    43 |
  • rewrite 页面跳转,但是不改变浏览器地址
  • 44 |
  • redirect 页面跳转,同时改变浏览器地址
  • 45 |
46 | 47 |

语法说明

48 |

{{指定}} {{匹配正则}} {{目标地址}}

49 |

server.conf 中,不是 rewrite 或者 redirect 打头的都被认为是注释。目标地址支持 $数字 来替换捕获结果。

50 | 51 |

jsp 页面转发

52 | 53 |

在 test 目录下面的 .jsp 文件,也是可以在直接在 jello 环境下预览的,如 test 目录下面的 file.jsp 文件可以通过 /test/file.jsp 路径预览。

54 | 55 |

如果再配合rewrite, 在 jello 环境下就可以模拟任何后端输出。

56 | 57 |
rewrite \/testpage$ /test/mock.jsp
58 | 
59 | 60 |
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
61 | <%
62 |     response.getWriter().write("hello world");
63 | %>
64 | 
65 | 66 |

示例

67 | 68 |

json 页面模拟

69 | 70 |

关于json 页面,其实可以用 jsp 页面模拟,如示例,也可以直接在预览 json 文件,只要此文件放在 test 目录下面。如示例。注意查看 jello-demo 项目下面的 test 目录和 server.conf 文件内容。

71 | 72 |
73 | 74 | 75 |
76 | -------------------------------------------------------------------------------- /page/jsp/widget.jsp: -------------------------------------------------------------------------------- 1 | <%@ page contentType="text/html;charset=utf-8" %> 2 | <%@ taglib uri="/fis" prefix="fis"%> 3 | <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

页面中比较常用的小的部分,可以抽离成widget,比如:登陆框、头部菜单、边栏菜单等等

15 | 16 |

抽离成小模板后,可以通过这样的方式引入页面的任意位置。

17 | 18 |
<fis:widget name="/widget/sidebarmenus/sidebarmenus.jsp" />
19 | 20 |

局部变量

21 | 22 |

除了模板复用外,有时候变量也需要复用,在通过 fis:widget 引入模板的时候,可以通过以下两种方式指定变量

23 | 24 |
    25 |
  • with 将指定变量下面的所有属性做为 widget 中的局部变量使用
  • 26 |
  • 其他attribute 设置 widget 中局部变量
  • 27 |
28 | 29 |
<fis:widget name="/widget/sidebarmenus/sidebarmenus.jsp" vara="test string" obja="\${reference}" with="\${object}" />
30 | 31 |
32 | 33 | 34 |
35 | -------------------------------------------------------------------------------- /page/layout/2columns-with-left-sidebar.jsp: -------------------------------------------------------------------------------- 1 | <%@ page contentType="text/html;charset=utf-8" %><%@ taglib uri="/fis" prefix="fis"%><%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 |
13 |
14 | 15 | 18 | 19 |
20 |
21 |
22 |
23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
32 | -------------------------------------------------------------------------------- /page/layout/2columns-with-left-sidebar.vm: -------------------------------------------------------------------------------- 1 | ## 两栏布局有左边栏 2 | #extends("./frame.vm") 3 | 4 | #block("body") 5 | #block("header") 6 | #widget("/widget/header/header.vm") 7 | #end 8 | 9 |
10 |
11 |
#block("sidebar")#end
12 |
13 | #if ($pageTitle) 14 | 17 | #end 18 |
#block("content")#end
19 |
20 |
21 |
22 | 23 | #block("footer") 24 | #widget("/widget/footer/footer.vm") 25 | #end 26 | #end 27 | 28 | ## 需要依赖一下自己,否则该 vm 中依赖没法自动加载进来。 29 | #require("./2columns-with-left-sidebar.vm") 30 | #end 31 | -------------------------------------------------------------------------------- /page/layout/2columns-with-right-sidebar.jsp: -------------------------------------------------------------------------------- 1 | <%@ page contentType="text/html;charset=utf-8" %><%@ taglib uri="/fis" prefix="fis"%><%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 |
13 | 14 | 17 | 18 |
19 |
20 |
21 |
22 |
23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
32 | -------------------------------------------------------------------------------- /page/layout/2columns-with-right-sidebar.vm: -------------------------------------------------------------------------------- 1 | ## 两栏布局有右边栏 2 | #extends("./frame.vm") 3 | 4 | #block("body") 5 | #block("header") 6 | #widget("/widget/header/header.vm") 7 | #end 8 | 9 |
10 |
11 |
12 | #if ($pageTitle) 13 | 16 | #end 17 |
#block("content")#end
18 |
19 |
#block("sidebar")#end
20 |
21 |
22 | 23 | #block("footer") 24 | #widget("/widget/footer/footer.vm") 25 | #end 26 | #end 27 | 28 | ## 需要依赖一下自己,否则该 vm 中依赖没法自动加载进来。 29 | #require("./2columns-with-right-sidebar.vm") 30 | #end 31 | -------------------------------------------------------------------------------- /page/layout/3columns.jsp: -------------------------------------------------------------------------------- 1 | <%@ page contentType="text/html;charset=utf-8" %><%@ taglib uri="/fis" prefix="fis"%><%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 |
13 |
14 | 15 | 18 | 19 |
20 |
21 |
22 |
23 |
24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 | 32 |
33 | -------------------------------------------------------------------------------- /page/layout/3columns.vm: -------------------------------------------------------------------------------- 1 | ## 两栏布局有左边栏 2 | #extends("./frame.vm") 3 | 4 | #block("body") 5 | #block("header") 6 | #widget("/widget/header/header.vm") 7 | #end 8 | 9 |
10 |
11 |
#block("sidebar")#end
12 |
13 | #if ($pageTitle) 14 | 17 | #end 18 |
#block("content")#end
19 |
20 |
#block("sidebarSecondary")#end
21 |
22 |
23 | 24 | #block("footer") 25 | #widget("/widget/footer/footer.vm") 26 | #end 27 | #end 28 | 29 | ## 需要依赖一下自己,否则该 vm 中依赖没法自动加载进来。 30 | #require("./3columns.vm") 31 | #end 32 | -------------------------------------------------------------------------------- /page/layout/ajax.vm: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 8 | 9 | 10 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /page/layout/frame.jsp: -------------------------------------------------------------------------------- 1 | <%@ page contentType="text/html;charset=utf-8" %><%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><%@ taglib uri="/fis" prefix="fis"%> 2 | 3 | 4 | 5 | 6 | ${title}<c:if test="${not empty titleAffix}"> - ${titleAffix}</c:if> 7 | 8 | 9 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 |
24 | 25 |
26 | 27 | 28 | // 启用 bootstrap 29 | require(['bootstrap']); 30 | 31 | 32 | 33 |
34 | -------------------------------------------------------------------------------- /page/layout/frame.vm: -------------------------------------------------------------------------------- 1 | 2 | #html("/static/js/require.js") 3 | 4 | #head() 5 | 6 | 7 | 8 | $title #if( $titleAffix ) - $titleAffix#end 9 | 10 | 11 | 12 | 16 | #require("/static/scss/normalize.css") 17 | #require("bootstrap/css/bootstrap.css") 18 | #require("bootstrap/css/bootstrap-theme.css") 19 | #require("/static/scss/global.scss") 20 | #end## end head 21 | 22 | #body() 23 |
24 | ## 定义一个区域 25 | #block("body")#end 26 |
27 | 28 | #end## end of body 29 | 30 | #script() 31 | // 启用 bootstrap 32 | require(['bootstrap']); 33 | #end 34 | 35 | ## 需要依赖一下自己,否则该 vm 中依赖没法自动加载进来。 36 | #require("./frame.vm") 37 | #end## end of html 38 | -------------------------------------------------------------------------------- /page/layout/front.jsp: -------------------------------------------------------------------------------- 1 | <%@ page contentType="text/html;charset=utf-8" %><%@ taglib uri="/fis" prefix="fis"%> 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /page/layout/front.vm: -------------------------------------------------------------------------------- 1 | ## 首页布局 2 | #extends("./frame.vm") 3 | 4 | #block("body") 5 | #block("header") 6 | #widget("/widget/header/header.vm") 7 | #end 8 | 9 | #block("content")#end 10 | 11 | #block("footer") 12 | #widget("/widget/footer/footer.vm") 13 | #end 14 | #end 15 | 16 | ## 需要依赖一下自己,否则该 vm 中依赖没法自动加载进来。 17 | #require("./front.vm") 18 | #end 19 | -------------------------------------------------------------------------------- /page/layout/smart.vm: -------------------------------------------------------------------------------- 1 | ## 只能判断,是否来源于 ajax 2 | #if ($request.getHeader("X-Requested-With").equals("XMLHttpRequest")) 3 | #extends("./spa.vm")#end 4 | #else 5 | #extends("./2columns-with-left-sidebar.vm")#end 6 | #end 7 | 8 | -------------------------------------------------------------------------------- /page/layout/spa.vm: -------------------------------------------------------------------------------- 1 | ## 单页面骨架,什么也不要,只要 html 内容,样式和 JS 2 | 3 | #block("content")#end 4 | 5 | 6 | -------------------------------------------------------------------------------- /page/velocity/index.vm: -------------------------------------------------------------------------------- 1 | #set($pageTitle="velocity 语法") 2 | #extends("/page/layout/2columns-with-left-sidebar.vm") 3 | 4 | #block("sidebar") 5 | #widget("/widget/sidebarmenus/sidebarmenus.vm" "var:menus=$menus.get(3).children") 6 | #end 7 | 8 | #block("content") 9 | 10 |

基本的逻辑标签不在这里多说了,请查看 velocity 官方文档。这里重点说一下 velocity 宏 和jello 基于 velocity 扩展的几个标签。

11 | 12 |

velocity 宏

13 | 14 |

Velocity 里面,不像 smarty 一样,可以写 PHP 函数,不过,它有它自己的机制,比如宏的定义。一些重用且复杂的渲染逻辑,可以封装成 velocity 宏来处理。

15 | 16 |

宏方法定义

17 |
#macro( tablerows $color $somelist )
 18 | #foreach( $something in $somelist )
 19 |     <tr><td bgcolor=$color>$something</td></tr>
 20 | #end
 21 | #end
 22 | 
23 | 24 |

宏方法使用

25 | 26 |
#set( $greatlakes = ["Superior","Michigan","Huron","Erie","Ontario"] )
 27 | #set( $color = "blue" )
 28 | <table>
 29 |     #tablerows( $color $greatlakes )
 30 | </table>
 31 | 
32 | 33 |

输出结果

34 |
<table>
 35 |     <tr><td bgcolor="blue">Superior</td></tr>
 36 |     <tr><td bgcolor="blue">Michigan</td></tr>
 37 |     <tr><td bgcolor="blue">Huron</td></tr>
 38 |     <tr><td bgcolor="blue">Erie</td></tr>
 39 |     <tr><td bgcolor="blue">Ontario</td></tr>
 40 | </table>
 41 | 
42 | 43 |

扩展标签

44 | 45 |

${esc.hash}require()

46 |

用来引入资源,不管是 JS 资源还是 CSS 资源,都统一通过 ${esc.hash}require() 引入。

47 | 48 |
$esc.hash$esc.hash  引入JS脚本
 49 | #require("[ns:]static/lib/dialog.js")
 50 | 
 51 | $esc.hash$esc.hash   引入样式文件
 52 | #require("[ns:]static/lib/dialog.css")
 53 | 
 54 | $esc.hash$esc.hash  引入模板文件,作用是把此模本文件的依赖(同名JS和同名CSS)引入进来。
 55 | #require("[ns:]page/index.vm")
 56 | 
57 | 58 |

通过${esc.hash}require()只能引入存在的 FIS 资源 ID 如果不是 FIS 资源 ID 或者不存在,则不会输出任何东西,同时还会提示 warning。如果想引入一个外部 JS 或者 CSS 请改用 ${esc.hash}script() 或者 ${esc.hash}style()

59 | 60 |
61 |

FIS 资源

62 |

63 | FIS 资源 ID 写法是 [namespace:]文件绝对路径。当项目设置了 namespace 的时候:common:static/js/jquery.js、当项目没有设置 namespace 的时候:static/js/query.js 64 |

65 |

为什么要用 FIS 资源 ID 因为,我们的静态资源很可能是部署在 CDN 上 或者 文件名加了 md5 截,所以我们需要用 FIS 资源 ID标识资源,这样我们可以通过查找 jello release 出来的 map.json 表,查找出最终的存放路径。

66 |
67 | 68 |
69 |

更新

70 |

Jello 新版本已经支持基于项目的绝对路径,或者相对路径。为了方便项目间迁移。请使用 namespace 无关的路径。除非你需要跨项目引用。

71 | 72 |

${esc.hash}require() 新加了两个参数, ${esc.d}prefix, ${esc.d}affix。prefix 和 affix 会对应的在标签头部和尾部补充内容。如:

73 | 74 |
${esc.hash}require("/static/scss/ie.scss" "<!--[if lt IE 8]>" "<![endif]-->")
 75 | 
76 |
77 | 78 |

${esc.hash}script() & ${esc.hash}style()

79 |

用来收集 js 和 css 如:

80 | 81 |
$esc.hash$esc.hash  内联脚本
 82 | #script()
 83 | console.log('hello world');
 84 | #end
 85 | 
 86 | $esc.hash$esc.hash  引入脚本
 87 | #script("[namespace:]static/lib/dialog.js")#end
 88 | $esc.hash$esc.hash  等价与 ${esc.hash}require("[namespace:]static/lib/dialog.js");
 89 | 
 90 | $esc.hash$esc.hash  引入外部资源,非 FIS ID
 91 | #script("//code.jquery.com/jquery-1.11.0.min.js")#end
 92 | 
93 | 94 |
95 |

更新

96 |

${esc.h}script ${esc.h}style 同样支持 prefix 和 affix,请参考 require 中的说明。

97 |
98 | 99 |

${esc.hash}html() & ${esc.hash}head() & ${esc.hash}body()

100 | 101 |

用来代替 html、head、body 标签,通过这些标签组织代码,jello 就知道把收集的 js 和 css 集中在什么地方输出了。

102 | 103 |

${esc.hash}uri()

104 |

FIS 资源 ID 转化成实际路径,可以跨模块。与 ${esc.hash}require() 的区别是,这个只会简单的输出路径,而不会引入此文件。

105 | 106 |

${esc.hash}widget()

107 |

引入一个模板碎片文件,支持指定局部变量。更多信息请查看此处说明

108 | 109 |

${esc.hash}extends() & ${esc.hash}blocks() & ${esc.h}parent()

110 | 111 |

请查看模板继承说明

112 | 113 | #end## end of body 114 | 115 | ## 需要依赖一下自己,否则该 vm 中依赖没法自动加载进来。 116 | #require("./index.vm") 117 | #end 118 | -------------------------------------------------------------------------------- /page/velocity/jello.vm: -------------------------------------------------------------------------------- 1 | #set($pageTitle="jello tools") 2 | #extends("/page/layout/2columns-with-left-sidebar.vm") 3 | 4 | #block("sidebar") 5 | #widget("/widget/sidebarmenus/sidebarmenus.vm" "var:menus=$menus.get(3).children") 6 | #end 7 | 8 | #block("content") 9 | 10 |

velocity tools 虽然多,但是好像少了一些东西,比如说将 java 对象转换成 json 给 js 使用。

11 | 12 |

jello tools 就是用来弥补 velocity tools 中缺失的东西。目前只有实现了 \$jello.jsonEncode(\$variable),即:把 java 对象转换成 json。demo

13 | 14 |
15 |

提交需求?

16 |

请在此页面提交

17 |
18 | 19 | 20 | #end## end of body 21 | 22 | ## 需要依赖一下自己,否则该 vm 中依赖没法自动加载进来。 23 | #require("./jello.vm") 24 | #end 25 | -------------------------------------------------------------------------------- /page/velocity/tools.vm: -------------------------------------------------------------------------------- 1 | #set($pageTitle="velocity tools") 2 | #set($title="velocity tools") 3 | 4 | #extends("/page/layout/2columns-with-left-sidebar.vm") 5 | 6 | #block("sidebar") 7 | #widget("/widget/sidebarmenus/sidebarmenus.vm" "var:menus=$menus.get(3).children") 8 | #end 9 | 10 | #block("content") 11 | 12 |

velocity toolsvelocity 官方开发一系列帮助开发的工具集,jello 中默认集成了所有的 GenericTools

13 |

AlternatorTool

14 |

更方便的操作列表(LIST),支持 auto 和 manual两种方式,输入:可遍历的对象,输出:该 List 的迭代器。如果是自动模式,该迭代器在输出完后会自动进入一个元素。

15 |

示例

16 |
## 自动模式
 17 |     #set( $color = $alternator.auto('red', 'blue') )
 18 |     ## 手动模式
 19 |     #set( $style = $alternator.manual('hip','fly','groovy') )
 20 |     ## $color 会每次自动进入下一个,而 $style 则需要手动调用 next
 21 |     #foreach( $i in [1..5] )
 22 |        Number $i is $color and $style. I dig $style.next numbers.
 23 |     #end
 24 | 
25 |

输出

26 |
Number 1 is red and hip. I dig hip numbers.
 27 | Number 2 is blue and fly. I dig fly numbers.
 28 | Number 3 is red and groovy. I dig groovy numbers.
 29 | Number 4 is blue and hip. I dig hip numbers.
 30 | Number 5 is red and fly. I dig fly numbers.
 31 | 
32 |

查看更多

33 |

DisplayTool

34 |

显示类工具,用来更好的显示数据,如:“优美”的输出数组或集合、截断文字长度、或者对 null 数据通其他文字替代。。。

35 |
#set( $list = [1..5] )
 36 |     $display.list($list)
 37 |     $display.truncate("This is a long string.", 10)
 38 |     Not Null: $display.a
 39 | Null: $display.alt($null, "--")
 40 | 
41 | 42 |

输出

43 | 44 |
1, 2, 3, 4 and 5
 45 | This is...
 46 | Not Null: not null
 47 | Null: --
 48 | 
49 | 50 |

查看更多

51 | 52 |

MathTool

53 | 54 |

一系列数学操作的工具,比起直接用 velocity 语句方便多了。

55 | 56 |

查看更多

57 | 58 | 59 |

NumberTool

60 | 61 |

一系列数字操作的工具,可以用来格式化输出数字。

62 | 63 |
$myNumber                   -> 13.55
 64 | $number.format($myNumber)   -> 13.6
 65 | $number.currency($myNumber) -> $13.55
 66 | $number.integer($myNumber)  -> 13
 67 | 
68 | 69 |

查看更多

70 | 71 |

ComparisonDateTool

72 | 73 |

一系列日期相关操作的工具,可以用来解析日期,格式化日期,和比较日期。

74 | 75 |
Example of formatting the "current" date:
 76 |   $date                         -> Oct 19, 2003 9:54:50 PM
 77 |   $date.long                    -> October 19, 2003 9:54:50 PM PDT
 78 |   $date.medium_time             -> 9:54:50 PM
 79 |   $date.full_date               -> Sunday, October 19, 2003
 80 |   $date.get('default','short')  -> Oct 19, 2003 9:54 PM
 81 |   $date.get('yyyy-M-d H:m:s')   -> 2003-10-19 21:54:50
 82 | 
 83 |  Example of formatting an arbitrary date:
 84 |   $myDate                        -> Tue Oct 07 03:14:50 PDT 2003
 85 |   $date.format('medium',$myDate) -> Oct 7, 2003 3:14:50 AM
 86 | 
87 | 88 |
$date.whenIs('2005-07-04')                -> 1 year ago
 89 | $date.whenIs('2007-02-15').full           -> 1 year 32 weeks 2 days 17 hours 38 minutes 44 seconds 178 milliseconds ago
 90 | $date.whenIs('2007-02-15').days           -> -730
 91 | $date.whenIs($date.calendar)              -> now
 92 | $date.whenIs('2005-07-04', '2005-07-04')  -> same time
 93 | $date.difference('2005-07-04','2005-07-04')      -> 0 milliseconds
 94 | $date.difference('2005-07-04','2007-02-15').abbr -> 1 yr
 95 | 
96 | 97 |

查看更多

98 | 99 |

ClassTool

100 | 101 |

Java 反射类相关的工具,用来方便文档的编写,一般很少用到。

102 | 103 |

查看更多

104 | 105 |

ConversionTool

106 | 107 |

可以用来把字符串装换成其他类型。

108 | 109 |
$convert.toNumber('12.6')   ->  12.6
110 | $convert.toInt('12.6')      ->  12
111 | $convert.toNumbers('12.6,42')  ->  [12.6, 42]
112 | 
113 | 114 | 115 |

查看更多

116 | 117 |

EscapeTool

118 | 119 |

用来转义输出 Velocity、 Java、 JavaScript、 HTML、 HTTP、 XML 和 SQL 格式的字符串

120 | 121 |
$velocity                    -> Please escape $ and #!
122 | $esc.velocity($velocity)     -> Please escape ${esc.d} and ${esc.h}!
123 | 
124 | $java                        -> He didn't say, "Stop!"
125 | $esc.java($java)             -> He didn't say, \"Stop!\"
126 | 
127 | $javascript                  -> He didn't say, "Stop!"
128 | $esc.javascript($javascript) -> He didn\'t say, \"Stop!\"
129 | 
130 | $html                        -> "bread" & "butter"
131 | $esc.html($html)             -> &quot;bread&quot; &amp; &quot;butter&quot;
132 | 
133 | $xml                         -> "bread" & "butter"
134 | $esc.xml($xml)               -> &quot;bread&quot; &amp; &quot;butter&quot;
135 | 
136 | $sql                         -> McHale's Navy
137 | $esc.sql($sql)               -> McHale''s Navy
138 | 
139 | $url                         -> hello here & there
140 | $esc.url                     -> hello+here+%26+there
141 | 
142 | $esc.dollar                  -> $
143 | $esc.d                       -> $
144 | 
145 | $esc.hash                    -> #
146 | $esc.h                       -> #
147 | 
148 | $esc.backslash               -> \
149 | $esc.b                       -> \
150 | 
151 | $esc.quote                   -> "
152 | $esc.q                       -> "
153 | 
154 | $esc.singleQuote             -> '
155 | $esc.s                       -> '
156 | 
157 | $esc.newline                 -> 
158 | 
159 | $esc.n                       -> 
160 | 
161 | 
162 | $esc.exclamation             -> !
163 | $esc.e                       -> !
164 | 
165 | 166 |

查看更多

167 | 168 | 169 |

FieldTool

170 | 171 |

提供一种可以访问到类静态属性的方法

172 | 173 |
## here we access a constant in a class include in the configuration
174 |   $field.COUNTER_NAME
175 | 
176 | ## here we dynamically lookup a class' fields to find another constant
177 |   $field.in("org.com.SomeClass").ANOTHER_CONSTANT
178 | 
179 | ## here we pass an object instance in (an Integer in this case) and
180 | ## retrieve a static constant from that instance's class
181 |   $field.in(0).MIN_VALUE
182 | 
183 | ## by default, once we've searched a class' fields, those fields stay
184 | ## available in the tool (change this by storeDynamicLookups="false")
185 | ## so here we get another constant from the Integer class
186 |   $field.MAX_VALUE
187 | 
188 | 189 |

查看更多

190 | 191 |

ListTool

192 | 193 |

提供一种操作列表(Collection)的工具,可以用来检测列表是否为空、是否含有某个值、获取某个成员、设置某个位置的值等等。

194 | 195 |
$primes                    -> new int[] {2, 3, 5, 7}
196 | $lists.size($primes)        -> 4
197 | $lists.get($primes, 2)      -> 5
198 | $lists.set($primes, 2, 1)   -> (primes[2] becomes 1)
199 | $lists.get($primes, 2)      -> 1
200 | $lists.isEmpty($primes)     -> false
201 | $lists.contains($primes, 7) -> true
202 | 
203 | 204 |

查看更多

205 |

ResourceTool

206 | 207 |

只知道是跟 i18n 多语言相关的,其他的没看懂。

208 | 209 |

查看更多

210 | 211 |

SortTool

212 | 213 |

针对集合(Collection)进行排序操作,支持任何可以被 \#foreach()操作的对象。

214 | 215 |
 Single Property Sort
216 |  #foreach($obj in $sorter.sort($objects, "name"))
217 |    $obj.name Ordinal= $obj.ordinal
218 |  #end
219 |  End
220 | 
221 |  Multiple Property Sort
222 |  #foreach($obj in $sorter.sort($objects, ["name", "ordinal"]))
223 |    $obj.name, $obj.ordinal
224 |  #end
225 |  End
226 | 
227 | 228 |

查看更多

229 | 230 | 231 |

LoopTool

232 | 233 |

A convenience tool to use with \#foreach loops. It wraps a list with a custom iterator to provide additional controls and feedback for managing loops.

234 | 235 |
 Template
236 |   ---
237 |   #set( $list = [1..7] )
238 |   #set( $others = [3..10] )
239 |   #foreach( $item in $loop.watch($list).sync($others, 'other') )
240 |   $item -> $loop.other
241 |   #if( $item >= 5 )$loop.stop()#end
242 |   #end
243 | 
244 |   Output
245 |   ------
246 |   1 -> 3
247 |   2 -> 4
248 |   3 -> 5
249 |   4 -> 6
250 |   5 -> 7
251 | 
252 | 253 |

查看更多

254 | 255 |

ContextTool

256 | 257 |

提供一种可以访问 context 的方法,通过他知道有哪些变量可以使用。

258 | 259 |
 #foreach( $key in $context.keys )
260 |     $key = $context.get($key)
261 |   #end
262 | 
263 | 264 |

查看更多

265 | 266 |

LinkTool

267 | 268 |

相当重要的一个工具,用来操作链接相关的工具,比如如果要生成 pagination 分页的连接,分页参数是 pageSize = xxx; 因为有可能 url 中一几个有这个参数了,或者有条件搜索,还有其他用来过滤的参数,所以得保证生成的分页链接时,这些参数不能丢失。如果不用这个工具,实现起来非常麻烦,但是用它会特别方便。如下:

269 | 270 |
#set($base = $link.query($request.getQueryString()))
271 | 
272 | <a href="$!{request.contextPath}$base.set('pageNum', 1)">第一页</a>
273 | 
274 | 275 |

查看更多

276 | 277 |

RenderTool

278 | 279 |

通过它可以把字符串当成 velocity 语句解析。

280 | 281 |
 Example of eval():
282 |       Input
283 |       -----
284 |       #set( $list = [1,2,3] )
285 |       #set( $object = '$list' )
286 |       #set( $method = 'size()' )
287 |       $render.eval("${object}.$method")
288 | 
289 |       Output
290 |       ------
291 |       3
292 | 
293 |  Example of recurse():
294 |       Input
295 |       -----
296 |       #macro( say_hi )hello world!#end
297 |       #set( $foo = '#say_hi()' )
298 |       #set( $bar = '$foo' )
299 |       $render.recurse($bar)
300 | 
301 |       Output
302 |       ------
303 |       hello world!
304 | 
305 | 306 |

查看更多

307 | 308 | #end## end of body 309 | 310 | ## 需要依赖一下自己,否则该 vm 中依赖没法自动加载进来。 311 | #require("./tools.vm") 312 | #end 313 | -------------------------------------------------------------------------------- /page/velocity/variables.vm: -------------------------------------------------------------------------------- 1 | #set($pageTitle="velocity 全局变量") 2 | #extends("/page/layout/2columns-with-left-sidebar.vm") 3 | 4 | #block("sidebar") 5 | #widget("/widget/sidebarmenus/sidebarmenus.vm" "var:menus=$menus.get(3).children") 6 | #end 7 | 8 | #block("content") 9 | 10 | 为了方便开发,还需要知道 velocity 里面有哪些全局变量可以使用。 11 | 12 |

\$request

13 | 14 |

通过它,可以知道当前页面请求,请求的参数是什么,或者知道当前请求头部是什么等等。

15 | 16 |
$esc.hash$esc.hash 请求 GET 参数 layout 是否等于 "right"
17 | $request.getParameter("layout").equals("right")
18 | 
19 | $esc.hash$esc.hash 是否是来源于 ajax 请求?
20 | $request.getHeader("X-Requested-With").equals("XMLHttpRequest")
21 | 
22 | 23 |

更多方法请查看HttpServletRequest文档。

24 | 25 |

\$response

26 | 27 |

暂没有想到用法,直接查看HttpServletResponse文档吧。

28 | 29 |

Velocity Tools

30 |

请查看velocity tools

31 |

\$jello

32 |

请查看jello tools

33 |

更多变量

34 |

35 | 36 | #end## end of body 37 | 38 | ## 需要依赖一下自己,否则该 vm 中依赖没法自动加载进来。 39 | #require("./variables.vm") 40 | #end 41 | -------------------------------------------------------------------------------- /server.conf: -------------------------------------------------------------------------------- 1 | # 页面转发 2 | rewrite ^\/abcdefg$ /xxxxxx 3 | 4 | 5 | # 首页 6 | rewrite ^\/$ /page/index 7 | 8 | # 文档页面 9 | redirect ^\/doc$ /doc/standard 10 | rewrite ^\/doc\/standard$ /page/doc/standard 11 | rewrite ^\/doc\/layout$ /page/doc/layout 12 | rewrite ^\/doc\/widget$ /page/doc/widget 13 | rewrite ^\/doc\/rewrite$ /page/doc/rewrite 14 | rewrite ^\/doc\/binding$ /page/doc/binding 15 | 16 | # 示例页面 17 | redirect ^\/examples$ /examples/pagination 18 | rewrite ^\/examples\/pagination$ /page/examples/pagination 19 | rewrite ^\/examples\/form$ /page/examples/form 20 | rewrite ^\/examples\/data$ /page/examples/data 21 | rewrite ^\/examples\/partial$ /page/examples/partial 22 | rewrite ^\/examples\/spa$ /page/examples/spa/page1 23 | rewrite ^\/examples\/spa\/page1$ /page/examples/spa/page1 24 | rewrite ^\/examples\/spa\/page2$ /page/examples/spa/page2 25 | rewrite ^\/examples\/spa\/page3$ /page/examples/spa/page3 26 | 27 | ## 借助 delay.jsp 添加 3 秒延时。 28 | rewrite ^\/examples\/partial/form$ /test/delay.jsp?forward=/page/examples/partial/form 29 | 30 | # jsp 31 | rewrite ^\/jsp$ /jsp/index 32 | rewrite ^\/jsp\/(.*)$ /page/jsp/$1 33 | 34 | # 模板说明页面 35 | redirect ^\/velocity$ /velocity/index 36 | rewrite ^\/velocity\/index$ /page/velocity/index 37 | rewrite ^\/velocity\/variables$ /page/velocity/variables 38 | rewrite ^\/velocity\/tools$ /page/velocity/tools 39 | rewrite ^\/velocity\/jello$ /page/velocity/jello 40 | 41 | # 后端 42 | redirect ^\/be$ /be/spring 43 | rewrite ^\/be\/spring$ /page/be/spring 44 | rewrite ^\/be\/variables$ /page/be/variables 45 | 46 | 47 | # 异步数据 48 | rewrite ^\/json$ /test/data/data.json 49 | rewrite ^\/jspdata$ /test/data/ajax.jsp 50 | 51 | 52 | # 测试页面 53 | rewrite \/testpage$ /test/mock.jsp 54 | 55 | # 模拟表单保存页面 56 | rewrite \/examples\/form\/save$ /test/delay.jsp?forward=/test/data/saveform.json 57 | -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/2betop/jello-demo/748a4bea4559c790ea3a10b60f59b3acfd4f9b94/static/favicon.ico -------------------------------------------------------------------------------- /static/js/README.md: -------------------------------------------------------------------------------- 1 | 说明 2 | =============== 3 | 4 | 5 | 各种 amd loader, 可以随意选择哪一款。 -------------------------------------------------------------------------------- /static/js/mod-amd.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 针对 fis 量身定做。 3 | * 由于编译期会处理并优化一些事情,所以此 amd loader 不需要考虑所有的用法。 4 | */ 5 | var require, define; 6 | (function(undef) { 7 | var defined = {}, 8 | waiting = {}, 9 | config = {}, 10 | defining = {}, 11 | slice = [].slice, 12 | hasOwn = Object.prototype.hasOwnProperty, 13 | head = document.getElementsByTagName('head')[0], 14 | timeout = 5000, 15 | ext = '.js', 16 | handlers, req; 17 | 18 | function hasProp(obj, prop) { 19 | return hasOwn.call(obj, prop); 20 | } 21 | 22 | handlers = { 23 | require: function() { 24 | return function() { 25 | return req.apply(undef, arguments); 26 | }; 27 | }, 28 | 29 | exports: function(name) { 30 | return hasProp(defined, name) ? defined[name] : (defined[name] = {}); 31 | }, 32 | 33 | module: function(name) { 34 | return { 35 | id: name, 36 | uri: '', 37 | exports: defined[name], 38 | config: function() { 39 | return (config && config.config && config.config[name]) || {}; 40 | } 41 | }; 42 | } 43 | }; 44 | 45 | function callDep(name, callback) { 46 | if (hasProp(waiting, name)) { 47 | var args = waiting[name]; 48 | 49 | delete waiting[name]; 50 | defining[name] = true; 51 | 52 | if (callback) { 53 | args[2] = (function(old, fn) { 54 | var hacked = function() { 55 | var ret = typeof old === 'function' ? old.apply(this, arguments) : old; 56 | 57 | // 要等 return ret 后 defined 里面才有数据。 58 | setTimeout(fn, 4); 59 | return ret; 60 | }; 61 | return (hacked._length = 1, hacked); 62 | })(args[2], callback); 63 | 64 | return main.apply(undef, args); 65 | } 66 | 67 | main.apply(undef, args); 68 | } 69 | 70 | if (!hasProp(defined, name) && !hasProp(defining, name)) { 71 | throw new Error('No ' + name); 72 | } 73 | 74 | return callback ? callback() : defined[name]; 75 | } 76 | 77 | function resolve(name) { 78 | var paths = config.paths || {}, 79 | path = paths[name] || name; 80 | 81 | return /\.js$/.test(path) ? path : (path + ext); 82 | } 83 | 84 | function loadJs(url, cb) { 85 | var script = document.createElement('script'), 86 | loaded = false, 87 | 88 | clean = function() { 89 | clearTimeout(timer); 90 | script.onload = script.onreadystatechange = script.onerror = null; 91 | head.removeChild(script); 92 | }, 93 | 94 | wrap = function() { 95 | if (!loaded && (!this.readyState || this.readyState === 'loaded' || this.readyState === 'complete')) { 96 | loaded = true; 97 | clean(); 98 | cb(); 99 | } 100 | }, 101 | 102 | onerror = function() { 103 | clean(); 104 | throw new Error('Can\'t load ' + url); 105 | }, 106 | 107 | timer; 108 | 109 | script.setAttribute('src', url); 110 | script.setAttribute('type', 'text/javascript'); 111 | script.onload = script.onreadystatechange = wrap; 112 | script.onerror = onerror; 113 | head.appendChild(script); 114 | timer = setTimeout(onerror, timeout); 115 | } 116 | 117 | function main(name, deps, callback) { 118 | var callbackType = typeof callback, 119 | args = [], 120 | usingExports = false, 121 | i = deps.length, 122 | next, len, cjsModule, depName; 123 | 124 | if (callbackType === 'undefined' || callbackType === 'function') { 125 | deps = !deps.length && (callback.length||callback._length) ? (i = 3, ['require', 'exports', 'module']) : deps; 126 | 127 | next = function() { 128 | var ret = callback ? callback.apply(defined[name], args) : undefined; 129 | 130 | if (name) { 131 | if (cjsModule && cjsModule.exports !== undef && 132 | cjsModule.exports !== defined[name]) { 133 | defined[name] = cjsModule.exports; 134 | } else if (ret !== undef || !usingExports) { 135 | defined[name] = ret; 136 | } 137 | } 138 | }; 139 | 140 | while (i--) { 141 | next = (function(next, depName, i) { 142 | return function() { 143 | var path; 144 | 145 | if (depName === "require") { 146 | args[i] = handlers.require(name); 147 | } else if (depName === "exports") { 148 | args[i] = handlers.exports(name); 149 | usingExports = true; 150 | } else if (depName === "module") { 151 | cjsModule = args[i] = handlers.module(name); 152 | } else if (hasProp(defined, depName) || 153 | hasProp(waiting, depName) || 154 | hasProp(defining, depName)) { 155 | 156 | return callDep(depName, function() { 157 | args[i] = callDep(depName); 158 | next(); 159 | }); 160 | } else { 161 | path = resolve(depName); 162 | 163 | return loadJs(path, function() { 164 | callDep(depName, function() { 165 | args[i] = callDep(depName); 166 | next(); 167 | }); 168 | }); 169 | } 170 | next(); 171 | } 172 | })(next, deps[i], i); 173 | } 174 | next(); 175 | } else if (name) { 176 | defined[name] = callback; 177 | } 178 | } 179 | 180 | require = req = function(deps, callback) { 181 | if (typeof deps === "string") { 182 | return callDep(deps); 183 | } 184 | 185 | setTimeout(function() { 186 | main(undef, deps, callback); 187 | }, 4); 188 | }; 189 | 190 | function extend(a, b) { 191 | var i, v; 192 | 193 | if (!a || !b || typeof b !== 'object') { 194 | return a; 195 | } 196 | 197 | for (i in b) { 198 | if (hasProp(b, i)) { 199 | v = b[i]; 200 | 201 | if (typeof v === 'object' && !v.splice) { 202 | extend(a[i] || (a[i] = {}), v); 203 | } else { 204 | a[i] = v; 205 | } 206 | } 207 | } 208 | } 209 | 210 | req.config = function(cfg) { 211 | extend(config, cfg); 212 | }; 213 | 214 | // define(id, deps ?, factory) 215 | define = function(name, deps, factory) { 216 | if (!deps.splice) { 217 | factory = deps; 218 | deps = []; 219 | } 220 | 221 | if (!hasProp(defined, name) && !hasProp(waiting, name)) { 222 | waiting[name] = [name, deps, factory]; 223 | } 224 | } 225 | 226 | // what does this mean? 227 | define.amd = { 228 | jQuery: true 229 | }; 230 | })(); -------------------------------------------------------------------------------- /static/libs/README.md: -------------------------------------------------------------------------------- 1 | 说明 2 | ======================= 3 | 4 | 5 | 用来存放项目级别公用组件库 -------------------------------------------------------------------------------- /static/libs/_alert.tmpl: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/libs/_confirm.tmpl: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/libs/_modal.tmpl: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/libs/alert.js: -------------------------------------------------------------------------------- 1 | require('bootstrap'); 2 | var modalTplFn = __inline('./_alert.tmpl'); 3 | 4 | // errorLevel 至少支持 danger、info、sucess、warning 5 | var alert = module.exports = function(content, errorLevel, title) { 6 | var dom = $(modalTplFn({ 7 | title: title || '提示信息', 8 | content: content, 9 | errorLevel: errorLevel || 'info' 10 | })); 11 | 12 | dom 13 | .appendTo('body') 14 | .modal({ 15 | backdrop: 'static' 16 | }) 17 | .on('hide.bs.modal', function() { 18 | dom.remove(); 19 | }); 20 | }; -------------------------------------------------------------------------------- /static/libs/confirm.js: -------------------------------------------------------------------------------- 1 | require('bootstrap'); 2 | var modalTplFn = __inline('./_confirm.tmpl'); 3 | var noop = function() {}; 4 | var defaultOptions = { 5 | title: '', 6 | confirmed: noop, 7 | canceled: noop, 8 | confirmTxt: '确认', 9 | cancelTxt: '取消' 10 | } 11 | 12 | var confirm = module.exports = function(content, opt) { 13 | if (typeof opt === 'function') { 14 | opt = { 15 | confirmed: opt 16 | }; 17 | } 18 | 19 | opt = $.extend({}, defaultOptions, opt); 20 | 21 | var dom = $(modalTplFn({ 22 | content: content, 23 | title: opt.title || '提示信息', 24 | confirmTxt: opt.confirmTxt, 25 | cancelTxt: opt.cancelTxt 26 | })); 27 | 28 | dom 29 | .appendTo('body') 30 | .on('click', '.btn-confirm', function() { 31 | opt.confirmed.apply(this, arguments); 32 | dom.modal('hide'); 33 | }) 34 | .on('click', '.btn-cancel', opt.canceled) 35 | .modal({ 36 | backdrop: 'static' 37 | }) 38 | .on('hide.bs.modal', function() { 39 | dom.remove(); 40 | }); 41 | }; -------------------------------------------------------------------------------- /static/libs/modal.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 标记 css 依赖, 需要用到 loading 图标。 3 | * @require font-awesome/css/font-awesome.css 4 | */ 5 | 6 | require('bootstrap'); 7 | var modalTplFn = __inline('./_modal.tmpl'); 8 | 9 | // errorLevel 至少支持 danger、info、sucess、warning 10 | var modal = module.exports = function(url, opt) { 11 | var dom = $(modalTplFn()); 12 | 13 | if ($.isPlainObject(url)) { 14 | opt = url; 15 | url = null; 16 | } 17 | 18 | dom 19 | .appendTo('body') 20 | .modal($.extend({ 21 | keyboard: false, 22 | backdrop: 'static', 23 | remote: url, 24 | show: true 25 | }, opt || {})) 26 | .on('hide.bs.modal', function() { 27 | dom.remove(); 28 | }); 29 | }; 30 | -------------------------------------------------------------------------------- /static/libs/scrollspy.js: -------------------------------------------------------------------------------- 1 | // fix bootstrap 对中文标题不支持的bug 2 | require('bootstrap'); 3 | var $ = require('jquery'); 4 | var ScrollSpy = $.fn.scrollspy.Constructor; 5 | 6 | 7 | ScrollSpy.prototype.refresh = function() { 8 | var offsetMethod = 'offset' 9 | var offsetBase = 0 10 | if (!$.isWindow(this.$scrollElement[0])) { 11 | offsetMethod = 'position' 12 | offsetBase = this.$scrollElement.scrollTop() 13 | } 14 | this.offsets = [] 15 | this.targets = [] 16 | this.scrollHeight = this.getScrollHeight() 17 | var self = this 18 | this.$body.find(this.selector).map(function() { 19 | var $el = $(this) 20 | var href = $el.data('target') || $el.attr('href') 21 | var $href = /^#./.test(href) && (/^#[a-zA-Z_\d]+$/.test(href) ? $(href) : $('[id="' + href.substr(1) + '"]')) 22 | return ($href && $href.length && $href.is(':visible') && [ 23 | [$href[offsetMethod]().top + offsetBase, href] 24 | ]) || null 25 | }).sort(function(a, b) { 26 | return a[0] - b[0] 27 | }).each(function() { 28 | self.offsets.push(this[0]) 29 | self.targets.push(this[1]) 30 | }) 31 | }; -------------------------------------------------------------------------------- /static/libs/validator.js: -------------------------------------------------------------------------------- 1 | var $ = require('jquery'); 2 | var validator = module.exports = require('jquery-validation'); 3 | 4 | require('jquery-validation/localization/messages_zh'); 5 | 6 | $.validator.setDefaults({ 7 | ignore: 'input[type=hidden]:not(.form-item)', 8 | highlight: function(element) { 9 | var tabcontent = $(element).closest('.tab-pane'); 10 | 11 | if (tabcontent.not('.active')) { 12 | $('a[href=#' + tabcontent.attr('id') + ']').tab('show'); 13 | } 14 | 15 | $(element).closest('.form-group').addClass('has-error'); 16 | }, 17 | unhighlight: function(element) { 18 | $(element).closest('.form-group').removeClass('has-error'); 19 | }, 20 | errorElement: 'span', 21 | errorClass: 'help-block', 22 | errorPlacement: function(error, element) { 23 | if (element.parent('.input-group').length) { 24 | error.insertAfter(element.parent()); 25 | } else if(element.is('input[type=radio]') && element.closest('.radio-inline').length) { 26 | error.insertAfter(element.closest('.radio-inline').parent()); 27 | } else { 28 | error.insertAfter(element); 29 | } 30 | } 31 | }); 32 | -------------------------------------------------------------------------------- /static/scss/_callout.scss: -------------------------------------------------------------------------------- 1 | $brand-primary: #428bca; 2 | $brand-success: #5cb85c; 3 | $brand-info: #5bc0de; 4 | $brand-warning: #f0ad4e; 5 | $brand-danger: #d9534f; 6 | 7 | @mixin bs-callout($color, $bgcolor) { 8 | display: block; 9 | margin: 20px 0; 10 | padding: 15px 30px 15px 15px; 11 | border: 1px solid; 12 | border-left-width: 5px; 13 | border-color: lighten($color, 25%); 14 | border-left-color: $color; 15 | background-color: $bgcolor; 16 | h1, h2, h3, h4, h5, h6 { 17 | margin-top: 0; 18 | color: $color; 19 | } 20 | p:last-child { 21 | margin-bottom: 0; 22 | } 23 | code, .highlight { 24 | background-color: #fff; 25 | } 26 | } 27 | 28 | .bs-callout-primary { 29 | @include bs-callout($brand-primary, lighten($brand-primary, 45%)); 30 | } 31 | 32 | .bs-callout-danger { 33 | @include bs-callout($brand-danger, lighten($brand-danger, 30%)); 34 | } 35 | 36 | .bs-callout-warning { 37 | @include bs-callout($brand-warning, lighten($brand-warning, 50%)); 38 | } 39 | 40 | .bs-callout-info { 41 | @include bs-callout($brand-info, lighten($brand-info, 40%)); 42 | } 43 | 44 | .bs-callout-success { 45 | @include bs-callout($brand-success, lighten($brand-success, 40%)); 46 | } 47 | -------------------------------------------------------------------------------- /static/scss/_highlight.scss: -------------------------------------------------------------------------------- 1 | code { 2 | font-family:'Bitstream Vera Sans Mono','Courier', monospace; 3 | } 4 | 5 | .highlight { 6 | margin-top: 1em; 7 | font-family:'Bitstream Vera Sans Mono','Courier', monospace; 8 | 9 | .c { color: #586E75 } /* Comment */ 10 | .err { color: #93A1A1 } /* Error */ 11 | .g { color: #93A1A1 } /* Generic */ 12 | .k { color: #859900 } /* Keyword */ 13 | .l { color: #93A1A1 } /* Literal */ 14 | .n { color: #93A1A1 } /* Name */ 15 | .o { color: #859900 } /* Operator */ 16 | .x { color: #CB4B16 } /* Other */ 17 | .p { color: #93A1A1 } /* Punctuation */ 18 | .cm { color: #586E75 } /* Comment.Multiline */ 19 | .cp { color: #859900 } /* Comment.Preproc */ 20 | .c1 { color: #586E75 } /* Comment.Single */ 21 | .cs { color: #859900 } /* Comment.Special */ 22 | .gd { color: #2AA198 } /* Generic.Deleted */ 23 | .ge { color: #93A1A1; font-style: italic } /* Generic.Emph */ 24 | .gr { color: #DC322F } /* Generic.Error */ 25 | .gh { color: #CB4B16 } /* Generic.Heading */ 26 | .gi { color: #859900 } /* Generic.Inserted */ 27 | .go { color: #93A1A1 } /* Generic.Output */ 28 | .gp { color: #93A1A1 } /* Generic.Prompt */ 29 | .gs { color: #93A1A1; font-weight: bold } /* Generic.Strong */ 30 | .gu { color: #CB4B16 } /* Generic.Subheading */ 31 | .gt { color: #93A1A1 } /* Generic.Traceback */ 32 | .kc { color: #CB4B16 } /* Keyword.Constant */ 33 | .kd { color: #268BD2 } /* Keyword.Declaration */ 34 | .kn { color: #859900 } /* Keyword.Namespace */ 35 | .kp { color: #859900 } /* Keyword.Pseudo */ 36 | .kr { color: #268BD2 } /* Keyword.Reserved */ 37 | .kt { color: #DC322F } /* Keyword.Type */ 38 | .ld { color: #93A1A1 } /* Literal.Date */ 39 | .m { color: #2AA198 } /* Literal.Number */ 40 | .s { color: #2AA198 } /* Literal.String */ 41 | .na { color: #93A1A1 } /* Name.Attribute */ 42 | .nb { color: #B58900 } /* Name.Builtin */ 43 | .nc { color: #268BD2 } /* Name.Class */ 44 | .no { color: #CB4B16 } /* Name.Constant */ 45 | .nd { color: #268BD2 } /* Name.Decorator */ 46 | .ni { color: #CB4B16 } /* Name.Entity */ 47 | .ne { color: #CB4B16 } /* Name.Exception */ 48 | .nf { color: #268BD2 } /* Name.Function */ 49 | .nl { color: #93A1A1 } /* Name.Label */ 50 | .nn { color: #93A1A1 } /* Name.Namespace */ 51 | .nx { color: #555 } /* Name.Other */ 52 | .py { color: #93A1A1 } /* Name.Property */ 53 | .nt { color: #268BD2 } /* Name.Tag */ 54 | .nv { color: #268BD2 } /* Name.Variable */ 55 | .ow { color: #859900 } /* Operator.Word */ 56 | .w { color: #93A1A1 } /* Text.Whitespace */ 57 | .mf { color: #2AA198 } /* Literal.Number.Float */ 58 | .mh { color: #2AA198 } /* Literal.Number.Hex */ 59 | .mi { color: #2AA198 } /* Literal.Number.Integer */ 60 | .mo { color: #2AA198 } /* Literal.Number.Oct */ 61 | .sb { color: #586E75 } /* Literal.String.Backtick */ 62 | .sc { color: #2AA198 } /* Literal.String.Char */ 63 | .sd { color: #93A1A1 } /* Literal.String.Doc */ 64 | .s2 { color: #2AA198 } /* Literal.String.Double */ 65 | .se { color: #CB4B16 } /* Literal.String.Escape */ 66 | .sh { color: #93A1A1 } /* Literal.String.Heredoc */ 67 | .si { color: #2AA198 } /* Literal.String.Interpol */ 68 | .sx { color: #2AA198 } /* Literal.String.Other */ 69 | .sr { color: #DC322F } /* Literal.String.Regex */ 70 | .s1 { color: #2AA198 } /* Literal.String.Single */ 71 | .ss { color: #2AA198 } /* Literal.String.Symbol */ 72 | .bp { color: #268BD2 } /* Name.Builtin.Pseudo */ 73 | .vc { color: #268BD2 } /* Name.Variable.Class */ 74 | .vg { color: #268BD2 } /* Name.Variable.Global */ 75 | .vi { color: #268BD2 } /* Name.Variable.Instance */ 76 | .il { color: #2AA198 } /* Literal.Number.Integer.Long */ 77 | } 78 | 79 | .pl-c { 80 | color: #969896 81 | } 82 | 83 | .pl-c1,.pl-mdh,.pl-mm,.pl-mp,.pl-mr,.pl-s1 .pl-v,.pl-s3,.pl-sc,.pl-sv { 84 | color: #0086b3 85 | } 86 | 87 | .pl-e,.pl-en { 88 | color: #795da3 89 | } 90 | 91 | .pl-s1 .pl-s2,.pl-smi,.pl-smp,.pl-stj,.pl-vo,.pl-vpf { 92 | color: #333 93 | } 94 | 95 | .pl-ent { 96 | color: #63a35c 97 | } 98 | 99 | .pl-k,.pl-s,.pl-st { 100 | color: #a71d5d 101 | } 102 | 103 | .pl-pds,.pl-s1,.pl-s1 .pl-pse .pl-s2,.pl-sr,.pl-sr .pl-cce,.pl-sr .pl-sra,.pl-sr .pl-sre,.pl-src { 104 | color: #df5000 105 | } 106 | 107 | .pl-mo,.pl-v { 108 | color: #1d3e81 109 | } 110 | 111 | .pl-id { 112 | color: #b52a1d 113 | } 114 | 115 | .pl-ii { 116 | background-color: #b52a1d; 117 | color: #f8f8f8 118 | } 119 | 120 | .pl-sr .pl-cce { 121 | color: #63a35c; 122 | font-weight: bold 123 | } 124 | 125 | .pl-ml { 126 | color: #693a17 127 | } 128 | 129 | .pl-mh,.pl-mh .pl-en,.pl-ms { 130 | color: #1d3e81; 131 | font-weight: bold 132 | } 133 | 134 | .pl-mq { 135 | color: #008080 136 | } 137 | 138 | .pl-mi { 139 | color: #333; 140 | font-style: italic 141 | } 142 | 143 | .pl-mb { 144 | color: #333; 145 | font-weight: bold 146 | } 147 | 148 | .pl-md,.pl-mdhf { 149 | background-color: #ffecec; 150 | color: #bd2c00 151 | } 152 | 153 | .pl-mdht,.pl-mi1 { 154 | background-color: #eaffea; 155 | color: #55a532 156 | } 157 | 158 | .pl-mdr { 159 | color: #795da3; 160 | font-weight: bold 161 | } 162 | 163 | 164 | -------------------------------------------------------------------------------- /static/scss/_modal.scss: -------------------------------------------------------------------------------- 1 | @import "compass/css3"; 2 | 3 | .modal { 4 | 5 | .modal-loading { 6 | text-align: center; 7 | min-height: 200px; 8 | line-height: 200px; 9 | font-size: 30px; 10 | } 11 | 12 | .modal-table { 13 | display: table; 14 | width: 100%; 15 | height: 100%; 16 | 17 | .modal-table-cell { 18 | display: table-cell; 19 | vertical-align: middle; 20 | } 21 | } 22 | 23 | .modal-info { 24 | color: #31708f; 25 | 26 | .modal-header { 27 | background: #d9edf7; 28 | @include border-radius(6px 6px 0 0); 29 | } 30 | } 31 | .modal-sucess { 32 | color: #3c763d; 33 | 34 | .modal-header { 35 | background: #d6e9c6; 36 | @include border-radius(6px 6px 0 0); 37 | } 38 | } 39 | .modal-danger { 40 | color: #a94442; 41 | 42 | .modal-header { 43 | background: #f2dede; 44 | @include border-radius(6px 6px 0 0); 45 | } 46 | 47 | } 48 | .modal-warning { 49 | color: #8a6d3b; 50 | 51 | .modal-header { 52 | background: #fcf8e3; 53 | @include border-radius(6px 6px 0 0); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /static/scss/global.scss: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 50px; 3 | } 4 | 5 | .navbar-brand.active { 6 | color: #eee; 7 | } 8 | 9 | #middle { 10 | min-height: 400px; 11 | } 12 | 13 | .markdown { 14 | h2 { 15 | padding-top: 70px; 16 | margin-top: -50px; 17 | } 18 | } 19 | 20 | @import "callout"; 21 | @import "highlight"; 22 | @import "modal"; 23 | -------------------------------------------------------------------------------- /static/scss/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */ 2 | 3 | /** 4 | * 1. Set default font family to sans-serif. 5 | * 2. Prevent iOS text size adjust after orientation change, without disabling 6 | * user zoom. 7 | */ 8 | 9 | html { 10 | font-family: sans-serif; /* 1 */ 11 | -ms-text-size-adjust: 100%; /* 2 */ 12 | -webkit-text-size-adjust: 100%; /* 2 */ 13 | } 14 | 15 | /** 16 | * Remove default margin. 17 | */ 18 | 19 | body { 20 | margin: 0; 21 | } 22 | 23 | /* HTML5 display definitions 24 | ========================================================================== */ 25 | 26 | /** 27 | * Correct `block` display not defined for any HTML5 element in IE 8/9. 28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11 29 | * and Firefox. 30 | * Correct `block` display not defined for `main` in IE 11. 31 | */ 32 | 33 | article, 34 | aside, 35 | details, 36 | figcaption, 37 | figure, 38 | footer, 39 | header, 40 | hgroup, 41 | main, 42 | menu, 43 | nav, 44 | section, 45 | summary { 46 | display: block; 47 | } 48 | 49 | /** 50 | * 1. Correct `inline-block` display not defined in IE 8/9. 51 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. 52 | */ 53 | 54 | audio, 55 | canvas, 56 | progress, 57 | video { 58 | display: inline-block; /* 1 */ 59 | vertical-align: baseline; /* 2 */ 60 | } 61 | 62 | /** 63 | * Prevent modern browsers from displaying `audio` without controls. 64 | * Remove excess height in iOS 5 devices. 65 | */ 66 | 67 | audio:not([controls]) { 68 | display: none; 69 | height: 0; 70 | } 71 | 72 | /** 73 | * Address `[hidden]` styling not present in IE 8/9/10. 74 | * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. 75 | */ 76 | 77 | [hidden], 78 | template { 79 | display: none; 80 | } 81 | 82 | /* Links 83 | ========================================================================== */ 84 | 85 | /** 86 | * Remove the gray background color from active links in IE 10. 87 | */ 88 | 89 | a { 90 | background-color: transparent; 91 | } 92 | 93 | /** 94 | * Improve readability when focused and also mouse hovered in all browsers. 95 | */ 96 | 97 | a:active, 98 | a:hover { 99 | outline: 0; 100 | } 101 | 102 | /* Text-level semantics 103 | ========================================================================== */ 104 | 105 | /** 106 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome. 107 | */ 108 | 109 | abbr[title] { 110 | border-bottom: 1px dotted; 111 | } 112 | 113 | /** 114 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. 115 | */ 116 | 117 | b, 118 | strong { 119 | font-weight: bold; 120 | } 121 | 122 | /** 123 | * Address styling not present in Safari and Chrome. 124 | */ 125 | 126 | dfn { 127 | font-style: italic; 128 | } 129 | 130 | /** 131 | * Address variable `h1` font-size and margin within `section` and `article` 132 | * contexts in Firefox 4+, Safari, and Chrome. 133 | */ 134 | 135 | h1 { 136 | font-size: 2em; 137 | margin: 0.67em 0; 138 | } 139 | 140 | /** 141 | * Address styling not present in IE 8/9. 142 | */ 143 | 144 | mark { 145 | background: #ff0; 146 | color: #000; 147 | } 148 | 149 | /** 150 | * Address inconsistent and variable font size in all browsers. 151 | */ 152 | 153 | small { 154 | font-size: 80%; 155 | } 156 | 157 | /** 158 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 159 | */ 160 | 161 | sub, 162 | sup { 163 | font-size: 75%; 164 | line-height: 0; 165 | position: relative; 166 | vertical-align: baseline; 167 | } 168 | 169 | sup { 170 | top: -0.5em; 171 | } 172 | 173 | sub { 174 | bottom: -0.25em; 175 | } 176 | 177 | /* Embedded content 178 | ========================================================================== */ 179 | 180 | /** 181 | * Remove border when inside `a` element in IE 8/9/10. 182 | */ 183 | 184 | img { 185 | border: 0; 186 | } 187 | 188 | /** 189 | * Correct overflow not hidden in IE 9/10/11. 190 | */ 191 | 192 | svg:not(:root) { 193 | overflow: hidden; 194 | } 195 | 196 | /* Grouping content 197 | ========================================================================== */ 198 | 199 | /** 200 | * Address margin not present in IE 8/9 and Safari. 201 | */ 202 | 203 | figure { 204 | margin: 1em 40px; 205 | } 206 | 207 | /** 208 | * Address differences between Firefox and other browsers. 209 | */ 210 | 211 | hr { 212 | -moz-box-sizing: content-box; 213 | box-sizing: content-box; 214 | height: 0; 215 | } 216 | 217 | /** 218 | * Contain overflow in all browsers. 219 | */ 220 | 221 | pre { 222 | overflow: auto; 223 | } 224 | 225 | /** 226 | * Address odd `em`-unit font size rendering in all browsers. 227 | */ 228 | 229 | code, 230 | kbd, 231 | pre, 232 | samp { 233 | font-family: monospace, monospace; 234 | font-size: 1em; 235 | } 236 | 237 | /* Forms 238 | ========================================================================== */ 239 | 240 | /** 241 | * Known limitation: by default, Chrome and Safari on OS X allow very limited 242 | * styling of `select`, unless a `border` property is set. 243 | */ 244 | 245 | /** 246 | * 1. Correct color not being inherited. 247 | * Known issue: affects color of disabled elements. 248 | * 2. Correct font properties not being inherited. 249 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. 250 | */ 251 | 252 | button, 253 | input, 254 | optgroup, 255 | select, 256 | textarea { 257 | color: inherit; /* 1 */ 258 | font: inherit; /* 2 */ 259 | margin: 0; /* 3 */ 260 | } 261 | 262 | /** 263 | * Address `overflow` set to `hidden` in IE 8/9/10/11. 264 | */ 265 | 266 | button { 267 | overflow: visible; 268 | } 269 | 270 | /** 271 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 272 | * All other form control elements do not inherit `text-transform` values. 273 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. 274 | * Correct `select` style inheritance in Firefox. 275 | */ 276 | 277 | button, 278 | select { 279 | text-transform: none; 280 | } 281 | 282 | /** 283 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 284 | * and `video` controls. 285 | * 2. Correct inability to style clickable `input` types in iOS. 286 | * 3. Improve usability and consistency of cursor style between image-type 287 | * `input` and others. 288 | */ 289 | 290 | button, 291 | html input[type="button"], /* 1 */ 292 | input[type="reset"], 293 | input[type="submit"] { 294 | -webkit-appearance: button; /* 2 */ 295 | cursor: pointer; /* 3 */ 296 | } 297 | 298 | /** 299 | * Re-set default cursor for disabled elements. 300 | */ 301 | 302 | button[disabled], 303 | html input[disabled] { 304 | cursor: default; 305 | } 306 | 307 | /** 308 | * Remove inner padding and border in Firefox 4+. 309 | */ 310 | 311 | button::-moz-focus-inner, 312 | input::-moz-focus-inner { 313 | border: 0; 314 | padding: 0; 315 | } 316 | 317 | /** 318 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 319 | * the UA stylesheet. 320 | */ 321 | 322 | input { 323 | line-height: normal; 324 | } 325 | 326 | /** 327 | * It's recommended that you don't attempt to style these elements. 328 | * Firefox's implementation doesn't respect box-sizing, padding, or width. 329 | * 330 | * 1. Address box sizing set to `content-box` in IE 8/9/10. 331 | * 2. Remove excess padding in IE 8/9/10. 332 | */ 333 | 334 | input[type="checkbox"], 335 | input[type="radio"] { 336 | box-sizing: border-box; /* 1 */ 337 | padding: 0; /* 2 */ 338 | } 339 | 340 | /** 341 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain 342 | * `font-size` values of the `input`, it causes the cursor style of the 343 | * decrement button to change from `default` to `text`. 344 | */ 345 | 346 | input[type="number"]::-webkit-inner-spin-button, 347 | input[type="number"]::-webkit-outer-spin-button { 348 | height: auto; 349 | } 350 | 351 | /** 352 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome. 353 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome 354 | * (include `-moz` to future-proof). 355 | */ 356 | 357 | input[type="search"] { 358 | -webkit-appearance: textfield; /* 1 */ 359 | -moz-box-sizing: content-box; 360 | -webkit-box-sizing: content-box; /* 2 */ 361 | box-sizing: content-box; 362 | } 363 | 364 | /** 365 | * Remove inner padding and search cancel button in Safari and Chrome on OS X. 366 | * Safari (but not Chrome) clips the cancel button when the search input has 367 | * padding (and `textfield` appearance). 368 | */ 369 | 370 | input[type="search"]::-webkit-search-cancel-button, 371 | input[type="search"]::-webkit-search-decoration { 372 | -webkit-appearance: none; 373 | } 374 | 375 | /** 376 | * Define consistent border, margin, and padding. 377 | */ 378 | 379 | fieldset { 380 | border: 1px solid #c0c0c0; 381 | margin: 0 2px; 382 | padding: 0.35em 0.625em 0.75em; 383 | } 384 | 385 | /** 386 | * 1. Correct `color` not being inherited in IE 8/9/10/11. 387 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 388 | */ 389 | 390 | legend { 391 | border: 0; /* 1 */ 392 | padding: 0; /* 2 */ 393 | } 394 | 395 | /** 396 | * Remove default vertical scrollbar in IE 8/9/10/11. 397 | */ 398 | 399 | textarea { 400 | overflow: auto; 401 | } 402 | 403 | /** 404 | * Don't inherit the `font-weight` (applied by a rule above). 405 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. 406 | */ 407 | 408 | optgroup { 409 | font-weight: bold; 410 | } 411 | 412 | /* Tables 413 | ========================================================================== */ 414 | 415 | /** 416 | * Remove most spacing between table cells. 417 | */ 418 | 419 | table { 420 | border-collapse: collapse; 421 | border-spacing: 0; 422 | } 423 | 424 | td, 425 | th { 426 | padding: 0; 427 | } -------------------------------------------------------------------------------- /test/data/ajax.jsp: -------------------------------------------------------------------------------- 1 | <%@ page contentType="application/json; charset=UTF-8" import="java.util.Date" %>{ 2 | "code": 0, 3 | "message": "成功", 4 | "data": { 5 | "username": "admin", 6 | "role": "admin", 7 | "nickname": "诸多借口", 8 | "time": "<%= new Date()%>", 9 | "description": "这个是动态的数据,因为是用 jsp 输出的,可以按自己的需求模拟。" 10 | } 11 | } -------------------------------------------------------------------------------- /test/data/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 0, 3 | "message": "成功", 4 | "data": { 5 | "username": "admin", 6 | "role": "admin", 7 | "nickname": "诸多借口", 8 | "description": "这是静态数据,写死的不会变" 9 | } 10 | } -------------------------------------------------------------------------------- /test/data/saveform.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": 0, 3 | "message": "保存成功" 4 | } -------------------------------------------------------------------------------- /test/delay.jsp: -------------------------------------------------------------------------------- 1 | <%@ page contentType="text/html;charset=UTF-8" language="java" %><% 2 | // sleep 3 second. 3 | Thread.sleep(1000); 4 | 5 | String url = request.getParameter("forward"); 6 | if (url != null && !url.isEmpty()) { 7 | request.getRequestDispatcher(url).forward(request, response); 8 | } else { 9 | response.getWriter().write("只负责延时,不负责具体输出,请指定重定向地址。"); 10 | } 11 | %> -------------------------------------------------------------------------------- /test/mock.jsp: -------------------------------------------------------------------------------- 1 | <%@ page contentType="text/html;charset=UTF-8" language="java" %> 2 | <% 3 | response.getWriter().write("hello world"); 4 | %> 5 | -------------------------------------------------------------------------------- /test/page.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "jello demo", 3 | "titleAffix": "jello demo", 4 | "pageTitle": "页面标题", 5 | 6 | "menus": [ 7 | { 8 | "label": "文档", 9 | "href": "/doc", 10 | "children": [ 11 | { 12 | "label": "目录规范", 13 | "href": "/doc/standard" 14 | }, 15 | 16 | { 17 | "label": "模板继承", 18 | "href": "/doc/layout" 19 | }, 20 | 21 | { 22 | "label": "widget", 23 | "href": "/doc/widget" 24 | }, 25 | 26 | { 27 | "label": "页面模拟", 28 | "href": "/doc/rewrite" 29 | }, 30 | 31 | { 32 | "label": "数据绑定", 33 | "href": "/doc/binding" 34 | } 35 | ] 36 | }, 37 | { 38 | "label": "示例", 39 | "href": "/examples", 40 | "children": [ 41 | { 42 | "label": "普通列表页面", 43 | "href": "/examples/pagination" 44 | }, 45 | { 46 | "label": "js 数据模拟", 47 | "href": "/examples/data" 48 | }, 49 | { 50 | "label": "表单验证", 51 | "href": "/examples/form" 52 | }, 53 | { 54 | "label": "ajax 页面", 55 | "href": "/examples/partial" 56 | }, 57 | { 58 | "label": "SPA 单页 demo", 59 | "href": "/examples/spa" 60 | } 61 | ] 62 | }, 63 | 64 | { 65 | "label": "Jsp", 66 | "href": "/jsp", 67 | "children": [ 68 | { 69 | "label": "Jsp 语法说明", 70 | "href": "/jsp/index" 71 | }, 72 | { 73 | "label": "jsp 模板继承", 74 | "href": "/jsp/layout" 75 | }, 76 | { 77 | "label": "jsp widget", 78 | "href": "/jsp/widget" 79 | }, 80 | { 81 | "label": "页面模拟", 82 | "href": "/jsp/rewrite" 83 | }, 84 | 85 | { 86 | "label": "数据绑定", 87 | "href": "/jsp/binding" 88 | } 89 | ] 90 | }, 91 | 92 | { 93 | "label": "模板技巧", 94 | "href": "/velocity", 95 | "children": [ 96 | { 97 | "label": "velocity 语法", 98 | "href": "/velocity/index" 99 | }, 100 | 101 | { 102 | "label": "全局变量", 103 | "href": "/velocity/variables" 104 | }, 105 | 106 | { 107 | "label": "velocity tools", 108 | "href": "/velocity/tools" 109 | }, 110 | 111 | { 112 | "label": "jello tools", 113 | "href": "/velocity/jello" 114 | } 115 | ] 116 | }, 117 | 118 | { 119 | "label": "后端使用", 120 | "href": "/be", 121 | "children": [ 122 | { 123 | "label": "spring 整合", 124 | "href": "/be/spring" 125 | }, 126 | 127 | { 128 | "label": "全局变量", 129 | "href": "/be/variables" 130 | } 131 | ] 132 | } 133 | ] 134 | } 135 | -------------------------------------------------------------------------------- /test/page.jsp: -------------------------------------------------------------------------------- 1 | <%! 2 | // 将 menus 中当前 url 标记为 active 3 | public Boolean markActiveMenus(JSONArray menus, String path) { 4 | Boolean hasActive = false; 5 | 6 | for (int i = 0, len = menus.size(); i < len; i++) { 7 | JSONObject item = menus.getJSONObject(i); 8 | 9 | Boolean childActive = false; 10 | if (item.containsKey("children")) { 11 | childActive = markActiveMenus(item.getJSONArray("children"), path); 12 | } 13 | 14 | if (childActive || item.containsKey("href") && path.startsWith(item.getString("href"))) { 15 | item.put("active", true); 16 | hasActive = true; 17 | } 18 | } 19 | 20 | return hasActive; 21 | } 22 | 23 | %><%@ page import="org.apache.velocity.context.Context" %> 24 | <%@ page import="com.alibaba.fastjson.JSONArray" %> 25 | <%@ page import="com.alibaba.fastjson.JSONObject" %> 26 | <% 27 | 28 | Context context = (Context)request.getAttribute("context"); 29 | 30 | 31 | JSONArray menus = (JSONArray)context.get("menus"); 32 | String path = request.getAttribute("javax.servlet.forward.request_uri").toString(); 33 | if (path == null) { 34 | path = request.getServletPath(); 35 | } 36 | 37 | path = path.substring(request.getContextPath().length()); 38 | 39 | context.put("currentUrl", path); 40 | 41 | if (menus != null && path != null) { 42 | markActiveMenus(menus, path); 43 | } 44 | %> 45 | -------------------------------------------------------------------------------- /test/page/doc/layout.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "模板继承", 3 | "pageTitle": "模板继承", 4 | 5 | "sidebar": { 6 | "menus": [ 7 | { 8 | "label": "示例", 9 | "href": "#示例" 10 | }, 11 | 12 | { 13 | "label": "说明", 14 | "href": "#说明" 15 | }, 16 | 17 | { 18 | "label": "局部变量", 19 | "href": "#局部变量" 20 | } 21 | ] 22 | } 23 | } -------------------------------------------------------------------------------- /test/page/doc/standard.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "目录规范", 3 | "pageTitle": "目录规范", 4 | 5 | "sidebar": { 6 | "menus": [ 7 | { 8 | "label": "前端目录", 9 | "href": "#%E5%89%8D%E7%AB%AF%E7%9B%AE%E5%BD%95" 10 | }, 11 | 12 | { 13 | "label": "产出目录", 14 | "href": "#%E4%BA%A7%E5%87%BA%E7%9B%AE%E5%BD%95" 15 | } 16 | ] 17 | } 18 | } -------------------------------------------------------------------------------- /test/page/examples.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "/examples/form/save" 3 | } -------------------------------------------------------------------------------- /test/page/examples/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "js 数据模拟", 3 | "pageTitle": "js 数据模拟", 4 | 5 | "user": { 6 | "name": "张三", 7 | "address": "中国" 8 | } 9 | } -------------------------------------------------------------------------------- /test/page/examples/pagination.json: -------------------------------------------------------------------------------- 1 | { 2 | "result": [ 3 | "结果内容 1", 4 | "结果内容 2", 5 | "结果内容 3", 6 | "结果内容 4", 7 | "结果内容 5", 8 | "结果内容 6", 9 | "结果内容 7", 10 | "结果内容 8", 11 | "结果内容 9", 12 | "结果内容 10" 13 | ] 14 | } -------------------------------------------------------------------------------- /test/page/examples/partial.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "ajax 页面", 3 | "pageTitle": "ajax 页面" 4 | } -------------------------------------------------------------------------------- /test/page/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "欢迎查看 jello demo 页面" 3 | } -------------------------------------------------------------------------------- /widget/footer/footer.jsp: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /widget/footer/footer.vm: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /widget/header/header.jsp: -------------------------------------------------------------------------------- 1 | <%@ page contentType="text/html;charset=utf-8" %><%@ taglib uri="/fis" prefix="fis"%><%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> 2 | 3 | 4 | 5 | 36 | 37 | -------------------------------------------------------------------------------- /widget/header/header.vm: -------------------------------------------------------------------------------- 1 | #if ($menus) 2 | 3 | ## velocity 宏示例 4 | #macro( renderMenus $menus $child) 5 | #if( $child ) 6 |