goview
Goview is a lightweight, minimalist and idiomatic template library based on golang html/template for building Go web application.
Contents
Install
go get github.com/foolin/goview
Features
- Lightweight - use golang html/template syntax.
- Easy - easy use for your web application.
- Fast - Support configure cache template.
- Include syntax - Support include file.
- Master layout - Support configure master layout file.
- Extension - Support configure template file extension.
- Easy - Support configure templates directory.
- Auto reload - Support dynamic reload template(disable cache mode).
- Multiple Engine - Support multiple templates for frontend and backend.
- No external dependencies - plain ol' Go html/template.
- Gorice - Support gorice for package resources.
- Gin/Iris/Echo/Chi - Support gin framework, Iris framework, echo framework, go-chi framework.
Docs
See https://www.godoc.org/github.com/foolin/goview
Supports
- ginview goview for gin framework
- irisview goview for Iris framework
- echoview goview for echo framework
- gorice goview for go.rice
Usage
Overview
Project structure:
|-- app/views/
|--- index.html
|--- page.html
|-- layouts/
|--- footer.html
|--- master.html
Use default instance:
//write http.ResponseWriter
//"index" -> index.html
goview.Render(writer, http.StatusOK, "index", goview.M{})
Use new instance with config:
gv := goview.New(goview.Config{
Root: "views",
Extension: ".tpl",
Master: "layouts/master",
Partials: []string{"partials/ad"},
Funcs: template.FuncMap{
"sub": func(a, b int) int {
return a - b
},
"copy": func() string {
return time.Now().Format("2006")
},
},
DisableCache: true,
Delims: Delims{Left: "{{", Right: "}}"},
})
//Set new instance
goview.Use(gv)
//write http.ResponseWriter
goview.Render(writer, http.StatusOK, "index", goview.M{})
Use multiple instance with config:
//============== Frontend ============== //
gvFrontend := goview.New(goview.Config{
Root: "views/frontend",
Extension: ".tpl",
Master: "layouts/master",
Partials: []string{"partials/ad"},
Funcs: template.FuncMap{
"sub": func(a, b int) int {
return a - b
},
"copy": func() string {
return time.Now().Format("2006")
},
},
DisableCache: true,
Delims: Delims{Left: "{{", Right: "}}"},
})
//write http.ResponseWriter
gvFrontend.Render(writer, http.StatusOK, "index", goview.M{})
//============== Backend ============== //
gvBackend := goview.New(goview.Config{
Root: "views/backend",
Extension: ".tpl",
Master: "layouts/master",
Partials: []string{"partials/ad"},
Funcs: template.FuncMap{
"sub": func(a, b int) int {
return a - b
},
"copy": func() string {
return time.Now().Format("2006")
},
},
DisableCache: true,
Delims: Delims{Left: "{{", Right: "}}"},
})
//write http.ResponseWriter
gvBackend.Render(writer, http.StatusOK, "index", goview.M{})
Config
goview.Config{
Root: "views", //template root path
Extension: ".tpl", //file extension
Master: "layouts/master", //master layout file
Partials: []string{"partials/head"}, //partial files
Funcs: template.FuncMap{
"sub": func(a, b int) int {
return a - b
},
// more funcs
},
DisableCache: false, //if disable cache, auto reload template file for debug.
Delims: Delims{Left: "{{", Right: "}}"},
}
Include syntax
//template file
{{include "layouts/footer"}}
Render name:
Render name use index
without .html
extension, that will render with master layout.
- "index" - Render with master layout.
- "index.html" - Not render with master layout.
Notice: `.html` is default template extension, you can change with config
Render with master
//use name without extension `.html`
goview.Render(w, http.StatusOK, "index", goview.M{})
The w
is instance of http.ResponseWriter
Render only file(not use master layout)
//use full name with extension `.html`
goview.Render(w, http.StatusOK, "page.html", goview.M{})
Custom template functions
We have two type of functions global functions
, and temporary functions
.
Global functions
are set within the config
.
goview.Config{
Funcs: template.FuncMap{
"reverse": e.Reverse,
},
}
//template file
{{ reverse "route-name" }}
Temporary functions
are set inside the handler.
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
err := goview.Render(w, http.StatusOK, "index", goview.M{
"reverse": e.Reverse,
})
if err != nil {
fmt.Fprintf(w, "Render index error: %v!", err)
}
})
//template file
{{ call $.reverse "route-name" }}
Examples
See _examples/ for a variety of examples.
Basic example
package main
import (
"fmt"
"github.com/foolin/goview"
"net/http"
)
func main() {
//render index use `index` without `.html` extension, that will render with master layout.
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
err := goview.Render(w, http.StatusOK, "index", goview.M{
"title": "Index title!",
"add": func(a int, b int) int {
return a + b
},
})
if err != nil {
fmt.Fprintf(w, "Render index error: %v!", err)
}
})
//render page use `page.tpl` with '.html' will only file template without master layout.
http.HandleFunc("/page", func(w http.ResponseWriter, r *http.Request) {
err := goview.Render(w, http.StatusOK, "page.html", goview.M{"title": "Page file title!!"})
if err != nil {
fmt.Fprintf(w, "Render page.html error: %v!", err)
}
})
fmt.Println("Listening and serving HTTP on :9090")
http.ListenAndServe(":9090", nil)
}
Project structure:
|-- app/views/
|--- index.html
|--- page.html
|-- layouts/
|--- footer.html
|--- master.html
See in "examples/basic" folder
Gin example
go get github.com/foolin/goview/supports/ginview
package main
import (
"github.com/foolin/goview/supports/ginview"
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
router := gin.Default()
//new template engine
router.HTMLRender = ginview.Default()
router.GET("/", func(ctx *gin.Context) {
//render with master
ctx.HTML(http.StatusOK, "index", gin.H{
"title": "Index title!",
"add": func(a int, b int) int {
return a + b
},
})
})
router.GET("/page", func(ctx *gin.Context) {
//render only file, must full name with extension
ctx.HTML(http.StatusOK, "page.html", gin.H{"title": "Page file title!!"})
})
router.Run(":9090")
}
Project structure:
|-- app/views/
|--- index.html
|--- page.html
|-- layouts/
|--- footer.html
|--- master.html
See in "examples/basic" folder
Iris example
$ go get github.com/foolin/goview/supports/irisview
package main
import (
"github.com/foolin/goview/supports/irisview"
"github.com/kataras/iris/v12"
)
func main() {
app := iris.New()
// Register the goview template engine.
app.RegisterView(irisview.Default())
app.Get("/", func(ctx iris.Context) {
// Render with master.
ctx.View("index", iris.Map{
"title": "Index title!",
"add": func(a int, b int) int {
return a + b
},
})
})
app.Get("/page", func(ctx iris.Context) {
// Render only file, must full name with extension.
ctx.View("page.html", iris.Map{"title": "Page file title!!"})
})
app.Listen(":9090")
}
Project structure:
|-- app/views/
|--- index.html
|--- page.html
|-- layouts/
|--- footer.html
|--- master.html
See in "examples/iris" folder
Iris multiple example
package main
import (
"html/template"
"time"
"github.com/foolin/goview"
"github.com/foolin/goview/supports/irisview"
"github.com/kataras/iris/v12"
)
func main() {
app := iris.New()
// Register a new template engine.
app.RegisterView(irisview.New(goview.Config{
Root: "views/frontend",
Extension: ".html",
Master: "layouts/master",
Partials: []string{"partials/ad"},
Funcs: template.FuncMap{
"copy": func() string {
return time.Now().Format("2006")
},
},
DisableCache: true,
}))
app.Get("/", func(ctx iris.Context) {
ctx.View("index", iris.Map{
"title": "Frontend title!",
})
})
//=========== Backend ===========//
// Assign a new template middleware.
mw := irisview.NewMiddleware(goview.Config{
Root: "views/backend",
Extension: ".html",
Master: "layouts/master",
Partials: []string{},
Funcs: template.FuncMap{
"copy": func() string {
return time.Now().Format("2006")
},
},
DisableCache: true,
})
backendGroup := app.Party("/admin", mw)
backendGroup.Get("/", func(ctx iris.Context) {
// Use the ctx.View as you used to. Zero changes to your codebase,
// even if you use multiple templates.
ctx.View("index", iris.Map{
"title": "Backend title!",
})
})
app.Listen(":9090")
}
Project structure:
|-- app/views/
|-- fontend/
|--- index.html
|-- layouts/
|--- footer.html
|--- head.html
|--- master.html
|-- partials/
|--- ad.html
|-- backend/
|--- index.html
|-- layouts/
|--- footer.html
|--- head.html
|--- master.html
See in "examples/iris-multiple" folder
Echo example
Echo <=v3 version:
go get github.com/foolin/goview/supports/echoview
Echo v4 version:
go get github.com/foolin/goview/supports/echoview-v4
package main
import (
"github.com/foolin/goview/supports/echoview"
"github.com/labstack/echo"
"github.com/labstack/echo/middleware"
"net/http"
)
func main() {
// Echo instance
e := echo.New()
// Middleware
e.Use(middleware.Logger())
e.Use(middleware.Recover())
//Set Renderer
e.Renderer = echoview.Default()
// Routes
e.GET("/", func(c echo.Context) error {
//render with master
return c.Render(http.StatusOK, "index", echo.Map{
"title": "Index title!",
"add": func(a int, b int) int {
return a + b
},
})
})
e.GET("/page", func(c echo.Context) error {
//render only file, must full name with extension
return c.Render(http.StatusOK, "page.html", echo.Map{"title": "Page file title!!"})
})
// Start server
e.Logger.Fatal(e.Start(":9090"))
}
Project structure:
|-- app/views/
|--- index.html
|--- page.html
|-- layouts/
|--- footer.html
|--- master.html
See in "examples/basic" folder
Go-chi example
package main
import (
"fmt"
"github.com/foolin/goview"
"github.com/go-chi/chi"
"net/http"
)
func main() {
r := chi.NewRouter()
//render index use `index` without `.html` extension, that will render with master layout.
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
err := goview.Render(w, http.StatusOK, "index", goview.M{
"title": "Index title!",
"add": func(a int, b int) int {
return a + b
},
})
if err != nil {
fmt.Fprintf(w, "Render index error: %v!", err)
}
})
//render page use `page.tpl` with '.html' will only file template without master layout.
r.Get("/page", func(w http.ResponseWriter, r *http.Request) {
err := goview.Render(w, http.StatusOK, "page.html", goview.M{"title": "Page file title!!"})
if err != nil {
fmt.Fprintf(w, "Render page.html error: %v!", err)
}
})
fmt.Println("Listening and serving HTTP on :9090")
http.ListenAndServe(":9090", r)
}
Project structure:
|-- app/views/
|--- index.html
|--- page.html
|-- layouts/
|--- footer.html
|--- master.html
See in "examples/basic" folder
Advance example
package main
import (
"fmt"
"github.com/foolin/goview"
"html/template"
"net/http"
"time"
)
func main() {
gv := goview.New(goview.Config{
Root: "views",
Extension: ".tpl",
Master: "layouts/master",
Partials: []string{"partials/ad"},
Funcs: template.FuncMap{
"sub": func(a, b int) int {
return a - b
},
"copy": func() string {
return time.Now().Format("2006")
},
},
DisableCache: true,
})
//Set new instance
goview.Use(gv)
//render index use `index` without `.html` extension, that will render with master layout.
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
err := goview.Render(w, http.StatusOK, "index", goview.M{
"title": "Index title!",
"add": func(a int, b int) int {
return a + b
},
})
if err != nil {
fmt.Fprintf(w, "Render index error: %v!", err)
}
})
//render page use `page.tpl` with '.html' will only file template without master layout.
http.HandleFunc("/page", func(w http.ResponseWriter, r *http.Request) {
err := goview.Render(w, http.StatusOK, "page.tpl", goview.M{"title": "Page file title!!"})
if err != nil {
fmt.Fprintf(w, "Render page.html error: %v!", err)
}
})
fmt.Println("Listening and serving HTTP on :9090")
http.ListenAndServe(":9090", nil)
}
Project structure:
|-- app/views/
|--- index.tpl
|--- page.tpl
|-- layouts/
|--- footer.tpl
|--- head.tpl
|--- master.tpl
|-- partials/
|--- ad.tpl
See in "examples/advance" folder
Multiple example
package main
import (
"html/template"
"net/http"
"time"
"github.com/foolin/goview"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
//new template engine
router.HTMLRender = gintemplate.New(gintemplate.TemplateConfig{
Root: "views/fontend",
Extension: ".html",
Master: "layouts/master",
Partials: []string{"partials/ad"},
Funcs: template.FuncMap{
"copy": func() string {
return time.Now().Format("2006")
},
},
DisableCache: true,
})
router.GET("/", func(ctx *gin.Context) {
// `HTML()` is a helper func to deal with multiple TemplateEngine's.
// It detects the suitable TemplateEngine for each path automatically.
gintemplate.HTML(ctx, http.StatusOK, "index", gin.H{
"title": "Fontend title!",
})
})
//=========== Backend ===========//
//new middleware
mw := gintemplate.NewMiddleware(gintemplate.TemplateConfig{
Root: "views/backend",
Extension: ".html",
Master: "layouts/master",
Partials: []string{},
Funcs: template.FuncMap{
"copy": func() string {
return time.Now().Format("2006")
},
},
DisableCache: true,
})
// You should use helper func `Middleware()` to set the supplied
// TemplateEngine and make `HTML()` work validly.
backendGroup := router.Group("/admin", mw)
backendGroup.GET("/", func(ctx *gin.Context) {
// With the middleware, `HTML()` can detect the valid TemplateEngine.
gintemplate.HTML(ctx, http.StatusOK, "index", gin.H{
"title": "Backend title!",
})
})
router.Run(":9090")
}
Project structure:
|-- app/views/
|-- fontend/
|--- index.html
|-- layouts/
|--- footer.html
|--- head.html
|--- master.html
|-- partials/
|--- ad.html
|-- backend/
|--- index.html
|-- layouts/
|--- footer.html
|--- head.html
|--- master.html
See in "examples/multiple" folder
go.rice example
go get github.com/foolin/goview/supports/gorice
package main
import (
"fmt"
"github.com/GeertJohan/go.rice"
"github.com/foolin/goview"
"github.com/foolin/goview/supports/gorice"
"net/http"
)
func main() {
//static
staticBox := rice.MustFindBox("static")
staticFileServer := http.StripPrefix("/static/", http.FileServer(staticBox.HTTPBox()))
http.Handle("/static/", staticFileServer)
//new view engine
gv := gorice.New(rice.MustFindBox("views"))
//set engine for default instance
goview.Use(gv)
//render index use `index` without `.html` extension, that will render with master layout.
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
err := goview.Render(w, http.StatusOK, "index", goview.M{
"title": "Index title!",
"add": func(a int, b int) int {
return a + b
},
})
if err != nil {
fmt.Fprintf(w, "Render index error: %v!", err)
}
})
//render page use `page.tpl` with '.html' will only file template without master layout.
http.HandleFunc("/page", func(w http.ResponseWriter, r *http.Request) {
err := goview.Render(w, http.StatusOK, "page.html", goview.M{"title": "Page file title!!"})
if err != nil {
fmt.Fprintf(w, "Render page.html error: %v!", err)
}
})
fmt.Println("Listening and serving HTTP on :9090")
http.ListenAndServe(":9090", nil)
}
Project structure:
|-- app/views/
|--- index.html
|--- page.html
|-- layouts/
|--- footer.html
|--- master.html
|-- app/static/
|-- css/
|--- bootstrap.css
|-- img/
|--- gopher.png
See in "examples/gorice" folder
More examples
See _examples/ for a variety of examples.
Todo
[ ] Add Partials support directory or glob [ ] Add functions support.