视频《一起学Beetl》记录

以一个博客项目,实战使用

项目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 + "]";
    }
    
    
}
View Code

  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');
View Code

  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:/";
}

---

原文地址:https://www.cnblogs.com/xy-ouyang/p/13926860.html