package builder import ( "fmt" "github.com/acaloiaro/frm" "github.com/acaloiaro/frm/types" "github.com/acaloiaro/frm/ui" "github.com/acaloiaro/frm/ui/collector" "github.com/acaloiaro/frm/ui/fields" "github.com/acaloiaro/frm/ui/selector" "github.com/google/uuid" ) // Application DOM events const ( // FieldsFormEvent is the event triggered when any of a form's fields have been updated in the UI. When triggered, // the fields form pushes updates from the UI to the draft form on the backend. FieldsFormUpdateEvent = "fields-form-updated" // FormSettingsEvent is the event triggered when any of a form's settings have been updated in the UI. When triggered, // the fields form pushes updates from the UI to the draft form on the backend. FormSettingsUpdateEvent = "form-settings-updated" // LogicConfiguratorTargetFieldSelected is the event triggered when a form field is selected as a the target for field logic LogicConfiguratorTargetFieldSelected = "logic-configurator-target-field-selected" ) // Logic field group form field names const ( // FieldLogicChosenFieldID is the name of a HTML form field. The value of the form field with this name represents the field that was chosen as the target for field-specific logic. // // TODO: provide an example FieldLogicTargetFieldID = "target_field_id" // FieldLogicChosenField is the name of a HTML form field. The value of the form field represents the boolean comparator to be used to evaluate the chosen field against its field value. // // TODO: provide an example FieldLogicComparator = "comparator" // FieldLogicChosenFieldValue is the name of a HTML Form field. The value of the form field represents the value that the target field's value is compared against when deciding whether or not to display a field in the UI. // // TODO: provide an example FieldLogicTargetFieldValue = "target_field_value" ) // Field groups within the the form field configuration screen const ( FieldGroupLogic = "logic" ) // Builder is the primary form builder UI, surrounded by the app chrome templ Builder(form frm.Form) { @ui.App("Form builder") {
@FormBuilderNav(form)
@builderColumnLeft(form) @collector.FormPreview(collector.ViewerArgs{Preview: true, Form: form}) @builderColumnRight(form)
} } // FormBuilderNavTitle renders the title of the form in the nav bar. This is a separate template so that the title can be updated when settings change, without updating, and losing the state of, the rest of the nav bar templ FormBuilderNavTitle(form frm.Form) {

