package mdform import ( "html" "strings" ) const ( InputTypeText = "text" InputTypeNumber = "number" InputTypeEmail = "email" InputTypePhone = "tel" InputTypePassword = "password" InputTypeRadio = "radio" InputTypeCheckbox = "checkbox" ) var ( formAttributes = []string{"exec", "path"} inputAttributes = []string{"checked", "description", "placeholder", "readonly", "required", "type", "value"} textareaAttributes = []string{"placeholder", "readonly", "required", "rows", "value"} selectAttributes = []string{"description", "readonly", "required", "selected"} ) // New creates a new form. func New(attributes ...string) *Form { assertEvenAttributes(attributes) form := &Form{} for i := 0; i < len(attributes); i += 2 { name, value := attributes[i], attributes[i+1] assertIsValidAttribute(name, formAttributes) form.attrs = append(form.attrs, formatAttribute(name, value)) } return form } // Form is a form that can be rendered to Gno-Flavored Markdown. type Form struct { attrs []string fields []string } // Input appends a new input to form fields. // Use `Form.Radio()` or `Form.Checkbox()` to append those types of inputs to the form. // Method panics when appending inputs of type radio or checkbox, or when attributes are not valid. func (f *Form) Input(name string, attributes ...string) *Form { name = strings.TrimSpace(name) if name == "" { panic("form input name is required") } assertEvenAttributes(attributes) attrs := []string{formatAttribute("name", name)} for i := 0; i < len(attributes); i += 2 { name, value := attributes[i], attributes[i+1] if name == "type" { switch value { case InputTypeRadio: panic("use form.Radio() to create inputs of type radio") case InputTypeCheckbox: panic("use form.Checkbox() to create inputs of type checkbox") } } assertIsValidAttribute(name, inputAttributes) attrs = append(attrs, formatAttribute(name, value)) } f.fields = append(f.fields, "") return f } // Radio appends a new input of type radio to form fields. // Method panics when attributes are not valid. func (f *Form) Radio(name, value string, attributes ...string) *Form { return f.appendInputType(InputTypeRadio, name, value, attributes...) } // Checkbox appends a new input of type checkbox to form fields. // Method panics when attributes are not valid. func (f *Form) Checkbox(name, value string, attributes ...string) *Form { return f.appendInputType(InputTypeCheckbox, name, value, attributes...) } // Textarea appends a new textarea to form fields. // Method panics when attributes are not valid. func (f *Form) Textarea(name string, attributes ...string) *Form { name = strings.TrimSpace(name) if name == "" { panic("form textarea name is required") } assertEvenAttributes(attributes) attrs := []string{formatAttribute("name", name)} for i := 0; i < len(attributes); i += 2 { name, value := attributes[i], attributes[i+1] assertIsValidAttribute(name, textareaAttributes) attrs = append(attrs, formatAttribute(name, value)) } f.fields = append(f.fields, "") return f } // Select appends a new select to form fields. // Method panics when attributes are not valid. func (f *Form) Select(name, value string, attributes ...string) *Form { name = strings.TrimSpace(name) if name == "" { panic("form select name is required") } assertEvenAttributes(attributes) attrs := []string{ formatAttribute("name", name), formatAttribute("value", value), } for i := 0; i < len(attributes); i += 2 { name, value := attributes[i], attributes[i+1] assertIsValidAttribute(name, selectAttributes) attrs = append(attrs, formatAttribute(name, value)) } f.fields = append(f.fields, "") return f } // String returns the form as Gno-Flavored Markdown. func (f Form) String() string { fields := strings.Join(f.fields, "\n") attrs := strings.Join(f.attrs, " ") if len(attrs) > 0 { attrs = " " + attrs } return "\n" + fields + "\n\n" } func (f *Form) appendInputType(typeName, name, value string, attributes ...string) *Form { name = strings.TrimSpace(name) if name == "" { panic("form " + typeName + " input name is required") } assertEvenAttributes(attributes) attrs := []string{ formatAttribute("type", typeName), formatAttribute("name", name), formatAttribute("value", value), } for i := 0; i < len(attributes); i += 2 { name, value := attributes[i], attributes[i+1] if name == "type" || name == "value" { continue } assertIsValidAttribute(name, inputAttributes) attrs = append(attrs, formatAttribute(name, value)) } f.fields = append(f.fields, "") return f } func formatAttribute(name, value string) string { return name + `="` + html.EscapeString(value) + `"` } func assertEvenAttributes(attrs []string) { if len(attrs)%2 != 0 { panic("expected an even number of attribute arguments") } } func assertIsValidAttribute(attr string, attrs []string) { for _, name := range attrs { if name == attr { return } } panic("invalid attribute: " + attr) }