summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSteve Manuel <nilslice@gmail.com>2016-09-29 19:08:58 -0400
committerSteve Manuel <nilslice@gmail.com>2016-09-29 19:08:58 -0400
commitce0750cfda5e207a03dacd7bf115cfc6e2ce367f (patch)
tree4483d0d3658757d5cb79ae7c4d1aa07e0ae58706
parentf37e5e3992f4526ab660b43d8aac8b0bafde7ecc (diff)
adding Select and Checkbox elements as well as necessary formatting functions. Needed to modify the valueFromStructField to return the more generic reflect.Value to then be cast to a type as necessary. Additionally, added a modified version of the tagNameFromStruct method for multi-value form inputs like checkbox and the way gorilla/schema package handles them
-rw-r--r--management/editor/elements.go238
1 files changed, 226 insertions, 12 deletions
diff --git a/management/editor/elements.go b/management/editor/elements.go
index 7c1c429..c1c418b 100644
--- a/management/editor/elements.go
+++ b/management/editor/elements.go
@@ -2,9 +2,19 @@ package editor
import (
"bytes"
+ "fmt"
"reflect"
)
+type element struct {
+ TagName string
+ Attrs map[string]string
+ Name string
+ label string
+ data string
+ viewBuf *bytes.Buffer
+}
+
// Input returns the []byte of an <input> HTML element with a label.
// IMPORTANT:
// The `fieldName` argument will cause a panic if it is not exactly the string
@@ -25,20 +35,128 @@ func Textarea(fieldName string, p interface{}, attrs map[string]string) []byte {
return domElement(e)
}
-type element struct {
- TagName string
- Attrs map[string]string
- Name string
- label string
- data string
- viewBuf *bytes.Buffer
+// Select returns the []byte of a <select> HTML element plus internal <options> with a label.
+// IMPORTANT:
+// The `fieldName` argument will cause a panic if it is not exactly the string
+// form of the struct field that this editor input is representing
+func Select(fieldName string, p interface{}, attrs, options map[string]string) []byte {
+ // options are the value attr and the display value, i.e.
+ // <option value="{map key}">{map value}</option>
+
+ // find the field value in p to determine if an option is pre-selected
+ fieldVal := valueFromStructField(fieldName, p).String()
+
+ // may need to alloc a buffer, as we will probably loop through options
+ // and append the []byte from domElement() called for each option
+ sel := newElement("select", attrs["label"], fieldName, p, attrs)
+ var opts []*element
+
+ // provide a call to action for the select element
+ cta := &element{
+ TagName: "option",
+ Attrs: map[string]string{"disabled": "true", "selected": "true"},
+ data: "Select an option...",
+ viewBuf: &bytes.Buffer{},
+ }
+
+ // provide a selection reset (will store empty string in db)
+ reset := &element{
+ TagName: "option",
+ Attrs: map[string]string{"value": ""},
+ data: "None",
+ viewBuf: &bytes.Buffer{},
+ }
+
+ opts = append(opts, cta, reset)
+
+ var val string
+ for k, v := range options {
+ if k == fieldVal {
+ val = "true"
+ } else {
+ val = "false"
+ }
+ opt := &element{
+ TagName: "option",
+ Attrs: map[string]string{"value": k, "selected": val},
+ data: v,
+ viewBuf: &bytes.Buffer{},
+ }
+
+ // if val is false (unselected option), delete the attr for clarity
+ if val == "false" {
+ delete(opt.Attrs, "selected")
+ }
+
+ opts = append(opts, opt)
+ }
+
+ return domElementWithChildren(sel, opts)
+}
+
+// Checkbox returns the []byte of a set of <input type="checkbox"> HTML elements
+// wrapped in a <div> with a label.
+// IMPORTANT:
+// The `fieldName` argument will cause a panic if it is not exactly the string
+// form of the struct field that this editor input is representing
+func Checkbox(fieldName string, p interface{}, attrs, options map[string]string) []byte {
+ // options are the value attr and the display value, i.e.
+ /*
+ <label>
+ {map value}
+ <input type="checkbox" name="{fieldName}" value="{map key}"/>
+ </label>
+ */
+
+ div := newElement("div", attrs["label"], "", p, attrs)
+ var opts []*element
+
+ // get the pre-checked options if this is already an existing post
+ checkedVals := valueFromStructField(fieldName, p) // returns refelct.Value
+ checked := checkedVals.Slice(0, checkedVals.Len()).Interface().([]string) // casts reflect.Value to []string
+
+ i := 0
+ for k, v := range options {
+ // check if k is in the pre-checked values and set to checked
+ var val string
+ for _, x := range checked {
+ if k == x {
+ val = "true"
+ }
+ }
+
+ // create a *element manually using the maodified tagNameFromStructFieldMulti
+ // func since this is for a multi-value name
+ input := &element{
+ TagName: "input",
+ Attrs: map[string]string{
+ "type": "checkbox",
+ "checked": val,
+ "value": k,
+ },
+ Name: tagNameFromStructFieldMulti(fieldName, i, p),
+ label: v,
+ data: "",
+ viewBuf: &bytes.Buffer{},
+ }
+
+ // if checked == false, delete it from input.Attrs for clarity
+ if input.Attrs["checked"] == "" {
+ delete(input.Attrs, "checked")
+ }
+
+ opts = append(opts, input)
+ i++
+ }
+
+ return domElementWithChildrenCheckbox(div, opts)
}
// domElementSelfClose is a special DOM element which is parsed as a
// self-closing tag and thus needs to be created differently
func domElementSelfClose(e *element) []byte {
if e.label != "" {
- e.viewBuf.Write([]byte(`<label>` + e.label + `</label>`))
+ e.viewBuf.Write([]byte(`<label>` + e.label))
}
e.viewBuf.Write([]byte(`<` + e.TagName + ` value="`))
e.viewBuf.Write([]byte(e.data + `" `))
@@ -49,13 +167,38 @@ func domElementSelfClose(e *element) []byte {
e.viewBuf.Write([]byte(` name="` + e.Name + `"`))
e.viewBuf.Write([]byte(` />`))
+ if e.label != "" {
+ e.viewBuf.Write([]byte(`</label>`))
+ }
+
+ return e.viewBuf.Bytes()
+}
+
+// domElementCheckbox is a special DOM element which is parsed as a
+// checkbox input tag and thus needs to be created differently
+func domElementCheckbox(e *element) []byte {
+ if e.label != "" {
+ e.viewBuf.Write([]byte(`<label>`))
+ }
+ e.viewBuf.Write([]byte(`<` + e.TagName + ` `))
+
+ for attr, value := range e.Attrs {
+ e.viewBuf.Write([]byte(attr + `="` + value + `" `))
+ }
+ e.viewBuf.Write([]byte(` name="` + e.Name + `"`))
+ e.viewBuf.Write([]byte(` /> `))
+
+ if e.label != "" {
+ e.viewBuf.Write([]byte(e.label + `</label>`))
+ }
+
return e.viewBuf.Bytes()
}
// domElement creates a DOM element
func domElement(e *element) []byte {
if e.label != "" {
- e.viewBuf.Write([]byte(`<label>` + e.label + `</label>`))
+ e.viewBuf.Write([]byte(`<label>` + e.label))
}
e.viewBuf.Write([]byte(`<` + e.TagName + ` `))
@@ -68,10 +211,72 @@ func domElement(e *element) []byte {
e.viewBuf.Write([]byte(e.data))
e.viewBuf.Write([]byte(`</` + e.TagName + `>`))
+ if e.label != "" {
+ e.viewBuf.Write([]byte(`</label>`))
+ }
+
+ return e.viewBuf.Bytes()
+}
+
+func domElementWithChildren(e *element, children []*element) []byte {
+ if e.label != "" {
+ e.viewBuf.Write([]byte(`<label>` + e.label))
+ }
+ e.viewBuf.Write([]byte(`<` + e.TagName + ` `))
+
+ for attr, value := range e.Attrs {
+ e.viewBuf.Write([]byte(attr + `="` + string(value) + `" `))
+ }
+ e.viewBuf.Write([]byte(` name="` + e.Name + `"`))
+ e.viewBuf.Write([]byte(` >`))
+
+ // loop over children and create domElement for each child
+ for _, child := range children {
+ e.viewBuf.Write(domElement(child))
+ }
+
+ e.viewBuf.Write([]byte(`</` + e.TagName + `>`))
+
+ if e.label != "" {
+ e.viewBuf.Write([]byte(`</label>`))
+ }
+
+ return e.viewBuf.Bytes()
+}
+
+func domElementWithChildrenCheckbox(e *element, children []*element) []byte {
+ if e.label != "" {
+ e.viewBuf.Write([]byte(`<label>` + e.label))
+ }
+ e.viewBuf.Write([]byte(`<` + e.TagName + ` `))
+
+ for attr, value := range e.Attrs {
+ e.viewBuf.Write([]byte(attr + `="` + value + `" `))
+ }
+ e.viewBuf.Write([]byte(` name="` + e.Name + `"`))
+ e.viewBuf.Write([]byte(` >`))
+
+ // loop over children and create domElement for each child
+ for _, child := range children {
+ e.viewBuf.Write(domElementCheckbox(child))
+ }
+
+ e.viewBuf.Write([]byte(`</` + e.TagName + `>`))
+
+ if e.label != "" {
+ e.viewBuf.Write([]byte(`</label>`))
+ }
+
return e.viewBuf.Bytes()
}
func tagNameFromStructField(name string, post interface{}) string {
+ // sometimes elements in these environments will not have a name,
+ // and thus no tag name in the struct which correlates to it.
+ if name == "" {
+ return name
+ }
+
field, ok := reflect.TypeOf(post).Elem().FieldByName(name)
if !ok {
panic("Couldn't get struct field for: " + name + ". Make sure you pass the right field name to editor field elements.")
@@ -85,10 +290,19 @@ func tagNameFromStructField(name string, post interface{}) string {
return tag
}
-func valueFromStructField(name string, post interface{}) string {
+// due to the format in which gorilla/schema expects form names to be when
+// one is associated with multiple values, we need to output the name as such.
+// Ex. 'category.0', 'category.1', 'category.2' and so on.
+func tagNameFromStructFieldMulti(name string, i int, post interface{}) string {
+ tag := tagNameFromStructField(name, post)
+
+ return fmt.Sprintf("%s.%d", tag, i)
+}
+
+func valueFromStructField(name string, post interface{}) reflect.Value {
field := reflect.Indirect(reflect.ValueOf(post)).FieldByName(name)
- return field.String()
+ return field
}
func newElement(tagName, label, fieldName string, p interface{}, attrs map[string]string) *element {
@@ -97,7 +311,7 @@ func newElement(tagName, label, fieldName string, p interface{}, attrs map[strin
Attrs: attrs,
Name: tagNameFromStructField(fieldName, p),
label: label,
- data: valueFromStructField(fieldName, p),
+ data: valueFromStructField(fieldName, p).String(),
viewBuf: &bytes.Buffer{},
}
}