以一个博客项目,实战使用
项目git地址:https://gitee.com/gavink/beetl-blog
视频《一起学Beetl》百度网盘下载: https://pan.baidu.com/s/1LyxAxlKpVXgVjwSXIbzBuA 提取码: 68im
视频《一起学Beetl》在线播放地址:bilibili (可以调节清晰度): https://www.bilibili.com/video/av36278644/?p=1
视频《一起学Beetl》作者博客:https://my.oschina.net/u/1590490?tab=newest&catalogId=6214598
beetl官方文档:https://www.kancloud.cn/xiandafu/beetl3_guide/1992542
1、Beetl介绍
2、集成SpringBoot2.x
3、Beetl模板的基础用法 [变量、循环、条件]
3.1、集成BeetlSql,用来查询数据库
3.2、BeetlSQL demo
3.3、修改模板文件为html结尾
3.4、for-in循环的使用
3.5、时间格式化
3.6、条件语句 if 和 虚拟属性 size
4、共享变量和自定义模板配置
4.1、session域
4.2、自定义模板配置
4.3、ctxPath
5、内置函数和安全输出
6、定界符、占位符
7、Beetl作用阶段
8、标签函数 layout
9、标签函数 include
10、自定义HTML标签
11、Beetl自定义方法以及直接访问java类方法
12、ajax局部渲染
13、重定向
1、Beetl介绍 <--返回目录
Beetl是什么:Beetl( 发音同Beetle ) 目前版本是3.2,相对于其他java模板引擎,具有功能齐全,语法直观,性能超高,以及编写的模板容易维护等特点。使得开发和维护模板有很好的体验。同时,Beetl具备引擎可定制性,可以打造自己的模板引擎。
模板是一种设计模式,只要这种设计模式存在,就有Beetl的用武之地。Beetl可以用于
-
动态页面生成,网站,后台管理系统等
-
静态内容生成,比如互联网世界的大部分静态网页的生成
-
短信,微信,邮件内容等生成
-
二进制文档生成,比如PDF,Word (需要通过模板生成中间文本格式,如markdown,xml,然后通过工具转成二进制)
Vue,React等JS框架符合现在前后端分离,但不代表所有项目,所有需求都是这样。除非你只定位自己是搞前后端分离的。一个项目,既可以用上Beetl,也可以用上Vue,比如Beetl+Vue,或者使用Beetl完成项目中的其他需求。
2、集成SpringBoot2.x <--返回目录
项目结构
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.5.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.oy</groupId> <artifactId>boot-beetl</artifactId> <version>0.0.1-SNAPSHOT</version> <name>boot-beetl</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.ibeetl</groupId> <artifactId>beetl-framework-starter</artifactId> <version>1.1.68.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
IndexController
@Controller public class IndexController { @GetMapping("/") public String index(HttpServletRequest request){ request.setAttribute("title","一起学Beetl"); request.setAttribute("test","springboot 集成 beetl 一起来学呀"); return "index1.btl"; } }
模板文件 index1.btl
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>${title}</title> </head> <body> ${test} </body> </html>
3、Beetl模板的基础用法 [变量、循环、条件] <--返回目录
3.1、集成BeetlSql,用来查询数据库 <--返回目录
创建数据库 create database beetl_test_blog;
引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.40</version> </dependency>
application.properties中增加数据库配置
spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/beetl_test_blog?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false&useInformationSchema=true spring.datasource.username=root spring.datasource.password=
添加数据源配置类 DBConfig
package com.oy; import javax.sql.DataSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; import com.zaxxer.hikari.HikariDataSource; @Configuration public class DBConfig { @Bean(name = "datasource") public DataSource datasource(Environment env) { HikariDataSource ds = new HikariDataSource(); ds.setJdbcUrl(env.getProperty("spring.datasource.url")); ds.setUsername(env.getProperty("spring.datasource.username")); ds.setPassword(env.getProperty("spring.datasource.password")); ds.setDriverClassName(env.getProperty("spring.datasource.driver-class-name")); return ds; } }
测试数据源配置是否成功
@Controller public class IndexController { @Autowired private DataSource dataSource; @GetMapping("/") public String index(HttpServletRequest request) throws SQLException{ System.out.println(dataSource.getConnection()); request.setAttribute("title","一起学Beetl"); request.setAttribute("test","springboot 集成 beetl 一起来学呀"); request.setAttribute("dataSource", dataSource); return "index1.btl"; } }
index1.btl
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>${title}</title> </head> <body> ${test} <br/> dataSource:${dataSource} </body> </html>
启动项目,访问 http://localhost:8080/ 。
3.2、BeetlSQL demo <--返回目录
实体类 Blog
/* CREATE TABLE `blog` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `content` text, `deleteFlag` bit(1) DEFAULT NULL, `img` varchar(100) DEFAULT NULL, `category` varchar(100) DEFAULT NULL, `title` varchar(100) DEFAULT NULL, `createTime` datetime DEFAULT NULL, `updateTime` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; */ package com.oy.model; import java.util.Date; import org.beetl.sql.core.annotatoin.Table; /** * @author oy * @version 1.0 * @date 2020年11月5日 * @time 下午9:28:29 */ @Table(name = "beetl_test_blog.blog") public class Blog { private Long id; private String content; private Boolean deleteFlag; private String img; private String category; private String title; private Date createTime; private Date updateTime; public Blog() { } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public Boolean getDeleteFlag() { return deleteFlag; } public void setDeleteFlag(Boolean deleteFlag) { this.deleteFlag = deleteFlag; } public String getImg() { return img; } public void setImg(String img) { this.img = img; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public Date getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this.createTime = createTime; } public Date getUpdateTime() { return updateTime; } public void setUpdateTime(Date updateTime) { this.updateTime = updateTime; } public String getCategory() { return category; } public void setCategory(String category) { this.category = category; } @Override public String toString() { return "Blog [id=" + id + ", content=" + content + ", deleteFlag=" + deleteFlag + ", img=" + img + ", category=" + category + ", title=" + title + ", createTime=" + createTime + ", updateTime=" + updateTime + "]"; } }
BlogDao
package com.oy.dao; import org.beetl.sql.test.BaseDao; import com.oy.model.Blog; public interface BlogDao extends BaseDao<Blog> { }
BlogServiceImpl
package com.oy.service.impl; import org.beetl.sql.core.engine.PageQuery; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.oy.dao.BlogDao; import com.oy.model.Blog; import com.oy.service.BlogService; @Service public class BlogServiceImpl implements BlogService { @Autowired private BlogDao blogDao; @Override public PageQuery<Blog> pageBlog(long pageNumber, long pageSize) { return blogDao.createLambdaQuery(). andEq(Blog::getDeleteFlag, false) .page(pageNumber, pageSize); } }
测试 IndexController
package com.oy.controller; import java.sql.SQLException; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.sql.DataSource; import org.beetl.sql.core.engine.PageQuery; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import com.oy.model.Blog; import com.oy.service.BlogService; @Controller public class IndexController { @Autowired private DataSource dataSource; @Autowired private BlogService blogService; @GetMapping("/") public String index(HttpServletRequest request) throws SQLException{ request.setAttribute("title","一起学Beetl"); request.setAttribute("test","springboot 集成 beetl 一起来学呀"); // 测试数据源配置是否成功 System.out.println(dataSource.getConnection()); request.setAttribute("dataSource", dataSource); // 测试 BeetlSQL集成是否成功 PageQuery<Blog> pageQuery = blogService.pageBlog(1, 10); List<Blog> blogList = pageQuery.getList(); blogList.stream().forEach(blog -> System.out.print(blog)); return "index1.btl"; } }
3.3、修改模板文件为html结尾 <--返回目录
在application.properties中增加配置
beetl.suffix=html
3.4、for-in循环的使用 <--返回目录
往blog表中添加一些测试数据
INSERT INTO `beetl_test_blog`.`blog`(`id`, `content`, `delete_flag`, `img`, `category`, `title`, `create_time`, `update_time`) VALUES (1, '博客1', b'0', 'img1', NULL, '标题1', '2020-11-05 21:44:29', '2020-11-05 21:44:34'); INSERT INTO `beetl_test_blog`.`blog`(`id`, `content`, `delete_flag`, `img`, `category`, `title`, `create_time`, `update_time`) VALUES (2, '博客2', b'0', 'img2', NULL, '标题2', '2020-11-05 21:44:29', '2020-11-05 21:44:34'); INSERT INTO `beetl_test_blog`.`blog`(`id`, `content`, `delete_flag`, `img`, `category`, `title`, `create_time`, `update_time`) VALUES (3, '博客3', b'0', 'img3', NULL, '标题3', '2020-11-05 21:44:29', '2020-11-05 21:44:34');
IndexController
@Controller public class IndexController { @Autowired private DataSource dataSource; @Autowired private BlogService blogService; @GetMapping("/") public String index(HttpServletRequest request) throws SQLException{ request.setAttribute("title","一起学Beetl"); request.setAttribute("test","springboot 集成 beetl 一起来学呀"); // 测试数据源配置是否成功 System.out.println(dataSource.getConnection()); request.setAttribute("dataSource", dataSource); // 测试 BeetlSQL集成是否成功 PageQuery<Blog> pageQuery = blogService.pageBlog(1, 10); List<Blog> blogList = pageQuery.getList(); blogList.stream().forEach(blog -> System.out.print(blog)); request.setAttribute("page",pageQuery); return "index1.html"; } }
模板页面使用 for-in 渲染 List<Blog>数据
index1.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>${title}</title> </head> <body> ${test} <br/> dataSource:${dataSource} <br/><br/> <!-- 循环取出page中的List<Blog> --> <% for (blog in page.list) { %> 博客标题:${blog.title}<br/> 博客内容:${blog.content}<br/>
创建时间:${blog.createTime}<br/><br/> <% } %> </body> </html>
效果:
3.5、时间格式化 <--返回目录
${blog.createTime, "yyyy-MM-dd HH:mm:ss.SSS"}
<!-- 循环取出page中的List<Blog> --> <% for (blog in page.list) { %> 博客标题:${blog.title}<br/> 博客内容:${blog.content}<br/> 创建时间:${blog.createTime, "yyyy-MM-dd"}<br/><br/> <% } %>
3.6、条件语句 if 和 虚拟属性 size <--返回目录
虚拟属性 for (var i = 0; i < page.list.~size; i = i + 2)
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <style type="text/css"> .even { background-color:#aaa; } .odd { background-color:#eee; } </style> <title>${title}</title> </head> <body> ${test} <br/> dataSource:${dataSource} <br/><br/> <!-- 循环取出page中的List<Blog> --> <% for (var i = 0; i < page.list.~size; i = i + 2) { %> <ul type="disc"> <li class="even">博客标题:${page.list[i].title}</li> <li class="even">博客内容:${page.list[i].content}</li> <li class="even">创建时间:${page.list[i].createTime, "yyyy-MM-dd HH:mm:ss.SSS"}</li> </ul> <% if (i+1 < page.list.~size) { %> <ul type="disc"> <li class="odd">博客标题:${page.list[i+1].title}</li> <li class="odd">博客内容:${page.list[i+1].content}</li> <li class="odd">创建时间:${page.list[i+1].createTime, "yyyy-MM-dd HH:mm:ss.SSS"}</li> </ul> <% } %> <br/> <% } %> </body> </html>
4、共享变量和自定义模板配置 <--返回目录
4.1、session域 <--返回目录
public class IndexController { @GetMapping("/") public String index(HttpServletRequest request) { request.getSession().setAttribute("title","我的博客"); return "index1.html"; } @GetMapping("/page/index2") public String index2() { return "index2.html"; } }
index2.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>${session.title}</title> </head> <body> index2 </body> </html>
注意:先访问 "/"才在session中添加变量title,如果先访问"/page/index2"则此时没有title变量。
4.2、自定义模板配置 <--返回目录
共享变量:所有模板都可以使用的变量。
BeetlConfig
package com.oy; import java.util.HashMap; import java.util.Map; import org.beetl.core.GroupTemplate; import org.beetl.core.resource.ClasspathResourceLoader; import org.beetl.ext.spring.BeetlGroupUtilConfiguration; import org.beetl.ext.spring.BeetlSpringViewResolver; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class BeetlConfig { //模板根目录 ,比如 "templates" @Value("${beetl.templatesPath}") String templatesPath; @Value("${blog.title}") String title; @Bean public GroupTemplate getGroupTemplate(BeetlGroupUtilConfiguration beetlGroupUtilConfiguration) { GroupTemplate gt = beetlGroupUtilConfiguration.getGroupTemplate(); Map<String,Object> shared = new HashMap<>(); // 设置共享变量 shared.put("blogSiteTitle", title); shared.put("blogCreateUser", "oy"); gt.setSharedVars(shared); return gt; } @Bean public BeetlGroupUtilConfiguration getBeetlGroupUtilConfiguration() { BeetlGroupUtilConfiguration beetlGroupUtilConfiguration = new BeetlGroupUtilConfiguration(); //获取Spring Boot 的ClassLoader ClassLoader loader = Thread.currentThread().getContextClassLoader(); if(loader==null){ loader = BeetlConfig.class.getClassLoader(); } ClasspathResourceLoader cploder = new ClasspathResourceLoader(loader, templatesPath); beetlGroupUtilConfiguration.setResourceLoader(cploder); beetlGroupUtilConfiguration.init(); //如果使用了优化编译器,涉及到字节码操作,需要添加ClassLoader beetlGroupUtilConfiguration.getGroupTemplate().setClassLoader(loader); return beetlGroupUtilConfiguration; } @Bean(name = "beetlViewResolver") public BeetlSpringViewResolver getBeetlSpringViewResolver(BeetlGroupUtilConfiguration beetlGroupUtilConfiguration) { BeetlSpringViewResolver beetlSpringViewResolver = new BeetlSpringViewResolver(); beetlSpringViewResolver.setContentType("text/html;charset=UTF-8"); beetlSpringViewResolver.setOrder(0); beetlSpringViewResolver.setConfig(beetlGroupUtilConfiguration); return beetlSpringViewResolver; } }
4.3、ctxPath <--返回目录
<#footer style=”simple”/>
<#richeditor id=”rid” path="${ctxPath}/upload" name=”rname” maxlength=”${maxlength}”> ${html} …其他模板内容 </#richdeitor>
<#html:input id=’aaa
5、内置函数和安全输出 <--返回目录
内置函数:https://www.kancloud.cn/xiandafu/beetl3_guide/1992563
安全输出(重要)
安全输出是任何一个模板引擎必须重视的问题,否则,将极大困扰模板开发者。Beetl中,如果要输出的模板变量为null,则beetl将不做输出,这点不同于JSP,JSP输出null,也不同于Freemarker,如果没有用!,它会报错.
模板中还有俩种情况会导致模板输出异常
- 有时候模板变量并不存在,这时候必须报错,如果简单忽略不输出(Velocity就这样),很容易留坑
- 模板变量为null,但输出的是此变量的一个属性,如${user.wife.name}
针对前俩种情况,可以在变量引用后加上!以提醒beetl这是一个安全输出的变量,变量确实有可能不存在
如${user.wife.name! },即使user不存在,或者user为null,或者user.wife为null,或者user.wife.name为null beetl都不将输出
可以在!后增加一个常量(字符串,数字类型等),或者另外一个变量,方法,本地调用,作为默认输出,譬如:
${user.wife.name!”单身”},如果user为null,或者user.wife为null,或者user.wife.name为null,输出”单身”
譬如
${user.birthday!@System.constants.DefaultBir}, 表示如果user为null,或者user. birthday为null,输出System.constants.DefaultBir
还有一种情况很少发生,但也有可能,输出模板变量发生的任何异常,如变量内部抛出的一个异常
这需要使用格式${!(变量)},这样,在变量引用发生任何异常情况下,都不作输出,譬如
${!(user.name)},,beetl将会调用user.getName()方法,如果发生异常,beetl将会忽略此异常,继续渲染
值得注意的是,在变量后加上!不仅仅可以应用于占位符输出(但主要是应用于占位符输出),也可以用于表达式中,如:
<% var k = user.name!'N/A'+user.age!; %> <% ${k} %>
如果user为null,则k值将为N/A
在有些模板里,可能整个模板都需要安全输出,也可能模板的部分需要安全输出,使用者不必为每一个表达式使用!,可以使用beetl的安全指示符号来完成安全输出 如:
<%
DIRECTIVE SAFE_OUTPUT_OPEN;
%>
${user.wife.name}
模板其他内容,均能安全输出……
<%
//关闭安全输出。
DIRECTIVE SAFE_OUTPUT_CLOSE;
%>
Beetl不建议每一个页面都使用DIRECTIVE SAFE_OUTPUT_OPEN,这样,如果真有不期望的错误,不容易及时发现,其次,安全输出意味着beetl会有额外的代码检测值是否存在或者是否为null,性能会略差点。所以建议及时关闭安全输出(这不是必须的,但页面所有地方是安全输出,可能不容易发现错误)
如果你的所有模板都想安全输出,可以配置,但不推荐。严格了错误,就像try catch吃掉异常一样不容易发现这是个错误
SAFE_OUTPUT=true
在for-in 循环中 ,也可以为集合变量增加安全输出指示符号,这样,如果集合变量为null,也可以不进入循环体,如:
<% var list = null; for(item in list!){ }elsefor{ print("no data"); } %>
变量是否存在
<%
if(has(flag)){
print("flag变量存在,可以访问")
}
%>
如果需要判断变量是否存在,如果存在,还有其他判断条件,通常都这么写
<% if(has(flag)&&flag==0){ //code } %>
如果flag存在,而且值是0,都将执行if语句
但是,有更为简便的方法是直接用安全输出,如
<% if(flag!0==0){ //code } %>
flag!0 取值是这样的,如果flag不存在,则为0,如果存在,则取值flag的值,类似三元表达式 if((has(flag)?flag:0)==0)
安全输出表达式可以包括
- 字符串常量,如 ${user.count!"无结果"}
- boolean常量 ${user.count!false}
- 数字常量,仅限于正数,因为如果是负数,则类似减号,容易误用,因此,如果需要表示负数,请用括号,如${user.count!(-1)}
- class直接调用,如${user.count!@User.DEFAULT_NUM}
- 方法调用,如 ${user.count!getDefault() }
- 属性引用,如 ${user.count!user.maxCount }
- 任何表达式,需要用括号
6、定界符、占位符 <--返回目录
通俗易懂的说:
定界符就是 界定动态beetl语言 与 html静态代码之间的符号。 比如,在<%%>中间的代码,是beetl代码(被包裹的代码会被Beetl模板引擎编译), 而定界符之外的代码就是html静态代码(beetl语法不会生效)。
<%if(a==1){%> <a href="xxxx">跳转</a> <%}%>
占位符,就是在静态代码中占一个位置。占位符中可以使用表达式,以及函数,占位符包裹住的代码会被Beetl引擎编译。
比如下面的url:
<a href="${url}">跳转</a>
因为都是beetl代码,所以在定界符中,不需要在使用占位符包裹变量,可以直接使用变量名!
定界符与占位符的默认配置如下:
#占位符开始符号 DELIMITER_PLACEHOLDER_START=${ #占位符的结束符号 DELIMITER_PLACEHOLDER_END=} #定界符开始符号 DELIMITER_STATEMENT_START=<% #定界符结束符号 DELIMITER_STATEMENT_END=%>
7、Beetl作用阶段 <--返回目录
Beetl的主要作用是把 数据(变量)+ 模板 => 编译成 静态代码。
静态代码产生之后就没有 Beetl 什么事情了,浏览器打开静态代码才开始执行JS脚本。
8、标签函数 layout <--返回目录
所谓标签函数,即允许处理模板文件里的一块内容,功能等于同jsp tag。
如Beetl内置的layout标签
index.html
<% layout("/inc/layout.html",{title:'主题'}){ %> Hello,this is main part <% } %>
layout.html
title is ${title}
body content ${layoutContent}
footer
第1行变量title来自于layout标签函数的参数
第2行layoutContent 是layout标签体{}渲染后的结果
关于layout标签,参考高级主题布局
layout标签函数,相当于把公共部分抽取出来,包裹主单个页面的个性化内容。
9、标签函数 include <--返回目录
include 标签与JSP、freemark等其他的模板引擎类似。
在一个模板中包含另一个模板,
第一个参数是模板路径,
第二个参数是一个json对象,用来向include的模板传递参数。
<%include("/common/page.html",{page:msgPage,action:"detail",condition:"&id="+blog.id!}){}%>
以下是page.html的模板内容
<div class="paging"> <%if(page.pageNumber <= 1){%> <a href="#">首页</i></a> <%}else{%> <a href="${ctxPath}/${action!}?pageNumber=${page.pageNumber-1}${condition!}">上一页</i></a> <%}%> <%if(page.pageNumber >= page.totalPage){%> <a href="#">末尾页</i></a> <%}else{%> <a href="${ctxPath}/${action!}?pageNumber=${page.pageNumber+1}${condition!}">下一页</i></a> <%}%> </div>
10、自定义HTML标签 <--返回目录
Beetl 也支持HTML tag形式的标签,个人认为,这是一种引用模板更加优雅的实现,能和静态HTML标签融为一体。
比如在一个模板中引用另外一个模板,我们可以使用include
<%include("/common/page.html",{page:msgPage,action:"detail",condition:"&id="+blog.id!}){}%>
我们也可以为 page.html定义一个HTML标签,那他的调用方法就变成了下面这种。
<#page page="${msgPage}" condition='${"&id="+blog.id!}' action="detail"/>
是不是更加符合,静态HTML标签的习惯。
一、配置 HTML标签默认配置
#支持HTML标签
HTML_TAG_SUPPORT = true
#标签以#号开头识别为HTML标签
HTML_TAG_FLAG = #
#自定义标签文件Root目录和后缀
RESOURCE.tagRoot = htmltag
RESOURCE.tagSuffix = tag
如果想修改配置,直接在根目录下的 beetl.properties
当中修改覆盖就行
二、使用 在templates(你定义的模板目录)下新建htmltag,并且新建标签page.tag
,定义标签内容:
<div class="paging"> <%if(page.pageNumber <= 1){%> <a href="#">首页</i></a> <%}else{%> <a href="${ctxPath}/${action!}?pageNumber=${page.pageNumber-1}${condition!}">上一页</i></a> <%}%> <%if(page.pageNumber >= page.totalPage){%> <a href="#">末尾页</i></a> <%}else{%> <a href="${ctxPath}/${action!}?pageNumber=${page.pageNumber+1}${condition!}">下一页</i></a> <%}%> </div>
在需要调用标签的模板中写入:
<#page page="${msgPage}" condition='${"&id="+blog.id!}' action="detail"/>
Beetl自定义标签的传参形式,与HTML习惯保持一致,采用“属性=值”的方式,值必须使用双引号或者单引号,引起来。
三、其他的注意事项
- 可以在自定义标签里引用标签体的内容,标签体可以是普通文本,beetl模板,以及嵌套的自定义标签等。如上<#richeditor 标签体里,可用“tagBody”来引用
- HTML自定义标签 的属性值均为字符串 如<#input value="123" />,在input.tag文件里 变量value的类型是字符串
- 可以在属性标签里引用beetl变量,如<#input value="${user.age}" />,此时在input.tag里,value的类型取决于user.age
- 在属性里引用beetl变量,不支持格式化,如<#input value="${user.date,'yyyy-MM-dd'}"/>,如果需要格式化,需要在input.tag文件里自行格式化
- 在标签属性里传json变量需要谨慎,因为json包含了"}",容易与占位符混合导致解析出错,因此得使用""符号,如<#input value="${ {age:25} }" />
- html tag 属性名将作为 其对应模板的变量名。如果属性名包含“-”,则将转为驼峰命名的变量,如data-name,转为dataName
- 默认机制下,HTMLTagSupportWrapper2 实现了标签(2.8.x以前使用HTMLTagSupportWrapper)
11、Beetl自定义方法以及直接访问java类方法 <--返回目录
11.1、自定义方法
我们想要实现类似于${ strutil.subString ("hello",1)}
这样的方法,在Beetl模板中直接使用函数。
一、集成Function 接口,实现call方法
call方法存在两个参数,一个是模板传输过来的参数列表Object[] objects
,另外一个是beetl模板的上下文Context
。
上下文中包含以下信息,需要用到可以自取。
- byteWriter 输出流
- template 模板本身
- gt GroupTemplate
- globalVar 该模板对应的全局变量
- byteOutputMode 模板的输出模式,是字节还是字符
- safeOutput 模板当前是否处于安全输出模式
- 其他属性建议不熟悉的开发人员不要乱动
有几个注意事项,需要注意下:
- call方法要求返回一个Object,如果无返回,返回null即可
- 为了便于类型判断,call方法最好返回一个具体的类,如date函数返回的就是java.util.Date
- call方法里的任何异常应该抛出成Runtime异常
实现代码如下:
package com.ibeetl.blog.function; import org.beetl.core.Context; import org.beetl.core.Function; import java.text.SimpleDateFormat; import java.util.Date; /** * @author GavinKing * @ClassName: PrintTime * @Description: * @date 2018/12/11 */ public class PrintTime implements Function { @Override public String call(Object[] objects, Context context) { Date date = (Date) objects[0]; Date now = new Date(); Long fiveM = date.getTime() + (5*60*1000); Long thM = date.getTime() + (30*60*1000); Long oneH = date.getTime() + (60*60*1000); if(now.getTime() < fiveM){ return "刚刚"; } if(now.getTime() < thM){ return "半小时前"; } if(now.getTime() < oneH){ return "一小时前"; } SimpleDateFormat sdf = new SimpleDateFormat(objects[1].toString()); return sdf.format(date); } }
二、注册function函数
在beetl.properties 中,增加 FN.printTime = com.ibeetl.blog.function.PrintTime
进行注册。
三、在模板中使用
${printTime(msg.createTime!,"yyyy-MM-dd HH:mm:ss")}
这就可以使用我们自定义的方法了。
11.2、直接访问java类与方法
直接使用java方法,步骤没有那么复杂。
可以通过符号@来表明后面表达式调用是java风格,可以调用对象的方法,属性,比如:
${@user.getMaxFriend(“lucy”)} ${@user.maxFriend[0].getName()} ${@com.xxxx.constants.Order.getMaxNum()} ${@com.xxxx.User$Gender.MAN} <% var max = @com.xxxx.constants.Order.MAX_NUM; var c =1; var d = @user.getAge(c); %>
可以调用instance的public方法和属性,也可以调用静态类的属性和方法 ,需要加一个 @指示此调用是直接调用class,其后的表达式是java风格的。
我们在代码中新建一个类
package com.ibeetl.blog.function; import java.text.SimpleDateFormat; import java.util.Date; /** * @author GavinKing * @ClassName: PrintTimeUtil * @Description: * @date 2018/12/11 */ public class PrintTimeUtil { public static String printTime(Date date,String format){ Date now = new Date(); Long fiveM = date.getTime() + (5*60*1000); Long thM = date.getTime() + (30*60*1000); Long oneH = date.getTime() + (60*60*1000); if(now.getTime() < fiveM){ return "刚刚"; } if(now.getTime() < thM){ return "半小时前"; } if(now.getTime() < oneH){ return "一小时前"; } SimpleDateFormat sdf = new SimpleDateFormat(format); return sdf.format(date); } }
在模板中直接使用
${@com.ibeetl.blog.function.PrintTimeUtil.printTime(page.list[i+1].createTime,"yyyy-MM-dd HH:mm:ss")}
不用注册函数,直接使用类的static公共方法。
12、ajax局部渲染 <--返回目录
Beetl满足了更加流行的方式,研发了ajax局部渲染技术。
后台处理后返回一个json,浏览器端将json数据拆开,拼成一条一条的行数据,然后生成dom节点,追加到表格里。 作为另外一种可选技术,beetl支持局部渲染技术,允许后台处理返回的是一个完成的html片段,这样,前端浏览器可以直接将这个html片段追加到表格里。在性能测试里,俩种方式性能差别不大(http://bbs.ibeetl.com/ajax//)
在beetl模板中,使用#ajax 局部命名:{ .... }
包裹起来的代码,就是ajax需要渲染的局部代码。
如果一个模板返回正常的渲染整个模板文件,将会忽略这一标记,比如:return "index.html"
如果返回的是 模板名称#局部命名,Beetl将会只渲染这一小段的代码。比如return "index.html#局部命名"
举个例子,在项目中layout.html
模板中,增加了一段ajax标记
<div class="widewrapper main"> <div class="container"> <div class="row"> <div class="col-md-8 blog-main"> ${layoutContent} </div> <%if(isEmpty(notShow)){%> <aside class="col-md-4 blog-aside" id="tagsDiv"> <%#ajax tags :{%> <div class="aside-widget"> <header> <h3>Tags</h3> </header> <div class="body clearfix"> <ul class="tags"> <%for(tag in categorys!){%> <li><a href="/?category=${tag}">${tag}</a></li> <%}%> </ul> </div> </div> <%} }%> </aside> </div> </div> </div>
大家可以看到有一段使用 #ajax tags{}
包裹起来的代码
<%#ajax tags :{%>
.....
<%}%>
当controller中直接 return "layout.html"
时,将会渲染整个页面。
我们在controller中增加一个方法。
@GetMapping("/tags") public String tags(HttpServletRequest request) { request.setAttribute("categorys", blogService.listCategory()); return "common/layout.html#tags"; }
告诉beetl模板引擎,只渲染tags中间的代码。
可以看到我们渲染的代码为:
<div class="aside-widget"> <header> <h3>Tags</h3> </header> <div class="body clearfix"> <ul class="tags"> <li><a href="/?category=beetl">beetl</a></li> <li><a href="/?category=beetlsql">beetlsql</a></li> <li><a href="/?category=java">java</a></li> <li><a href="/?category=一起学Beetl">一起学Beetl</a></li> </ul> </div> </div>
仅仅渲染了我们中间这一段代码,能让我们更加优雅使用。
我们在前端只要使用js加入这段渲染的html代码即可。
<script type="text/javascript"> $(function(){ $.get("${ctxPath}/tags",function (data) { $("#tagsDiv").html(data); console.info(data); }); }); </script>
13、重定向 <--返回目录
@PostMapping("/saveBlog") public String saveBlog( Blog blog, HttpServletRequest request) { blogService.saveBlog(blog); return "redirect:/"; }
---