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 @@
-Releases +Releases Examples @@ -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) }