golymer

package module
v0.0.0-...-aecb3d9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 6, 2018 License: MIT Imports: 5 Imported by: 23

README

golymer

golymer logo

Create HTML custom elements with go (gopherjs)

With golymer you can create your own HTML custom elements, just by registering a go struct. The content of the shadowDOM has automatic data bindings to the struct fields. Read an blog post.

Contribution of all kind is welcome. Tips for improvement or api simplification also :)

package main

import "github.com/microo8/golymer"

var myAwesomeTemplate = golymer.NewTemplate(`
<style>
	:host {
		background-color: blue;
		width: 500px;
		height: [[FooAttr]]px;
	}
</style>
<p>[[privateProperty]]</p>
<input type="text" value="{{BarAttr}}"/>
`)

type MyAwesomeElement struct {
	golymer.Element
	FooAttr int
	BarAttr string
	privateProperty float64
}

func NewMyAwesomeElement() *MyAwesomeElement {
	e := &MyAwesomeElement{
		FooAttr: 800,
	}
	e.SetTemplate(myAwesomeTemplate)
	return e
}

func main() {
	//pass the element constructor to the Define function
	err := golymer.Define(NewMyAwesomeElement)
	if err != nil {
		panic(err)
	}
}

Then just run $ gopherjs build, import the generated script to your html <script src="my_awesome_element.js"></script> and you can use your new element

<my-awesome-element foo-attr="1" bar-attr="hello"></my-awesome-element>

define an element

To define your own custom element, you must create an struct that embeds the golymer.Element struct and a function that is the constructor for the struct. Then add the constructor to the golymer.Define function. This is an minimal example.:

type MyElem struct {
	golymer.Element
}

func NewMyElem() *MyElem {
	return new(MyElem)
}

func init() {
	err := golymer.Define(NewMyElem)
	if err != nil {
		panic(err)
	}
}

The struct name, in CamelCase, is converted to the kebab-case. Because html custom elements must have at least one dash in the name, the struct name must also have at least one "hump" in the camel case name. (MyElem -> my-elem). So, for example, an struct named Foo will not be defined and the Define function will return an error.

Also the constructor fuction must have a special shape. It can't take no arguments and must return a pointer to an struct that embeds the golymer.Element struct.

element attributes

golymer creates attributes on the custom element from the exported fields and syncs the values.

type MyElem struct {
	golymer.Element
	ExportedField string
	unexportedField int
	Foo float64
	Bar bool
}

Exported fields have attributes on the element. This enables to declaratively set the api of the new element. The attributes are also converted to kebab-case.

<my-elem exported-field="value" foo="3.14" bar="true"></my-elem>

HTML element attributes can have just text values, so golymer parses these values to convert it to the field types with the strconv package.

lifecycle callbacks

golymer.Element implemets the golymer.CustomElement interface. It's an interface for the custom elements lifecycle in the DOM.

ConnectedCallback() called when the element is connected to the DOM. Override this callback for setting some fields, or spin up some goroutines, but remember to call the golymer.Element also (myElem.Element.ConnectedCallback()).

DisconnectedCallback() called when the element is disconnected from the DOM. Use this to release some resources, or stop goroutines.

AttributeChangedCallback(attributeName string, oldValue string, newValue string, namespace string) this callback called when an observed attribute value is changed. golymer automatically observes all exported fields. When overriding this, also remember to call golymer.Element callback (myElem.Element.AttributeChangedCallback(attributeName, oldValue, newValue, namespace)).

template

The function golymer.NewTemplate will create a new template element, from which the new custom element's shadowDOM will be instantiated. It is better to use the template element for this, and not innerHTML, because the browser must parse the html only once.

If you want to create an custom element with a template, you must set the elements template in the constructor with the SetTemplate function.


var myTemplate = golymer.NewTemplate(`<h1>Hello golymer</h1>`)

func NewMyElem() *MyElem {
	e := new(MyElem)
	e.SetTemplate(myTemplate)
	return e
}

The element will then have an shadowDOM thats content will be set from the Template element at the connectedCallback.

one way data bindings

golymer has build in data bindings. One way data bindings are used for presenting an struct field's value. For defining an one way databinding you can use double square brackets with the path to the field ([[Field]] or [[subObject.Field]]) Eg:

<p>[[Text]]!!!</p>

Where the host struct has an Text field. Or the name in brackets can be an path to an fielt subObject.subSubObject.Field. The field value is then converted to it's string representation. One way data bindings can be used in text nodes, like in the example above, and also in element attributes eg. <div style="display: [[MyDisplay]];"></div>

Every time the fields value is changed, the template will be automaticaly changed. Changing the Text fields value eg myElem.Text = "foo" also changes the <p> element's innerHTML.

two way data bindings

Two way data bindings are declared with two curly brackets ({{Field}} or {{subObject.Field}}) and work only in attributes of elements in the template. So every time the elements attribute is changed, the declared struct field will also be changed. golymer makes also an workaround for html input elements, so it is posible to just bind to the value attribute.

<input id="username" name="username" type="text" value="{{Username}}">

Changing elem.Username changes the input.value, and also changing the input.value or the value attribute document.getElementById("username").setAttribute("value", "newValue") or the user adds some text, the elem.Username will be also changed.

It's also possible to pass complex data structures to the sub element with two way data bindings.

type User struct {
	ID   int
	Name string
}

type ElemOne struct {
	golymer.Element
	Usr  *User
	...
}
type ElemTwo struct {
	golymer.Element
	Usr  *User
	...
}

ElemOne template:

<div>
	<elem-two usr="{{Usr}}"></elem-two>
</div>

This will keep the elemOne.Usr and elemTwo.Usr the same pointer to the same object.

connecting to events

Connecting to the events of elements can be done with the javascript addEventListener function, but it is also possible to connect some struct method with an on-<eventName> attribute.

<button on-click="ButtonClicked"></button>

Event handlers get the event as a pointer to golymer.Event

func (e *MyElem) ButtonClicked(event *golymer.Event) {
	print("the button was clicked!")
}

custom events

golymer adds the DispatchEvent method so you can fire your own events.

event := golymer.NewEvent(
	"my-event",
	map[string]interface{}{
		"detail": map[string]interface{}{
			"data": "foo",
		},
		"bubbles": true,
	},
)
elem.DispatchEvent(event)

and these events can be also connected to:

<my-second-element on-my-event="MyCustomHandler"></my-second-element>

observers

On changing an fields value, you can have an observer, that will get the old and new value of the field. It must just be an method with the name: Observer<FieldName>. eg:

func (e *MyElem) ObserverText(oldValue, newValue string) {
	print("Text field changed from", oldValue, "to", newValue)
}

children

