A form/doc.go => form/doc.go +6 -0
@@ 0,0 1,6 @@
+// Copyright 2017 Sam Whited.
+// Use of this source code is governed by the BSD 2-clause license that can be
+// found in the LICENSE file.
+
+// The form package is an implementation of XEP-0004: Data Forms.
+package form // import "mellium.im/xmpp/form"
A form/fields.go => form/fields.go +139 -0
@@ 0,0 1,139 @@
+// Copyright 2017 Sam Whited.
+// Use of this source code is governed by the BSD 2-clause license that can be
+// found in the LICENSE file.
+
+package form
+
+import (
+ "encoding/xml"
+)
+
+// A field represents a data field that may be added to a form.
+type field struct {
+ XMLName xml.Name `xml:"jabber:x:data field"`
+ Typ string `xml:"type,attr"`
+ Var string `xml:"var,attr,omitempty"`
+ Label string `xml:"label,attr,omitempty"`
+ Desc string `xml:"desc,omitempty"`
+ Value []string `xml:"value,omitempty"`
+ Required struct{} `xml:"required,omitempty"`
+ Option []fieldopt `xml:"option,omitempty"`
+}
+
+type fieldopt struct {
+ XMLName xml.Name `xml:"jabber:x:data option"`
+ Value string `xml:"value,omitempty"`
+}
+
+// Boolean fields enable an entity to gather or provide an either-or choice
+// between two options.
+func Boolean(varName string, o ...FieldOption) Option {
+ return func(data *Data) {
+ f := field{
+ Typ: "boolean",
+ Var: varName,
+ }
+ getFieldOpts(&f, o...)
+ data.children = append(data.children, f)
+ }
+}
+
+// Hidden fields are not shown by the form-submitting entity, but instead are
+// returned, generally unmodified, with the form.
+func Hidden(varName string, o ...FieldOption) Option {
+ return func(data *Data) {
+ f := field{
+ Typ: "hidden",
+ Var: varName,
+ }
+ getFieldOpts(&f, o...)
+ data.children = append(data.children, f)
+ }
+}
+
+// JIDMulti enables an entity to gather or provide multiple Jabber IDs.
+func JIDMulti(varName string, o ...FieldOption) Option {
+ return func(data *Data) {
+ f := field{
+ Typ: "jid-multi",
+ Var: varName,
+ }
+ getFieldOpts(&f, o...)
+ data.children = append(data.children, f)
+ }
+}
+
+// JID enables an entity to gather or provide a Jabber ID.
+func JID(varName string, o ...FieldOption) Option {
+ return func(data *Data) {
+ f := field{
+ Typ: "jid-single",
+ Var: varName,
+ }
+ getFieldOpts(&f, o...)
+ data.children = append(data.children, f)
+ }
+}
+
+// ListMulti enables an entity to gather or provide one or more entries from a
+// list.
+func ListMulti(varName string, o ...FieldOption) Option {
+ return func(data *Data) {
+ f := field{
+ Typ: "list-multi",
+ Var: varName,
+ }
+ getFieldOpts(&f, o...)
+ data.children = append(data.children, f)
+ }
+}
+
+// ListSingle enables an entity to gather or provide a single entry from a list.
+func ListSingle(varName string, o ...FieldOption) Option {
+ return func(data *Data) {
+ f := field{
+ Typ: "list-single",
+ Var: varName,
+ }
+ getFieldOpts(&f, o...)
+ data.children = append(data.children, f)
+ }
+}
+
+// TextMulti enables an entity to gather or provide multiple lines of text.
+func TextMulti(varName string, o ...FieldOption) Option {
+ return func(data *Data) {
+ f := field{
+ Typ: "text-multi",
+ Var: varName,
+ }
+ getFieldOpts(&f, o...)
+ data.children = append(data.children, f)
+ }
+}
+
+// TextPrivate enables an entity to gather or provide a line of text that should
+// be obscured in the submitting entities interface (eg. with multiple
+// asterisks).
+func TextPrivate(varName string, o ...FieldOption) Option {
+ return func(data *Data) {
+ f := field{
+ Typ: "text-private",
+ Var: varName,
+ }
+ getFieldOpts(&f, o...)
+ data.children = append(data.children, f)
+ }
+}
+
+// TextSingle enables an entity to gather or provide a line of text.
+func TextSingle(varName string, o ...FieldOption) Option {
+ return func(data *Data) {
+ f := field{
+ Typ: "text-single",
+ Var: varName,
+ }
+ getFieldOpts(&f, o...)
+ data.children = append(data.children, f)
+ }
+}
A form/form.go => form/form.go +66 -0
@@ 0,0 1,66 @@
+// Copyright 2017 Sam Whited.
+// Use of this source code is governed by the BSD 2-clause license that can be
+// found in the LICENSE file.
+
+package form
+
+import (
+ "encoding/xml"
+)
+
+var (
+ formName = xml.Name{Space: "jabber:x:data", Local: "x"}
+)
+
+// Data represents a data form.
+type Data struct {
+ title struct {
+ XMLName xml.Name `xml:"title"`
+ Text string `xml:",chardata"`
+ }
+ typ string
+ children []interface{}
+}
+
+// MarshalXML satisfies the xml.Marshaler interface for *Data.
+func (d *Data) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) {
+ start = xml.StartElement{Name: formName}
+ start.Attr = append(start.Attr, xml.Attr{
+ Name: xml.Name{Local: "type"},
+ Value: d.typ,
+ })
+ if err = e.EncodeToken(start); err != nil {
+ return
+ }
+
+ // Encode the title.
+ if d.title.Text != "" {
+ if err = e.Encode(d.title); err != nil {
+ return
+ }
+ }
+
+ for _, c := range d.children {
+ if err = e.Encode(c); err != nil {
+ return
+ }
+ }
+
+ // Encode the end element of the form.
+ if err = e.EncodeToken(start.End()); err != nil {
+ return
+ }
+ return e.Flush()
+}
+
+type instructions struct {
+ XMLName xml.Name `xml:"instructions"`
+ Text string `xml:",chardata"`
+}
+
+// New builds a new data form from the provided options.
+func New(o ...Option) *Data {
+ form := &Data{typ: "form"}
+ getOpts(form, o...)
+ return form
+}
A form/options.go => form/options.go +86 -0
@@ 0,0 1,86 @@
+// Copyright 2017 Sam Whited.
+// Use of this source code is governed by the BSD 2-clause license that can be
+// found in the LICENSE file.
+
+package form
+
+// An Option is used to define the behavior and appearance of a data form.
+type Option func(*Data)
+
+// Title sets a form's title.
+func Title(s string) Option {
+ return func(data *Data) {
+ data.title.Text = s
+ }
+}
+
+// Instructions adds new textual instructions to the form.
+// Multiple uses of the option result in multiple sets of instructions.
+func Instructions(s string) Option {
+ return func(data *Data) {
+ data.children = append(data.children, instructions{Text: s})
+ }
+}
+
+func getOpts(data *Data, o ...Option) {
+ for _, f := range o {
+ f(data)
+ }
+ return
+}
+
+// A FieldOption is used to define the behavior and appearance of a form field.
+type FieldOption func(*field)
+
+var (
+ // Required flags the field as required in order for the form to be considered
+ // valid.
+ Required = required
+)
+
+var (
+ required FieldOption = func(f *field) {
+ f.Required = struct{}{}
+ }
+)
+
+// Desc provides a natural-language description of the field, intended for
+// presentation in a user-agent (e.g., as a "tool-tip", help button, or
+// explanatory text provided near the field).
+// Desc should not contain newlines (the \n and \r characters), since layout is
+// the responsibility of a user agent.
+// However, it does nothing to prevent them from being added.
+func Desc(s string) FieldOption {
+ return func(f *field) {
+ f.Desc = s
+ }
+}
+
+// Value defines the default value for the field (according to the
+// form-processing entity) in a data form of type "form", the data provided by a
+// form-submitting entity in a data form of type "submit", or a data result in a
+// data form of type "result".
+// Fields of type ListMulti, JidMulti, TextMulti, and Hidden may contain more
+// than one Value; all other field types will only use the first Value.
+func Value(s string) FieldOption {
+ return func(f *field) {
+ f.Value = append(f.Value, s)
+ }
+}
+
+// ListOption is one of the values in a list.
+// It has no effect on any non-list field type.
+func ListOption(s string) FieldOption {
+ return func(f *field) {
+ f.Option = append(f.Option, fieldopt{
+ Value: s,
+ })
+ }
+}
+
+func getFieldOpts(f *field, o ...FieldOption) {
+ for _, opt := range o {
+ opt(f)
+ }
+ return
+}