mdform.gno
5.15 Kb ยท 198 lines
1package mdform
2
3import (
4 "html"
5 "strings"
6)
7
8const (
9 InputTypeText = "text"
10 InputTypeNumber = "number"
11 InputTypeEmail = "email"
12 InputTypePhone = "tel"
13 InputTypePassword = "password"
14 InputTypeRadio = "radio"
15 InputTypeCheckbox = "checkbox"
16)
17
18var (
19 formAttributes = []string{"exec", "path"}
20 inputAttributes = []string{"checked", "description", "placeholder", "readonly", "required", "type", "value"}
21 textareaAttributes = []string{"placeholder", "readonly", "required", "rows", "value"}
22 selectAttributes = []string{"description", "readonly", "required", "selected"}
23)
24
25// New creates a new form.
26func New(attributes ...string) *Form {
27 assertEvenAttributes(attributes)
28
29 form := &Form{}
30 for i := 0; i < len(attributes); i += 2 {
31 name, value := attributes[i], attributes[i+1]
32
33 assertIsValidAttribute(name, formAttributes)
34
35 form.attrs = append(form.attrs, formatAttribute(name, value))
36 }
37 return form
38}
39
40// Form is a form that can be rendered to Gno-Flavored Markdown.
41type Form struct {
42 attrs []string
43 fields []string
44}
45
46// Input appends a new input to form fields.
47// Use `Form.Radio()` or `Form.Checkbox()` to append those types of inputs to the form.
48// Method panics when appending inputs of type radio or checkbox, or when attributes are not valid.
49func (f *Form) Input(name string, attributes ...string) *Form {
50 name = strings.TrimSpace(name)
51 if name == "" {
52 panic("form input name is required")
53 }
54
55 assertEvenAttributes(attributes)
56
57 attrs := []string{formatAttribute("name", name)}
58 for i := 0; i < len(attributes); i += 2 {
59 name, value := attributes[i], attributes[i+1]
60 if name == "type" {
61 switch value {
62 case InputTypeRadio:
63 panic("use form.Radio() to create inputs of type radio")
64 case InputTypeCheckbox:
65 panic("use form.Checkbox() to create inputs of type checkbox")
66 }
67 }
68
69 assertIsValidAttribute(name, inputAttributes)
70
71 attrs = append(attrs, formatAttribute(name, value))
72 }
73
74 f.fields = append(f.fields, "<gno-input "+strings.Join(attrs, " ")+" />")
75 return f
76}
77
78// Radio appends a new input of type radio to form fields.
79// Method panics when attributes are not valid.
80func (f *Form) Radio(name, value string, attributes ...string) *Form {
81 return f.appendInputType(InputTypeRadio, name, value, attributes...)
82}
83
84// Checkbox appends a new input of type checkbox to form fields.
85// Method panics when attributes are not valid.
86func (f *Form) Checkbox(name, value string, attributes ...string) *Form {
87 return f.appendInputType(InputTypeCheckbox, name, value, attributes...)
88}
89
90// Textarea appends a new textarea to form fields.
91// Method panics when attributes are not valid.
92func (f *Form) Textarea(name string, attributes ...string) *Form {
93 name = strings.TrimSpace(name)
94 if name == "" {
95 panic("form textarea name is required")
96 }
97
98 assertEvenAttributes(attributes)
99
100 attrs := []string{formatAttribute("name", name)}
101 for i := 0; i < len(attributes); i += 2 {
102 name, value := attributes[i], attributes[i+1]
103
104 assertIsValidAttribute(name, textareaAttributes)
105
106 attrs = append(attrs, formatAttribute(name, value))
107 }
108
109 f.fields = append(f.fields, "<gno-textarea "+strings.Join(attrs, " ")+" />")
110 return f
111}
112
113// Select appends a new select to form fields.
114// Method panics when attributes are not valid.
115func (f *Form) Select(name, value string, attributes ...string) *Form {
116 name = strings.TrimSpace(name)
117 if name == "" {
118 panic("form select name is required")
119 }
120
121 assertEvenAttributes(attributes)
122
123 attrs := []string{
124 formatAttribute("name", name),
125 formatAttribute("value", value),
126 }
127
128 for i := 0; i < len(attributes); i += 2 {
129 name, value := attributes[i], attributes[i+1]
130
131 assertIsValidAttribute(name, selectAttributes)
132
133 attrs = append(attrs, formatAttribute(name, value))
134 }
135
136 f.fields = append(f.fields, "<gno-select "+strings.Join(attrs, " ")+" />")
137 return f
138}
139
140// String returns the form as Gno-Flavored Markdown.
141func (f Form) String() string {
142 fields := strings.Join(f.fields, "\n")
143 attrs := strings.Join(f.attrs, " ")
144 if len(attrs) > 0 {
145 attrs = " " + attrs
146 }
147
148 return "<gno-form" + attrs + ">\n" + fields + "\n</gno-form>\n"
149}
150
151func (f *Form) appendInputType(typeName, name, value string, attributes ...string) *Form {
152 name = strings.TrimSpace(name)
153 if name == "" {
154 panic("form " + typeName + " input name is required")
155 }
156
157 assertEvenAttributes(attributes)
158
159 attrs := []string{
160 formatAttribute("type", typeName),
161 formatAttribute("name", name),
162 formatAttribute("value", value),
163 }
164
165 for i := 0; i < len(attributes); i += 2 {
166 name, value := attributes[i], attributes[i+1]
167 if name == "type" || name == "value" {
168 continue
169 }
170
171 assertIsValidAttribute(name, inputAttributes)
172
173 attrs = append(attrs, formatAttribute(name, value))
174 }
175
176 f.fields = append(f.fields, "<gno-input "+strings.Join(attrs, " ")+" />")
177 return f
178}
179
180func formatAttribute(name, value string) string {
181 return name + `="` + html.EscapeString(value) + `"`
182}
183
184func assertEvenAttributes(attrs []string) {
185 if len(attrs)%2 != 0 {
186 panic("expected an even number of attribute arguments")
187 }
188}
189
190func assertIsValidAttribute(attr string, attrs []string) {
191 for _, name := range attrs {
192 if name == attr {
193 return
194 }
195 }
196
197 panic("invalid attribute: " + attr)
198}