- fields.yaml trigger (action disable|hide, field, condition checked|unchecked) compiled to FormField.Trigger - boot errors for malformed triggers, controlling fields that are not a switch or checkbox, and triggers in repeater rows, settings and relation forms - Administrators permissions overrides are disabled while Superuser is on (D-06) - SPA: trigger.ts (triggerState, applyTriggers), FORM_TRIGGER_NOTES, the trigger note in FormField, FormView tabs/banner/payload after triggers, PreviewView hide only - regenerated admin OpenAPI and schema.d.ts; rebuilt modules/boardwalk/dist; docs and README
102 lines
3.4 KiB
Go
102 lines
3.4 KiB
Go
package cabana
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/goccy/go-yaml/ast"
|
|
)
|
|
|
|
// FieldTrigger is a field's fields.yaml `trigger` (D-21): the admin disables
|
|
// or hides the field while a switch or checkbox of the same form is checked
|
|
// or unchecked. Action is disable or hide, Field the controlling field's
|
|
// name, Condition checked or unchecked. It is a display rule only: the
|
|
// server never trusts it, and the admin leaves a disabled or hidden field
|
|
// out of the save body, so the stored value is kept.
|
|
type FieldTrigger struct {
|
|
Action string `json:"action"`
|
|
Field string `json:"field"`
|
|
Condition string `json:"condition"`
|
|
}
|
|
|
|
// triggerKeys are the sub-keys a trigger mapping must hold, each exactly once.
|
|
var triggerKeys = []string{"action", "field", "condition"}
|
|
|
|
// compileTrigger decodes a field's `trigger`: a mapping with exactly action
|
|
// (disable or hide), field (an identifier) and condition (checked or
|
|
// unchecked). The controlling field is checked against the form in
|
|
// checkTriggers.
|
|
func compileTrigger(values map[string]ast.Node, field *FormField) error {
|
|
node, ok := values["trigger"]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
mapping, ok := node.(*ast.MappingNode)
|
|
if !ok {
|
|
return fmt.Errorf("trigger must be a mapping with action, field and condition")
|
|
}
|
|
got := map[string]string{}
|
|
for _, entry := range mapping.Values {
|
|
key, err := nodeString(unwrapNode(entry.Key))
|
|
if err != nil {
|
|
return fmt.Errorf("trigger: %w", err)
|
|
}
|
|
switch key {
|
|
case "action", "field", "condition":
|
|
default:
|
|
return fmt.Errorf("trigger: unknown key %s (want action, field and condition)", key)
|
|
}
|
|
if _, dup := got[key]; dup {
|
|
return fmt.Errorf("trigger: duplicate key %s", key)
|
|
}
|
|
value, err := nodeString(unwrapNode(entry.Value))
|
|
if err != nil {
|
|
return fmt.Errorf("trigger: %s: %w", key, err)
|
|
}
|
|
got[key] = value
|
|
}
|
|
for _, key := range triggerKeys {
|
|
if _, ok := got[key]; !ok {
|
|
return fmt.Errorf("trigger: %s is required", key)
|
|
}
|
|
}
|
|
trigger := FieldTrigger{Action: got["action"], Field: got["field"], Condition: got["condition"]}
|
|
if trigger.Action != "disable" && trigger.Action != "hide" {
|
|
return fmt.Errorf("trigger: action %s is not supported (want disable or hide)", trigger.Action)
|
|
}
|
|
if trigger.Condition != "checked" && trigger.Condition != "unchecked" {
|
|
return fmt.Errorf("trigger: condition %s is not supported (want checked or unchecked)", trigger.Condition)
|
|
}
|
|
if !identifier(trigger.Field) {
|
|
return fmt.Errorf("trigger: field %q is not a field name", trigger.Field)
|
|
}
|
|
field.Trigger = &trigger
|
|
return nil
|
|
}
|
|
|
|
// checkTriggers checks every trigger of a controller form against its
|
|
// fields: the controlling field must be another field of the same form, of
|
|
// type switch or checkbox (a virtual switch counts).
|
|
func checkTriggers(fields []FormField) error {
|
|
types := make(map[string]string, len(fields))
|
|
for _, field := range fields {
|
|
types[field.Name] = field.Type
|
|
}
|
|
for _, field := range fields {
|
|
if field.Trigger == nil {
|
|
continue
|
|
}
|
|
name := field.Trigger.Field
|
|
if name == field.Name {
|
|
return fmt.Errorf("field %s: trigger field names the field itself", field.Name)
|
|
}
|
|
typ, ok := types[name]
|
|
if !ok {
|
|
return fmt.Errorf("field %s: trigger field %s is not a field of this form", field.Name, name)
|
|
}
|
|
if typ != "switch" && typ != "checkbox" {
|
|
return fmt.Errorf("field %s: trigger field %s must be a switch or checkbox, not type %s", field.Name, name, typ)
|
|
}
|
|
}
|
|
return nil
|
|
}
|