Spring Boot 模板引擎Thymeleaf自定义标签

Thymeleaf
placeholder image
admin 发布于:2022-05-09 10:19:39
阅读:loading

背景介绍

章接前文,自定义标签一直都是个人非常喜欢的一个技术点,它可以与实际需要相结合来简化我们功能的实现,本站就较多的JSP自定义标签的文章介绍,所以在本站建站初期在使用Thymeleaf的时候,也层花了一些时间专门用于它的自定义标签实现,但是也许是时间花的不够吧,一些知识细节属于云里雾里的,一些应用的深度不够,比如它的自定义标签如何获取内容体等,并未掌握,所以本篇所谓的自定义标签则是提供几个属性值处理的标签实现,详细实现过程如下。

自定义标签方言

package cn.chendd.base.thymeleaf.dialects;

import ...;

/**
 * 自定义标签方言类
 *
 * @author chendd
 * @date 2020/5/31 21:51
 */
@Component
public class DdDialect extends AbstractProcessorDialect {

    /**
     * 定义方言名称
     */
    private static final String DIALECT_NAME = "Dd Dialect";
    private static final String PREFIX = "dd";

    protected DdDialect() {
        super(DIALECT_NAME, PREFIX, StandardDialect.PROCESSOR_PRECEDENCE);
    }

    @Override
    public Set<IProcessor> getProcessors(String dialectPrefix) {
        Set<IProcessor> processors = Sets.newHashSet();
        processors.add(new FormatProcessor(dialectPrefix));
        processors.add(new ContentBodyProcessor(dialectPrefix));
        processors.add(new FormatAttributeTagProcessor(TemplateMode.HTML , dialectPrefix));
        return processors;
    }
}

自定义格式化标签

package cn.chendd.base.thymeleaf.processors;

import ...;

/**
 * 格式化处理类
 *
 * @author chendd
 * @date 2020/5/31 21:56
 */
public class FormatProcessor extends AbstractElementTagProcessor {

    private static final String TAG_NAME = "format";//标签名
    private static final int PRECEDENCE = 100000;//优先级

    public FormatProcessor(String dialectPrefix) {
        super(
                TemplateMode.HTML, // 此处理器将仅应用于HTML模式
                dialectPrefix,     // 要应用于名称的匹配前缀
                TAG_NAME,          // 标签名称:匹配此名称的特定标签
                true,              // 将标签前缀应用于标签名称
                null,              // 无属性名称:将通过标签名称匹配
                false,             // 没有要应用于属性名称的前缀
                PRECEDENCE);       // 优先(内部方言自己的优先)
    }

    @Override
    protected void doProcess(ITemplateContext context, IProcessableElementTag tag, IElementTagStructureHandler structure) {
        String value = tag.getAttributeValue("value");
        String type = tag.getAttributeValue("type");
        FormatTypeEnum formatType = FormatTypeEnum.getInstance(type);
        String result;
        if (formatType == null || StringUtils.isEmpty(value)) {
            result = new UndefinedFormat().format(value);
        } else {
            Class<? extends FormatType> formatTypeClazz = formatType.getClazz();
            try {
                result = ((FormatType) formatTypeClazz.newInstance()).format(value);
            } catch (InstantiationException | IllegalAccessException e) {
                throw new UnsupportedOperationException(e);
            }
        }
        //替换原有标签的内容体
        //structure.setBody(result , false);
        //删除原有标签
        //structure.removeTags();
        structure.replaceWith(result , false);
    }
}

自定义标签属性

package cn.chendd.base.thymeleaf.processors;

import ...;

/**
 * 自定义属性
 * @author chendd
 * @date 2020/6/1 20:09
 */
public class FormatAttributeTagProcessor extends AbstractStandardExpressionAttributeTagProcessor {

    public static final int PRECEDENCE = 1300;
    public static final String ATTR_NAME = "raw-format";

    public FormatAttributeTagProcessor(final TemplateMode templateMode, final String dialectPrefix) {
        super(templateMode, dialectPrefix, ATTR_NAME, PRECEDENCE, true, (templateMode == TemplateMode.TEXT));
    }

    @Override
    protected void doProcess(ITemplateContext context, IProcessableElementTag tag, AttributeName attributeName, String attributeValue, 
                             Object expressionResult, IElementTagStructureHandler structureHandler) {
        if(expressionResult == null) {
            structureHandler.removeTags();
            return;
        }
        String result = RamFormat.Ram.getInstance(Double.valueOf(expressionResult.toString()));
        structureHandler.replaceWith(result , false);
    }
}

