Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

form.gno

3.24 Kb · 96 lines
 1package toolbox
 2
 3import "strings"
 4
 5// Query builds the gnoweb form a read-only tool needs: a text box whose
 6// submission navigates to one of the realm's own Render paths, with the typed
 7// values as query parameters.
 8//
 9// This is the "template of a query" a toolbox page is for. Rendered by gnoweb,
10//
11//	NewQuery("lookup").Field("q", "username or g1 address").String()
12//
13// posts to the realm and redirects to `/r/moul/toolbox/usernames:lookup?q=moul`
14// (`gno.land/pkg/gnoweb/handler_http.go`, `Post`: the `path` attribute travels
15// as the hidden `__gno_path` field and becomes the render args, every other
16// field becomes a query parameter). Nothing is signed and no transaction is
17// built: the result is a `Render` call, which is exactly what
18// `gnokey query vm/qrender` would have done from a terminal.
19//
20// A form whose fields should CALL a function instead is a different element,
21// `<gno-form exec="Fn">`, and it is not what this type builds. A query form is
22// free and a call is not, so the two are deliberately not one constructor with
23// a flag.
24type Query struct {
25	path   string
26	fields []queryField
27}
28
29type queryField struct {
30	name        string
31	placeholder string
32}
33
34// NewQuery starts a form submitting to the Render path args.
35//
36// Pass "" to submit to the realm's root page. Do not include a leading colon
37// or the realm path itself: gnoweb supplies both, and a value that looks like
38// a URL is percent-encoded into one path element rather than followed.
39func NewQuery(path string) *Query {
40	return &Query{path: path}
41}
42
43// Field adds a text input. name is the query parameter the realm will read;
44// placeholder is the grey text in the empty box.
45//
46// A field with an empty name is ignored: gnoweb rejects the whole form for it,
47// and losing one input beats losing the page.
48func (q *Query) Field(name, placeholder string) *Query {
49	if name == "" {
50		return q
51	}
52	q.fields = append(q.fields, queryField{name: name, placeholder: placeholder})
53	return q
54}
55
56// Len is how many fields the form carries.
57func (q *Query) Len() int { return len(q.fields) }
58
59// String renders the form as the markdown-embedded HTML gnoweb parses.
60//
61// A form with no field renders as the empty string rather than as a submit
62// button with nothing to submit.
63func (q *Query) String() string {
64	if len(q.fields) == 0 {
65		return ""
66	}
67	var b strings.Builder
68	b.WriteString(`<gno-form`)
69	if q.path != "" {
70		b.WriteString(` path="` + attr(q.path) + `"`)
71	}
72	b.WriteString(">\n")
73	for _, f := range q.fields {
74		b.WriteString(`<gno-input name="` + attr(f.name) + `" type="text"`)
75		if f.placeholder != "" {
76			b.WriteString(` placeholder="` + attr(f.placeholder) + `"`)
77		}
78		b.WriteString(" />\n")
79	}
80	b.WriteString("</gno-form>\n")
81	return b.String()
82}
83
84// attr escapes a value for an HTML attribute in double quotes.
85//
86// gnoweb parses these with a real HTML tokenizer, so an unescaped quote does
87// not merely look wrong: it ends the attribute and whatever follows becomes
88// further attributes of the same tag. Ampersand first, or the escapes of the
89// later replacements would themselves be escaped.
90func attr(s string) string {
91	s = strings.ReplaceAll(s, "&", "&amp;")
92	s = strings.ReplaceAll(s, `"`, "&quot;")
93	s = strings.ReplaceAll(s, "<", "&lt;")
94	s = strings.ReplaceAll(s, ">", "&gt;")
95	return s
96}