+
+
\ No newline at end of file
diff --git a/_examples/view/context-view-engine/views/on-fly/index.html b/_examples/view/context-view-engine/views/on-fly/index.html
new file mode 100644
index 00000000..4b3fd536
--- /dev/null
+++ b/_examples/view/context-view-engine/views/on-fly/index.html
@@ -0,0 +1,2 @@
+
On-fly
+
{{.Message}}
\ No newline at end of file
diff --git a/_examples/view/context-view-engine/views/public/500.html b/_examples/view/context-view-engine/views/public/500.html
new file mode 100644
index 00000000..37c58b58
--- /dev/null
+++ b/_examples/view/context-view-engine/views/public/500.html
@@ -0,0 +1,12 @@
+
+{{ define "content" }}
+
Internal Server Error
+{{ end }}
+
+{{ define "message" }}
+
{{.Message}}
+{{ end }}
\ No newline at end of file
diff --git a/_examples/view/context-view-engine/views/public/index.html b/_examples/view/context-view-engine/views/public/index.html
new file mode 100644
index 00000000..b11758cf
--- /dev/null
+++ b/_examples/view/context-view-engine/views/public/index.html
@@ -0,0 +1 @@
+
Index Body
\ No newline at end of file
diff --git a/_examples/view/context-view-engine/views/public/layouts/error.html b/_examples/view/context-view-engine/views/public/layouts/error.html
new file mode 100644
index 00000000..48598789
--- /dev/null
+++ b/_examples/view/context-view-engine/views/public/layouts/error.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+ {{.Code}}
+
+
+ {{ template "content" .}}
+
+ {{block "message" .}}{{end}}
+
+
\ No newline at end of file
diff --git a/_examples/view/context-view-engine/views/public/layouts/main.html b/_examples/view/context-view-engine/views/public/layouts/main.html
new file mode 100644
index 00000000..5b5a509f
--- /dev/null
+++ b/_examples/view/context-view-engine/views/public/layouts/main.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+ {{ if .Title }}{{ .Title }}{{ else }}Default Main Title{{ end }}
+
+
+ {{ template "content" . }}
+
+
+
+
\ No newline at end of file
diff --git a/_examples/view/context-view-engine/views/public/partials/footer.html b/_examples/view/context-view-engine/views/public/partials/footer.html
new file mode 100644
index 00000000..61ee8461
--- /dev/null
+++ b/_examples/view/context-view-engine/views/public/partials/footer.html
@@ -0,0 +1 @@
+
Footer Partial
\ No newline at end of file
diff --git a/aliases.go b/aliases.go
index 37bd621d..b2109572 100644
--- a/aliases.go
+++ b/aliases.go
@@ -23,6 +23,9 @@ type (
// Developers send responses to the client's request through a Context.
// Developers get request information from the client's request by a Context.
Context = *context.Context
+ // ViewEngine is an alias of `context.ViewEngine`.
+ // See HTML, Blocks, Django, Jet, Pug, Ace, Handlebars, Amber and e.t.c.
+ ViewEngine = context.ViewEngine
// UnmarshalerFunc a shortcut, an alias for the `context#UnmarshalerFunc` type
// which implements the `context#Unmarshaler` interface for reading request's body
// via custom decoders, most of them already implement the `context#UnmarshalerFunc`
diff --git a/configuration.go b/configuration.go
index 5906a309..e9dcf3ab 100644
--- a/configuration.go
+++ b/configuration.go
@@ -756,18 +756,30 @@ type Configuration struct {
// via a middleware through `SetVersion` method, e.g. `versioning.SetVersion(ctx, "1.0, 1.1")`.
// Defaults to "iris.api.version".
VersionContextKey string `json:"versionContextKey" yaml:"VersionContextKey" toml:"VersionContextKey"`
- // GetViewLayoutContextKey is the key of the context's user values' key
- // which is being used to set the template
- // layout from a middleware or the main handler.
- // Overrides the parent's or the configuration's.
+ // ViewEngineContextKey is the context's values key
+ // responsible to store and retrieve(view.Engine) the current view engine.
+ // A middleware or a Party can modify its associated value to change
+ // a view engine that `ctx.View` will render through.
+ // If not an engine is registered by the end-developer
+ // then its associated value is always nil,
+ // meaning that the default value is nil.
+ // See `Party.RegisterView` and `Context.ViewEngine` methods as well.
//
- // Defaults to "iris.ViewLayout"
+ // Defaults to "iris.view.engine".
+ ViewEngineContextKey string `json:"viewEngineContextKey,omitempty" yaml:"ViewEngineContextKey" toml:"ViewEngineContextKey"`
+ // ViewLayoutContextKey is the context's values key
+ // responsible to store and retrieve(string) the current view layout.
+ // A middleware can modify its associated value to change
+ // the layout that `ctx.View` will use to render a template.
+ //
+ // Defaults to "iris.view.layout".
ViewLayoutContextKey string `json:"viewLayoutContextKey,omitempty" yaml:"ViewLayoutContextKey" toml:"ViewLayoutContextKey"`
- // GetViewDataContextKey is the key of the context's user values' key
- // which is being used to set the template
- // binding data from a middleware or the main handler.
+ // ViewDataContextKey is the context's values key
+ // responsible to store and retrieve(interface{}) the current view binding data.
+ // A middleware can modify its associated value to change
+ // the template's data on-fly.
//
- // Defaults to "iris.viewData"
+ // Defaults to "iris.view.data".
ViewDataContextKey string `json:"viewDataContextKey,omitempty" yaml:"ViewDataContextKey" toml:"ViewDataContextKey"`
// RemoteAddrHeaders are the allowed request headers names
// that can be valid to parse the client's IP based on.
@@ -945,6 +957,11 @@ func (c Configuration) GetVersionContextKey() string {
return c.VersionContextKey
}
+// GetViewEngineContextKey returns the ViewEngineContextKey field.
+func (c Configuration) GetViewEngineContextKey() string {
+ return c.ViewEngineContextKey
+}
+
// GetViewLayoutContextKey returns the ViewLayoutContextKey field.
func (c Configuration) GetViewLayoutContextKey() string {
return c.ViewLayoutContextKey
@@ -1094,10 +1111,12 @@ func WithConfiguration(c Configuration) Configurator {
main.VersionContextKey = v
}
+ if v := c.ViewEngineContextKey; v != "" {
+ main.ViewEngineContextKey = v
+ }
if v := c.ViewLayoutContextKey; v != "" {
main.ViewLayoutContextKey = v
}
-
if v := c.ViewDataContextKey; v != "" {
main.ViewDataContextKey = v
}
@@ -1169,8 +1188,9 @@ func DefaultConfiguration() Configuration {
LocaleContextKey: "iris.locale",
LanguageContextKey: "iris.locale.language",
VersionContextKey: "iris.api.version",
- ViewLayoutContextKey: "iris.viewLayout",
- ViewDataContextKey: "iris.viewData",
+ ViewEngineContextKey: "iris.view.engine",
+ ViewLayoutContextKey: "iris.view.layout",
+ ViewDataContextKey: "iris.view.data",
RemoteAddrHeaders: nil,
RemoteAddrHeadersForce: false,
RemoteAddrPrivateSubnets: []netutil.IPRange{
diff --git a/context/configuration.go b/context/configuration.go
index 26103e47..fa9091d7 100644
--- a/context/configuration.go
+++ b/context/configuration.go
@@ -51,6 +51,9 @@ type ConfigurationReadOnly interface {
GetLanguageContextKey() string
// GetVersionContextKey returns the VersionContextKey field.
GetVersionContextKey() string
+
+ // GetViewEngineContextKey returns the ViewEngineContextKey field.
+ GetViewEngineContextKey() string
// GetViewLayoutContextKey returns the ViewLayoutContextKey field.
GetViewLayoutContextKey() string
// GetViewDataContextKey returns the ViewDataContextKey field.
diff --git a/context/context.go b/context/context.go
index 3986bb33..ab4f11bd 100644
--- a/context/context.go
+++ b/context/context.go
@@ -2392,16 +2392,22 @@ func (ctx *Context) CompressReader(enable bool) error {
// | Rich Body Content Writers/Renderers |
// +------------------------------------------------------------+
-const (
- // NoLayout to disable layout for a particular template file
- NoLayout = "iris.nolayout"
-)
+// ViewEngine registers a view engine for the current chain of handlers.
+// It overrides any previously registered engines, including the application's root ones.
+// Note that, because performance is everything,
+// the "engine" MUST be already ready-to-use,
+// meaning that its `Load` method should be called once before this method call.
+//
+// To register a view engine per-group of groups too see `Party.RegisterView` instead.
+func (ctx *Context) ViewEngine(engine ViewEngine) {
+ ctx.values.Set(ctx.app.ConfigurationReadOnly().GetViewEngineContextKey(), engine)
+}
// ViewLayout sets the "layout" option if and when .View
// 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.
//
-// Note that the 'layoutTmplFile' argument can be set to iris.NoLayout || view.NoLayout || context.NoLayout
+// Note that the 'layoutTmplFile' argument can be set to iris.NoLayout
// to disable the layout for a specific view render action,
// it disables the engine's configuration's layout property.
//
@@ -2418,7 +2424,7 @@ func (ctx *Context) ViewLayout(layoutTmplFile string) {
//
// If .View'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 context.Map then these data are being added to the view data
+// If binding is a map or iris.Map then these data are being added to the view data
// and passed to the template.
//
// After .View, the data are not destroyed, in order to be re-used if needed (again, in the same request as everything else),
@@ -2457,7 +2463,7 @@ func (ctx *Context) ViewData(key string, value interface{}) {
// A check for nil is always a good practise if different
// kind of values or no data are registered via `ViewData`.
//
-// Similarly to `viewData := ctx.Values().Get("iris.viewData")` or
+// Similarly to `viewData := ctx.Values().Get("iris.view.data")` or
// `viewData := ctx.Values().Get(ctx.Application().ConfigurationReadOnly().GetViewDataContextKey())`.
func (ctx *Context) GetViewData() map[string]interface{} {
viewDataContextKey := ctx.app.ConfigurationReadOnly().GetViewDataContextKey()
@@ -2514,7 +2520,21 @@ func (ctx *Context) View(filename string, optionalViewModel ...interface{}) erro
bindingData = ctx.values.Get(cfg.GetViewDataContextKey())
}
- err := ctx.app.View(ctx, filename, layout, bindingData)
+ if key := cfg.GetViewEngineContextKey(); key != "" {
+ if engineV := ctx.values.Get(key); engineV != nil {
+ if engine, ok := engineV.(ViewEngine); ok {
+ err := engine.ExecuteWriter(ctx, filename, layout, bindingData)
+ if err != nil {
+ ctx.app.Logger().Errorf("View [%v] [%T]: %v", ctx.getLogIdentifier(), engine, err)
+ return err
+ }
+
+ return nil
+ }
+ }
+ }
+
+ err := ctx.app.View(ctx, filename, layout, bindingData) // if failed it logs the error.
if err != nil {
ctx.StopWithStatus(http.StatusInternalServerError)
}
@@ -2522,6 +2542,16 @@ func (ctx *Context) View(filename string, optionalViewModel ...interface{}) erro
return err
}
+// getLogIdentifier returns the ID, or the client remote IP address,
+// useful for internal logging of context's method failure.
+func (ctx *Context) getLogIdentifier() interface{} {
+ if id := ctx.GetID(); id != nil {
+ return id
+ }
+
+ return ctx.RemoteAddr()
+}
+
const (
// ContentBinaryHeaderValue header value for binary data.
ContentBinaryHeaderValue = "application/octet-stream"
diff --git a/context/view.go b/context/view.go
new file mode 100644
index 00000000..ec8111e8
--- /dev/null
+++ b/context/view.go
@@ -0,0 +1,22 @@
+package context
+
+import "io"
+
+// ViewEngine is the interface which all view engines should be implemented in order to be registered inside iris.
+type ViewEngine interface {
+ // Load should load the templates from a physical system directory or by an embedded one (assets/go-bindata).
+ Load() error
+ // ExecuteWriter should execute a template by its filename with an optional layout and bindingData.
+ ExecuteWriter(w io.Writer, filename string, layout string, bindingData interface{}) error
+ // Ext should return the final file extension which this view engine is responsible to render.
+ Ext() string
+}
+
+// ViewEngineFuncer is an addition of a view engine,
+// if a view engine implements that interface
+// then iris can add some closed-relative iris functions
+// like {{ url }}, {{ urlpath }} and {{ tr }}.
+type ViewEngineFuncer interface {
+ // AddFunc should adds a function to the template's function map.
+ AddFunc(funcName string, funcBody interface{})
+}
diff --git a/core/router/api_builder.go b/core/router/api_builder.go
index fc5af9e0..e3d7c775 100644
--- a/core/router/api_builder.go
+++ b/core/router/api_builder.go
@@ -1022,6 +1022,24 @@ func (api *APIBuilder) OnAnyErrorCode(handlers ...context.Handler) (routes []*Ro
return
}
+// RegisterView registers and loads a view engine middleware for that group of routes.
+// It overrides any of the application's root registered view engines.
+// To register a view engine per handler chain see the `Context.ViewEngine` instead.
+// Read `Configuration.ViewEngineContextKey` documentation for more.
+func (api *APIBuilder) RegisterView(viewEngine context.ViewEngine) {
+ if err := viewEngine.Load(); err != nil {
+ api.errors.Add(err)
+ return
+ }
+
+ api.Use(func(ctx *context.Context) {
+ ctx.ViewEngine(viewEngine)
+ ctx.Next()
+ })
+ // Note (@kataras): It does not return the Party in order
+ // to keep the iris.Application a compatible Party.
+}
+
// Layout overrides the parent template layout with a more specific layout for this Party.
// It returns the current Party.
//
diff --git a/core/router/party.go b/core/router/party.go
index 8cb55a32..61b8e888 100644
--- a/core/router/party.go
+++ b/core/router/party.go
@@ -241,6 +241,11 @@ type Party interface {
// Returns the GET *Route.
Favicon(favPath string, requestPath ...string) *Route
+ // RegisterView registers and loads a view engine middleware for that group of routes.
+ // It overrides any of the application's root registered view engines.
+ // To register a view engine per handler chain see the `Context.ViewEngine` instead.
+ // Read `Configuration.ViewEngineContextKey` documentation for more.
+ RegisterView(viewEngine context.ViewEngine)
// Layout overrides the parent template layout with a more specific layout for this Party.
// It returns the current Party.
//
diff --git a/iris.go b/iris.go
index f98640be..dd87d90b 100644
--- a/iris.go
+++ b/iris.go
@@ -280,7 +280,7 @@ func (app *Application) View(writer io.Writer, filename string, layout string, b
}
// ConfigureHost accepts one or more `host#Configuration`, these configurators functions
-// can access the host created by `app.Run`,
+// can access the host created by `app.Run` or `app.Listen`,
// they're being executed when application is ready to being served to the public.
//
// It's an alternative way to interact with a host that is automatically created by
diff --git a/view/ace.go b/view/ace.go
index 329ffc1f..d658fd0c 100644
--- a/view/ace.go
+++ b/view/ace.go
@@ -21,10 +21,11 @@ func Ace(directory, extension string) *HTMLEngine {
once := new(sync.Once)
s.middleware = func(name string, text []byte) (contents string, err error) {
once.Do(func() { // on first template parse, all funcs are given.
- for k, v := range s.funcs {
+ for k, v := range emptyFuncs {
funcs[k] = v
}
- for k, v := range emptyFuncs {
+
+ for k, v := range s.funcs {
funcs[k] = v
}
})
diff --git a/view/engine.go b/view/engine.go
deleted file mode 100644
index 70fac87f..00000000
--- a/view/engine.go
+++ /dev/null
@@ -1,31 +0,0 @@
-package view
-
-import (
- "io"
-)
-
-// NoLayout disables the configuration's layout for a specific execution.
-const NoLayout = "iris.nolayout"
-
-// returns empty if it's no layout or empty layout and empty configuration's layout.
-func getLayout(layout string, globalLayout string) string {
- if layout == NoLayout {
- return ""
- }
-
- if layout == "" && globalLayout != "" {
- return globalLayout
- }
-
- return layout
-}
-
-// Engine is the interface which all view engines should be implemented in order to be registered inside iris.
-type Engine interface {
- // Load should load the templates from a physical system directory or by an embedded one (assets/go-bindata).
- Load() error
- // ExecuteWriter should execute a template by its filename with an optional layout and bindingData.
- ExecuteWriter(w io.Writer, filename string, layout string, bindingData interface{}) error
- // Ext should return the final file extension which this view engine is responsible to render.
- Ext() string
-}
diff --git a/view/funcs.go b/view/funcs.go
deleted file mode 100644
index c3cdd1b5..00000000
--- a/view/funcs.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package view
-
-// EngineFuncer is an addition of a view engine,
-// if a view engine implements that interface
-// then iris can add some closed-relative iris functions
-// like {{ urlpath }} and {{ urlpath }}.
-type EngineFuncer interface {
- // AddFunc should adds a function to the template's function map.
- AddFunc(funcName string, funcBody interface{})
-}
diff --git a/view/view.go b/view/view.go
index 9d2d1775..05abdf78 100644
--- a/view/view.go
+++ b/view/view.go
@@ -5,6 +5,18 @@ import (
"io"
"path/filepath"
"strings"
+
+ "github.com/kataras/iris/v12/context"
+)
+
+type (
+ // Engine is the interface for a compatible Iris view engine.
+ // It's an alias of context.ViewEngine.
+ Engine = context.ViewEngine
+ // EngineFuncer is the interface for a compatible Iris view engine
+ // which accepts builtin framework functions such as url, urlpath and tr.
+ // It's an alias of context.ViewEngineFuncer.
+ EngineFuncer = context.ViewEngineFuncer
)
// View is responsible to
@@ -73,3 +85,19 @@ func (v *View) Load() error {
}
return nil
}
+
+// NoLayout disables the configuration's layout for a specific execution.
+const NoLayout = "iris.nolayout"
+
+// returns empty if it's no layout or empty layout and empty configuration's layout.
+func getLayout(layout string, globalLayout string) string {
+ if layout == NoLayout {
+ return ""
+ }
+
+ if layout == "" && globalLayout != "" {
+ return globalLayout
+ }
+
+ return layout
+}