mirror of
https://gitee.com/johng/gf
synced 2026-06-06 16:21:40 +08:00
简化模板引擎功能,完善模板引擎示例
This commit is contained in:
@ -8,10 +8,9 @@ package gmvc
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"html/template"
|
||||
"gitee.com/johng/gf/g/os/gview"
|
||||
"gitee.com/johng/gf/g/frame/gins"
|
||||
"gitee.com/johng/gf/g/net/ghttp"
|
||||
"gitee.com/johng/gf/g/frame/gins"
|
||||
)
|
||||
|
||||
// MVC视图基类(一个请求一个视图对象,用完即销毁)
|
||||
@ -49,23 +48,15 @@ func (view *View) Assign(key string, value interface{}) {
|
||||
|
||||
// 解析模板,并返回解析后的内容
|
||||
func (view *View) Parse(file string) ([]byte, error) {
|
||||
// 查询模板
|
||||
tpl, err := view.view.Template(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 绑定函数,对基类的include进行覆盖(由于涉及到模板变量的传递)
|
||||
tpl.BindFunc("include", view.funcInclude)
|
||||
// 执行解析
|
||||
view.mu.RLock()
|
||||
content, err := tpl.Parse(view.data)
|
||||
content, err := view.view.Parse(file, view.data)
|
||||
view.mu.RUnlock()
|
||||
return content, err
|
||||
}
|
||||
|
||||
// 解析指定模板
|
||||
func (view *View) Display(files...string) error {
|
||||
file := "default"
|
||||
file := "index.tpl"
|
||||
if len(files) > 0 {
|
||||
file = files[0]
|
||||
}
|
||||
@ -76,17 +67,4 @@ func (view *View) Display(files...string) error {
|
||||
view.response.Write(content)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 模板内置方法:include
|
||||
func (view *View) funcInclude(file string) template.HTML {
|
||||
tpl, err := view.view.Template(file)
|
||||
if err != nil {
|
||||
return template.HTML(err.Error())
|
||||
}
|
||||
content, err := tpl.Parse(view.data)
|
||||
if err != nil {
|
||||
return template.HTML(err.Error())
|
||||
}
|
||||
return template.HTML(content)
|
||||
}
|
||||
}
|
||||
@ -8,30 +8,21 @@
|
||||
package gview
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/container/gmap"
|
||||
"html/template"
|
||||
"gitee.com/johng/gf/g/os/gfile"
|
||||
"sync"
|
||||
"strings"
|
||||
"bytes"
|
||||
"errors"
|
||||
"strings"
|
||||
"html/template"
|
||||
"gitee.com/johng/gf/g/os/gfile"
|
||||
"gitee.com/johng/gf/g/container/gmap"
|
||||
)
|
||||
|
||||
// 视图对象
|
||||
type View struct {
|
||||
mu sync.RWMutex
|
||||
path string // 模板目录(绝对路径)
|
||||
tpls *gmap.StringInterfaceMap // 已解析的模板对象指针,防止重复解析
|
||||
suffix string // 模板文件名后缀
|
||||
}
|
||||
|
||||
// 模板对象
|
||||
type Template struct {
|
||||
mu sync.RWMutex // 并发互斥锁
|
||||
path string // 模板文件(绝对路径)
|
||||
data map[string]interface{} // 全局的模板变量
|
||||
content string // 模板文件内容(解析之后保存到内存中)
|
||||
funcmap map[string]interface{} // FuncMap
|
||||
mu sync.RWMutex
|
||||
path string // 模板目录(绝对路径)
|
||||
funcmap map[string]interface{} // FuncMap
|
||||
contents *gmap.StringStringMap // 已解析的模板文件内容
|
||||
}
|
||||
|
||||
// 视图表
|
||||
@ -49,11 +40,13 @@ func Get(path string) *View {
|
||||
|
||||
// 生成一个视图对象
|
||||
func New(path string) *View {
|
||||
return &View {
|
||||
path : path,
|
||||
tpls : gmap.NewStringInterfaceMap(),
|
||||
suffix : "tpl",
|
||||
view := &View {
|
||||
path : path,
|
||||
funcmap : make(map[string]interface{}),
|
||||
contents : gmap.NewStringStringMap(),
|
||||
}
|
||||
view.BindFunc("include", view.funcInclude)
|
||||
return view
|
||||
}
|
||||
|
||||
// 设置模板目录绝对路径
|
||||
@ -70,89 +63,67 @@ func (view *View) GetPath() string {
|
||||
return view.path
|
||||
}
|
||||
|
||||
// 获取模板文件内容
|
||||
func (view *View) GetTplContent() string {
|
||||
view.mu.RLock()
|
||||
defer view.mu.RUnlock()
|
||||
return view.path
|
||||
}
|
||||
|
||||
// 直接解析模板,返回解析后的内容
|
||||
func (view *View) Parse(file string, params map[string]interface{}) ([]byte, error) {
|
||||
if t, err := view.Template(file); err == nil {
|
||||
return t.Parse(params)
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 根据文件名称生成一个模板对象,或者获取一个现有的模板对象
|
||||
func (view *View) Template(file string) (*Template, error) {
|
||||
path := strings.TrimRight(view.GetPath(), gfile.Separator) + gfile.Separator + file + "." + view.suffix
|
||||
if t := view.tpls.Get(path); t != nil {
|
||||
return t.(*Template), nil
|
||||
}
|
||||
if !gfile.Exists(path) {
|
||||
return nil, errors.New("template '" + path + "' does not exist")
|
||||
}
|
||||
if !gfile.IsReadable(path) {
|
||||
return nil, errors.New("template '" + path + "' is not readable")
|
||||
}
|
||||
t := &Template{
|
||||
path : path,
|
||||
data : make(map[string]interface{}),
|
||||
content : gfile.GetContents(path),
|
||||
funcmap : make(map[string]interface{}),
|
||||
}
|
||||
// 绑定内置inluce方法
|
||||
t.BindFunc("include", t.funcInclude)
|
||||
// 将模板对象注册到内部map中
|
||||
view.tpls.Set(path, t)
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// 绑定自定义函数,该函数是全局有效,即调用之后每个线程都会生效,因此有并发安全控制
|
||||
func (t *Template) BindFunc(name string, function interface{}) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
t.funcmap[name] = function
|
||||
}
|
||||
|
||||
// 批量绑定模板变量,即调用之后每个线程都会生效,因此有并发安全控制
|
||||
func (t *Template) Assigns(data map[string]interface{}) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
for k, v := range data {
|
||||
t.data[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// 绑定模板变量,即调用之后每个线程都会生效,因此有并发安全控制
|
||||
func (t *Template) Assign(k string, v interface{}) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
t.data[k] = v
|
||||
}
|
||||
|
||||
// 返回解析后的模板内容,可以额外指定模板变量,如果没有可以传入nil
|
||||
// 函数内部的底层template必须每次调用都新生成一个,防止错误:html/template: cannot Parse after Execute
|
||||
func (t *Template) Parse(data map[string]interface{}) ([]byte, error) {
|
||||
t.mu.RLock()
|
||||
defer t.mu.RUnlock()
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
if tpl, err := template.New(t.path).Funcs(t.funcmap).Parse(t.content); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
m := t.data
|
||||
for k, v := range data {
|
||||
m[k] = v
|
||||
// 获取模板文件路径及内容
|
||||
path := strings.TrimRight(view.GetPath(), gfile.Separator) + gfile.Separator + file
|
||||
content := view.contents.Get(path)
|
||||
if content == "" {
|
||||
content = gfile.GetContents(path)
|
||||
if content == "" {
|
||||
content = gfile.GetContents(file)
|
||||
}
|
||||
if err := tpl.Execute(buffer, m); err != nil {
|
||||
view.contents.Set(path, content)
|
||||
}
|
||||
if content == "" {
|
||||
return nil, errors.New("invalid tpl \"" + file + "\"")
|
||||
}
|
||||
// 执行模板解析
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
if tpl, err := template.New(path).Funcs(view.getFuncs()).Parse(content); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
if err := tpl.Execute(buffer, params); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
// 绑定自定义函数,该函数是全局有效,即调用之后每个线程都会生效,因此有并发安全控制
|
||||
func (view *View) BindFunc(name string, function interface{}) {
|
||||
view.mu.Lock()
|
||||
defer view.mu.Unlock()
|
||||
view.funcmap[name] = function
|
||||
}
|
||||
|
||||
// 获取模板自定义函数,每一次都是一份拷贝
|
||||
func (view *View) getFuncs() map[string]interface{} {
|
||||
m := make(map[string]interface{})
|
||||
view.mu.RLock()
|
||||
for k, v := range view.funcmap {
|
||||
m[k] = v
|
||||
}
|
||||
view.mu.RUnlock()
|
||||
return m
|
||||
}
|
||||
|
||||
// 模板内置方法:include
|
||||
func (t *Template) funcInclude(file string) template.HTML {
|
||||
content, err := t.Parse(nil)
|
||||
func (view *View) funcInclude(file string, datas...map[string]interface{}) template.HTML {
|
||||
var data map[string]interface{} = nil
|
||||
if len(datas) > 0 {
|
||||
data = datas[0]
|
||||
}
|
||||
content, err := view.Parse(file, data)
|
||||
if err != nil {
|
||||
return template.HTML(err.Error())
|
||||
}
|
||||
return template.HTML(content)
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ package demo
|
||||
import (
|
||||
"gitee.com/johng/gf/g/net/ghttp"
|
||||
"gitee.com/johng/gf/g/frame/gmvc"
|
||||
"gitee.com/johng/gf/g/frame/gins"
|
||||
)
|
||||
|
||||
type ControllerTemplate struct {
|
||||
@ -10,7 +11,7 @@ type ControllerTemplate struct {
|
||||
}
|
||||
|
||||
func init() {
|
||||
ghttp.GetServer().BindControllerMethod("/template/info", &ControllerTemplate{}, "Info")
|
||||
ghttp.GetServer().BindControllerMethod("/template", &ControllerTemplate{}, "Info")
|
||||
}
|
||||
|
||||
func (c *ControllerTemplate) Info() {
|
||||
@ -19,7 +20,7 @@ func (c *ControllerTemplate) Info() {
|
||||
"age" : 18,
|
||||
"score" : 100,
|
||||
})
|
||||
c.View.Display("user/index")
|
||||
c.View.Display("user/index.tpl")
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -7,9 +7,7 @@ import (
|
||||
|
||||
func init() {
|
||||
ghttp.GetServer().BindHandler("/template2", func(r *ghttp.Request){
|
||||
view := gins.View()
|
||||
view.SetPath("/home/www/template/")
|
||||
content, _ := view.Parse("index", map[string]interface{}{
|
||||
content, _ := gins.View().Parse("index.tpl", map[string]interface{}{
|
||||
"id" : 123,
|
||||
"name" : "john",
|
||||
})
|
||||
|
||||
17
geg/frame/mvc/controller/demo/template3.go
Normal file
17
geg/frame/mvc/controller/demo/template3.go
Normal file
@ -0,0 +1,17 @@
|
||||
package demo
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/net/ghttp"
|
||||
"gitee.com/johng/gf/g/frame/gins"
|
||||
)
|
||||
|
||||
func init() {
|
||||
gins.View().SetPath("/home/www/template/")
|
||||
ghttp.GetServer().BindHandler("/template3", func(r *ghttp.Request){
|
||||
content, _ := gins.View().Parse("index.tpl", map[string]interface{}{
|
||||
"id" : 123,
|
||||
"name" : "john",
|
||||
})
|
||||
r.Response.Write(content)
|
||||
})
|
||||
}
|
||||
@ -6,6 +6,6 @@
|
||||
<body>
|
||||
<h3>This is index</h3>
|
||||
<p>tpl vals: {{.}}</p>
|
||||
{{include "user/footer"}}
|
||||
{{include "user/footer.tpl" }}
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user