diff --git a/HISTORY.md b/HISTORY.md index 74af6f46..656cbdac 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -8,13 +8,21 @@ ## 6.1.4 -> 6.2.0 (√Νεxτ) + + +_Update: 28 March 2017_ + +- **View**: Provide an easier method on the community's question about "injecting" additional data outside of the route's main handler which calls the .Render, via middleware. + - As discussed [above](http://support.iris-go.com/d/27-using-middleware-to-inject-properties-for-templates). + - Click [here](https://github.com/kataras/iris/tree/v6/_examples/intermediate/view/context-view-data) for an example. + _Update: 18 March 2017_ -- **Sessions**: Enchance the community feature request about custom encode and decode methods for the cookie value(sessionid) as requested [here](http://support.iris-go.com/d/29-mark-cookie-for-session-as-secure). +- **Sessions**: Enchance the community's feature request about custom encode and decode methods for the cookie value(sessionid) as requested [here](http://support.iris-go.com/d/29-mark-cookie-for-session-as-secure). _Update: 12 March 2017_ -- Enhance Custom http errors with gzip and static files handler, as requested/reported [here](http://support.iris-go.com/d/17-fallback-handler-for-non-matched-routes/9). +- Enhance Custom http errors with gzip and static files handler, as requested/reported [here](http://support.iris-go.com/d/17-fallback-handler-for-non-matched-routes). - Enhance per-party custom http errors (now it works on any wildcard path too). - Add a third parameter on `app.OnError(...)` for custom http errors with regexp validation, see [status_test.go](https://github.com/kataras/iris/blob/v6/status_test.go) for an example. - Add a `context.ParamIntWildcard(...)` to skip the first slash, useful for wildcarded paths' parameters. diff --git a/README.md b/README.md index 1faa8bf5..d75b1ef9 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,76 @@ Feature Overview - Feels like you used iris forever, thanks to its Fluent API - And many others... + +Table of Contents +----------- + + + +* [Level: Beginner](_examples/beginner) + * [Hello World](_examples/beginner/hello-world/main.go) + * [Routes (using httprouter)](_examples/beginner/routes-using-httprouter/main.go) + * [Routes (using gorillamux)](_examples/beginner/routes-using-gorillamux/main.go) + * [Internal Application File Logger](_examples/beginner/file-logger/main.go) + * [Write JSON](_examples/beginner/write-json/main.go) + * [Read JSON](_examples/beginner/read-json/main.go) + * [Read Form](_examples/beginner/read-form/main.go) + * [Favicon](_examples/beginner/favicon/main.go) + * [File Server](_examples/beginner/file-server/main.go) + * [Send Files](_examples/beginner/send-files/main.go) + * [Stream Writer](_examples/beginner/stream-writer/main.go) + * [Listen UNIX Socket](_examples/beginner/listen-unix/main.go) + * [Listen TLS](_examples/beginner/listen-tls/main.go) + * [Listen Letsencrypt (Automatic Certifications)](_examples/beginner/listen-letsencrypt/main.go) +* [Level: Intermediate](_examples/intermediate) + * [Send An E-mail](_examples/intermediate/e-mail/main.go) + * [Upload/Read Files](_examples/intermediate/upload-files/main.go) + * [Request Logger](_examples/intermediate/request-logger/main.go) + * [Profiling (pprof)](_examples/intermediate/pprof/main.go) + * [Basic Authentication](_examples/intermediate/basicauth/main.go) + * [HTTP Access Control](_examples/intermediate/cors/main.go) + * [Cache Markdown](_examples/intermediate/cache-markdown/main.go) + * [Localization and Internationalization](_examples/intermediate/i18n/main.go) + * [Recovery](_examples/intermediate/recover/main.go) + * [Graceful Shutdown](_examples/intermediate/graceful-shutdown/main.go) + * [Custom TCP Listener](_examples/intermediate/custom-listener/main.go) + * [Custom HTTP Server](_examples/intermediate/custom-httpserver/main.go) + * [View Engine](_examples/intermediate/view) + * [Overview](_examples/intermediate/view/overview/main.go) + * [Template HTML: Part Zero](_examples/intermediate/view/template_html_0/main.go) + * [Template HTML: Part One](_examples/intermediate/view/template_html_1/main.go) + * [Template HTML: Part Two](_examples/intermediate/view/template_html_2/main.go) + * [Template HTML: Part Three](_examples/intermediate/view/template_html_3/main.go) + * [Template HTML: Part Four](_examples/intermediate/view/template_html_4/main.go) + * [Inject Data Between Handlers](_examples/intermediate/view/context-view-data/main.go) + * [Embedding Templates Into Executable](_examples/intermediate/embedding-templates-into-app) + * [Custom Renderer](_examples/intermediate/view/custom-renderer/main.go) + * [Password Hashing](_examples/intermediate/password-hashing/main.go) + * [Sessions](_examples/intermediate/sessions) + * [Overview](_examples/intermediate/sessions/overview/main.go) + * [Encoding & Decoding the Session ID: Secure Cookie](_examples/intermediate/sessions/securecookie/main.go) + * [Standalone](_examples/intermediate/sessions/standalone/main.go) + * [With A Back-End Database](_examples/intermediate/sessions/database/main.go) + * [Flash Messages](_examples/intermediate/flash-messages/main.go) + * [Websockets](_examples/intermediate/websockets) + * [Ridiculous Simple](_examples/intermediate/websockets/ridiculous-simple/main.go) + * [Overview](_examples/intermediate/websockets/overview/main.go) + * [Connection List](_examples/intermediate/websockets/connectionlist/main.go) + * [Native Messages](_examples/intermediate/websockets/naive-messages/main.go) + * [Secure](_examples/intermediate/websockets/secure/main.go) + * [Custom Go Client](_examples/intermediate/websockets/custom-go-client/main.go) +* [Level: Advanced](_examples/advanced) + * [Transactions](_examples/advanced/transactions/main.go) + * [HTTP Testing](_examples/advanced/httptest/main_test.go) + * [Watch & Compile Typescript source files](_examples/advanced/typescript/main.go) + * [Cloud Editor](_examples/advanced/cloud-editor/main.go) + * [Online Visitors](_examples/advanced/online-visitors/main.go) + * [URL Shortener using BoltDB](_examples/advanced/url-shortener/main.go) + * [Subdomains](_examples/advanced/subdomains) + * [Single](_examples/advanced/subdomains/single/main.go) + * [Multi](_examples/advanced/subdomains/multi/main.go) + * [Wildcard](_examples/advanced/subdomains/wildcard/main.go) + Installation ----------- diff --git a/_examples/README.md b/_examples/README.md index 6fb2a080..23b67041 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -38,12 +38,13 @@ It doesn't contains "best ways" neither explains all its features. It's just a s * [Custom HTTP Server](intermediate/custom-httpserver/main.go) * [View Engine](intermediate/view) * [Overview](intermediate/view/overview/main.go) - * [Embedding Templates Into Executable](intermediate/embedding-templates-into-app) * [Template HTML: Part Zero](intermediate/view/template_html_0/main.go) * [Template HTML: Part One](intermediate/view/template_html_1/main.go) * [Template HTML: Part Two](intermediate/view/template_html_2/main.go) * [Template HTML: Part Three](intermediate/view/template_html_3/main.go) * [Template HTML: Part Four](intermediate/view/template_html_4/main.go) + * [Inject Data Between Handlers](intermediate/view/context-view-data/main.go) + * [Embedding Templates Into Executable](intermediate/embedding-templates-into-app) * [Custom Renderer](intermediate/view/custom-renderer/main.go) * [Password Hashing](intermediate/password-hashing/main.go) * [Sessions](intermediate/sessions) diff --git a/_examples/intermediate/view/context-view-data/main.go b/_examples/intermediate/view/context-view-data/main.go new file mode 100644 index 00000000..1838fbb9 --- /dev/null +++ b/_examples/intermediate/view/context-view-data/main.go @@ -0,0 +1,58 @@ +// this example will show you how you can set per-request data for a template outside of the main handler which calls +// the .Render, via middleware. +// +// Remember: .Render has the "binding" argument which can be used to send data to the template at any case. +package main + +import ( + "time" + + "gopkg.in/kataras/iris.v6" + "gopkg.in/kataras/iris.v6/adaptors/httprouter" + "gopkg.in/kataras/iris.v6/adaptors/view" +) + +const ( + DefaultTitle = "My Awesome Site" + DefaultLayout = "layouts/layout.html" +) + +func main() { + app := iris.New() + // output startup banner and error logs on os.Stdout + app.Adapt(iris.DevLogger()) + // set the router, you can choose gorillamux too + app.Adapt(httprouter.New()) + // set the view engine target to ./templates folder + app.Adapt(view.HTML("./templates", ".html").Reload(true)) + + app.UseFunc(func(ctx *iris.Context) { + // set the title, current time and a layout in order to be used if and when the next handler(s) calls the .Render function + ctx.ViewData("Title", DefaultTitle) + now := time.Now().Format(app.Config.TimeFormat) + ctx.ViewData("CurrentTime", now) + ctx.ViewLayout(DefaultLayout) + + ctx.Next() + }) + + app.Get("/", func(ctx *iris.Context) { + ctx.ViewData("BodyMessage", "a sample text here... setted by the route handler") + if err := ctx.Render("index.html", nil); err != nil { + app.Log(iris.DevMode, err.Error()) + } + }) + + app.Get("/about", func(ctx *iris.Context) { + ctx.ViewData("Title", "My About Page") + ctx.ViewData("BodyMessage", "about text here... setted by the route handler") + + // same file, just to keep things simple. + if err := ctx.Render("index.html", nil); err != nil { + app.Log(iris.DevMode, err.Error()) + } + }) + + // Open localhost:8080 and localhost:8080/about + app.Listen(":8080") +} diff --git a/_examples/intermediate/view/context-view-data/templates/index.html b/_examples/intermediate/view/context-view-data/templates/index.html new file mode 100644 index 00000000..6e3eff8e --- /dev/null +++ b/_examples/intermediate/view/context-view-data/templates/index.html @@ -0,0 +1,8 @@ +