其它标签测试

package cn.chendd.base.thymeleaf.processors;

import ...;

/**
 * 内容体标签
 *
 * @author chendd
 * @date 2022/6/4 11:17
 */
public class ContentBodyProcessor extends AbstractElementTagProcessor {

    public static final int PRECEDENCE = 100000;
    public static final String TAG_NAME = "content";

    public ContentBodyProcessor(String dialectPrefix) {
        super(
                TemplateMode.HTML, // 此处理器将仅应用于HTML模式
                dialectPrefix,     // 要应用于名称的匹配前缀
                TAG_NAME,          // 标签名称:匹配此名称的特定标签
                true,              // 将标签前缀应用于标签名称
                null,              // 无属性名称:将通过标签名称匹配
                false,             // 没有要应用于属性名称的前缀
                PRECEDENCE);       // 优先(内部方言自己的优先)
    }

    @Override
    protected void doProcess(ITemplateContext context, IProcessableElementTag tag, IElementTagStructureHandler structure) {
        structure.setAttribute("hello" , "world");
        ArrayList<Point> list = Lists.newArrayList(new Point(1, 2), new Point(3, 4));
        structure.setLocalVariable(tag.getAttribute("var").getValue() , list);
    }

}

调用代码示例

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:dd="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Thymeleaf简单使用</title>
</head>
<body>

  <fieldset>
    <legend>自定义标签(格式化)</legend>
      <h3>千分位金额:<dd:format th:type="amount" th:value="'5201314'">出生时间</dd:format></h3>
      <h3>时间范围:<dd:format th:type="datetime" th:value="'2021-07-09 11:18:00'">出生时间</dd:format></h3>
      <h3>磁盘大小:<dd:format th:type="ram" th:value="'5201314'">出生时间</dd:format></h3>
      <h3>百分比:<dd:format th:type="percentage" th:value="'3.1414926'">出生时间</dd:format></h3>
      <h3>undefined:<dd:format th:type="undefined" th:value="'undefined原样输出'">undefined</dd:format></h3>
  </fieldset>

  <fieldset>
    <legend>自定义属性</legend>
      <h3>标签使用方式:<th:block dd:raw-format="'5201314'">标签使用</th:block></h3>
      <h3>属性使用方式:<th:block data-dd-raw-format="'5201314'">属性使用</th:block></h3>
  </fieldset>

</body>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:dd="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Thymeleaf简单使用</title>
</head>
<body>

  <dd:content var="list" sql="select 1 x , 2 y union 3 x , 4 y from dual">
      <div>
          <th:block th:each="item : ${list}">
              <h3 th:text="${item.x} + ' - ' + ${item.y}">x - y</h3>
          </th:block>
      </div>
  </dd:content>

</body>
</html>

运行结果示例

image.png

(浏览器显示效果)

image.png

(swagger响应页面结果)

image.png

(swagger响应结果)

知识点说明

(1)引入<html xmlns:th="http://www.thymeleaf.org" xmlns:dd="http://www.w3.org/1999/xhtml" >空间,自定义标签的dd为标签的方言;

(2)<dd:format/>标签分别有th:type和th:value两个属性,均接收字符串类型参数值,type分别处理amount/datetime/ram/percentage/undefined几种类型的格式化类型,分别为格式化千分位金额/日期差/磁盘大小/百分比/未知类型等;

(3)raw-format为自定义属性,自定义属性有两种使用格式,分别是dd:raw-format='xxx'和data-dd-raw-format='yyy';

(4)<dd:content/>是一个用于测试将自定义标签与内置标签的结合使用,是个人比较喜欢的一种应用方式,比如上述示例中定义了sql属性和var属性,即将sql查询出来的结果集List赋值到list属性中,list属性保存在自定义标签dd:content的标签体范围内,再结合th:each进行循环输出最终得到上述的结果;

(5)其它API细节介绍:

//给标签增加自定义属性和属性值
structure.setAttribute("hello" , "world");
//将参数名和参数值保存在标签范围内中使用(未验证是在标签体范围内有效还是在整个页面范围有效)
structure.setLocalVariable("list" , list);
//获取属性名var的属性值
tag.getAttribute("var").getValue()
tag.getAttributeValue("value");
//删除原有标签
structure.removeTags();
//替换原有标签的内容体
structure.setBody(result , false);
//替换原有标签
structure.replaceWith(result , false);

源码下载

源码工程下载可转至https://gitee.com/88911006/chendd-blog-examples项目的Thymeleaf分支;


 点赞


 发表评论

当前回复:作者

 评论列表


留言区