{ form.Name }

} // FormBuilderNav is the top-of-the-page navigation bar templ FormBuilderNav(form frm.Form) {
@ui.MutedButton(ui.ButtonArgs{Label: "Build", Classes: []string{"cursor-pointer tab-active [--fallback-p:white] [--fallback-pc:black] place-self-center"}}, templ.Attributes{ "role": "tab", "tabindex": 0, "_": "on click take .tab-active from .tab-active then add .hidden to .active-section then take .active-section for #form-fields then remove .hidden from #form-fields", }) @ui.MutedButton(ui.ButtonArgs{Label: "Settings", Classes: []string{"cursor-pointer [--fallback-p:white] [--fallback-pc:black] place-self-center"}}, templ.Attributes{ "role": "tab", "tabindex": 2, "_": "on click take .tab-active from .tab-active then add .hidden to .active-section take .active-section for #settings-main then remove .hidden from #settings-main", })
@FormBuilderNavTitle(form)
} // FormSettings is the UI for configuring form-level settings. templ FormSettings(form frm.Form) { } // builderColumnLeft is the left-hand panel of the form builder UI templ builderColumnLeft(form frm.Form) {
@FormFields(form) @FormSettings(form)
} // FormFields lists a form's fields as a sortable list, that when re-sorted, updates the fields' sort order in the form templ FormFields(form frm.Form) {
@FormFieldsForm(form)
} // FormFieldsForm is the list of fields on the left-hand side of the screen that displays a sorted list of form fields templ FormFieldsForm(form frm.Form) {
@ui.Button(ui.ButtonArgs{ Label: "Add field", Classes: []string{"flex-grow", "justify-center", "uppercase"}, }, templ.Attributes{ "_": "on click toggle .hidden on .active-configurator then take .active-configurator from .active-configurator for #configure-add-field then remove .hidden from #configure-add-field", }) { @ui.HeroIcon("solid", "plus") }
for _, field := range fields.SortFields(form.Fields) {

{ field.Label }

if field.Required {
}
@ui.HeroIcon("solid", "bars-3")
}
} // builerColumnRight is the right-hand panel of the form build UI, used for adding and configuring fields templ builderColumnRight(form frm.Form) {
@FormFieldConfigurator(form)
} // FormFieldConfigurator is the view on the right-hand side of the screen for configuring individual fields templ FormFieldConfigurator(form frm.Form) {
for _, field := range fields.SortFields(form.Fields) { }
} // fieldSettingsConfiguration configures the settings for a specific field // FIELD_TYPES: field types may be added/modified/removed below templ fieldSettingsConfiguration(form frm.Form, field types.FormField) {
@fields.RequiredFieldIndicator()
switch field.Type { case types.FormFieldTypeTextSingle, types.FormFieldTypeTextMultiple:
case types.FormFieldTypeMultiSelect, types.FormFieldTypeSingleSelect, types.FormFieldTypeSingleChoice:
@selector.Selector(selector.SelectArgs{ ID: fields.FieldName(field, "", "options"), Name: fields.FieldName(field, "", "options"), Placeholder: "Add, remove, or create new options", Multiple: true, EditItems: true, Options: fields.ToSelectorOpts(field.Options, true), SelectionChangeEvent: FieldsFormUpdateEvent, }) if field.Type == types.FormFieldTypeSingleChoice {
@selector.Selector(selector.SelectArgs{ ID: fields.FieldName(field, "", "option_labels"), Name: fields.FieldName(field, "", "option_labels"), Placeholder: "Create labels for your options", Multiple: true, EditItems: true, Options: fields.ToSelectorOptsStr(field.OptionLabels, true), SelectionChangeEvent: FieldsFormUpdateEvent, }) } }
's checked to false end then trigger '%s'`, fields.FieldName(field, "","hidden"), FieldsFormUpdateEvent) } />
's checked to false end then trigger '%s'`, fields.FieldName(field, "","required"), FieldsFormUpdateEvent) } />
@ui.Button(ui.ButtonArgs{Type: "button", Label: "Delete field"}, templ.Attributes{ "data-hx-delete": ui.FormUrl[string](ctx, form, fmt.Sprintf("/fields/%s", field.ID)), "data-hx-trigger": "click", "data-hx-confirm": "Are you sure?", })
} templ fieldLogicConfiguration(form frm.Form, field types.FormField) { } // comparatorOptionsFor returns the comparators available for a logic field given its Type func comparatorOptionsFor(field types.FormField) (options selector.FieldOptions) { switch field.Type { default: options = selector.FieldOptions{ selector.Option{ Value: fmt.Sprint(types.FieldLogicComparatorContains), Label: "Contains", Selected: field.Logic != nil && field.Logic.TriggerComparator == types.FieldLogicComparatorContains, }, selector.Option{ Value: fmt.Sprint(types.FieldLogicComparatorEqual), Label: "Equal to =", Selected: field.Logic != nil && field.Logic.TriggerComparator == types.FieldLogicComparatorEqual, }, selector.Option{ Value: fmt.Sprint(types.FieldLogicComparatorNot), Label: "NOT", Selected: field.Logic != nil && field.Logic.TriggerComparator == types.FieldLogicComparatorNot, }, } } return } // LogicConfiguratorStepThree returns HTML input elments appropriate for choosing values for `targetField` in the logic configurator. // // field: the field being configured // targetField: the target field chosen as the logic target templ LogicConfiguratorStepThree(form frm.Form, field types.FormField, targetField types.FormField) { switch targetField.Type { case types.FormFieldTypeMultiSelect, types.FormFieldTypeSingleSelect, types.FormFieldTypeSingleChoice: @selector.Selector(selector.SelectArgs{ ID: fmt.Sprintf("%s-logic-chosen-field-value", field.ID.String()), Label: "", Name: fields.FieldName(field, FieldGroupLogic, FieldLogicTargetFieldValue), Options: ui.FieldOptionsAsSelectorOptions(form, targetField), Placeholder: "Choose a value", SelectionChangeEvent: FieldsFormUpdateEvent, }) case types.FormFieldTypeTextSingle, types.FormFieldTypeTextMultiple: 0 { value={ field.Logic.TriggerValues[0] } } _={ fmt.Sprintf("on keyup debounced at 600ms trigger '%s'", FieldsFormUpdateEvent) } /> } }