Ir para o conteúdo
Trilha
Capítulos

Aprender

HTML com o pacote h

Elementos como funções, escape por padrão, condicionais, listas e quando usar templates.

O pacote h gera HTML sem arquivos de template: cada elemento é uma função Go que aceita atributos e filhos em qualquer ordem. Tudo é verificado pelo compilador e escapado na saída.

Elementos, atributos e texto#

h.Article(h.Class("evento", "destaque"),
	h.H2(h.Text(ev.Nome)),
	h.P(h.Textf("%s, %d vagas", ev.Cidade, ev.Vagas)),
	h.A(h.Href("/eventos/"+ev.Slug), h.Text("Detalhes")),
)
  • h.Text e h.Textf escapam. h.Raw não escapa e é a única porta para HTML pronto.
  • Atributos (h.Class, h.Href, h.ID, h.Data("x", v), h.Attr("nome", v)) podem vir depois dos filhos; eles sempre acabam na tag de abertura.
  • Elementos vazios (h.Br, h.Img, h.Input, h.Meta) não fecham.
  • Atributos booleanos são funções sem argumento: h.Required(), h.Disabled().
  • Quando o nome colide com um elemento, o atributo ganha o sufixo Attr: h.StyleAttr, h.TitleAttr, h.LabelAttr.
app/…/page.go
nome := "<script>alert('oi')</script>"

return h.P(
	h.Text("Olá, "),
	h.Strong(h.Text(nome)),
), nil
resultado

Olá, <script>alert('oi')</script>

Condicionais e listas#

h.Ul(
	h.If(len(eventos) == 0, h.Li(h.Em(h.Text("nenhum evento")))),
	h.Map(eventos, func(ev Evento) h.Node {
		return h.Li(h.Text(ev.Nome))
	}),
)

h.If devolve um nó vazio quando a condição é falsa; h.IfElse escolhe entre dois; h.Map aplica uma função a cada item; h.Fragment agrupa vários nós sem elemento em volta. nil como filho é ignorado, então um func() h.Node que devolve nil também é seguro.

app/…/page.go
func Page(c *trilha.Ctx) (h.Node, error) {
	return h.Ul(h.Class("eventos"),
		h.Map(eventos, func(e Evento) h.Node {
			return h.Li(
				h.Strong(h.Text(e.Nome)),
				h.Textf(" · %s · ", e.Cidade),
				h.IfElse(e.Vagas > 0,
					h.Textf("%d vagas", e.Vagas),
					h.Em(h.Text("lotado"))),
			)
		}),
	), nil
}
resultado
  • Encontro Go Campinas · Campinas · 12 vagas
  • Oficina de HTTP · Recife · lotado
  • Noite de código · Curitiba · 4 vagas

Componentes são funções#

Não existe um tipo "componente". Uma função que devolve h.Node já é um:

func CartaoEvento(ev Evento) h.Node {
	return h.Article(h.Class("cartao"),
		h.H3(h.Text(ev.Nome)),
		h.P(h.Text(ev.Cidade)),
	)
}

// na página
h.Div(h.Class("grade"), h.Map(eventos, CartaoEvento))

Prefere templates?#

O pacote tmpl encaixa html/template no mesmo pipeline. Os arquivos ficam ao lado da página e são embutidos no binário:

package relatorio

import (
	"embed"

	"github.com/emersonjoe/trilha"
	"github.com/emersonjoe/trilha/h"
	"github.com/emersonjoe/trilha/tmpl"
)

//go:embed relatorio.html
var arquivos embed.FS

var t = tmpl.Must(arquivos, "*.html") // falha na subida, nunca no request

func Page(c *trilha.Ctx) (h.Node, error) {
	c.SetTitle("Relatório")
	return tmpl.Node(t, "relatorio", dados), nil
}

Layouts, título e erros funcionam igual. O escape é o contextual do próprio html/template.

Desafio#

Escreva um componente Vagas(n int) h.Node que mostre "lotado" em itálico quando n == 0, "1 vaga" no singular e "N vagas" no plural, e use-o na lista de eventos.

Mostrar solução
func Vagas(n int) h.Node {
	switch {
	case n == 0:
		return h.Em(h.Text("lotado"))
	case n == 1:
		return h.Text("1 vaga")
	default:
		return h.Textf("%d vagas", n)
	}
}