Skip to content

Use yaml to create layout markups and separate concerns into blocks and templates

Notifications You must be signed in to change notification settings

wilsonross/gomvvm

Repository files navigation

About


Provides a foundation for creating a model-view-viewmodel style architecture in Go. Define page layouts in YAML files that look up associated templates and view models—designed to allow for more orthogonal templating in Go and reusable components. Future releases aim to include layout overrides, caching, improved error handling and escapement.


It is inspired by Magento 2 layouts and layout instructions. Where ‘blocks’ are created that contain methods the templates can invoke. In my opinion, this makes for a more manageable templating experience for large, unwieldy pages. Instead of bloated Magento layout files, this library uses layouts written in a more straightforward schema.


Lifecycle


Blocks that match an interface can be added to a registry. The interface only requires a single receiver function that fetches the block’s name. A layout file will then look up templates and their associated block. We can create a simple system of reusable nested components containing their own model from these basic building blocks. Each child component can include its unique data and receiver functions.


Installation


go get github.com/wilsonross/gomvvm

Getting Started


The following code snippet shows how to create a basic block and add it to a registry. We can also define additional data on the struct and receiver functions. This next section looks verbose but would be suitable when grouping by type. I have intentionally added two components to demonstrate how nested children work.


package main

import (
	layout "github.com/wilsonross/gomvvm"
)

func main() {
	header := Header{
		name: "header",
	}

	menu := Menu{
		name:      "menu",
		menuLinks: []string{"Home", "About", "Contact"},
	}

	// Add the blocks to the registry
	layout.AddBlocks(menu, header)
}

type Header struct {
	name string
}

// Required for our interface
func (h Header) GetName() string {
	return h.name
}

type Menu struct {
	name      string
	menuLinks []string
}

// Required for our interface
func (m Menu) GetName() string {
	return m.name
}

// Data we would like to pass to our template
// We could also export the field directly from our struct
func (m Menu) GetMenuLinks() []string {
	return m.menuLinks
}

Let’s now focus on our layout. We have a root layout key, followed by three simple directives. The block directive is required and is the identifier of the block we have previously set. In our example, it will be menu. The ‘template’ directive is a required path to our HTML file. The children directive is an optional array where we can set child components using the same schema.


layout:
  - block: "header"
    template: "header.html"
    children:
      - block: "menu"
        template: "menu.html"

Here are the two HTML templates that we just defined. We are invoking getChildHtml to produce our nested HTML. Additionally, we are retrieving the data we described earlier. Because it is necessary to embed the HTML content in the parent component, we are losing the escapement of the Go templater. I am currently investigating ways around this caveat. However, I strongly recommend you escape the final string for the moment to prevent XSS.


<div class="header">
    {{ .GetChildHtml "menu" }}
</div>

<ul class="menu">
  {{range $name := .GetBlock.GetMenuLinks }}
  <li>{{$name}}</li>
  {{end}}
</ul>

Currently, the library requires the user to read the layouts. This will likely change at a later date. We can finally call the TemplateLayout function to produce a string of HTML. By adding more components, more complex layouts can be created.


func main() {
	header := Header{
		name: "header",
	}

	menu := Menu{
		name:      "menu",
		menuLinks: []string{"Home", "About", "Contact"},
	}

	// Add the blocks to the registry
	layout.AddBlocks(menu, header)

	data, err := os.ReadFile("layout.yml")
	if err != nil {
		panic(err)
	}

	// Render the template
	output, err := layout.TemplateLayout(data)
	if err != nil {
		panic(err)
	}

	fmt.Println(output)
	/*<div class="header">
	  <ul class="menu">
	    <li>Home</li>
	    <li>About</li>
	    <li>Contact</li>
	  </ul>
	</div>*/
}

Roadmap


  • First release
  • Escapement
  • Improved error handling
  • Caching and improved file reading
  • Layout overrides

Contact


Ross Wilson - ross.wilson.190298@gmail.com


About

Use yaml to create layout markups and separate concerns into blocks and templates

Topics

Resources

Stars

Watchers

Forks

Languages