Skip to content

Instantly share code, notes, and snippets.

@iansmith
Created November 8, 2019 17:47
Show Gist options
  • Save iansmith/528c10f2d89e2b87bdf782a445216e09 to your computer and use it in GitHub Desktop.
Save iansmith/528c10f2d89e2b87bdf782a445216e09 to your computer and use it in GitHub Desktop.
one file for a HTML generation tool from go -- also has some support for bootstrap 4 classes
// +build wasm
//
// The value returned by a tag name function is actually a function (closure) that can be
// instantiated by invoking it with a particular document.
//
// The params to a tag name function can be other tag name functions, Text() to
// set innerHtml, Attribute{attrName:"value"}, Events{eventName:handlerFunc},
// or Clazz (css classes) names. If you pass something else, the tag name
// functions panic.
//
// Example to create a bootstrap alert div, appropriately decorated, and invoke
// the instantiation of it on a doc.
//
// <div class="alert alert-warning" role="alert" >
// This is a primary alert—check it out!
// <button type="button" class="close" data-dismiss="alert" aria-label="Close">
// <span aria-hidden="true">&times;</span>
// </button>
// </div>
// func BootstrapAlert(doc dom.Document, alertText string) *dom.BasicHTMLElement {
//
// //you can also do event handlers with Events{Click:functionName}
// return Div(Alert, AlertWarning, AlertDismissable, Fade, Show, Attributes{Role: "alert"}, Text(alertText),
// Button(Close, Attributes{Type: "button", ArialLabel: "Close", DataDismiss: "alert"},
// Span(Attributes{AriaHidden: "true"}, Text("\u00D7")),
// ),
// )(doc)
//
// }
package web
import (
"fmt"
"log"
"strings"
"syscall/js"
dom "honnef.co/go/js/dom/v2"
)
type Frob interface {
Apply(*dom.BasicHTMLElement)
}
type Elem func(dom.Document) *dom.BasicHTMLElement
func (e Elem) Apply(*dom.BasicHTMLElement) {
panic("should not call Apply on an Element!")
}
type AttributeName string
type Attributes map[AttributeName]string
func (a Attributes) Apply(elem *dom.BasicHTMLElement) {
for k, v := range a {
elem.SetAttribute(string(k), v)
}
}
type EventName string
type Events map[EventName]interface{}
const CaptureSuffix = "-capture"
func (e Events) Apply(elem *dom.BasicHTMLElement) {
outbound := make(map[EventName]js.Func)
for k, v := range e {
fn, ok := v.(func(dom.Event))
if !ok {
log.Printf("function of %s cannot be converted to func(dom.Event): %T", v)
return
}
s := string(k)
capture := false
if strings.HasSuffix(strings.ToLower(s), CaptureSuffix) {
s = strings.TrimSuffix(s, CaptureSuffix)
capture = true
}
f := elem.AddEventListener(s, capture, fn)
outbound[k] = f
}
for k, newFunc := range outbound {
e[k] = newFunc
}
}
type Clazz string
func (c Clazz) Apply(elem *dom.BasicHTMLElement) {
classes := elem.Class()
if !classes.Contains(string(c)) {
classes.Add(string(c))
}
}
type Text string
func (t Text) Apply(elem *dom.BasicHTMLElement) {
elem.SetInnerHTML(string(t))
}
// Div is <div> tag. Frobs can be any Frob but anything else panics.
func Div(frobs ...Frob) Elem {
return func(doc dom.Document) *dom.BasicHTMLElement {
div := doc.CreateElement("div").(*dom.HTMLDivElement)
e := div.BasicHTMLElement
return processFrobs(doc, e, frobs...)
}
}
func processFrobs(doc dom.Document, e *dom.BasicHTMLElement, frobs ...Frob) *dom.BasicHTMLElement {
for _, f := range frobs {
if f == nil {
continue
}
child, ok := f.(Elem)
if ok {
if child == nil {
continue
}
e.AppendChild(child(doc))
} else {
f.Apply(e)
}
}
return e
}
// Button is <button> tag. Frobs can be any Frob but anything else panics.
func Button(frobs ...Frob) Elem {
return func(doc dom.Document) *dom.BasicHTMLElement {
button := doc.CreateElement("button").(*dom.HTMLButtonElement)
e := button.BasicHTMLElement
return processFrobs(doc, e, frobs...)
}
}
// Span is <span> tag. Frobs can be any Frob but anything else panics.
func Span(frobs ...Frob) Elem {
return func(doc dom.Document) *dom.BasicHTMLElement {
span := doc.CreateElement("span").(*dom.HTMLSpanElement)
e := span.BasicHTMLElement
return processFrobs(doc, e, frobs...)
}
}
// Form is <form> tag. Frobs can be any Frob but anything else panics.
func Form(frobs ...Frob) Elem {
return func(doc dom.Document) *dom.BasicHTMLElement {
span := doc.CreateElement("form").(*dom.HTMLFormElement)
e := span.BasicHTMLElement
return processFrobs(doc, e, frobs...)
}
}
// TextArea is <textarea> tag. Frobs can be any Frob but anything else panics.
func TextArea(frobs ...Frob) Elem {
return func(doc dom.Document) *dom.BasicHTMLElement {
span := doc.CreateElement("textarea").(*dom.HTMLTextAreaElement)
e := span.BasicHTMLElement
return processFrobs(doc, e, frobs...)
}
}
// P is <p> tag. Frobs can be any Frob but anything else panics.
func P(frobs ...Frob) Elem {
return func(doc dom.Document) *dom.BasicHTMLElement {
p := doc.CreateElement("p").(*dom.HTMLParagraphElement)
e := p.BasicHTMLElement
return processFrobs(doc, e, frobs...)
}
}
// I is <i> tag. Frobs can be any Frob but anything else panics.
func I(frobs ...Frob) Elem {
return func(doc dom.Document) *dom.BasicHTMLElement {
i := doc.CreateElement("i").(*dom.BasicHTMLElement)
return processFrobs(doc, i, frobs...)
}
}
// A is <a> tag. Frobs can be any Frob but anything else panics.
func A(frobs ...Frob) Elem {
return func(doc dom.Document) *dom.BasicHTMLElement {
a := doc.CreateElement("a").(*dom.HTMLAnchorElement)
e := a.BasicHTMLElement
return processFrobs(doc, e, frobs...)
}
}
// H6 is <h6> tag. Frobs can be any Frob but anything else panics.
func H6(frobs ...Frob) Elem {
return func(doc dom.Document) *dom.BasicHTMLElement {
a := doc.CreateElement("h6").(*dom.HTMLHeadingElement)
e := a.BasicHTMLElement
return processFrobs(doc, e, frobs...)
}
}
// Ul is <ul> tag. Frobs can be any Frob but anything else panics.
func Ul(frobs ...Frob) Elem {
return func(doc dom.Document) *dom.BasicHTMLElement {
ul := doc.CreateElement("ul").(*dom.HTMLUListElement)
e := ul.BasicHTMLElement
return processFrobs(doc, e, frobs...)
}
}
// Li is <li> tag. Frobs can be any Frob but anything else panics.
func Li(frobs ...Frob) Elem {
return func(doc dom.Document) *dom.BasicHTMLElement {
li := doc.CreateElement("li").(*dom.HTMLLIElement)
e := li.BasicHTMLElement
return processFrobs(doc, e, frobs...)
}
}
// Img is <img> tag. Frobs can be any Frob but anything else panics.
func Img(frobs ...Frob) Elem {
return func(doc dom.Document) *dom.BasicHTMLElement {
img := doc.CreateElement("img").(*dom.HTMLImageElement)
e := img.BasicHTMLElement
return processFrobs(doc, e, frobs...)
}
}
//
// Useful stuff from bootstrap
//
const Alt = AttributeName("alt")
const DataDismiss = AttributeName("data-dismiss")
const DataTarget = AttributeName("data-target")
const DataToggle = AttributeName("data-toggle")
const Draggable = AttributeName("draggable")
const Height = AttributeName("height")
const Href = AttributeName("href")
const ID = AttributeName("id")
const OnClick = AttributeName("onclick")
const Placeholder = AttributeName("placeholder")
const Role = AttributeName("role")
const Rows = AttributeName("rows")
const Style = AttributeName("style")
const Src = AttributeName("src")
const TabIndex = AttributeName("tabindex")
const Title = AttributeName("title")
const Type = AttributeName("type")
const Width = AttributeName("width")
const AriaHidden = AttributeName("aria-hidden")
const AriaHasPopup = AttributeName("aria-haspopup")
const AriaExpanded = AttributeName("aria-expanded")
const ArialLabel = AttributeName("aria-label")
const Alert = Clazz("alert")
const AlertDismissable = Clazz("alert-dismissable")
const AlertWarning = Clazz("alert-warning")
const AlignItemsCenter = Clazz("align-items-center")
const BorderPrimary = Clazz("border-primary")
const Btn = Clazz("btn")
const BtnLink = Clazz("btn-link")
const BtnSm = Clazz("btn-sm")
const BtnOptions = Clazz("btn-options")
const Close = Clazz("close")
const Dflex = Clazz("d-flex")
const Dnone = Clazz("d-none")
const Dropdown = Clazz("dropdown")
const DropdownItem = Clazz("dropdown-item")
const DropdownMenu = Clazz("dropdown-menu")
const DropdownMenuRight = Clazz("dropdown-menu-right")
const Fade = Clazz("fade")
const FormControl = Clazz("form-control")
const JustifyContentBetween = Clazz("justify-content-between")
const H1Size = Clazz("h1")
const H6Size = Clazz("h6")
const ListGroup = Clazz("list-group")
const ListGroupFlush = Clazz("list-group-flush")
const ListGroupItem = Clazz("list-group-item")
const MaterialIcons = Clazz("material-icons")
const NoMargin = Clazz("m-0")
const NoPadding = Clazz("p-0")
const Row = Clazz("row")
const Small = Clazz("small")
const Show = Clazz("show")
const TextCenter = Clazz("text-center")
const TextDanger = Clazz("text-danger")
const TextPrimary = Clazz("text-primary")
const TextSmall = Clazz("text-small")
const TextSecondary = Clazz("text-secondary")
func Margin(n int) Clazz {
return Clazz(fmt.Sprintf("m-%d", n))
}
func Padding(n int) Clazz {
return Clazz(fmt.Sprintf("p-%d", n))
}
func Offset(n int) Clazz {
return Clazz(fmt.Sprintf("offset-%d", n))
}
func Col(n int) Clazz {
return Clazz(fmt.Sprintf("col-%d", n))
}
const Click = EventName("click")
const ClickCapture = EventName("click" + CaptureSuffix)
const DoubleClick = EventName("dblclick")
const DoubleClickCapture = EventName("dblclick" + CaptureSuffix)
const MouseDown = EventName("mousedown")
const MouseUp = EventName("mouseup")
const MouseDownCapture = EventName("mousedown" + CaptureSuffix)
const MouseUpCapture = EventName("mouseup" + CaptureSuffix)
func BootstrapAlert(doc dom.Document, alertText string) *dom.BasicHTMLElement {
//you can also do event handlers with Events{Click:functionName}
return Div(Alert, AlertWarning, AlertDismissable, Fade, Show, Attributes{Role: "alert"}, Text(alertText),
Button(Close, Attributes{Type: "button", ArialLabel: "Close", DataDismiss: "alert"},
Span(Attributes{AriaHidden: "true"}, Text("\u00D7")),
),
)(doc)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment