Skip to content

Struct Mode

Core receivers are not the only way to define a component or page. Kyoto allows you to use structure with methods instead of core receiver with adapters. It might be useful for highly customizable components. Thanks to struct mode, you can use struct fields as arguments during instance creation.

Usage

Pages

Let's start from page definition, the most basic version. We need to define a structure and Template method for that structure.

package main

import (
    "html/template"

    "github.com/kyoto-framework/kyoto/render"
)


type PageIndex struct {}

func (p *PageIndex) Template() *template.Template {
    template.Must(template.New("page.index.html").Funcs(smode.FuncMap(p)).ParseGlob("*.html"))
}

It would be nice to open Basics • Pages to compare approach. You will definitely notice similar parts. Struct mode tries to replicate existing adapters as much as possible.

Next,let's attach this page to our router.

...

mux.HandleFunc("/", render.PageHandler(smode.Adapt(&PageIndex{})))

...

I agree, it looks a bit explicit. But it will make a sense if I will tell you that smode.Adapt function translates our structure to core receiver. In that way we can use adapted structure in existing functional code.

To simplify struct pages registration, you can use own small wrapper.

func myhandler(page smode.Page) http.HandlerFunc {
    return render.PageHandler(smode.Adapt(page))
}

You can define Init method, that will represent lifecycle.Init in this structure.

...

func (p *PageIndex) Init() {
    // Do what you want here
}

...

Components

As well as pages, you can define structure components. Let's start right from an example.

package main

type ComponentUUID struct {
    UUID string
}

func (c *ComponentUUID) Init() {
    c.UUID = "None"
}

func (c *ComponentUUID) Async() error {
    resp, _ := http.Get("http://httpbin.org/uuid")
    data := map[string]string{}
    json.NewDecoder(resp.Body).Decode(&data)
    c.UUID = data["uuid"]
    return nil
}

You may notice how we defined lifecycle methods here. Feel free to open Basics • Components to compare approach. smode.Adapt function takes care about registration of our methods. Also, in case of struct definitions we are using struct fields as a state.

And now let's attach this component to the page multiple times.

...

type PageIndex struct {
    UUID1 smode.Component
    UUID2 smode.Component
}

func (p *PageIndex) Init() {
    p.UUID1 = smode.RegC(p, &ComponentUUID{})
    p.UUID2 = smode.RegC(p, &ComponentUUID{})
}

...

As you can see, we are using smode.RegC for attaching components as an alternative to core.Component. You can pass parameters to a component on initialization.

Context

Instead of core.Context we can use smode.SetContext, smode.GetContext and smode.DelContext for context management. Context uses page instance as namespace for correct concurrency handling on requests level (page instance is creating for each new request).

...

type PageIndex struct{}

func (p *PageIndex) Init() {
    smode.SetContext(p, "key", "value")
}

func (p *PageIndex) Async() error {
    println(smode.GetContext(p, "key"))
    return nil
}

...

Actions

To define actions for a component you can implement Actions method. This method must return smode.ActionMap, a map which holds your methods.

...

type ComponentFoo struct {
    Status string
}

func (c *ComponentFoo) Actions() smode.ActionMap {
    return smode.ActionMap{
        `Bar`: func(args ...interface{}) {
            c.Status = "Baz"
        }
    }
}

Limitations

  • You can't use custom kyoto modules, only built-in smode functions.
Back to top