+ Title: {{.Title}} +

+

{{.BodyMessage}}

+ +
+ +Current time: {{.CurrentTime}} \ No newline at end of file diff --git a/_examples/intermediate/view/context-view-data/templates/layouts/layout.html b/_examples/intermediate/view/context-view-data/templates/layouts/layout.html new file mode 100644 index 00000000..37c5343f --- /dev/null +++ b/_examples/intermediate/view/context-view-data/templates/layouts/layout.html @@ -0,0 +1,10 @@ + + +My WebsiteLayout + + + + + {{ yield }} + + diff --git a/context.go b/context.go index 63815490..aa6309c0 100644 --- a/context.go +++ b/context.go @@ -869,13 +869,6 @@ func (ctx *Context) TryWriteGzip(b []byte) (int, error) { // ------------------------------------------------------------------------------------- // ------------------------------------------------------------------------------------- -const ( - // NoLayout to disable layout for a particular template file - NoLayout = "@.|.@no_layout@.|.@" - // TemplateLayoutContextKey is the name of the user values which can be used to set a template layout from a middleware and override the parent's - TemplateLayoutContextKey = "templateLayout" -) - // getGzipOption receives a default value and the render options map and returns if gzip is enabled for this render action func getGzipOption(defaultValue bool, options map[string]interface{}) bool { gzipOpt := options["gzip"] // we only need that, so don't create new map to keep the options. @@ -933,8 +926,66 @@ func (ctx *Context) fastRenderWithStatus(status int, cType string, data []byte) return } +const ( + // NoLayout to disable layout for a particular template file + NoLayout = "@.|.@no_layout@.|.@" + // ViewLayoutContextKey is the name of the user values which can be used to set a template layout from a middleware and override the parent's + ViewLayoutContextKey = "@viewLayout" + ViewDataContextKey = "@viewData" + + // conversions + // TemplateLayoutContextKey same as ViewLayoutContextKey + TemplateLayoutContextKey = ViewLayoutContextKey +) + +// ViewLayout sets the "layout" option if and when .Render +// is being called afterwards, in the same request. +// Useful when need to set or/and change a layout based on the previous handlers in the chain. +// +// Look: .ViewData | .Render +// .MustRender | .RenderWithStatus also. +// +// Example: https://github.com/kataras/iris/tree/v6/_examples/intermediate/view/context-view-data/ +func (ctx *Context) ViewLayout(layoutTmplFile string) { + ctx.Set(ViewLayoutContextKey, layoutTmplFile) +} + +// ViewData saves one or more key-value pair in order to be passed if and when .Render +// is being called afterwards, in the same request. +// Useful when need to set or/and change template data from previous hanadlers in the chain. +// +// If .Render's "binding" argument is not nil and it's not a type of map +// then these data are being ignored, binding has the priority, so the main route's handler can still decide. +// If binding is a map or iris.Map then theese data are being added to the view data +// and passed to the template. +// +// After .Render, the data are not destroyed, in order to be re-used if needed (again, in the same request as everything else), +// to manually clear the view data, developers can call: +// ctx.Set(iris.ViewDataContextKey, iris.Map{}) +// +// Look: .ViewLayout | .Render +// .MustRender | .RenderWithStatus also. +// +// Example: https://github.com/kataras/iris/tree/v6/_examples/intermediate/view/context-view-data/ +func (ctx *Context) ViewData(key string, value interface{}) { + v := ctx.Get(ViewDataContextKey) + if v == nil { + ctx.Set(ViewDataContextKey, Map{key: value}) + return + } + + if data, ok := v.(Map); ok { + data[key] = value + } +} + // RenderWithStatus builds up the response from the specified template or a serialize engine. -// Note: the options: "gzip" and "charset" are built'n support by Iris, so you can pass these on any template engine or serialize engines +// Note: the options: "gzip" and "charset" are built'n support by Iris, so you can pass these on any template engine or serialize engines. +// +// Look: .ViewData | .Render +// .ViewLayout | .MustRender also. +// +// Examples: https://github.com/kataras/iris/tree/v6/_examples/intermediate/view/ func (ctx *Context) RenderWithStatus(status int, name string, binding interface{}, options ...map[string]interface{}) (err error) { if _, shouldFirstStatusCode := ctx.ResponseWriter.(*responseWriter); shouldFirstStatusCode { ctx.SetStatusCode(status) @@ -957,6 +1008,38 @@ func (ctx *Context) RenderWithStatus(status int, name string, binding interface{ } } + // optional, view data (useful for middleware, although user can already do that with ctx.Set/Get) + // check if user sets any view data + if v := ctx.Get(ViewDataContextKey); v != nil { + // if so, then check its type, to make sure + if data, ok := ctx.Get(ViewDataContextKey).(Map); ok { + if len(data) > 0 { + if binding != nil { + // if binding is passed to the Render function then + // two things can happen: + // if binding is a custom type, we ignore the data + // if binding is a map of interface{} or string then, add these to the view data + // finally, set the binding to the new data and pass it to the view engine, as we did before. + + if irisMap, ok := binding.(Map); ok { + for key, val := range irisMap { + data[key] = val + } // a little of necessary duplication here... + } else if stdMap, ok := binding.(map[string]interface{}); ok { + for key, val := range stdMap { + data[key] = val + } + } else if stdMapStr, ok := binding.(map[string]string); ok { + for key, val := range stdMapStr { + data[key] = val + } + } + } + binding = data + } + } + } + // Find Content type // if it the name is not a template file, then take that as the content type. cType := contentHTML @@ -999,7 +1082,12 @@ func (ctx *Context) RenderWithStatus(status int, name string, binding interface{ // Render same as .RenderWithStatus but with status to iris.StatusOK (200) if no previous status exists // builds up the response from the specified template or a serialize engine. -// Note: the options: "gzip" and "charset" are built'n support by Iris, so you can pass these on any template engine or serialize engine +// Note: the options: "gzip" and "charset" are built'n support by Iris, so you can pass these on any template engine or serialize engine. +// +// Look: .ViewData | .MustRender +// .ViewLayout | .RenderWithStatus also. +// +// Examples: https://github.com/kataras/iris/tree/v6/_examples/intermediate/view/ func (ctx *Context) Render(name string, binding interface{}, options ...map[string]interface{}) error { errCode := ctx.ResponseWriter.StatusCode() if errCode <= 0 { @@ -1009,7 +1097,12 @@ func (ctx *Context) Render(name string, binding interface{}, options ...map[stri } // MustRender same as .Render but returns 503 service unavailable http status with a (html) message if render failed -// Note: the options: "gzip" and "charset" are built'n support by Iris, so you can pass these on any template engine or serialize engine +// Note: the options: "gzip" and "charset" are built'n support by Iris, so you can pass these on any template engine or serialize engine. +// +// Look: .ViewData | .Render +// .ViewLayout | .RenderWithStatus also. +// +// Examples: https://github.com/kataras/iris/tree/v6/_examples/intermediate/view/ func (ctx *Context) MustRender(name string, binding interface{}, options ...map[string]interface{}) { if err := ctx.Render(name, binding, options...); err != nil { htmlErr := ctx.HTML(StatusServiceUnavailable,