ez-gin-template

作者: FinaLone | 来源:发表于2020-11-13 21:30 被阅读0次

    今天打算给博客添加导航栏。先写了一个header模板插入到了article_list模板中,并在article_list中引入了相关的css文件。

    如果要给所有页面设置,需要在每个页面的模板中都来这么一下,工作量虽然不大,但不符合软件设计的理念。

    想了一下,打算通过一个layout模板来实现,在layout中引入各个页面的模板,渲染时将页面模板传入到layout模板中。想法很美好,但实现的时候发现,template包似乎不支持在{{}}中的内容解析成字符串,没法实现。

    上gin的github看了看,发现官方推荐使用ez-gin-template这个包来实现这种方式。https://github.com/michelloworld/ez-gin-template

    ez-gin-template的所有代码只有124行,可以实现模板的框架。其思路是实现了新的HTMLRender,将指定目录下(默认为views目录)的模板当作子模板嵌套到layout模板中,并适配了模板查找。

    使用如下:

      r := gin.Default()
      render := eztemplate.New()
      r.HTMLRender = render.Init()
      r.Run(":9000")
    

    1 数据结构

    ez的核心数据结构如下:

    type Render struct {
        Templates       map[string]*template.Template // 
        TemplatesDir    string // 模板所在的目录
        PartialDir      string // 模板组件所在的目录
        Layout          string // layout模板名
        Ext             string // layout模板后缀名
        TemplateFuncMap map[string]interface{}  // 
        Debug           bool // 是否调试模式
    }
    

    对外暴露的方法只有两个:

    • Instance(name string, data interface{}) render.Render
      Instance方法是为了实现HTMLRender接口。其入参string指明要渲染的模板名,interface{}为渲染的数据。
    type HTMLRender interface {
        // Instance returns an HTML instance.
        Instance(string, interface{}) Render
    }
    

    注意这里的Render不是前面ez定义的Render,而是gin中的render包定义的Render接口。

    // Render interface is to be implemented by JSON, XML, HTML, YAML and so on.
    type Render interface {
        // Render writes data with custom ContentType.
        Render(http.ResponseWriter) error
        // WriteContentType writes custom ContentType.
        WriteContentType(w http.ResponseWriter)
    }
    

    在调用c.HTML()方法后,在底层会调用render.Render接口的Render方法,将数据写入response中。

    • Init() Render
      Init方法扫描目录,将组合之后的模板编译后,存到Templates中。

    2 Instance方法

    Instance方法的实现,返回了一个render.HTML实例,包括Templates集合中对应的组合模板和渲染数据。

    func (r Render) Instance(name string, data interface{}) render.Render {
        return render.HTML{
            Template: r.Templates[name],
            Data:     data,
        }
    }
    

    render.HTML结构如下,还有一个Name字段暂时为空,表示渲染该模板组中的所有模板。
    (模板组的概念详见该文:https://www.cnblogs.com/f-ck-need-u/p/10035768.html

    // HTML contains template reference and its name with given interface object.
    type HTML struct {
        Template *template.Template
        Name     string
        Data     interface{}
    }
    

    通过实现Instance方法,ez使用自己实现的HTMLRender替换系统默认的HTMLRender,在调用时,通过传入的模板名称关联到自己生成的组合模板上,达到了组件框架的目的。

    3 Init方法

    Init方法是ez的核心,对多个模板和layout模板进行了组合。

    func (r Render) Init() Render {
        globalPartials := r.getGlobalPartials()
            //layout模板的位置
        layout := r.TemplatesDir + r.Layout + r.Ext
            //查找所有待组合的模板
        viewDirs, _ := filepath.Glob(r.TemplatesDir + "**" + string(os.PathSeparator) + "*" + r.Ext)
    
        fullPartialDir := filepath.Join(r.TemplatesDir + r.PartialDir)
        for _, view := range viewDirs {
                    // 组合之后的模板名,和所有非layout,非组件的模板一一对应
                    // 以_开头或者在PartialDir中的为模板组件
            templateFileName := filepath.Base(view)
            //skip partials
            if strings.Index(templateFileName, "_") != 0 && strings.Index(view, fullPartialDir) != 0 {
                localPartials := r.findPartials(filepath.Dir(view))
    
                renderName := r.getRenderName(view)
                if r.Debug {
                    log.Printf("[GIN-debug] %-6s %-25s --> %s\n", "LOAD", view, renderName)
                }
                            // allFiles 包含了layout模板,要渲染的主题页面模板,全局组件模板和本页面关联的组件模板
                allFiles := []string{layout, view}
                allFiles = append(allFiles, globalPartials...)
                allFiles = append(allFiles, localPartials...)
                r.AddFromFiles(renderName, allFiles...)
            }
        }
    
        return r
    }
    
    func (r Render) getGlobalPartials() []string {
        return r.findPartials(filepath.Join(r.TemplatesDir, r.PartialDir))
    }
    
    //findPartials 找到模板文件所在目录下所有以_开头到模板组件
    func (r Render) findPartials(findPartialDir string) []string {
        files := []string{}
        path := filepath.Join(findPartialDir, "*"+r.Ext)
        partialDir, _ := filepath.Glob(path)
        for _, view := range partialDir {
            templateFileName := filepath.Base(view)
            //skip partials
            if strings.Index(templateFileName, "_") == 0 {
                renderName := r.getRenderName(view)
                if r.Debug {
                    log.Printf("[GIN-debug] %-6s %-25s --> %s\n", "LOAD Partial", view, renderName)
                }
    
                files = append(files, view)
            }
        }
        return files
    }
    
    //getRenderName将模板文件路径转化为模板文件夹下的相对路径,用于适配路由
    func (r Render) getRenderName(tpl string) string {
        dir, file := filepath.Split(tpl)
        dir = strings.Replace(dir, string(os.PathSeparator), "/", -1)
        tempdir := strings.Replace(r.TemplatesDir, string(os.PathSeparator), "/", -1)
        dir = strings.Replace(dir, tempdir, "", 1)
        file = strings.TrimSuffix(file, r.Ext)
        return dir + file
    }
    
    func (r Render) Add(name string, tmpl *template.Template) {
        if tmpl == nil {
            panic("template can not be nil")
        }
        if len(name) == 0 {
            panic("template name cannot be empty")
        }
        r.Templates[name] = tmpl
    }
    
    func (r Render) AddFromFiles(name string, files ...string) *template.Template {
            // 使用template生成layout+view+partial的模板,只会返回列表中第一个模板,即layout
        tmpl := template.Must(template.New(filepath.Base(r.Layout + r.Ext)).Funcs(r.TemplateFuncMap).ParseFiles(files...))
            // 将生成的layout模板关联到view的名字上,调用c.HTML会使用名字查找到相应的模板
        r.Add(name, tmpl)
        return tmpl
    }
    

    相关文章

      网友评论

        本文标题:ez-gin-template

        本文链接:https://www.haomeiwen.com/subject/rbusbktx.html