import "github.com/CloudyKit/jet/v3"
- 动态开发,静态加载,灵活的开发模式和快速运行。
- 语法和默认模板类似,支持通道运算
{{ expression|pipe|pipe }}
yield
(渲染调用) ,block
(组件布局) 多种组合功能。- 局部变量,内置函数,类似
len
,isset
等 - 字符串处理,省略
printf
功能,直接"song" + filed
输出字符串 - isset 变量检测,支持三元运算
- 自定义扩展支持
imported
,included
,extends
,文件名.jet
,.html.jet
,.jet.html
开始
- 使用
jet.NewHTMLSet(path)
设定要监控的目录。 - 创建模板文件
- 实例化模板文件
main.go
import "github.com/CloudyKit/jet/v3"
// 定义模板路径
var tpls = jet.NewHTMLSet("./tpls")
// 初始化模板
func initView() {
// 设定开发模式 动态更新
tpls.SetDevelopmentMode(true)
// 修改定界符 {{ }} 为 {{ }}
// tpls.Delims("{%", "%}")
// 追加全局变量
tpls.AddGlobal("HOST", "http://127.0.0.1/")
// 追加全局函数
tpls.AddGlobalFunc("timeFmt", jet.Func(func(args jet.Arguments) reflect.Value {
expression := args.Get(0).Interface().(time.Time)
return reflect.ValueOf(expression.Format("2006-01-02"))
}))
}
// 首页部分
func home(w http.ResponseWriter, r *http.Request) {
templateName := "home.jet"
view, err := tpls.GetTemplate(templateName)
if err != nil {
// 模板加载错误处理
}
// 变量设定
vars := make(jet.VarMap)
vars.Set("anum", 1)
// 数据设定
context := struct {
Data string
}{
Data: "apples",
}
if err = view.Execute(w, vars, context); err != nil {
// 模板渲染错误处理
}
}
func main() {
http.HandleFunc("/", home)
log.Fatal(http.ListenAndServe(":8080", nil))
}
jet.NewHTMLSet
设置模板路径,初始化模板。tpls.SetDevelopmentMode(true)
设定开发模式,可以动态加载jet
模板文件,在不重启服务情况下调试模板文件。tpls.Delims("{%", "%}")
修改模板定界符jet.VarMap
用于向模板传输变量,在模板中直接访问。view.Execute
模板写入io.Writer
。
layout.jet
模板文件类型为
jet
语法格式采用类似jinja
模板类型
<!DOCTYPE html>
<html>
<head></head>
<body>
{{yield body()}}
</body>
</html>
home.jet
{{extends "layout.jet"}}
{{block body()}}
<main>
在 body 布局中渲染。
</main>
测试变量: {{ anum }}。
<div>
测试数据: {{ .Data }}
</div>
{{end}}
extends
继承模板文件block body
实现了yield body
部分
模板渲染
通过阅读文件 main.go
中 home
部分
templateName := "home.jet"
view, err := tpls.GetTemplate(templateName)
if err != nil {
// 模板加载错误处理
}
// var b bytes.Buffer
// 变量设定
vars := make(jet.VarMap)
vars.Set("anum", 1)
// 数据设定
context := struct {
Data string
}{
Data: "apples",
}
if err = view.Execute(w, vars, context); err != nil {
// 模板渲染错误处理
}
Execute
用来渲染模板, 参数分别为 写入对象, 变量, 数据。
func (t *Template) Execute(w io.Writer, variables VarMap, data interface{}) error
- 使用
jet.VarMap
字典类型设定输出到模板中的变量,一般使用vars.Set("field",interface{})
来输出数据到模板, 访问直接使用{{ field }}
。 - context 模板上下文数据,自定义结构 ,在模板数据中使用
{{ .data }}
来渲染显示(类似原生模板数据渲染)。
jet 参数部分,模板中设计
block/yield
和 Execute 都类似,参数部分和上下文部分 来传输数据结构,具体的使用,按照使用内容而定,上下文一般用于约定好格式的数据内 容,参数的定义更接近函数的使用方式。参照 block 语法。
语法规则
下方 tpls
为 jet.NewHTMLSet("./tpls")
实例。
设定
- 开发模式,动态加载模板
tpls.SetDevelopmentMode(true)
- Delimiters 界定符号,默认为
{{
和}}
,可以使用tpls.Delims("{%","%}")
来设定自己的界定符。如果兼容 vue/js 的话,定界符可能会冲突,建议模板中使用{%
,%}
作为定界符。
tpls.Delims("{%", "%}")
- 模板注释, 使用
{* 注释内容 *}
。
模板输出
全局变量 Global Variables
tpls.AddGlobal("version", "v1.1.145")
<footer>Version: {{ version }}</footer>
全局函数 Global Functions
定义函数有两种方式,AddGlobal
, AddGlobalFunc
,官方言明第二种更有有效率,在解析上执行效率更好。
tpls.AddGlobal("noblue", func(s string) string {
return strings.Replace(s, "blue", "", -1)
})
// 考虑执行效率使用 GlobalFunc
tpls.AddGlobalFunc("noblue", func(args jet.Arguments) reflect.Value {
args.RequireNumOfArguments("noblue", 1, 1)
s := args.Get(0).String()
return reflect.ValueOf(strings.Replace(s, "blue", "b2", -1))
})
全局函数还是放在一个模板的字典中,同变量相同使用健值来调用,同样健值会覆盖之前的变量。
在模板中调用函数,一种普通的方式,另一种为类似管道(pipelining)的语法调用,后者可以递进使用多个函数。
<div>{{ noblue("red blue green") }}</div>
<div>{{ "red blue green"|noblue }}</div>
模板变量 Varmap
vars := make(jet.VarMap)
vars.Set("user", &User{})
Set(key, value)
追加变量,在模板中直接使用 key
调用。
<div>{{ user }}</div>
上下文数据 Context
该数据为 Execute
第三参数,用于作为 .
起始传入模板的结构体。
data := &http.Request{Method: http.MethodPost}
err := t.Execute(&w, vars, data)
<div>Request method is: {{ .Method }}</div> <!-- Would be "POST" -->
表达式 Expressions
算数
{{ 1 + 2 * 3 - 4 }} <!-- 结果 3 -->
{{ (1 + 2) * 3 - 4.1 }} <!-- 结果 4.9 -->
字符串连接
{{ "HELLO"+" WORLD!" }}
比较运算符
{{ if item == true || !item2 && item3 != "test" }}
{{ if item >= 12.5 || item < 6 }}
变量声明
{* VarMap *}
{{ item := item[1] }}
{* Context 数据 *}
{{ item2 := .["test"] }}
三元表达式
{{ .HasTitle ? .Title : "标题不存在"}}
切片表达式
对于数组和切片(slice)类型数据, 可以使用类似 Go 语言中 [start:end)
语法,其中 end 不在范围之内.
{{range v := .[1:3]}}{{ v }}{{end}}
<!-- context is []string{"0", "1", "2", "3"}, will print "12" -->
管道Pipe
函数调用基本方式为 管道前为管道之后函数的参数使用,其他的参数使用:
跟随。
{{ "HELLO WORLD"|lower }} <!-- "hello world" -->
参数表达,func(args,...)
也有 func:args,...
方式。
chaining: {{ "HELLO"|lower|repeat:2 }} <br/><!-- hellohello -->
prefix: {{ lower:"Hello"|upper|repeat:2 }} <br/><!-- HELLOHELLO -->
prefix2: {{ repeat:"Hello",2 }} <br/> <!-- HelloHello -->
simple function call: {{ lower("HELLO") }} <!-- hello -->
使用 raw
, unsafe
转义函数的时候必须为管道最后执行。
{{ "hello"|upper|raw }} <!-- valid -->
{{ raw:"hello" }} <!-- valid -->
{{ raw:"hello"|upper }} <!-- invalid -->
遍历数据 Traversing Data
结构 Struct
可以调用字段和方法
The user's firstname is {{ user.Firstname }}.
The user's name is {{ user.Fullname() }}.
Now his name is {{ user.Rename("Frederick", "Johnson") }}.
字典 Maps
使用 []
调用字段
The test map's value is {{ testMap["testKey"] }}.
也可以使用二值反馈判定结果, 也可以使用 isset() ? a : b
达到同样的效果。
{{if v, ok := testMap["testKey"]; ok}}
{{ v }}
{{end}}
数组 Slices and Arrays
The test slice's value is {{ testSlice[0] }}.
遍历 maps 和 slices
{{range .}}
{{end}}
{* 赋值遍历 *}
{{range value := .}}
{{end}}
{* 健值/索引,赋值,遍历 map,slice *}
{{range keyOrIndex, value := .}}
{{end}}
{* 这里的 . 会根据上下本判定显示内容,类似赋值遍历 *}
{{range favoriteColors}}
<p>I like {{.}}</p>
{{end}}
如果遍历的值为空对象可以执行 else
部分的渲染。
{{range index, value := .}}
{{else}}
{{end}}
自定义
jet 提供 interface jet.Ranger
可以定义自己的遍历方法。
type Ranger interface {
Range() (reflect.Value, reflect.Value, bool)
}
控制结构
if
, else
, 和 else if
{{if expression}}
{{end}}
{* assignment is possible as well *}
{{if ok := expression; ok }}
{{else if expression}}
{{else}}
{{end}}
block/yield 模板组件
block
可以定义部分模板,类似前端中 components
组件, 定义 block
片段后,使用 yield
调用即可渲染。
jet 中包装代码块都是以
{{end}}
来结束。下方yield
没有包装内容则不需要end
。
<!-- 定义 命名为 copyright 的 block -->
{{block copyright()}}
<div>© ACME, Inc. 2018</div>
{{end}}
<!-- 通过 "yield" 调用-->
<footer>
{{yield copyright()}}
</footer>
<!-- 输出结果 -->
<footer>
<div>© ACME, Inc. 2018</div>
</footer>
想要调用定义的
block
, 需要yield
和block
在同级别模块中调用,可以参考Import
语法来导入需要调用的block
。
block 中无法定义 block, block 中可以存在 yield 调用。
定义block
{{block inputField(type="text", label, id, value="", required=false)}}
<div class="form-field">
<label for="{{ id }}">{{ label }}</label>
<input type="{{ type }}" value="{{ value }}" id="{{ id }}" {{if required}}required{{end}} />
</div>
{{end}}
定义 block
同函数相同,参数可以设定默认值,但参数没有顺序之分,参数默认值可以为任意表达式。
参数值定义:
- 全局或者局部变量
- 函数表达式
- isset 定义
block 的命名不可以为 jet 的关键词。
调用block
{{yield inputField(id="firstname", label="First name", required=true)}}
使用 yield 来调用 block ,参数没有顺序之分,单是没有默认值的参数必须提供值,否则回渲染错误。
另外 block 也支持上下文 context
传参。
<!-- Define block -->
{{block buff()}}
<strong>{{.}}</strong>
{{end}}
<!-- Invoke & provide a context -->
{{yield buff() "Man"}}
<!-- Output -->
<strong>Man</strong>
递归调用
<!-- Define a block which calls itself -->
{{block menu()}}
<ul>
{{range .}}
<li>{{ .Text }}{{if len(.Children)}}{{yield menu() .Children}}{{end}}</li>
{{end}}
</ul>
{{end}}
<!-- Invoke -->
<nav>
{{yield menu() navItems}}
</nav>
包装调用
有需要将 yield
包装的内容直接传递到 block
中的情况下, 在block
使用 {{yield content}}
来渲染 ,即使用 content
作为上下文数据传递到 block
中 调用渲染。
yield
包含内容的时候需要{{end}}
来结束块
。
<!-- Define block -->
{{block link(target)}}
<a href="{{target}}">{{yield content}}</a>
{{end}}
<!-- Invoke -->
{{yield link(target="https://www.example.com") content}}
Example.com
{{end}}
<!-- Output -->
<a href="https://www.example.com">Example.com</a>
一个更加复杂的完整实例
<!-- Define block -->
{{block cols(class="col-md-12", wrapInContainer=false, wrapInRow=true)}}
{{if wrapInContainer}}<div class="container">{{end}}
{{if wrapInRow}}<div class="row">{{end}}
<div class="{{ class }}">{{yield content}}</div>
{{if wrapInRow}}</div>{{end}}
{{if wrapInContainer}}</div>{{end}}
{{end}}
<!-- Invoke -->
{{yield cols(class="col-xs-12") content}}
<p>This content will be wrapped.</p>
{{end}}
<!-- Output -->
<div class="row">
<div class="col-xs-12">
<p>This content will be wrapped.</p>
</div>
</div>
这种上下文传递的方式比参数传输性能更好,在第一次调用渲染的时候就已经缓存处理,block中调用就不需要额外的处理了。
同时定义和调用
{{block pageHeader() .Header}}
{{ .Title }}
{{end}}
这是一种表达式定义上下文的一种方式。
include 声明
{{include filepath context}}
include
和其他的模板语言类似,都是将模板文件插入到现有的模板中,可以使用全局和局部的变量,类似将代码块存放到特定的文件中。 include
最后的参数可以传递上下文。
<!-- file: "views/users/_user.jet" -->
<div class="user">
{{ .Firstname }} {{ .Lastname }}: {{ .Email }}
</div>
<!-- file: "views/users/index.jet" -->
{{range user := users}}
{{include "users/_user.jet" user}}
{{end}}
IncludeIfExists
当不确认要插入的模板存在与否,可以使用 IncludeIfExists
代码块来使用
{{if ok := includeIfExists("sidebars/"+user.ID, user); !ok}}
<p>The template does not exist.</p>
{{end}}
import 声明
import
用来导入已经定义的 block
来渲染当前的模板。
<!-- file: "views/common/_menu.jet" -->
{{block menu()}}
<ul>
{{range .}}
<li>{{ .Text }}{{if len(.Children)}}{{yield menu() .Children}}{{end}}</li>
{{end}}
</ul>
{{end}}
<!-- file: "views/home.jet" -->
{{import "common/_menu.jet"}}
{{yield menu() navItems}}
<main>
Content.
</main>
利用 import 和 include 基本上就组成了组件系统,用 block 来定义组件,yield 渲染,即可完成多数的功能。
extends 声明
extends 扩展模板,用来布局使用。可以理解为模板文件A 在 extend 另一个模板文件B的时候,基本继承B的布局结构。 实际上是 B 来渲染 A。
<!-- file: "views/layouts/application.jet" -->
<!DOCTYPE html>
<html>
<head>
<title>{{yield title()}}</title>
</head>
<body>
{{yield body()}}
</body>
</html>
使用该布局,则需要为该布局的 yield 实现 block。
<!-- file: "views/home.jet" -->
{{extends "layouts/application.jet"}}
{{block title()}}My title{{end}}
{{block body()}}
<main>
This content will be yielded in the layout above.
</main>
{{end}}
继承了布局,意味只能继承一个模板,在实际应用的时候可以扩展多层模板来满足需要。
模板相对路径查找
模板中 include/import/extends
参数跟随的是 jet 模板文件路径,其查找方式根据当前模板文件的相对路径文件进行查找。
<!-- file: "views/auth/_logo.jet" -->
<img src="{{ .LogoURL }}" />
<!-- file: "views/auth/login.jet" -->
{{extends "layouts/application.jet"}}
{{block body()}}
{{include "_logo" company}}
{{end}}
如果文件名称后缀为.jet
,.html.jet
或 .html.jet
可以省略模板的后缀文件。
内置函数
jet 模板提供了一些基本的模板函数。
isset()
isset(var) // variables
isset(.var) // struct
isset(.["key"]) // map
len()
计算元素长度, 适用于 arrays, channels, slices, maps, and strings. 如果是 struct 则返回字段数量。
String
- lower (strings.ToLower)
- upper (strings.ToUpper)
- hasPrefix (strings.HasPrefix)
- hasSuffix (strings.HasSuffix)
- repeat (strings.Repeat)
- replace (strings.Replace)
- split (strings.Split)
- trimSpace (strings.TrimSpace)
其他
- html (html.EscapeString)
- url (url.QueryEscape)
- safeHtml (escape HTML)
- safeJs (escape JavaScript)
- raw, unsafe (no escaping)
- writeJson, json to dump variables as JSON strings
- map: map(key1, value1, key2, value2) – use with caution because accessing these is slow when used with lots of elements and checked/read in loops.
扩展和自定义
函数定义
views.AddGlobalFunc("base64", func(args jet.Arguments) reflect.Value {
args.RequireNumOfArguments("base64", 1, 1)
buffer := bytes.NewBuffer(nil)
fmt.Fprint(buffer, args.Get(0))
return reflect.ValueOf(base64.URLEncoding.EncodeToString(buffer.Bytes()))
})
定义快速函数,参数由 args.RequireNumOfArguments
来定义函数参数的长度, 0
不限制。参数获取使用index索引 args.Get(index)
获取。
另外可以扩展的部分还有
jet.Ranger
jet.Renderer
SafeWriter
模板变量保护互斥锁处理,可以参考官方文档