docs: removing the psql and redis-cli dependencies from README.md because they are now provided through their docker containers
This commit is contained in:
parent
b4621afbd9
commit
9dc83a65cf
1 changed files with 135 additions and 108 deletions
243
README.md
243
README.md
|
|
@ -10,98 +10,99 @@
|
||||||
<p align="center"><img alt="Logo" src="https://user-images.githubusercontent.com/552328/147838644-0efac538-a97e-4a46-86a0-41e3abdf9f20.png" height="200px"/></p>
|
<p align="center"><img alt="Logo" src="https://user-images.githubusercontent.com/552328/147838644-0efac538-a97e-4a46-86a0-41e3abdf9f20.png" height="200px"/></p>
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
* [Introduction](#introduction)
|
|
||||||
* [Overview](#overview)
|
- [Introduction](#introduction)
|
||||||
* [Foundation](#foundation)
|
- [Overview](#overview)
|
||||||
* [Backend](#backend)
|
- [Foundation](#foundation)
|
||||||
* [Frontend](#frontend)
|
- [Backend](#backend)
|
||||||
* [Storage](#storage)
|
- [Frontend](#frontend)
|
||||||
* [Screenshots](#screenshots)
|
- [Storage](#storage)
|
||||||
* [Getting started](#getting-started)
|
- [Screenshots](#screenshots)
|
||||||
* [Dependencies](#dependencies)
|
- [Getting started](#getting-started)
|
||||||
* [Start the application](#start-the-application)
|
- [Dependencies](#dependencies)
|
||||||
* [Running tests](#running-tests)
|
- [Start the application](#start-the-application)
|
||||||
* [Clients](#clients)
|
- [Running tests](#running-tests)
|
||||||
* [Service container](#service-container)
|
- [Clients](#clients)
|
||||||
* [Dependency injection](#dependency-injection)
|
- [Service container](#service-container)
|
||||||
* [Test dependencies](#test-dependencies)
|
- [Dependency injection](#dependency-injection)
|
||||||
* [Configuration](#configuration)
|
- [Test dependencies](#test-dependencies)
|
||||||
* [Environment overrides](#environment-overrides)
|
- [Configuration](#configuration)
|
||||||
* [Environments](#environments)
|
- [Environment overrides](#environment-overrides)
|
||||||
* [Database](#database)
|
- [Environments](#environments)
|
||||||
* [Auto-migrations](#auto-migrations)
|
- [Database](#database)
|
||||||
* [Separate test database](#separate-test-database)
|
- [Auto-migrations](#auto-migrations)
|
||||||
* [ORM](#orm)
|
- [Separate test database](#separate-test-database)
|
||||||
* [Entity types](#entity-types)
|
- [ORM](#orm)
|
||||||
* [New entity type](#new-entity-type)
|
- [Entity types](#entity-types)
|
||||||
* [Sessions](#sessions)
|
- [New entity type](#new-entity-type)
|
||||||
* [Encryption](#encryption)
|
- [Sessions](#sessions)
|
||||||
* [Authentication](#authentication)
|
- [Encryption](#encryption)
|
||||||
* [Login / Logout](#login--logout)
|
- [Authentication](#authentication)
|
||||||
* [Forgot password](#forgot-password)
|
- [Login / Logout](#login--logout)
|
||||||
* [Registration](#registration)
|
- [Forgot password](#forgot-password)
|
||||||
* [Authenticated user](#authenticated-user)
|
- [Registration](#registration)
|
||||||
* [Middleware](#middleware)
|
- [Authenticated user](#authenticated-user)
|
||||||
* [Email verification](#email-verification)
|
- [Middleware](#middleware)
|
||||||
* [Routes](#routes)
|
- [Email verification](#email-verification)
|
||||||
* [Custom middleware](#custom-middleware)
|
- [Routes](#routes)
|
||||||
* [Controller / Dependencies](#controller--dependencies)
|
- [Custom middleware](#custom-middleware)
|
||||||
* [Patterns](#patterns)
|
- [Controller / Dependencies](#controller--dependencies)
|
||||||
* [Errors](#errors)
|
- [Patterns](#patterns)
|
||||||
* [Testing](#testing)
|
- [Errors](#errors)
|
||||||
* [HTTP server](#http-server)
|
- [Testing](#testing)
|
||||||
* [Request / Request helpers](#request--response-helpers)
|
- [HTTP server](#http-server)
|
||||||
* [Goquery](#goquery)
|
- [Request / Request helpers](#request--response-helpers)
|
||||||
* [Controller](#controller)
|
- [Goquery](#goquery)
|
||||||
* [Page](#page)
|
- [Controller](#controller)
|
||||||
* [Flash messaging](#flash-messaging)
|
- [Page](#page)
|
||||||
* [Pager](#pager)
|
- [Flash messaging](#flash-messaging)
|
||||||
* [CSRF](#csrf)
|
- [Pager](#pager)
|
||||||
* [Automatic template parsing](#automatic-template-parsing)
|
- [CSRF](#csrf)
|
||||||
* [Cached responses](#cached-responses)
|
- [Automatic template parsing](#automatic-template-parsing)
|
||||||
* [Cache tags](#cache-tags)
|
- [Cached responses](#cached-responses)
|
||||||
* [Cache middleware](#cache-middleware)
|
- [Cache tags](#cache-tags)
|
||||||
* [Data](#data)
|
- [Cache middleware](#cache-middleware)
|
||||||
* [Forms](#forms)
|
- [Data](#data)
|
||||||
* [Submission processing](#submission-processing)
|
- [Forms](#forms)
|
||||||
* [Inline validation](#inline-validation)
|
- [Submission processing](#submission-processing)
|
||||||
* [Headers](#headers)
|
- [Inline validation](#inline-validation)
|
||||||
* [Status code](#status-code)
|
- [Headers](#headers)
|
||||||
* [Metatags](#metatags)
|
- [Status code](#status-code)
|
||||||
* [URL and link generation](#url-and-link-generation)
|
- [Metatags](#metatags)
|
||||||
* [HTMX support](#htmx-support)
|
- [URL and link generation](#url-and-link-generation)
|
||||||
* [Rendering the page](#rendering-the-page)
|
- [HTMX support](#htmx-support)
|
||||||
* [Template renderer](#template-renderer)
|
- [Rendering the page](#rendering-the-page)
|
||||||
* [Custom functions](#custom-functions)
|
- [Template renderer](#template-renderer)
|
||||||
* [Caching](#caching)
|
- [Custom functions](#custom-functions)
|
||||||
* [Hot-reload for development](#hot-reload-for-development)
|
- [Caching](#caching)
|
||||||
* [File configuration](#file-configuration)
|
- [Hot-reload for development](#hot-reload-for-development)
|
||||||
* [Funcmap](#funcmap)
|
- [File configuration](#file-configuration)
|
||||||
* [Cache](#cache)
|
- [Funcmap](#funcmap)
|
||||||
* [Set data](#set-data)
|
- [Cache](#cache)
|
||||||
* [Get data](#get-data)
|
- [Set data](#set-data)
|
||||||
* [Flush data](#flush-data)
|
- [Get data](#get-data)
|
||||||
* [Flush tags](#flush-tags)
|
- [Flush data](#flush-data)
|
||||||
* [Tasks](#tasks)
|
- [Flush tags](#flush-tags)
|
||||||
* [Queues](#queues)
|
- [Tasks](#tasks)
|
||||||
* [Scheduled tasks](#scheduled-tasks)
|
- [Queues](#queues)
|
||||||
* [Worker](#worker)
|
- [Scheduled tasks](#scheduled-tasks)
|
||||||
* [Monitoring](#monitoring)
|
- [Worker](#worker)
|
||||||
* [Static files](#static-files)
|
- [Monitoring](#monitoring)
|
||||||
* [Cache control headers](#cache-control-headers)
|
- [Static files](#static-files)
|
||||||
* [Cache-buster](#cache-buster)
|
- [Cache control headers](#cache-control-headers)
|
||||||
* [Email](#email)
|
- [Cache-buster](#cache-buster)
|
||||||
* [HTTPS](#https)
|
- [Email](#email)
|
||||||
* [Logging](#logging)
|
- [HTTPS](#https)
|
||||||
* [Roadmap](#roadmap)
|
- [Logging](#logging)
|
||||||
* [Credits](#credits)
|
- [Roadmap](#roadmap)
|
||||||
|
- [Credits](#credits)
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
### Overview
|
### Overview
|
||||||
|
|
||||||
_Pagoda_ is not a framework but rather a base starter-kit for rapid, easy full-stack web development in Go, aiming to provide much of the functionality you would expect from a complete web framework as well as establishing patterns, procedures and structure for your web application.
|
_Pagoda_ is not a framework but rather a base starter-kit for rapid, easy full-stack web development in Go, aiming to provide much of the functionality you would expect from a complete web framework as well as establishing patterns, procedures and structure for your web application.
|
||||||
|
|
||||||
Built on a solid [foundation](#foundation) of well-established frameworks and modules, _Pagoda_ aims to be a starting point for any web application with the benefit over a mega-framework in that you have full control over all of the code, the ability to easily swap any frameworks or modules in or out, no strict patterns or interfaces to follow, and no fear of lock-in.
|
Built on a solid [foundation](#foundation) of well-established frameworks and modules, _Pagoda_ aims to be a starting point for any web application with the benefit over a mega-framework in that you have full control over all of the code, the ability to easily swap any frameworks or modules in or out, no strict patterns or interfaces to follow, and no fear of lock-in.
|
||||||
|
|
||||||
While separate JavaScript frontends have surged in popularity, many prefer the reliability, simplicity and speed of a full-stack approach with server-side rendered HTML. Even the popular JS frameworks all have SSR options. This project aims to highlight that _Go_ templates can be powerful and easy to work with, and interesting [frontend](#frontend) libraries can provide the same modern functionality and behavior without having to write any JS at all.
|
While separate JavaScript frontends have surged in popularity, many prefer the reliability, simplicity and speed of a full-stack approach with server-side rendered HTML. Even the popular JS frameworks all have SSR options. This project aims to highlight that _Go_ templates can be powerful and easy to work with, and interesting [frontend](#frontend) libraries can provide the same modern functionality and behavior without having to write any JS at all.
|
||||||
|
|
@ -148,11 +149,9 @@ Go server-side rendered HTML combined with the projects below enable you to crea
|
||||||
|
|
||||||
Ensure the following are installed on your system:
|
Ensure the following are installed on your system:
|
||||||
|
|
||||||
- [Go](https://go.dev/)
|
- [Go](https://go.dev/)
|
||||||
- [Docker](https://www.docker.com/)
|
- [Docker](https://www.docker.com/)
|
||||||
- [Docker Compose](https://docs.docker.com/compose/install/)
|
- [Docker Compose](https://docs.docker.com/compose/install/)
|
||||||
- [psql](https://www.postgresql.org/docs/13/app-psql.html) _(optional)_
|
|
||||||
- [redis-cli](https://redis.io/topics/rediscli) _(optional)_
|
|
||||||
|
|
||||||
### Start the application
|
### Start the application
|
||||||
|
|
||||||
|
|
@ -203,7 +202,7 @@ A new container can be created and initialized via `services.NewContainer()`. It
|
||||||
### Dependency injection
|
### Dependency injection
|
||||||
|
|
||||||
The container exists to faciliate easy dependency-injection both for services within the container as well as areas of your application that require any of these dependencies. For example, the container is passed to and stored within the `Controller`
|
The container exists to faciliate easy dependency-injection both for services within the container as well as areas of your application that require any of these dependencies. For example, the container is passed to and stored within the `Controller`
|
||||||
so that the controller and the route using it have full, easy access to all services.
|
so that the controller and the route using it have full, easy access to all services.
|
||||||
|
|
||||||
### Test dependencies
|
### Test dependencies
|
||||||
|
|
||||||
|
|
@ -211,7 +210,7 @@ It is common that your tests will require access to dependencies, like the datab
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
The `config` package provides a flexible, extensible way to store all configuration for the application. Configuration is added to the `Container` as a _Service_, making it accessible across most of the application.
|
The `config` package provides a flexible, extensible way to store all configuration for the application. Configuration is added to the `Container` as a _Service_, making it accessible across most of the application.
|
||||||
|
|
||||||
Be sure to review and adjust all of the default configuration values provided.
|
Be sure to review and adjust all of the default configuration values provided.
|
||||||
|
|
||||||
|
|
@ -269,7 +268,7 @@ When a `Container` is created, if the [environment](#environments) is set to `co
|
||||||
|
|
||||||
## ORM
|
## ORM
|
||||||
|
|
||||||
As previously mentioned, [Ent](https://entgo.io/) is the supplied ORM. It can swapped out, but I highly recommend it. I don't think there is anything comparable for Go, at the current time. If you're not familiar with Ent, take a look through their top-notch [documentation](https://entgo.io/docs/getting-started).
|
As previously mentioned, [Ent](https://entgo.io/) is the supplied ORM. It can swapped out, but I highly recommend it. I don't think there is anything comparable for Go, at the current time. If you're not familiar with Ent, take a look through their top-notch [documentation](https://entgo.io/docs/getting-started).
|
||||||
|
|
||||||
An Ent client is included in the `Container` to provide easy access to the ORM throughout the application.
|
An Ent client is included in the `Container` to provide easy access to the ORM throughout the application.
|
||||||
|
|
||||||
|
|
@ -278,6 +277,7 @@ Ent relies on code-generation for the entities you create to provide robust, typ
|
||||||
### Entity types
|
### Entity types
|
||||||
|
|
||||||
The two included entity types are:
|
The two included entity types are:
|
||||||
|
|
||||||
- User
|
- User
|
||||||
- PasswordToken
|
- PasswordToken
|
||||||
|
|
||||||
|
|
@ -395,8 +395,8 @@ A `middleware` package is included which you can easily add to along with the cu
|
||||||
|
|
||||||
The `Controller`, which is described in a section below, serves two purposes for routes:
|
The `Controller`, which is described in a section below, serves two purposes for routes:
|
||||||
|
|
||||||
1) It provides base functionality which can be embedded in each route, most importantly `Page` rendering (described in the `Controller` section below)
|
1. It provides base functionality which can be embedded in each route, most importantly `Page` rendering (described in the `Controller` section below)
|
||||||
2) It stores a pointer to the `Container`, making all _Services_ available within your route
|
2. It stores a pointer to the `Container`, making all _Services_ available within your route
|
||||||
|
|
||||||
While using the `Controller` is not required for your routes, it will certainly make development easier.
|
While using the `Controller` is not required for your routes, it will certainly make development easier.
|
||||||
|
|
||||||
|
|
@ -522,6 +522,7 @@ Flash messaging requires that [sessions](#sessions) and the session middleware a
|
||||||
#### Creating messages
|
#### Creating messages
|
||||||
|
|
||||||
There are four types of messages, and each can be created as follows:
|
There are four types of messages, and each can be created as follows:
|
||||||
|
|
||||||
- Success: `msg.Success(ctx echo.Context, message string)`
|
- Success: `msg.Success(ctx echo.Context, message string)`
|
||||||
- Info: `msg.Info(ctx echo.Context, message string)`
|
- Info: `msg.Info(ctx echo.Context, message string)`
|
||||||
- Warning: `msg.Warning(ctx echo.Context, message string)`
|
- Warning: `msg.Warning(ctx echo.Context, message string)`
|
||||||
|
|
@ -575,10 +576,10 @@ page.Layout = "main"
|
||||||
|
|
||||||
That alone will result in the following templates being parsed and executed when the `Page` is rendered:
|
That alone will result in the following templates being parsed and executed when the `Page` is rendered:
|
||||||
|
|
||||||
1) `layouts/main.gohtml` as the base template
|
1. `layouts/main.gohtml` as the base template
|
||||||
2) `pages/home.gohtml` to provide the `content` template for the layout
|
2. `pages/home.gohtml` to provide the `content` template for the layout
|
||||||
3) All template files located within the `components` directory
|
3. All template files located within the `components` directory
|
||||||
4) The entire [funcmap](#funcmap)
|
4. The entire [funcmap](#funcmap)
|
||||||
|
|
||||||
The [template renderer](#template-renderer) also provides caching and local hot-reloading.
|
The [template renderer](#template-renderer) also provides caching and local hot-reloading.
|
||||||
|
|
||||||
|
|
@ -599,8 +600,9 @@ You can use the [cache client](#cache) on the `Container` to easily [flush cache
|
||||||
Cached pages are served via the middleware `ServeCachedPage()` in the `middleware` package.
|
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:
|
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
|
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.
|
Cached pages are looked up for a key that matches the exact, full URL of the given request.
|
||||||
|
|
||||||
|
|
@ -638,12 +640,14 @@ Form submission processing is made extremely simple by leveraging functionality
|
||||||
Using the example form above, these are the steps you would take within the _POST_ callback for your route:
|
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:
|
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
|
```go
|
||||||
var form ContactForm
|
var form ContactForm
|
||||||
ctx.Set(context.FormKey, &form)
|
ctx.Set(context.FormKey, &form)
|
||||||
```
|
```
|
||||||
|
|
||||||
Parse the input in the POST data to map to the struct so it becomes populated. This uses the `form` struct tags to map form values to the struct fields.
|
Parse the input in the POST data to map to the struct so it becomes populated. This uses the `form` struct tags to map form values to the struct fields.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
if err := ctx.Bind(&form); err != nil {
|
if err := ctx.Bind(&form); err != nil {
|
||||||
// Something went wrong...
|
// Something went wrong...
|
||||||
|
|
@ -651,6 +655,7 @@ if err := ctx.Bind(&form); err != nil {
|
||||||
```
|
```
|
||||||
|
|
||||||
Process the submission which uses [validator](https://github.com/go-playground/validator) to check for validation errors:
|
Process the submission which uses [validator](https://github.com/go-playground/validator) to check for validation errors:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
if err := form.Submission.Process(ctx, form); err != nil {
|
if err := form.Submission.Process(ctx, form); err != nil {
|
||||||
// Something went wrong...
|
// Something went wrong...
|
||||||
|
|
@ -658,6 +663,7 @@ if err := form.Submission.Process(ctx, form); err != nil {
|
||||||
```
|
```
|
||||||
|
|
||||||
Check if the form submission has any validation errors:
|
Check if the form submission has any validation errors:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
if !form.Submission.HasErrors() {
|
if !form.Submission.HasErrors() {
|
||||||
// All good, now execute something!
|
// All good, now execute something!
|
||||||
|
|
@ -665,6 +671,7 @@ if !form.Submission.HasErrors() {
|
||||||
```
|
```
|
||||||
|
|
||||||
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_:
|
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
|
```go
|
||||||
if form.Submission.HasErrors() {
|
if form.Submission.HasErrors() {
|
||||||
return c.Get(ctx)
|
return c.Get(ctx)
|
||||||
|
|
@ -672,6 +679,7 @@ if form.Submission.HasErrors() {
|
||||||
```
|
```
|
||||||
|
|
||||||
Then, in your _GET_ handler, extract the form from the context so it can be passed to the templates:
|
Then, in your _GET_ handler, extract the form from the context so it can be passed to the templates:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
page := controller.NewPage(ctx)
|
page := controller.NewPage(ctx)
|
||||||
page.Form = ContactForm{}
|
page.Form = ContactForm{}
|
||||||
|
|
@ -682,8 +690,15 @@ if form := ctx.Get(context.FormKey); form != nil {
|
||||||
```
|
```
|
||||||
|
|
||||||
And finally, your template:
|
And finally, your template:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<input id="email" name="email" type="email" class="input" value="{{.Form.Email}}">
|
<input
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
type="email"
|
||||||
|
class="input"
|
||||||
|
value="{{.Form.Email}}"
|
||||||
|
/>
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Inline validation
|
#### Inline validation
|
||||||
|
|
@ -695,11 +710,14 @@ While [validator](https://github.com/go-playground/validator) is a great package
|
||||||
To provide the inline validation in your template, there are two things that need to be done.
|
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:
|
First, include a status class on the element so it will highlight green or red based on the validation:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<input id="email" name="email" type="email" class="input {{.Form.Submission.GetFieldStatusClass "Email"}}" value="{{.Form.Email}}">
|
<input id="email" name="email" type="email" class="input
|
||||||
|
{{.Form.Submission.GetFieldStatusClass "Email"}}" value="{{.Form.Email}}">
|
||||||
```
|
```
|
||||||
|
|
||||||
Second, render the error messages, if there are any for a given field:
|
Second, render the error messages, if there are any for a given field:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
{{template "field-errors" (.Form.Submission.GetFieldErrors "Email")}}
|
{{template "field-errors" (.Form.Submission.GetFieldErrors "Email")}}
|
||||||
```
|
```
|
||||||
|
|
@ -744,15 +762,17 @@ A _component_ template is included to render metatags in `core.gohtml` which can
|
||||||
|
|
||||||
### URL and link generation
|
### URL and link generation
|
||||||
|
|
||||||
Generating URLs in the templates is made easy if you follow the [routing patterns](#patterns) and provide names for your routes. Echo provides a `Reverse` function to generate a route URL with a given route name and optional parameters. This function is made accessible to the templates via the `Page` field `ToURL`.
|
Generating URLs in the templates is made easy if you follow the [routing patterns](#patterns) and provide names for your routes. Echo provides a `Reverse` function to generate a route URL with a given route name and optional parameters. This function is made accessible to the templates via the `Page` field `ToURL`.
|
||||||
|
|
||||||
As an example, if you have route such as:
|
As an example, if you have route such as:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
profile := Profile{Controller: ctr}
|
profile := Profile{Controller: ctr}
|
||||||
e.GET("/user/profile/:user", profile.Get).Name = "user_profile"
|
e.GET("/user/profile/:user", profile.Get).Name = "user_profile"
|
||||||
```
|
```
|
||||||
|
|
||||||
And you want to generate a URL in the template, you can:
|
And you want to generate a URL in the template, you can:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
{{call .ToURL "user_profile" 1}
|
{{call .ToURL "user_profile" 1}
|
||||||
```
|
```
|
||||||
|
|
@ -766,9 +786,11 @@ There is also a helper function provided in the [funcmap](#funcmap) to generate
|
||||||
```
|
```
|
||||||
|
|
||||||
Will generate:
|
Will generate:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<a href="/user/profile/1" class="is-active extra-class">Profile</a>
|
<a href="/user/profile/1" class="is-active extra-class">Profile</a>
|
||||||
```
|
```
|
||||||
|
|
||||||
Assuming the current _path_ is `/user/profile/1`; otherwise the `is-active` class will be excluded.
|
Assuming the current _path_ is `/user/profile/1`; otherwise the `is-active` class will be excluded.
|
||||||
|
|
||||||
### HTMX support
|
### HTMX support
|
||||||
|
|
@ -776,6 +798,7 @@ Assuming the current _path_ is `/user/profile/1`; otherwise the `is-active` clas
|
||||||
[HTMX](https://htmx.org/) is an awesome JavaScript library allows you to access AJAX, CSS Transitions, WebSockets and Server Sent Events directly in HTML, using attributes, so you can build modern user interfaces with the simplicity and power of hypertext.
|
[HTMX](https://htmx.org/) is an awesome JavaScript library allows you to access AJAX, CSS Transitions, WebSockets and Server Sent Events directly in HTML, using attributes, so you can build modern user interfaces with the simplicity and power of hypertext.
|
||||||
|
|
||||||
Many examples of its usage are available in the included examples:
|
Many examples of its usage are available in the included examples:
|
||||||
|
|
||||||
- All navigation links use [boost](https://htmx.org/docs/#boosting) which dynamically replaces the page content with an AJAX request, providing a SPA-like experience.
|
- All navigation links use [boost](https://htmx.org/docs/#boosting) which dynamically replaces the page content with an AJAX request, providing a SPA-like experience.
|
||||||
- All forms use either [boost](https://htmx.org/docs/#boosting) or [hx-post](https://htmx.org/docs/#triggers) to submit via AJAX.
|
- All forms use either [boost](https://htmx.org/docs/#boosting) or [hx-post](https://htmx.org/docs/#triggers) to submit via AJAX.
|
||||||
- The mock search autocomplete modal uses [hx-get](https://htmx.org/docs/#targets) to fetch search results from the server via AJAX and update the UI.
|
- The mock search autocomplete modal uses [hx-get](https://htmx.org/docs/#targets) to fetch search results from the server via AJAX and update the UI.
|
||||||
|
|
@ -848,6 +871,7 @@ buf, err = c.TemplateRenderer.
|
||||||
```
|
```
|
||||||
|
|
||||||
This will do the following:
|
This will do the following:
|
||||||
|
|
||||||
- [Cache](#caching) the parsed template with a _group_ of `page` and _key_ of `home` so this parse only happens once
|
- [Cache](#caching) the parsed template with a _group_ of `page` and _key_ of `home` so this parse only happens once
|
||||||
- Set the _base template file_ as `main`
|
- Set the _base template file_ as `main`
|
||||||
- Include the templates `templates/layout/main.gohtml` and `templates/pages/home.gohtml`
|
- Include the templates `templates/layout/main.gohtml` and `templates/pages/home.gohtml`
|
||||||
|
|
@ -1041,6 +1065,7 @@ err := c.Tasks.
|
||||||
```
|
```
|
||||||
|
|
||||||
In this example, this task will be:
|
In this example, this task will be:
|
||||||
|
|
||||||
- Assigned a task type of `my_task`
|
- Assigned a task type of `my_task`
|
||||||
- The task worker will be sent `taskData` as the payload
|
- The task worker will be sent `taskData` as the payload
|
||||||
- Put in to the `critical` queue
|
- Put in to the `critical` queue
|
||||||
|
|
@ -1138,13 +1163,15 @@ The cache max-life is controlled by the configuration at `Config.Cache.Expiratio
|
||||||
While it's ideal to use cache control headers on your static files so browsers cache the files, you need a way to bust the cache in case the files are changed. In order to do this, a function is provided in the [funcmap](#funcmap) to generate a static file URL for a given file that appends a cache-buster query. This query string is randomly generated and persisted until the application restarts.
|
While it's ideal to use cache control headers on your static files so browsers cache the files, you need a way to bust the cache in case the files are changed. In order to do this, a function is provided in the [funcmap](#funcmap) to generate a static file URL for a given file that appends a cache-buster query. This query string is randomly generated and persisted until the application restarts.
|
||||||
|
|
||||||
For example, to render a file located in `static/picture.png`, you would use:
|
For example, to render a file located in `static/picture.png`, you would use:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<img src="{{File "picture.png"}}"/>
|
<img src="{{File "picture.png"}}"/>
|
||||||
```
|
```
|
||||||
|
|
||||||
Which would result in:
|
Which would result in:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<img src="/files/picture.png?v=9fhe73kaf3"/>
|
<img src="/files/picture.png?v=9fhe73kaf3" />
|
||||||
```
|
```
|
||||||
|
|
||||||
Where `9fhe73kaf3` is the randomly-generated cache-buster.
|
Where `9fhe73kaf3` is the randomly-generated cache-buster.
|
||||||
|
|
@ -1201,7 +1228,7 @@ Logging is provided by [Echo](https://echo.labstack.com/guide/customization/#log
|
||||||
```go
|
```go
|
||||||
func (c *home) Get(ctx echo.Context) error {
|
func (c *home) Get(ctx echo.Context) error {
|
||||||
ctx.Logger().Info("something happened")
|
ctx.Logger().Info("something happened")
|
||||||
|
|
||||||
if err := someOperation(); err != nil {
|
if err := someOperation(); err != nil {
|
||||||
ctx.Logger().Errorf("the operation failed: %v", err)
|
ctx.Logger().Errorf("the operation failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -1246,4 +1273,4 @@ Thank you to all of the following amazing projects for making this possible.
|
||||||
- [sprig](https://github.com/Masterminds/sprig)
|
- [sprig](https://github.com/Masterminds/sprig)
|
||||||
- [sessions](https://github.com/gorilla/sessions)
|
- [sessions](https://github.com/gorilla/sessions)
|
||||||
- [testify](https://github.com/stretchr/testify)
|
- [testify](https://github.com/stretchr/testify)
|
||||||
- [validator](https://github.com/go-playground/validator)
|
- [validator](https://github.com/go-playground/validator)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue