diff options
Diffstat (limited to 'management')
-rw-r--r-- | management/editor/editor.go | 41 | ||||
-rw-r--r-- | management/editor/elements.go | 117 | ||||
-rw-r--r-- | management/manager/manager.go | 46 |
3 files changed, 204 insertions, 0 deletions
diff --git a/management/editor/editor.go b/management/editor/editor.go new file mode 100644 index 0000000..24b514e --- /dev/null +++ b/management/editor/editor.go @@ -0,0 +1,41 @@ +// Package editor enables users to create edit views from their content +// structs so that admins can manage content +package editor + +import "bytes" + +// Editable ensures data is editable +type Editable interface { + ContentID() int + Editor() *Editor + MarshalEditor() ([]byte, error) +} + +// Editor is a view containing fields to manage content +type Editor struct { + ViewBuf *bytes.Buffer +} + +// Field is used to create the editable view for a field +// within a particular content struct +type Field struct { + View []byte +} + +// New takes editable content and any number of Field funcs to describe the edit +// page for any content struct added by a user +func New(post Editable, fields ...Field) ([]byte, error) { + editor := post.Editor() + + editor.ViewBuf = &bytes.Buffer{} + + for _, f := range fields { + addFieldToEditorView(editor, f) + } + + return editor.ViewBuf.Bytes(), nil +} + +func addFieldToEditorView(e *Editor, f Field) { + e.ViewBuf.Write(f.View) +} diff --git a/management/editor/elements.go b/management/editor/elements.go new file mode 100644 index 0000000..59d74f0 --- /dev/null +++ b/management/editor/elements.go @@ -0,0 +1,117 @@ +package editor + +import ( + "bytes" + "reflect" +) + +// 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 +// form of the struct field that this editor input is representing +func Input(fieldName string, p interface{}, attrs map[string]string) []byte { + var wrapInLabel = true + label, found := attrs["label"] + if !found { + wrapInLabel = false + label = "" + } + + e := newElement("input", label, fieldName, p, attrs) + + return domElementSelfClose(e, wrapInLabel) +} + +// Textarea returns the []byte of a <textarea> HTML element 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 Textarea(fieldName string, p interface{}, attrs map[string]string) []byte { + var wrapInLabel = true + label, found := attrs["label"] + if !found { + wrapInLabel = false + label = "" + } + + e := newElement("textarea", label, fieldName, p, attrs) + + return domElement(e, wrapInLabel) +} + +type element struct { + TagName string + Attrs map[string]string + Name string + label string + data []byte + viewBuf *bytes.Buffer +} + +// 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, wrapInLabel bool) []byte { + if wrapInLabel { + e.viewBuf.Write([]byte(`<label>` + e.label + `</label>`)) + } + e.viewBuf.Write([]byte(`<` + e.TagName + ` value="`)) + e.viewBuf.Write(append(e.data, []byte(`" `)...)) + + for attr, value := range e.Attrs { + e.viewBuf.Write([]byte(attr + `="` + string(value) + `"`)) + } + e.viewBuf.Write([]byte(` name="` + e.Name + `"`)) + e.viewBuf.Write([]byte(` />`)) + + return e.viewBuf.Bytes() +} + +// domElement creates a DOM element +func domElement(e *element, wrapInLabel bool) []byte { + if wrapInLabel { + e.viewBuf.Write([]byte(`<label>` + e.label + `</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(` >`)) + + e.viewBuf.Write([]byte(e.data)) + e.viewBuf.Write([]byte(`</` + e.TagName + `>`)) + + return e.viewBuf.Bytes() +} + +func tagNameFromStructField(name string, post interface{}) string { + 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.") + } + + tag, ok := field.Tag.Lookup("json") + if !ok { + panic("Couldn't get json struct tag for: " + name + ". Struct fields for content types must have 'json' tags.") + } + + return tag +} + +func valueFromStructField(name string, post interface{}) []byte { + field := reflect.Indirect(reflect.ValueOf(post)).FieldByName(name) + + return field.Bytes() +} + +func newElement(tagName, label, fieldName string, p interface{}, attrs map[string]string) *element { + return &element{ + TagName: tagName, + Attrs: attrs, + Name: tagNameFromStructField(fieldName, p), + label: label, + data: valueFromStructField(fieldName, p), + viewBuf: &bytes.Buffer{}, + } +} diff --git a/management/manager/manager.go b/management/manager/manager.go new file mode 100644 index 0000000..83ed63a --- /dev/null +++ b/management/manager/manager.go @@ -0,0 +1,46 @@ +package manager + +import ( + "bytes" + "fmt" + "html/template" + "reflect" + + "github.com/nilslice/cms/management/editor" +) + +var html = ` +<div class="manager"> + <form method="post" action="/admin/edit?type={{.Kind}}&contentId={{.ID}}"> + {{.Editor}} + <input type="submit" value="Save"/> + </form> +</div> +` + +type form struct { + ID int + Kind string + Editor template.HTML +} + +// Manage ... +func Manage(e editor.Editable) ([]byte, error) { + v, err := e.MarshalEditor() + if err != nil { + return nil, fmt.Errorf("Couldn't marshal editor for content %T. %s", e, err.Error()) + } + + f := form{ + ID: e.ContentID(), + Kind: reflect.TypeOf(e).Name(), + Editor: template.HTML(v), + } + + // execute html template into buffer for func return val + buf := &bytes.Buffer{} + tmpl := template.Must(template.New("manager").Parse(html)) + tmpl.Execute(buf, f) + + return buf.Bytes(), nil +} |