200 lines
4.4 KiB
Go
200 lines
4.4 KiB
Go
package form
|
|
|
|
// @license GNU AGPL version 3 or any later version
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as
|
|
// published by the Free Software Foundation, either version 3 of the
|
|
// License, or (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import (
|
|
"encoding/json"
|
|
"reflect"
|
|
|
|
"github.com/spf13/cast"
|
|
"gitnet.fr/deblan/go-form/validation"
|
|
)
|
|
|
|
type Choice struct {
|
|
Value string
|
|
Label string
|
|
Data any
|
|
}
|
|
|
|
func (c Choice) Match(value string) bool {
|
|
return c.Value == value
|
|
}
|
|
|
|
type Choices struct {
|
|
Data any `json:"data"`
|
|
ValueBuilder func(key int, item any) string `json:"-"`
|
|
LabelBuilder func(key int, item any) string `json:"-"`
|
|
}
|
|
|
|
func (c *Choices) Match(f *Field, value string) bool {
|
|
if f.Data == nil {
|
|
return false
|
|
}
|
|
|
|
if f.IsSlice {
|
|
v := reflect.ValueOf(f.Data)
|
|
|
|
for key, _ := range c.GetChoices() {
|
|
for i := 0; i < v.Len(); i++ {
|
|
item := v.Index(i).Interface()
|
|
|
|
switch item.(type) {
|
|
case string:
|
|
if item == value {
|
|
return true
|
|
}
|
|
default:
|
|
if c.ValueBuilder(key, item) == value {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
return f.Data == value
|
|
}
|
|
|
|
func (c *Choices) WithValueBuilder(builder func(key int, item any) string) *Choices {
|
|
c.ValueBuilder = builder
|
|
|
|
return c
|
|
}
|
|
|
|
func (c *Choices) WithLabelBuilder(builder func(key int, item any) string) *Choices {
|
|
c.LabelBuilder = builder
|
|
|
|
return c
|
|
}
|
|
|
|
func (c *Choices) GetChoices() []Choice {
|
|
choices := []Choice{}
|
|
|
|
v := reflect.ValueOf(c.Data)
|
|
|
|
switch v.Kind() {
|
|
case reflect.Slice, reflect.Array, reflect.String, reflect.Map:
|
|
for i := 0; i < v.Len(); i++ {
|
|
choices = append(choices, Choice{
|
|
Value: c.ValueBuilder(i, v.Index(i).Interface()),
|
|
Label: c.LabelBuilder(i, v.Index(i).Interface()),
|
|
Data: v.Index(i).Interface(),
|
|
})
|
|
}
|
|
}
|
|
|
|
return choices
|
|
}
|
|
|
|
func (c Choices) MarshalJSON() ([]byte, error) {
|
|
var choices []map[string]string
|
|
|
|
v := reflect.ValueOf(c.Data)
|
|
|
|
switch v.Kind() {
|
|
case reflect.Slice, reflect.Array, reflect.String, reflect.Map:
|
|
for i := 0; i < v.Len(); i++ {
|
|
choices = append(choices, map[string]string{
|
|
"value": c.ValueBuilder(i, v.Index(i).Interface()),
|
|
"label": c.LabelBuilder(i, v.Index(i).Interface()),
|
|
})
|
|
}
|
|
}
|
|
|
|
return json.Marshal(choices)
|
|
}
|
|
|
|
// Generates an instance of Choices
|
|
func NewChoices(items any) *Choices {
|
|
builder := func(key int, item any) string {
|
|
return cast.ToString(key)
|
|
}
|
|
|
|
choices := Choices{
|
|
ValueBuilder: builder,
|
|
LabelBuilder: builder,
|
|
Data: items,
|
|
}
|
|
|
|
return &choices
|
|
}
|
|
|
|
// Generates inputs (checkbox or radio) or selects
|
|
func NewFieldChoice(name string) *Field {
|
|
f := NewField(name, "choice").
|
|
WithOptions(
|
|
NewOption("choices", &Choices{}),
|
|
NewOption("expanded", false),
|
|
NewOption("multiple", false),
|
|
NewOption("empty_choice_label", "None"),
|
|
)
|
|
|
|
f.Validate = func(field *Field) bool {
|
|
isValid := field.Validate(field)
|
|
|
|
if len(validation.NewNotBlank().Validate(field.Data)) == 0 {
|
|
choices := field.GetOption("choices").Value.(*Choices)
|
|
isValidChoice := false
|
|
|
|
for _, choice := range choices.GetChoices() {
|
|
if choices.Match(field, choice.Value) {
|
|
isValidChoice = true
|
|
}
|
|
}
|
|
|
|
if !isValidChoice {
|
|
field.Errors = append(field.Errors, validation.Error("This value is not valid."))
|
|
isValid = false
|
|
}
|
|
}
|
|
|
|
return isValid
|
|
}
|
|
|
|
f.WithBeforeBind(func(data any) (any, error) {
|
|
choices := f.GetOption("choices").Value.(*Choices)
|
|
|
|
switch data.(type) {
|
|
case string:
|
|
v := data.(string)
|
|
for _, c := range choices.GetChoices() {
|
|
if c.Match(v) {
|
|
return c.Data, nil
|
|
}
|
|
}
|
|
case []string:
|
|
v := reflect.ValueOf(data)
|
|
var res []interface{}
|
|
|
|
for _, choice := range choices.GetChoices() {
|
|
for i := 0; i < v.Len(); i++ {
|
|
item := v.Index(i).Interface().(string)
|
|
if choice.Match(item) {
|
|
res = append(res, choice.Data)
|
|
}
|
|
}
|
|
}
|
|
|
|
return res, nil
|
|
}
|
|
|
|
return data, nil
|
|
})
|
|
|
|
return f
|
|
}
|