diff --git a/HISTORY.md b/HISTORY.md
index 4e5a54b1..5c363e9b 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -2,6 +2,33 @@
**How to upgrade**: remove your `$GOPATH/src/github.com/kataras` folder, open your command-line and execute this command: `go get -u github.com/kataras/iris/iris`.
+## 4.4.0 -> 4.4.1
+
+- **NEW**: Template PreRenders, as requested [here](https://github.com/kataras/iris/issues/412).
+
+```go
+// ...
+ iris.UsePreRender(func(ctx *iris.Context, filename string, binding interface{}, options ...map[string]interface{}) bool {
+ // put the 'Error' binding here, for the shake of the test
+ if b, isMap := binding.(map[string]interface{}); isMap {
+ b["Error"] = "error!"
+ }
+ // true= continue to the next PreRender
+ // false= do not continue to the next PreRender
+ // * but the actual Render will be called at any case *
+ return true
+ })
+
+ iris.Get("/", func(ctx *Context) {
+ ctx.Render("hi.html", map[string]interface{}{"Username": "anybody"})
+ // hi.html:
HI {{.Username}}. Error: {{.Error}}
+ })
+
+// ...
+```
+
+
+
## 4.3.0 -> 4.4.0
**NOTE**: For normal users this update offers nothing, read that only if you run Iris behind a proxy or balancer like `nginx` or you need to serve using a custom `net.Listener`.
diff --git a/README.md b/README.md
index e10a3887..02df065e 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,7 @@
-
+
@@ -197,7 +197,7 @@ I recommend writing your API tests using this new library, [httpexpect](https://
Versioning
------------
-Current: **v4.4.0**
+Current: **v4.4.1**
> Iris is an active project
@@ -213,6 +213,7 @@ Todo
- [x] Implement, even, a better way to manage configuration/options, devs will be able to set their own custom options inside there. ` I'm thinking of something the last days, but it will have breaking changes. `
- [x] Implement an internal updater, as requested [here](https://github.com/kataras/iris/issues/401).
- [x] Support of using native servers and net.Listener instead of Iris' defined.[*](https://github.com/kataras/iris/issues/438)
+- [x] Add context processors(PreRenders).[*](https://github.com/kataras/iris/issues/412)
Iris is a **Community-Driven** Project, waiting for your suggestions and [feature requests](https://github.com/kataras/iris/issues?utf8=%E2%9C%93&q=label%3A%22feature%20request%22)!
diff --git a/context.go b/context.go
index 918cc853..907c0ac9 100644
--- a/context.go
+++ b/context.go
@@ -596,7 +596,7 @@ func (ctx *Context) RenderTemplateSource(status int, src string, binding interfa
// Note: the options: "gzip" and "charset" are built'n support by Iris, so you can pass these on any template engine or serialize engines
func (ctx *Context) RenderWithStatus(status int, name string, binding interface{}, options ...map[string]interface{}) (err error) {
if strings.IndexByte(name, '.') > -1 { //we have template
- err = ctx.framework.templates.render(ctx, name, binding, options...)
+ err = ctx.framework.templates.renderFile(ctx, name, binding, options...)
} else {
err = ctx.renderSerialized(name, binding, options...)
}
diff --git a/context_test.go b/context_test.go
index 90710a4e..74d55374 100644
--- a/context_test.go
+++ b/context_test.go
@@ -657,5 +657,48 @@ func TestContextRenderRest(t *testing.T) {
markdownT := e.GET("/markdown").Expect().Status(StatusOK)
markdownT.Header("Content-Type").Equal("text/html; charset=UTF-8")
markdownT.Body().Equal("" + markdownContents[2:] + "
\n")
-
+}
+
+func TestContextPreRender(t *testing.T) {
+ initDefault()
+ errMsg1 := "thereIsAnError"
+ UsePreRender(func(ctx *Context, src string, binding interface{}, options ...map[string]interface{}) bool {
+ // put the 'Error' binding here, for the shake of the test
+ if b, isMap := binding.(map[string]interface{}); isMap {
+ b["Error"] = errMsg1
+ }
+ // continue to the next prerender
+ return true
+ })
+ errMsg2 := "thereIsASecondError"
+ UsePreRender(func(ctx *Context, src string, binding interface{}, options ...map[string]interface{}) bool {
+ // put the 'Error' binding here, for the shake of the test
+ if b, isMap := binding.(map[string]interface{}); isMap {
+ prev := b["Error"].(string)
+ msg := prev + errMsg2
+ b["Error"] = msg
+ }
+ // DO NOT CONTINUE to the next prerender
+ return false
+ })
+
+ errMsg3 := "thereisAThirdError"
+ UsePreRender(func(ctx *Context, src string, binding interface{}, options ...map[string]interface{}) bool {
+ // put the 'Error' binding here, for the shake of the test
+ if b, isMap := binding.(map[string]interface{}); isMap {
+ prev := b["Error"].(string)
+ msg := prev + errMsg3
+ b["Error"] = msg
+ }
+ // doesn't matters the return statement, we don't have other prerender
+ return true
+ })
+
+ Get("/", func(ctx *Context) {
+ ctx.RenderTemplateSource(StatusOK, "HI {{.Username}}. Error: {{.Error}}
", map[string]interface{}{"Username": "kataras"})
+ })
+
+ e := Tester(t)
+ expected := "HI kataras. Error: " + errMsg1 + errMsg2 + "
"
+ e.GET("/").Expect().Status(StatusOK).Body().Contains(expected)
}
diff --git a/iris.go b/iris.go
index e23be50b..76c54536 100644
--- a/iris.go
+++ b/iris.go
@@ -79,7 +79,7 @@ import (
const (
// Version is the current version of the Iris web framework
- Version = "4.4.0"
+ Version = "4.4.1"
banner = ` _____ _
|_ _| (_)
@@ -149,6 +149,7 @@ type (
UseSessionDB(sessions.Database)
UseSerializer(string, serializer.Serializer)
UseTemplate(template.Engine) *template.Loader
+ UsePreRender(PreRender)
UseGlobal(...Handler)
UseGlobalFunc(...HandlerFunc)
Lookup(string) Route
@@ -773,6 +774,28 @@ func (s *Framework) UseSerializer(forContentType string, e serializer.Serializer
s.serializers.For(forContentType, e)
}
+// UsePreRender adds a Template's PreRender
+// PreRender is typeof func(*iris.Context, filenameOrSource string, binding interface{}, options ...map[string]interface{}) bool
+// PreRenders helps developers to pass middleware between the route Handler and a context.Render call
+// all parameter receivers can be changed before passing it to the actual context's Render
+// so, you can change the filenameOrSource, the page binding, the options, and even add cookies, session value or a flash message through ctx
+// the return value of a PreRender is a boolean, if returns false then the next PreRender will not be executed, keep note
+// that the actual context's Render will be called at any case.
+func UsePreRender(pre PreRender) {
+ Default.UsePreRender(pre)
+}
+
+// UsePreRender adds a Template's PreRender
+// PreRender is typeof func(*iris.Context, filenameOrSource string, binding interface{}, options ...map[string]interface{}) bool
+// PreRenders helps developers to pass middleware between the route Handler and a context.Render call
+// all parameter receivers can be changed before passing it to the actual context's Render
+// so, you can change the filenameOrSource, the page binding, the options, and even add cookies, session value or a flash message through ctx
+// the return value of a PreRender is a boolean, if returns false then the next PreRender will not be executed, keep note
+// that the actual context's Render will be called at any case.
+func (s *Framework) UsePreRender(pre PreRender) {
+ s.templates.usePreRender(pre)
+}
+
// UseTemplate adds a template engine to the iris view system
// it does not build/load them yet
func UseTemplate(e template.Engine) *template.Loader {
@@ -1942,6 +1965,20 @@ func (api *muxAPI) Favicon(favPath string, requestPath ...string) RouteNameFunc
return api.Get(reqPath, h)
}
+// Layout oerrides the parent template layout with a more specific layout for this Party
+// returns this Party, to continue as normal
+// example:
+// my := iris.Party("/my").Layout("layouts/mylayout.html")
+// {
+// my.Get("/", func(ctx *iris.Context) {
+// ctx.MustRender("page1.html", nil)
+// })
+// }
+//
+func Layout(tmplLayoutFile string) MuxAPI {
+ return Default.Layout(tmplLayoutFile)
+}
+
// Layout oerrides the parent template layout with a more specific layout for this Party
// returns this Party, to continue as normal
// example:
@@ -1957,6 +1994,7 @@ func (api *muxAPI) Layout(tmplLayoutFile string) MuxAPI {
ctx.Set(TemplateLayoutContextKey, tmplLayoutFile)
ctx.Next()
})
+
return api
}
diff --git a/template.go b/template.go
index 575af69f..cb395587 100644
--- a/template.go
+++ b/template.go
@@ -22,11 +22,21 @@ type (
// an example of this is the "layout" or "gzip" option
// same as Map but more specific name
RenderOptions map[string]interface{}
+
+ // PreRender is typeof func(*iris.Context, string, interface{},...map[string]interface{}) bool
+ // PreRenders helps developers to pass middleware between
+ // the route Handler and a context.Render (THAT can render 'rest' types also but the PreRender applies ONLY to template rendering(file or source)) call
+ // all parameter receivers can be changed before passing it to the actual context's Render
+ // so, you can change the filenameOrSource, the page binding, the options, and even add cookies, session value or a flash message through ctx
+ // the return value of a PreRender is a boolean, if returns false then the next PreRender will not be executed, keep note
+ // that the actual context's Render will be called at any case.
+ PreRender func(ctx *Context, filenameOrSource string, binding interface{}, options ...map[string]interface{}) bool
)
// templateEngines just a wrapper of template.Mux in order to use it's execute without break the whole of the API
type templateEngines struct {
*template.Mux
+ prerenders []PreRender
}
func newTemplateEngines(sharedFuncs map[string]interface{}) *templateEngines {
@@ -51,12 +61,30 @@ func getCharsetOption(defaultValue string, options map[string]interface{}) strin
return defaultValue
}
+func (t *templateEngines) usePreRender(pre PreRender) {
+ if t.prerenders == nil {
+ t.prerenders = make([]PreRender, 0)
+ }
+ t.prerenders = append(t.prerenders, pre)
+}
+
// render executes a template and write its result to the context's body
// options are the optional runtime options can be passed by user and catched by the template engine when render
// an example of this is the "layout"
// note that gzip option is an iris dynamic option which exists for all template engines
// the gzip and charset options are built'n with iris
-func (t *templateEngines) render(ctx *Context, filename string, binding interface{}, options ...map[string]interface{}) (err error) {
+// template is passed as file or souce
+func (t *templateEngines) render(isFile bool, ctx *Context, filenameOrSource string, binding interface{}, options []map[string]interface{}) error {
+ if len(t.prerenders) > 0 {
+ for i := range t.prerenders {
+ // I'm not making any checks here for performance reasons, means that
+ // if binding is pointer it can be changed, otherwise not.
+ if shouldContinue := t.prerenders[i](ctx, filenameOrSource, binding, options...); !shouldContinue {
+ break
+ }
+ }
+ }
+
// we do all these because we don't want to initialize a new map for each execution...
gzipEnabled := ctx.framework.Config.Gzip
charset := ctx.framework.Config.Charset
@@ -65,12 +93,14 @@ func (t *templateEngines) render(ctx *Context, filename string, binding interfac
charset = getCharsetOption(charset, options[0])
}
- ctxLayout := ctx.GetString(TemplateLayoutContextKey)
- if ctxLayout != "" {
- if len(options) > 0 {
- options[0]["layout"] = ctxLayout
- } else {
- options = []map[string]interface{}{map[string]interface{}{"layout": ctxLayout}}
+ if isFile {
+ ctxLayout := ctx.GetString(TemplateLayoutContextKey)
+ if ctxLayout != "" {
+ if len(options) > 0 {
+ options[0]["layout"] = ctxLayout
+ } else {
+ options = []map[string]interface{}{map[string]interface{}{"layout": ctxLayout}}
+ }
}
}
@@ -87,35 +117,17 @@ func (t *templateEngines) render(ctx *Context, filename string, binding interfac
} else {
out = ctx.Response.BodyWriter()
}
+ if isFile {
+ return t.ExecuteWriter(out, filenameOrSource, binding, options...)
+ }
+ return t.ExecuteRaw(filenameOrSource, out, binding)
- err = t.ExecuteWriter(out, filename, binding, options...)
- return err
}
-// renderSource executes a template source raw contents (string) and write its result to the context's body
-// note that gzip option is an iris dynamic option which exists for all template engines
-// the gzip and charset options are built'n with iris
-func (t *templateEngines) renderSource(ctx *Context, src string, binding interface{}, options ...map[string]interface{}) (err error) {
- // we do all these because we don't want to initialize a new map for each execution...
- gzipEnabled := ctx.framework.Config.Gzip
- charset := ctx.framework.Config.Charset
- if len(options) > 0 {
- gzipEnabled = template.GetGzipOption(gzipEnabled, options[0])
- charset = template.GetCharsetOption(charset, options[0])
- }
-
- ctx.SetContentType(contentHTML + "; charset=" + charset)
-
- var out io.Writer
- if gzipEnabled && ctx.clientAllowsGzip() {
- ctx.RequestCtx.Response.Header.Add(varyHeader, acceptEncodingHeader)
- ctx.SetHeader(contentEncodingHeader, "gzip")
-
- gzipWriter := fs.AcquireGzipWriter(ctx.Response.BodyWriter())
- defer fs.ReleaseGzipWriter(gzipWriter)
- out = gzipWriter
- } else {
- out = ctx.Response.BodyWriter()
- }
- return t.ExecuteRaw(src, out, binding)
+func (t *templateEngines) renderFile(ctx *Context, filename string, binding interface{}, options ...map[string]interface{}) error {
+ return t.render(true, ctx, filename, binding, options)
+}
+
+func (t *templateEngines) renderSource(ctx *Context, src string, binding interface{}, options ...map[string]interface{}) error {
+ return t.render(false, ctx, src, binding, options)
}