diff --git a/README.md b/README.md
index 5567fd7..6c498b7 100644
--- a/README.md
+++ b/README.md
@@ -50,7 +50,8 @@
* [CSRF](#csrf)
* [Automatic template parsing](#automatic-template-parsing)
* [Cached responses](#cached-responses)
- * [Cache tags](#cache-tags)
+ * [Cache tags](#cache-tags)
+ * [Cache middleware](#cache-middleware)
* [Data](#data)
* [Forms](#forms)
* [Submission processing](#submission-processing)
@@ -65,8 +66,6 @@
* [Hot-reload for development](#)
* [Funcmap](#)
* [Cache](#cache)
- * Responses
- * Tags
* [Static files](#static-files)
* Cache control headers
* Cache-buster
@@ -370,7 +369,7 @@ Then create the route and add to the router:
g.POST("/", home.Post).Name = "home.post"
```
-Your route will not have all methods available on the `Controller` as well as access to the `Container`. It's not required to name the route methods to match the HTTP method.
+Your route will now have all methods available on the `Controller` as well as access to the `Container`. It's not required to name the route methods to match the HTTP method.
**It is highly recommended** that you name your routes. Most methods on the back and frontend leverage the route name and parameters in order to generate URLs.
@@ -521,11 +520,133 @@ That alone will result in the following templates being parsed and executed when
The [template renderer](#template-renderer) also provides caching and local hot-reloading.
### Cached responses
-### Cache tags
+
+A `Page` can have cached enabled just by setting `Page.Cache.Enabled` to `true`. The `Controller` will automatically handle caching the HTML output, headers and status code. Cached pages are stored using a key that matches the full request URL and [middleware](#cache-middleware) is used to serve it on matching requests.
+
+By default, the cache expiration time will be set according to the configuration value located at `Config.Cache.Expiration.Page` but it can be set per-page at `Page.Cache.Expiration`.
+
+#### Cache tags
+
+You can optionally specify cache tags for the `Page` by setting a slice of strings on `Page.Cache.Tags`. This provides the ability to build in cache invalidation logic in your application driven by events such as entity operations, for example.
+
+The cache client on the `Container` is currently handled by [gocache](https://github.com/eko/gocache) which makes it easy to perform operations such as tag-invalidation, for example:
+
+```go
+c.Cache.Invalidate(ctx, store.InvalidateOptions{
+ Tags: []string{"my-tag"},
+})
+```
+
+#### Cache middleware
+
+Cached pages are served via the middleware `ServeCachedPage()` in the `middleware` package.
+
+The cache is bypassed if the requests meet any of the following criteria:
+1) Is not a GET request
+2) Is made by an authenticated user
+
+Cached pages are looked up for a key that matches the exact, full URL of the given request.
+
### Data
+
+The `Data` field on the `Page` is of type `interface{}` and is what allows your route to pass whatever it requires to the templates, alongside the `Page` itself.
+
### Forms
+
+The `Form` field on the `Page` is similar to the `Data` field in that it's an `interface{}` type but it's meant to store a struct that represents a form being rendered on the page.
+
+An example of this pattern is:
+
+```go
+type ContactForm struct {
+ Email string `form:"email" validate:"required,email"`
+ Message string `form:"message" validate:"required"`
+ Submission controller.FormSubmission
+}
+```
+
+Then in your page:
+
+```go
+page := controller.NewPage(ctx)
+page.Form = ContactForm{}
+```
+
+How the _form_ gets populated with values so that your template can render them is covered in the next section.
+
#### Submission processing
+
+Form submission processing is made extremely simple by leveraging functionality provided by [Echo binding](https://echo.labstack.com/guide/binding/), [validator](https://github.com/go-playground/validator) and the `FormSubmission` struct located in `controller/form.go`.
+
+Using the example form above, these are the steps you would take within the _POST_ callback for your route:
+
+Start by storing a pointer to the form in the conetxt so that your _GET_ callback can access the form values, which will be showed at the end:
+```go
+var form ContactForm
+ctx.Set(context.FormKey, &form)
+```
+
+Parse the input in the POST data to map to the struct so it becomes populated:
+```go
+if err := ctx.Bind(&form); err != nil {
+ // Something went wrong...
+}
+```
+
+Process the submissions which uses [validator](https://github.com/go-playground/validator) to check for validation errors:
+```go
+if err := form.Submission.Process(ctx, form); err != nil {
+ // Something went wrong...
+}
+```
+
+Check if the form submission has any validation errors:
+```go
+if !form.Submission.HasErrors() {
+ // All good, now execute something!
+}
+```
+
+In the event of a validation error, you most likely want to re-render the form with the values provided and any error messages. Since you stored a pointer to the _form_ in the context in the first step, you can first have the _POST_ handler call the _GET_:
+```go
+if form.Submission.HasErrors() {
+ return c.Get(ctx)
+}
+```
+
+Then, in your _GET_ handler, extract the form from the context so it can be passed to the templates:
+```go
+page := controller.NewPage(ctx)
+page.Form = ContactForm{}
+
+if form := ctx.Get(context.FormKey); form != nil {
+ page.Form = form.(*ContactForm)
+}
+```
+
+And finally, your template:
+```
+
+```
+
#### Inline validation
+
+The `FormSubmission` makes inline validation easier because it will store all validation errors in a map, keyed by the form struct field name. It also contains helper methods that your templates can use to provide classes and extract the error messages.
+
+While [validator](https://github.com/go-playground/validator) is an incredible package that is used to validate based on struct tags, the downside is that the messaging, by default, is not very human-readable or easy to override. Within `FormSubmission.setErrorMessages()` the validation errors are converted to more readable messages based on the tag that failed validation. Only a few tags are provided as an example, so be sure to expand on that as needed.
+
+To provide the inline validation in your template, there are two things that need to be done.
+
+First, include a status class on the element so it will highlight green or red based on the validation:
+```
+
+```
+
+Second, render the error messages, if there are any for a given field:
+```
+{{template "field-errors" (.Form.Submission.GetFieldErrors "Email")}}
+```
+
### Headers
### Status code
### Metatags
diff --git a/routes/contact.go b/routes/contact.go
index d8f67bc..0f76036 100644
--- a/routes/contact.go
+++ b/routes/contact.go
@@ -20,17 +20,17 @@ type (
)
func (c *Contact) Get(ctx echo.Context) error {
- p := controller.NewPage(ctx)
- p.Layout = "main"
- p.Name = "contact"
- p.Title = "Contact us"
- p.Form = ContactForm{}
+ page := controller.NewPage(ctx)
+ page.Layout = "main"
+ page.Name = "contact"
+ page.Title = "Contact us"
+ page.Form = ContactForm{}
if form := ctx.Get(context.FormKey); form != nil {
- p.Form = form.(*ContactForm)
+ page.Form = form.(*ContactForm)
}
- return c.RenderPage(ctx, p)
+ return c.RenderPage(ctx, page)
}
func (c *Contact) Post(ctx echo.Context) error {