diff --git a/_examples/README.md b/_examples/README.md
index 5ac8802a..6c7346a3 100644
--- a/_examples/README.md
+++ b/_examples/README.md
@@ -110,6 +110,7 @@
* [Inject Engine Between Handlers](view/context-view-engine/main.go)
* [Embedding Templates Into App Executable File](view/embedding-templates-into-app/main.go)
* [Write to a custom `io.Writer`](view/write-to)
+ * [Parse a Template manually](view/parse-template/main.go)
* [Blocks](view/template_blocks_0)
* [Blocks Embedded](view/template_blocks_1_embedded)
* [Pug: `Actions`](view/template_pug_0)
diff --git a/_examples/view/parse-template/main.go b/_examples/view/parse-template/main.go
new file mode 100644
index 00000000..bfddf399
--- /dev/null
+++ b/_examples/view/parse-template/main.go
@@ -0,0 +1,35 @@
+// Package main shows how to parse a template through custom byte slice content.
+package main
+
+import "github.com/kataras/iris/v12"
+
+func main() {
+ app := iris.New()
+ // To not load any templates from files or embedded data,
+ // pass nil or empty string on the first argument:
+ // view := iris.HTML(nil, ".html")
+
+ view := iris.HTML("./views", ".html")
+ view.ParseTemplate("program.html", []byte(`
{{greet .Name}}
`), iris.Map{
+ "greet": func(name string) string {
+ return "Hello, " + name + "!"
+ },
+ })
+
+ app.RegisterView(view)
+ app.Get("/", index)
+ app.Get("/layout", layout)
+
+ app.Listen(":8080")
+}
+
+func index(ctx iris.Context) {
+ ctx.View("program.html", iris.Map{
+ "Name": "Gerasimos",
+ })
+}
+
+func layout(ctx iris.Context) {
+ ctx.ViewLayout("layouts/main.html")
+ index(ctx)
+}
diff --git a/_examples/view/parse-template/views/layouts/main.html b/_examples/view/parse-template/views/layouts/main.html
new file mode 100644
index 00000000..3f6b65a6
--- /dev/null
+++ b/_examples/view/parse-template/views/layouts/main.html
@@ -0,0 +1,11 @@
+
+
+My Layout
+
+
+
+ [layout] Body content is below...
+
+ {{ yield }}
+
+
diff --git a/view/fs.go b/view/fs.go
index 0732724b..8c7b13e0 100644
--- a/view/fs.go
+++ b/view/fs.go
@@ -49,6 +49,10 @@ func assetNames(fs http.FileSystem, name string) ([]string, error) {
return nil, err
}
+ if f == nil {
+ return nil, nil
+ }
+
infos, err := f.Readdir(-1)
f.Close()
if err != nil {
@@ -82,9 +86,17 @@ func asset(fs http.FileSystem, name string) ([]byte, error) {
}
func getFS(fsOrDir interface{}) (fs http.FileSystem) {
+ if fsOrDir == nil {
+ return noOpFS{}
+ }
+
switch v := fsOrDir.(type) {
case string:
- fs = httpDirWrapper{http.Dir(v)}
+ if v == "" {
+ fs = noOpFS{}
+ } else {
+ fs = httpDirWrapper{http.Dir(v)}
+ }
case http.FileSystem:
fs = v
default:
@@ -94,6 +106,10 @@ func getFS(fsOrDir interface{}) (fs http.FileSystem) {
return
}
+type noOpFS struct{}
+
+func (fs noOpFS) Open(name string) (http.File, error) { return nil, nil }
+
// fixes: "invalid character in file path"
// on amber engine (it uses the virtual fs directly
// and it uses filepath instead of the path package...).
@@ -101,6 +117,6 @@ type httpDirWrapper struct {
http.Dir
}
-func (d httpDirWrapper) Open(name string) (http.File, error) {
- return d.Dir.Open(filepath.ToSlash(name))
+func (fs httpDirWrapper) Open(name string) (http.File, error) {
+ return fs.Dir.Open(filepath.ToSlash(name))
}
diff --git a/view/html.go b/view/html.go
index 63be0fb1..d00b9601 100644
--- a/view/html.go
+++ b/view/html.go
@@ -215,12 +215,6 @@ func (s *HTMLEngine) Funcs(funcMap template.FuncMap) *HTMLEngine {
//
// Returns an error if something bad happens, caller is responsible to handle that.
func (s *HTMLEngine) Load() error {
- s.rmu.Lock()
- defer s.rmu.Unlock()
-
- s.Templates = template.New(s.rootDir)
- s.Templates.Delims(s.left, s.right)
-
return walk(s.fs, s.rootDir, func(path string, info os.FileInfo, err error) error {
if info == nil || info.IsDir() {
return nil
@@ -236,30 +230,51 @@ func (s *HTMLEngine) Load() error {
if err != nil {
return fmt.Errorf("%s: %w", path, err)
}
- contents := string(buf)
- name := strings.TrimPrefix(path, "/")
- tmpl := s.Templates.New(name)
- tmpl.Option(s.options...)
-
- if s.middleware != nil {
- contents, err = s.middleware(name, buf)
- if err != nil {
- return err
- }
- }
-
- // Add our funcmaps.
- _, err = tmpl.
- Funcs(emptyFuncs).
- // Funcs(s.makeDefaultLayoutFuncs(name)).
- // Funcs(s.layoutFuncs).
- Funcs(s.funcs).
- Parse(contents)
- return err
+ return s.ParseTemplate(path, buf, nil)
})
}
+func (s *HTMLEngine) initRootTmpl() { // protected by the caller.
+ if s.Templates == nil {
+ // the root template should be the same,
+ // no matter how many reloads as the
+ // following unexported fields cannot be modified.
+ s.Templates = template.New(s.rootDir)
+ s.Templates.Delims(s.left, s.right)
+ }
+}
+
+// ParseTemplate adds a custom template to the root template.
+func (s *HTMLEngine) ParseTemplate(name string, contents []byte, funcMap template.FuncMap) (err error) {
+ s.rmu.Lock()
+ defer s.rmu.Unlock()
+
+ s.initRootTmpl()
+
+ name = strings.TrimPrefix(name, "/")
+ tmpl := s.Templates.New(name)
+ tmpl.Option(s.options...)
+
+ var text string
+
+ if s.middleware != nil {
+ text, err = s.middleware(name, contents)
+ if err != nil {
+ return
+ }
+ } else {
+ text = string(contents)
+ }
+
+ tmpl.Funcs(emptyFuncs).Funcs(s.funcs)
+ if len(funcMap) > 0 {
+ tmpl.Funcs(funcMap) // custom for this template.
+ }
+ _, err = tmpl.Parse(text)
+ return
+}
+
func (s *HTMLEngine) executeTemplateBuf(name string, binding interface{}) (*bytes.Buffer, error) {
buf := new(bytes.Buffer)
err := s.Templates.ExecuteTemplate(buf, name, binding)