Jet模板文件语法

Jet的模板编写起来相对简单,不仅如此你也可以找到相应的插件来进行代码高亮辅助你的代码编写。

基本渲染

渲染一个模板所使用语句如下:

func (t *Template) Execute(w io.Writer, variables VarMap, data interface{}) error

一个完整的例子看起来如下:

var viewSet = jet.NewHTMLSet("./views"))

t, err := viewSet.GetTemplate(templateName)
if err != nil {
    // handle error
}

context := struct{
    Data string
}{
    Data: "apples",
}

var w bytes.Buffer // (or gin's context.Writer, for example)
vars := make(jet.VarMap)
if err = t.Execute(&w, vars, context); err != nil {
    // error when executing template
}

当模板渲染时,你可以使用三种方式传入动态数据。

定界符

Jet模板引擎中,默认变量使用{{}}将变量包裹起来。你也可以改变这个符号使用自己的符号来定义,例如使用中括号将数量进行包裹。

var viewSet = jet.NewHTMLSet("./views"))

viewSet.Delims("[[", "]]")

你的模板文件写起来就可以使用如下方式。

<!-- file: "views/home.jet" -->
[[extends "layouts/application.jet"]]
[[block body()]]
  <main>
    This content will be yielded in the layout above.
  </main>
[[end]]

定界符设定是一个全局的设定,你无法将用多种定界符混用。

在模板中,注释可以使用{**}进行包裹。

{* this is a comment *}

访问输入

全局变量

添加一个全局变量要在jet.Set中设置。

viewSet.AddGlobal("version", "v1.1.145")

模板中使用

<footer>Version: {{ version }}</footer>

全局函数

和全局变量类似

viewSet.AddGlobal("noblue", func(s string) string {
    return strings.Replace(s, "blue", "", -1)
})

// If speed is a consideration, use GlobalFunc instead
viewSet.AddGlobalFunc("noblue", func(args jet.Arguments) reflect.Value {
    a.RequireNumOfArguments("noblue", 1, 1)
    return reflect.ValueOf(strings.Replace(s, "blue", "", -1))
})

模板引擎中使用如下

<div>{{ noblue("red blue green") }}</div>
<!-- Alternate call syntax, using pipelining (see below) -->
<div>{{ "red blue green"|noblue }}</div>
Jet中提供了一些默认全局函数,你可以在另外一篇文章中查看。

VarMap

使用jet.VarMap在前面已经见过多次了,基本用法如下:

vars := make(jet.VarMap)
vars.Set("user", &User{})

模板中用法如下

<div>{{ user }}</div>

上下文Context

上下文在渲染过程中位于顶层,你可以在模板中使用.语法来访问。

data := &http.Request{Method: http.MethodPost}
err := t.Execute(&w, vars, data)
// ...
<div>Request method is: {{ .Method }}</div> <!-- Would be "POST" -->

遍历数据

Structs

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"] }}.

如果要遍历所有的key和value,可以使用如下方式

{{if v, ok := testMap["testKey"]; ok}}
  {{ v }} <!-- will print "test-value" -->
{{end}}

Slices 和 Arrays

The test slice's value is {{ testSlice[0] }}.

遍历切片和Map

{{range .}}
{{end}}

{* you can do an assign in a range statement;
this value will be assigned in every iteration *}
{{range value := .}}
{{end}}

{* the syntax is the same for maps as well as slices *}
{{range keyOrIndex, value := .}}
{{end}}

{* Ranging over a collection changes the context to each item in turn. *}
{{range favoriteColors}}
  <p>I like {{.}}</p>
{{end}}

在遍历的最后可以使用{{else}},来保证进行空数组时进行的默认操作.

{{range index, value := .}}
{{else}}
{{end}}

自定义范围遍历

如果你需要使用自定义范围遍历,那么数据要符合jet.Ranger接口,该接口定义如下:

Range() (reflect.Value, reflect.Value, bool)

表达式

数据表达式

支持表达式:+,-,*,/,%

{{ 1 + 2 * 3 - 4 }} <!-- will print 3 -->
{{ (1 + 2) * 3 - 4.1 }} <!-- will print 4.9 -->

字符串拼接

{{ "HELLO"+" WORLD!" }} <!-- will print "HELLO WORLD!" -->

比较运算符

{{ if item == true || !item2 && item3 != "test" }}

{{ if item >= 12.5 || item < 6 }}

变量声明

{{ item := items[1] }}
{{ item2 := .["test"] }} <!-- also works on the context -->

三元运算符

{{ .HasTitle ? .Title : "Title not set" }}

切片运算符

{{range v := .[1:3]}}{{ v }}{{end}} 
<!-- context is []string{"0", "1", "2", "3"}, will print "12" -->

管道

{{ "HELLO WORLD"|lower }} <!-- will print "hello world" -->

管道还可以像如下方式使用

chaining: {{ "HELLO"|lower|repeat:2 }} (will print hellohello)
prefix: {{ lower:"HELLO"|upper|repeat:2 }} (will print HELLOHELLO)
simple function call: {{ lower("HELLO") }}

条件语句

{{if expression}}
{{end}}

{* assignment is possible as well *}
{{if ok := expression; ok }}

{{else if expression}}

{{else}}

{{end}}

Blocks

你可以按块为单位调用所需要的内容

<!-- Define a block -->
{{block copyright()}}
  <div>© ACME, Inc. 2018</div>
{{end}}

<!-- Invoke with "yield" -->
<footer>
  {{yield copyright()}}
</footer>

<!-- Output -->
<footer>
  <div>© ACME, Inc. 2018</div>
</footer>

需要注意的是,在调用块之前必须先定义它,如果块定义不在当前模板内,那需要先导入其他模板。

定义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定义中,参数之间用,号分割,并且支持默认值。

Block的命名不能使contentyield或者其他表达式。

调用block

{{yield inputField(id="firstname", label="First name", required=true)}}

使用yield来调用block,在调用时传入参数,当然你也可以传入上下文。

递归调用

<!-- 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>

调用包装

在定义block时,你可以在内部使用{{yield content}}元素,当调用block时,在yeild关键词后使用关键词content来传递上下文。

<!-- 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 pageHeader() .Header}}
  {{ .Title }}
{{end}}

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}}。当模板不存在是,渲染引擎不会发生恐慌或者抛出错误。他可以返回一个bool值,来告知你导入结果。

{{includeIfExists "sidebars/"+user.ID+".jet" user}} <!-- no content if the template does not exist -->

{{if ok := includeIfExists("sidebars/"+user.ID, user); !ok}}
    <p>The template does not exist.</p>
  {{end}}
{{end}}

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>

Extend

扩展功能一般都使用在布局功能中。下面是一个布局的例子

<!-- file: "views/layouts/application.jet" -->
<!DOCTYPE html>
<html>
  <head>
    <title>{{yield title()}}</title>
  </head>
  <body>
    {{yield body()}}
  </body>
</html>

你可以在主模板中扩展布局,覆盖布局中的定义。

<!-- 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}}

最终渲染结果

<!DOCTYPE html>
<html>
  <head>
    <title>My title</title>
  </head>
  <body>
      <main>
        This content will be yielded in the layout above.
      </main>
  </body>
</html>
扩展模板使用时,要求`{{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.jet.html