refactor: refactor the example
This commit is contained in:
parent
b9c5f6a2fd
commit
1d40aa6b09
5 changed files with 338 additions and 235 deletions
76
example.go
Normal file
76
example.go
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/yassinebenaid/godump"
|
||||
"gitnet.fr/deblan/go-form/example"
|
||||
"gitnet.fr/deblan/go-form/theme"
|
||||
)
|
||||
|
||||
//go:embed example/view/*.html
|
||||
var templates embed.FS
|
||||
|
||||
func handler(view, action string, formRenderer *theme.Renderer, w http.ResponseWriter, r *http.Request) {
|
||||
entity := example.ExampleData{}
|
||||
form := example.CreateDataForm(action)
|
||||
|
||||
form.Mount(entity)
|
||||
|
||||
if r.Method == form.Method {
|
||||
form.HandleRequest(r)
|
||||
|
||||
if form.IsSubmitted() && form.IsValid() {
|
||||
form.Bind(&entity)
|
||||
}
|
||||
}
|
||||
|
||||
content, _ := templates.ReadFile(view)
|
||||
|
||||
formAsJson, _ := json.MarshalIndent(form, " ", " ")
|
||||
|
||||
tpl, _ := template.New("page").
|
||||
Funcs(formRenderer.FuncMap()).
|
||||
Parse(string(content))
|
||||
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
|
||||
var dump godump.Dumper
|
||||
dump.Theme = godump.Theme{}
|
||||
|
||||
tpl.Execute(w, map[string]any{
|
||||
"isSubmitted": form.IsSubmitted(),
|
||||
"isValid": form.IsValid(),
|
||||
"form": form,
|
||||
"json": string(formAsJson),
|
||||
"dump": template.HTML(dump.Sprint(entity)),
|
||||
})
|
||||
}
|
||||
|
||||
func main() {
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
handler(
|
||||
"example/view/html5.html",
|
||||
"/",
|
||||
theme.NewRenderer(theme.Html5),
|
||||
w,
|
||||
r,
|
||||
)
|
||||
})
|
||||
|
||||
http.HandleFunc("/bootstrap", func(w http.ResponseWriter, r *http.Request) {
|
||||
handler(
|
||||
"example/view/bootstrap.html",
|
||||
"/bootstrap",
|
||||
theme.NewRenderer(theme.Bootstrap5),
|
||||
w,
|
||||
r,
|
||||
)
|
||||
})
|
||||
|
||||
log.Fatal(http.ListenAndServe(":1122", nil))
|
||||
}
|
||||
|
|
@ -50,7 +50,7 @@ type CollectionItem struct {
|
|||
ValueB string
|
||||
}
|
||||
|
||||
func CreateDataForm() *form.Form {
|
||||
func CreateDataForm(action string) *form.Form {
|
||||
items := []Item{
|
||||
Item{Id: 1, Name: "Item 1"},
|
||||
Item{Id: 2, Name: "Item 2"},
|
||||
|
|
@ -66,19 +66,6 @@ func CreateDataForm() *form.Form {
|
|||
})
|
||||
|
||||
return form.NewForm(
|
||||
form.NewFieldCollection("Collection").
|
||||
WithOptions(
|
||||
form.NewOption("label", "Collection"),
|
||||
form.NewOption("form", form.NewForm(
|
||||
form.NewFieldText("ValueA").
|
||||
WithOptions(form.NewOption("label", "Value A")).
|
||||
WithConstraints(
|
||||
validation.NewNotBlank(),
|
||||
),
|
||||
form.NewFieldText("ValueB").
|
||||
WithOptions(form.NewOption("label", "Value B")),
|
||||
)),
|
||||
),
|
||||
form.NewFieldText("Bytes").
|
||||
WithOptions(
|
||||
form.NewOption("label", "Bytes"),
|
||||
|
|
@ -196,6 +183,19 @@ func CreateDataForm() *form.Form {
|
|||
form.NewOption("multiple", true),
|
||||
),
|
||||
),
|
||||
form.NewFieldCollection("Collection").
|
||||
WithOptions(
|
||||
form.NewOption("label", "Collection"),
|
||||
form.NewOption("form", form.NewForm(
|
||||
form.NewFieldText("ValueA").
|
||||
WithOptions(form.NewOption("label", "Value A")).
|
||||
WithConstraints(
|
||||
validation.NewNotBlank(),
|
||||
),
|
||||
form.NewFieldText("ValueB").
|
||||
WithOptions(form.NewOption("label", "Value B")),
|
||||
)),
|
||||
),
|
||||
form.NewFieldCsrf("_csrf_token").WithData("my-token"),
|
||||
form.NewSubmit("submit").
|
||||
WithOptions(
|
||||
|
|
@ -206,8 +206,8 @@ func CreateDataForm() *form.Form {
|
|||
).
|
||||
End().
|
||||
WithOptions(
|
||||
form.NewOption("help", "Form help"),
|
||||
form.NewOption("help", "Form global help"),
|
||||
).
|
||||
WithMethod(http.MethodPost).
|
||||
WithAction("/")
|
||||
WithAction(action)
|
||||
}
|
||||
|
|
|
|||
113
example/view/bootstrap.html
Normal file
113
example/view/bootstrap.html
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Form with Bootstrap</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-LN+7fdVzj6u52u30Kp6M/trliBMCMKTyK833zpbD+pXdCLuTusPj697FH4R/5mcr" crossorigin="anonymous">
|
||||
<style>
|
||||
fieldset {
|
||||
border: 1px solid #999;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
form > div:not(:last-child), fieldset > div:not(:last-child) {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.gf-help {
|
||||
font-style: italic;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<details>
|
||||
<summary>Debug view</summary>
|
||||
<div class="py-2">
|
||||
<strong>Submitted:</strong>
|
||||
{{ .isSubmitted }}
|
||||
</div>
|
||||
<div class="py-2">
|
||||
<strong>Valid:</strong>
|
||||
{{ .isValid }}
|
||||
</div>
|
||||
|
||||
<details class="py-2">
|
||||
<summary><strong>Dump of data</strong></summary>
|
||||
<pre class="p-2">{{ .dump }}</pre>
|
||||
</details>
|
||||
|
||||
<details class="py-2">
|
||||
<summary><strong>Form as JSON</strong></summary>
|
||||
<pre class="p-2">{{ .json }}</pre>
|
||||
</details>
|
||||
</details>
|
||||
|
||||
{{if .isValid}}
|
||||
<div class="alert alert-success">The form is valid!</div>
|
||||
{{else}}
|
||||
<div class="alert alert-warning">The form is invalid!</div>
|
||||
{{end}}
|
||||
|
||||
{{ form .form }}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const collections = document.querySelectorAll('*[data-prototype]')
|
||||
|
||||
const collectionItemAddToolBar = (item) => {
|
||||
const toolbar = document.createElement('div')
|
||||
|
||||
const createBtn = () => {
|
||||
const btn = document.createElement('button')
|
||||
btn.textContent = '-'
|
||||
btn.type = 'button'
|
||||
btn.className = "btn btn-primary"
|
||||
|
||||
btn.addEventListener('click', () => {
|
||||
item.remove()
|
||||
})
|
||||
|
||||
return btn
|
||||
}
|
||||
|
||||
toolbar.appendChild(createBtn())
|
||||
item.querySelector('fieldset').appendChild(toolbar)
|
||||
}
|
||||
|
||||
const collectionAddToolbar = (collection) => {
|
||||
const container = collection.parentNode
|
||||
const toolbar = document.createElement('div')
|
||||
|
||||
const createBtn = () => {
|
||||
const btn = document.createElement('button')
|
||||
btn.textContent = '+'
|
||||
btn.type = 'button'
|
||||
btn.className = "btn btn-primary"
|
||||
|
||||
btn.addEventListener('click', () => {
|
||||
const form = collection.getAttribute('data-prototype')
|
||||
.replace(/__name__/g, collection.children.length)
|
||||
|
||||
collection.insertAdjacentHTML("beforeend", form)
|
||||
collectionItemAddToolBar(collection.lastChild)
|
||||
})
|
||||
|
||||
return btn
|
||||
}
|
||||
|
||||
toolbar.appendChild(createBtn())
|
||||
container.appendChild(toolbar)
|
||||
}
|
||||
|
||||
collections.forEach((collection) => {
|
||||
collectionAddToolbar(collection)
|
||||
|
||||
for (let item of collection.children) {
|
||||
collectionItemAddToolBar(item)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
133
example/view/html5.html
Normal file
133
example/view/html5.html
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Form HTML5 (with Pico)</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.colors.min.css">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="color-scheme" content="light">
|
||||
|
||||
<style>
|
||||
* {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.p10 {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
border: 1px solid var(--pico-form-element-border-color);
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
form > div:not(:last-child), fieldset > div:not(:last-child) {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.gf-errors {
|
||||
color: var(--pico-form-element-invalid-border-color);
|
||||
}
|
||||
|
||||
.gf-help {
|
||||
font-style: italic;
|
||||
color: var(--pico-form-element-placeholder-color);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="container">
|
||||
<details class="p10">
|
||||
<summary>Debug view</summary>
|
||||
|
||||
<div class="p10">
|
||||
<strong>Submitted:</strong>
|
||||
{{ .isSubmitted }}
|
||||
</div>
|
||||
<div class="p10">
|
||||
<strong>Valid:</strong>
|
||||
{{ .isValid }}
|
||||
</div>
|
||||
|
||||
<details class="p10">
|
||||
<summary><strong>Dump of data</strong></summary>
|
||||
<pre>{{ .dump }}</pre>
|
||||
</details>
|
||||
|
||||
<details class="p10">
|
||||
<summary><strong>Form as JSON</strong></summary>
|
||||
<pre>{{ .json }}</pre>
|
||||
</details>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
{{if .isValid}}
|
||||
<p class="pico-color-green-500">The form is valid!</p>
|
||||
{{else}}
|
||||
<p class="pico-color-red-500">The form is invalid!</p>
|
||||
{{end}}
|
||||
|
||||
{{ form .form }}
|
||||
|
||||
<script>
|
||||
const collections = document.querySelectorAll('*[data-prototype]')
|
||||
|
||||
const collectionItemAddToolBar = (item) => {
|
||||
const toolbar = document.createElement('div')
|
||||
|
||||
const createBtn = () => {
|
||||
const btn = document.createElement('button')
|
||||
btn.textContent = '-'
|
||||
btn.type = 'button'
|
||||
|
||||
btn.addEventListener('click', () => {
|
||||
item.remove()
|
||||
})
|
||||
|
||||
return btn
|
||||
}
|
||||
|
||||
toolbar.appendChild(createBtn())
|
||||
item.querySelector('fieldset').appendChild(toolbar)
|
||||
}
|
||||
|
||||
const collectionAddToolbar = (collection) => {
|
||||
const container = collection.parentNode
|
||||
const toolbar = document.createElement('div')
|
||||
|
||||
const createBtn = () => {
|
||||
const btn = document.createElement('button')
|
||||
btn.textContent = '+'
|
||||
btn.type = 'button'
|
||||
|
||||
btn.addEventListener('click', () => {
|
||||
const form = collection.getAttribute('data-prototype')
|
||||
.replace(/__name__/g, collection.children.length)
|
||||
|
||||
collection.insertAdjacentHTML("beforeend", form)
|
||||
collectionItemAddToolBar(collection.lastChild)
|
||||
})
|
||||
|
||||
return btn
|
||||
}
|
||||
|
||||
toolbar.appendChild(createBtn())
|
||||
container.appendChild(toolbar)
|
||||
}
|
||||
|
||||
collections.forEach((collection) => {
|
||||
collectionAddToolbar(collection)
|
||||
|
||||
for (let item of collection.children) {
|
||||
collectionItemAddToolBar(item)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
219
main.go
219
main.go
|
|
@ -1,219 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/yassinebenaid/godump"
|
||||
"gitnet.fr/deblan/go-form/example"
|
||||
"gitnet.fr/deblan/go-form/theme"
|
||||
)
|
||||
|
||||
func main() {
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
data := example.ExampleData{}
|
||||
data.Collection = []example.CollectionItem{
|
||||
{"Value a 1", "Value b 1"},
|
||||
}
|
||||
|
||||
f := example.CreateDataForm()
|
||||
f.Mount(data)
|
||||
|
||||
if r.Method == f.Method {
|
||||
f.HandleRequest(r)
|
||||
|
||||
if f.IsSubmitted() && f.IsValid() {
|
||||
f.Bind(&data)
|
||||
// godump.Dump(data)
|
||||
}
|
||||
}
|
||||
|
||||
render := theme.NewRenderer(theme.Html5)
|
||||
|
||||
tpl, _ := template.New("page").Funcs(render.FuncMap()).Parse(`
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Form</title>
|
||||
<style>
|
||||
input[type="text"],
|
||||
input[type="date"],
|
||||
input[type="datetime"],
|
||||
input[type="time"],
|
||||
input[type="range"],
|
||||
input[type="email"],
|
||||
input[type="number"],
|
||||
input[type="password"],
|
||||
select,
|
||||
input[type="datetime-local"],
|
||||
textarea {
|
||||
box-sizing: border-box;
|
||||
padding: 9px;
|
||||
margin: 10px 0;
|
||||
display: block;
|
||||
width: 100%;
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
.form-errors {
|
||||
margin: 0;
|
||||
padding: 5px 0 0 0;
|
||||
color: red;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.form-errors li {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.form-help {
|
||||
color: blue;
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
.debug {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.debug .debug-value {
|
||||
color: #555;
|
||||
padding: 10px 0 0 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="debug">
|
||||
<div>
|
||||
<strong>Submitted</strong>
|
||||
<span class="debug-value">{{ .Form.IsSubmitted }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Valid</strong>
|
||||
<span class="debug-value">{{ .Form.IsValid }}</span>
|
||||
</div>
|
||||
<table width="100%">
|
||||
<tr>
|
||||
<td width="50%" valign="top">
|
||||
<pre class="debug-valid">{{ .Dump }}</pre>
|
||||
</td>
|
||||
<td valign="top">
|
||||
<details>
|
||||
<summary>JSON</summary>
|
||||
<pre class="debug-valid">{{ .Json }}</pre>
|
||||
</details>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{{ form .Form }}
|
||||
|
||||
<script>
|
||||
const collections = document.querySelectorAll('*[data-prototype]')
|
||||
|
||||
collections.forEach((collection) => {
|
||||
const container = collection.parentNode
|
||||
const toolbar = document.createElement('div')
|
||||
|
||||
const createAdd = () => {
|
||||
const btn = document.createElement('button')
|
||||
btn.textContent = '+'
|
||||
btn.type = 'button'
|
||||
|
||||
const form = collection.getAttribute('data-prototype')
|
||||
.replace(/__name__/g, collection.children.length)
|
||||
|
||||
btn.addEventListener('click', () => {
|
||||
collection.insertAdjacentHTML("beforeend", form)
|
||||
})
|
||||
|
||||
return btn
|
||||
}
|
||||
|
||||
toolbar.appendChild(createAdd())
|
||||
|
||||
container.appendChild(toolbar)
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`)
|
||||
|
||||
var dump godump.Dumper
|
||||
dump.Theme = godump.Theme{}
|
||||
|
||||
j, _ := json.MarshalIndent(f, " ", " ")
|
||||
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
tpl.Execute(w, map[string]any{
|
||||
"Form": f,
|
||||
"Json": string(j),
|
||||
"Dump": template.HTML(dump.Sprint(data)),
|
||||
})
|
||||
})
|
||||
|
||||
http.HandleFunc("/bootstrap", func(w http.ResponseWriter, r *http.Request) {
|
||||
data := example.ExampleData{}
|
||||
|
||||
f := example.CreateDataForm()
|
||||
f.WithAction("/bootstrap")
|
||||
f.Mount(data)
|
||||
|
||||
if r.Method == f.Method {
|
||||
f.HandleRequest(r)
|
||||
|
||||
if f.IsSubmitted() && f.IsValid() {
|
||||
f.Bind(&data)
|
||||
godump.Dump(data)
|
||||
}
|
||||
}
|
||||
|
||||
render := theme.NewRenderer(theme.Bootstrap5)
|
||||
|
||||
tpl, _ := template.New("page").Funcs(render.FuncMap()).Parse(`
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Form</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-LN+7fdVzj6u52u30Kp6M/trliBMCMKTyK833zpbD+pXdCLuTusPj697FH4R/5mcr" crossorigin="anonymous">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="list-group">
|
||||
<div class="list-group-item">
|
||||
<strong>Submitted</strong>
|
||||
<span class="debug-value">{{ .Form.IsSubmitted }}</span>
|
||||
</div>
|
||||
<div class="list-group-item">
|
||||
<strong>Valid</strong>
|
||||
<span class="debug-value">{{ .Form.IsValid }}</span>
|
||||
</div>
|
||||
<div class="list-group-item">
|
||||
<strong>Data</strong>
|
||||
<pre class="debug-valid">{{ .Dump }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ form .Form }}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`)
|
||||
|
||||
var dump godump.Dumper
|
||||
dump.Theme = godump.Theme{}
|
||||
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
tpl.Execute(w, map[string]any{
|
||||
"Form": f,
|
||||
"Dump": template.HTML(dump.Sprint(data)),
|
||||
})
|
||||
})
|
||||
|
||||
log.Fatal(http.ListenAndServe(":1122", nil))
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue