338 lines
8.8 KiB
Go
338 lines
8.8 KiB
Go
package plugin_ui
|
|
|
|
import (
|
|
"github.com/dop251/goja"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type FormManager struct {
|
|
ctx *Context
|
|
}
|
|
|
|
func NewFormManager(ctx *Context) *FormManager {
|
|
return &FormManager{
|
|
ctx: ctx,
|
|
}
|
|
}
|
|
|
|
type FormField struct {
|
|
ID string `json:"id"`
|
|
Type string `json:"type"`
|
|
Name string `json:"name"`
|
|
Label string `json:"label"`
|
|
Placeholder string `json:"placeholder,omitempty"`
|
|
Value interface{} `json:"value,omitempty"`
|
|
Options []FormFieldOption `json:"options,omitempty"`
|
|
Props map[string]interface{} `json:"props,omitempty"`
|
|
}
|
|
|
|
type FormFieldOption struct {
|
|
Label string `json:"label"`
|
|
Value interface{} `json:"value"`
|
|
}
|
|
|
|
type Form struct {
|
|
Name string `json:"name"`
|
|
ID string `json:"id"`
|
|
Type string `json:"type"`
|
|
Props FormProps `json:"props"`
|
|
manager *FormManager
|
|
}
|
|
|
|
type FormProps struct {
|
|
Name string `json:"name"`
|
|
Fields []FormField `json:"fields"`
|
|
}
|
|
|
|
// jsNewForm
|
|
//
|
|
// Example:
|
|
// const form = tray.newForm("form-1")
|
|
func (f *FormManager) jsNewForm(call goja.FunctionCall) goja.Value {
|
|
name, ok := call.Argument(0).Export().(string)
|
|
if !ok {
|
|
f.ctx.handleTypeError("newForm requires a name")
|
|
}
|
|
|
|
form := &Form{
|
|
Name: name,
|
|
ID: uuid.New().String(),
|
|
Type: "form",
|
|
Props: FormProps{Fields: make([]FormField, 0), Name: name},
|
|
manager: f,
|
|
}
|
|
|
|
formObj := f.ctx.vm.NewObject()
|
|
|
|
// Form methods
|
|
formObj.Set("render", form.jsRender)
|
|
formObj.Set("onSubmit", form.jsOnSubmit)
|
|
|
|
// Field creation methods
|
|
formObj.Set("inputField", form.jsInputField)
|
|
formObj.Set("numberField", form.jsNumberField)
|
|
formObj.Set("selectField", form.jsSelectField)
|
|
formObj.Set("checkboxField", form.jsCheckboxField)
|
|
formObj.Set("radioField", form.jsRadioField)
|
|
formObj.Set("dateField", form.jsDateField)
|
|
formObj.Set("switchField", form.jsSwitchField)
|
|
formObj.Set("submitButton", form.jsSubmitButton)
|
|
formObj.Set("reset", form.jsReset)
|
|
formObj.Set("setValues", form.jsSetValues)
|
|
|
|
return formObj
|
|
}
|
|
|
|
func (f *Form) jsRender(call goja.FunctionCall) goja.Value {
|
|
if len(call.Arguments) < 1 {
|
|
f.manager.ctx.handleTypeError("render requires a config object")
|
|
}
|
|
|
|
config, ok := call.Argument(0).Export().(map[string]interface{})
|
|
if !ok {
|
|
f.manager.ctx.handleTypeError("render requires a config object")
|
|
}
|
|
|
|
if fields, ok := config["fields"].([]interface{}); ok {
|
|
f.Props.Fields = make([]FormField, 0)
|
|
for _, field := range fields {
|
|
if fieldMap, ok := field.(FormField); ok {
|
|
f.Props.Fields = append(f.Props.Fields, fieldMap)
|
|
}
|
|
}
|
|
}
|
|
|
|
return f.manager.ctx.vm.ToValue(f)
|
|
}
|
|
|
|
func (f *Form) jsOnSubmit(call goja.FunctionCall) goja.Value {
|
|
if len(call.Arguments) < 1 {
|
|
f.manager.ctx.handleTypeError("onSubmit requires a callback function")
|
|
}
|
|
|
|
callback, ok := goja.AssertFunction(call.Argument(0))
|
|
if !ok {
|
|
f.manager.ctx.handleTypeError("onSubmit requires a callback function")
|
|
}
|
|
|
|
eventListener := f.manager.ctx.RegisterEventListener(ClientFormSubmittedEvent)
|
|
|
|
eventListener.SetCallback(func(event *ClientPluginEvent) {
|
|
var payload ClientFormSubmittedEventPayload
|
|
if event.ParsePayloadAs(ClientFormSubmittedEvent, &payload) && payload.FormName == f.Name {
|
|
f.manager.ctx.scheduler.ScheduleAsync(func() error {
|
|
_, err := callback(goja.Undefined(), f.manager.ctx.vm.ToValue(payload.Data))
|
|
return err
|
|
})
|
|
}
|
|
})
|
|
|
|
// go func() {
|
|
// for event := range eventListener.Channel {
|
|
// if event.ParsePayloadAs(ClientFormSubmittedEvent, &payload) {
|
|
// if payload.FormName == f.Name {
|
|
// f.manager.ctx.scheduler.ScheduleAsync(func() error {
|
|
// _, err := callback(goja.Undefined(), f.manager.ctx.vm.ToValue(payload.Data))
|
|
// if err != nil {
|
|
// f.manager.ctx.logger.Error().Err(err).Msg("error running form submit callback")
|
|
// }
|
|
// return err
|
|
// })
|
|
// }
|
|
// }
|
|
// }
|
|
// }()
|
|
|
|
return goja.Undefined()
|
|
}
|
|
|
|
func (f *Form) jsReset(call goja.FunctionCall) goja.Value {
|
|
fieldToReset := ""
|
|
if len(call.Arguments) > 0 {
|
|
var ok bool
|
|
fieldToReset, ok = call.Argument(0).Export().(string)
|
|
if !ok {
|
|
f.manager.ctx.handleTypeError("reset requires a field name")
|
|
}
|
|
}
|
|
|
|
f.manager.ctx.SendEventToClient(ServerFormResetEvent, ServerFormResetEventPayload{
|
|
FormName: f.Name,
|
|
FieldToReset: fieldToReset,
|
|
})
|
|
|
|
return goja.Undefined()
|
|
}
|
|
|
|
func (f *Form) jsSetValues(call goja.FunctionCall) goja.Value {
|
|
if len(call.Arguments) < 1 {
|
|
f.manager.ctx.handleTypeError("setValues requires a config object")
|
|
}
|
|
|
|
props, ok := call.Argument(0).Export().(map[string]interface{})
|
|
if !ok {
|
|
f.manager.ctx.handleTypeError("setValues requires a config object")
|
|
}
|
|
|
|
f.manager.ctx.SendEventToClient(ServerFormSetValuesEvent, ServerFormSetValuesEventPayload{
|
|
FormName: f.Name,
|
|
Data: props,
|
|
})
|
|
|
|
return goja.Undefined()
|
|
}
|
|
|
|
func (f *Form) createField(fieldType string, props map[string]interface{}) goja.Value {
|
|
nameRaw, ok := props["name"]
|
|
name := ""
|
|
if ok {
|
|
name, ok = nameRaw.(string)
|
|
if !ok {
|
|
f.manager.ctx.handleTypeError("name must be a string")
|
|
}
|
|
}
|
|
label := ""
|
|
labelRaw, ok := props["label"]
|
|
if ok {
|
|
label, ok = labelRaw.(string)
|
|
if !ok {
|
|
f.manager.ctx.handleTypeError("label must be a string")
|
|
}
|
|
}
|
|
placeholder, ok := props["placeholder"]
|
|
if ok {
|
|
placeholder, ok = placeholder.(string)
|
|
if !ok {
|
|
f.manager.ctx.handleTypeError("placeholder must be a string")
|
|
}
|
|
}
|
|
field := FormField{
|
|
ID: uuid.New().String(),
|
|
Type: fieldType,
|
|
Name: name,
|
|
Label: label,
|
|
Value: props["value"],
|
|
Options: nil,
|
|
}
|
|
|
|
// Handle options if present
|
|
if options, ok := props["options"].([]interface{}); ok {
|
|
fieldOptions := make([]FormFieldOption, len(options))
|
|
for i, opt := range options {
|
|
if optMap, ok := opt.(map[string]interface{}); ok {
|
|
fieldOptions[i] = FormFieldOption{
|
|
Label: optMap["label"].(string),
|
|
Value: optMap["value"],
|
|
}
|
|
}
|
|
}
|
|
field.Options = fieldOptions
|
|
}
|
|
|
|
return f.manager.ctx.vm.ToValue(field)
|
|
}
|
|
|
|
func (f *Form) jsInputField(call goja.FunctionCall) goja.Value {
|
|
if len(call.Arguments) < 1 {
|
|
f.manager.ctx.handleTypeError("inputField requires a config object")
|
|
}
|
|
|
|
props, ok := call.Argument(0).Export().(map[string]interface{})
|
|
if !ok {
|
|
f.manager.ctx.handleTypeError("inputField requires a config object")
|
|
}
|
|
|
|
return f.createField("input", props)
|
|
}
|
|
|
|
func (f *Form) jsNumberField(call goja.FunctionCall) goja.Value {
|
|
if len(call.Arguments) < 1 {
|
|
f.manager.ctx.handleTypeError("numberField requires a config object")
|
|
}
|
|
|
|
props, ok := call.Argument(0).Export().(map[string]interface{})
|
|
if !ok {
|
|
f.manager.ctx.handleTypeError("numberField requires a config object")
|
|
}
|
|
|
|
return f.createField("number", props)
|
|
}
|
|
|
|
func (f *Form) jsSelectField(call goja.FunctionCall) goja.Value {
|
|
if len(call.Arguments) < 1 {
|
|
f.manager.ctx.handleTypeError("selectField requires a config object")
|
|
}
|
|
|
|
props, ok := call.Argument(0).Export().(map[string]interface{})
|
|
if !ok {
|
|
f.manager.ctx.handleTypeError("selectField requires a config object")
|
|
}
|
|
|
|
return f.createField("select", props)
|
|
}
|
|
|
|
func (f *Form) jsCheckboxField(call goja.FunctionCall) goja.Value {
|
|
if len(call.Arguments) < 1 {
|
|
f.manager.ctx.handleTypeError("checkboxField requires a config object")
|
|
}
|
|
|
|
props, ok := call.Argument(0).Export().(map[string]interface{})
|
|
if !ok {
|
|
f.manager.ctx.handleTypeError("checkboxField requires a config object")
|
|
}
|
|
|
|
return f.createField("checkbox", props)
|
|
}
|
|
|
|
func (f *Form) jsSwitchField(call goja.FunctionCall) goja.Value {
|
|
if len(call.Arguments) < 1 {
|
|
f.manager.ctx.handleTypeError("switchField requires a config object")
|
|
}
|
|
|
|
props, ok := call.Argument(0).Export().(map[string]interface{})
|
|
if !ok {
|
|
f.manager.ctx.handleTypeError("switchField requires a config object")
|
|
}
|
|
|
|
return f.createField("switch", props)
|
|
}
|
|
|
|
func (f *Form) jsRadioField(call goja.FunctionCall) goja.Value {
|
|
if len(call.Arguments) < 1 {
|
|
f.manager.ctx.handleTypeError("radioField requires a config object")
|
|
}
|
|
|
|
props, ok := call.Argument(0).Export().(map[string]interface{})
|
|
if !ok {
|
|
f.manager.ctx.handleTypeError("radioField requires a config object")
|
|
}
|
|
|
|
return f.createField("radio", props)
|
|
}
|
|
|
|
func (f *Form) jsDateField(call goja.FunctionCall) goja.Value {
|
|
if len(call.Arguments) < 1 {
|
|
f.manager.ctx.handleTypeError("dateField requires a config object")
|
|
}
|
|
|
|
props, ok := call.Argument(0).Export().(map[string]interface{})
|
|
if !ok {
|
|
f.manager.ctx.handleTypeError("dateField requires a config object")
|
|
}
|
|
|
|
return f.createField("date", props)
|
|
}
|
|
|
|
func (f *Form) jsSubmitButton(call goja.FunctionCall) goja.Value {
|
|
if len(call.Arguments) < 1 {
|
|
f.manager.ctx.handleTypeError("submitButton requires a config object")
|
|
}
|
|
|
|
props, ok := call.Argument(0).Export().(map[string]interface{})
|
|
if !ok {
|
|
f.manager.ctx.handleTypeError("submitButton requires a config object")
|
|
}
|
|
|
|
return f.createField("submit", props)
|
|
}
|