golymer scans the template and checks the id of all elements in it. The id will then be used to map the children of the custom element and can be accessed from the Childen map (map[string]*js.Object). Attribute id cannot be databinded (it's value must be constant).

var myTemplate = golymer.NewTemplate(`
<h1 id="heading">Heading</h1>
<my-second-element id="second"></my-second-element>
<button on-click="Click">click me</button>
`)

func (e *MyElem) Click(event *golymer.Event) {
	secondElem := e.Children["second"].Interface().(*MySecondElement)
	secondElem.DoSomething()
}

golymer also populates fields of the custom element from the id. If the element's id is equal to the field name and if it's type is the same as the element in template. eg:

var tmpl = golymer.NewTemplate(`
<my-second-element id="second"></my-second-element>
`)

type MyElem struct {
	golymer.Element
	second *MySecondElement //this field will after ConnectedCallback have an pointer to the `<my-second-element>` in the template
}

type assertion

It is possible to type assert the node object to your custom struct type. With selecting the node from the DOM directly

myElem := js.Global.Get("document").Call("getElementById", "myElem").Interface().(*MyElem)

and also from the Children map

secondElem := e.Children["second"].Interface().(*MySecondElement)

element creation

Calling your custom element's constructor creates you just the struct type. The node must be created by the DOM. So you must create your registered element with document.createElement:

myElem := js.Global.Get("document").Call("createElement", "my-elem").Interface().(*MyElem)

Or you can use the helper function from golymer:

myElem := golymer.CreateElement("my-elem").(*MyElem)

golymer elements

The subpackage golymer elements contains common elemets needed in building websites.

Like some material design elements and also some common declarative dom manipulation elements.

But it's still an work in progress. The collection isn't complete, but elements that are already complete are functioning and usable.

SPA

The subpackage spa contains utils/helpers for building single page applications.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CreateElement

func CreateElement(elementName string) interface{}

CreateElement creates a new instance of an element that can be type asserted to custom element

func Define

func Define(f interface{}) error

Define registers an new custom element takes the constructor of the element func()*YourElemType element is registered under the name converted from your element type (YourElemType -> your-elem-type)

func MustDefine

func MustDefine(f interface{})

MustDefine registers an new custom element takes the constructor of the element func()*YourElemType element is registered under the name converted from your element type (YourElemType -> your-elem-type) if an error occures it panics

Types

type CustomElement

type CustomElement interface {
	ConnectedCallback()
	DisconnectedCallback()
	AttributeChangedCallback(attributeName string, oldValue string, newValue string, namespace string)
	AdoptedCallback(oldDocument, newDocument interface{})
	DispatchEvent(customEvent *Event)
}

CustomElement the interface to create the CustomElement

type Element

type Element struct {
	*js.Object
	ObjValue reflect.Value //custom element struct type
	Children map[string]*js.Object
	// contains filtered or unexported fields
}

Element wrapper for the HTML element

func (*Element) AddEventListener

func (e *Element) AddEventListener(eventName string, f func(*Event))

AddEventListener is an wrapper for the js addEventListener

func (*Element) AdoptedCallback

func (e *Element) AdoptedCallback(oldDocument, newDocument interface{})

AdoptedCallback ...

func (*Element) AttributeChangedCallback

func (e *Element) AttributeChangedCallback(attributeName string, oldValue string, newValue string, namespace string)

AttributeChangedCallback ...

func (*Element) ConnectedCallback

func (e *Element) ConnectedCallback()

ConnectedCallback called when the element is attached to the DOM

func (*Element) DisconnectedCallback

func (e *Element) DisconnectedCallback()

DisconnectedCallback called when the element is dettached from the DOM

func (*Element) DispatchEvent

func (e *Element) DispatchEvent(ce *Event)

DispatchEvent dispatches an Event at the specified EventTarget, invoking the affected EventListeners in the appropriate order

func (*Element) SetTemplate

func (e *Element) SetTemplate(template Template)

SetTemplate sets this element's template, must be called in own element's constructor, before connectedCallback

type Event

type Event struct {
	*js.Object
	//A Boolean indicating whether the event bubbles up through the DOM or not.
	Bubbles bool `js:"bubbles"`
	//A Boolean indicating whether the event is cancelable.
	Cancelable bool `js:"cancelable"`
	//A Boolean value indicating whether or not the event can bubble across the boundary between the shadow DOM and the regular DOM.
	Composed bool `js:"composed"`
	//A reference to the currently registered target for the event.
	//This is the object to which the event is currently slated to be sent to;
	//it's possible this has been changed along the way through retargeting.
	CurrentTarget *js.Object `js:"currentTarget"`
	//An Array of DOM Nodes through which the event has bubbled.
	DeepPath []*js.Object `js:"deepPath"`
	//Indicates whether or not event.preventDefault() has been called on the event.
	DefaultPrevented bool `js:"defaultPrevented"`
	//Any data passed when initializing the event
	Detail map[string]interface{} `js:"detail"`
	//Indicates which phase of the event flow is being processed.
	EventPhase int `js:"eventPhase"`
	//A reference to the target to which the event was originally dispatched.
	Target *js.Object `js:"target"`
	//The time at which the event was created, in milliseconds.
	//By specification, this value is time since epoch, but in reality browsers' definitions vary;
	//in addition, work is underway to change this to be a DOMHighResTimeStamp instead.
	TimeStamp time.Time `js:"timeStamp"`
	//The name of the event (case-insensitive).
	Type string `js:"type"`
	//Indicates whether not the event was initiated by the browser (after a user click for instance)
	//or by a script (using an event creation method, like event.initEvent)
	IsTrusted bool `js:"isTrusted"`
}

Event represents any event which takes place in the DOM

func NewEvent

func NewEvent(typ string, customEventInit map[string]interface{}) *Event

NewEvent creates new event

func (*Event) StopPropagation

func (e *Event) StopPropagation()

StopPropagation prevents further propagation of the current event in the capturing and bubbling phases

type Template

type Template *js.Object

Template is an html template element ant it's used to instantite new golymer.Element shadowDOM

func NewTemplate

func NewTemplate(str string) Template

NewTemplate creates an template element

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL