view.mdin: /docs/internal/view/page/


This defines the page structure: html element, title, css, js files, etc.

This is one we can use both veun/template and veun/el.

For this, we opt to use go templating directly.

import (
	_ "embed"

	t ""

//go:embed template.tpl
var tpl string
var template = t.MustParse("page", tpl)

You can see the actual template here.


We see in the main function, that declare the assets (css/js) for the view external to it, it's pretty non-functional on its own.

type Data struct {
	Title    string
	CSSFiles []string
	JSFiles  []string
	IsMobile bool

The View

Now that we have the html defined, let's give it an actual view. This one is really simple, just passing through the data and the view for the body slot.

type view struct {
	body veun.AsView
	data Data

func (v view) View(_ context.Context) (*veun.View, error) {
	return veun.V(t.Template{
		Tpl:   template,
		Slots: t.Slots{"body": v.body},
	}), nil

The view struct itself is very small, and we can add more things to it as we go, but let's take a closer tlook at that data.

Mutation hooks

A thing we provide specifically for the page.Data is an option for the view that gets wrapped by the page to provide some configuration back up the tree (optionally).

We do this by giving the view an interface to fullfill.

type DataMutator interface {
	SetPageData(d *Data)

And the function we use to for construction will invoke this for us.

func View(v veun.AsView, data Data) view {
	return view{
		body: v,
		data: mutateData(data, v),

func mutateData(d Data, with any) Data {
	if m, ok := with.(DataMutator); ok {

	return d