go-form/form/field_choice.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
}