Views
Overview
Views in this framework are service-based components that generate HTML and JSON responses through composition rather than traditional templates. The framework uses two main view types: HTML Views for web pages and JSON Views for API responses.
View Architecture
View Types
The framework implements views as services that compose layouts, sections, and components:
// HTML View for complete page generation
type ShowGardenView struct {
Kit *kit.Kit
StructuredLayout *layouts.StructuredLayout
NavbarSection *sections.NavbarSection
TitleSection *sections.TitleSection
ShowSection *sections.ShowSection
}
// JSON View for API responses
type ShowGardenJsonView struct {
GardenSerializer *serializers.GardenSerializer
}
Component-Based Architecture
Views are built by composing multiple services: - Layouts - Overall page structure (structured, tiled, vertical, empty) - Sections - Page components (navbar, title, content, actions) - Kit - UI component library (atoms, molecules, organisms) - Serializers - Data transformation for JSON responses
HTML Views
Basic HTML View Structure
func (this *ShowGardenView) H(out *maes.ShowGardenMaeOut) goc.HTML {
mod := &layouts.LayoutMod{
Messages: out.Extra.Messages,
Scope: out.Extra.Scope,
Ctx: out.Ctx,
Title: "Garden Details",
}
return this.StructuredLayout.H(this.content(out), mod)
}
func (this *ShowGardenView) content(out *maes.ShowGardenMaeOut) goc.HTML {
return this.Kit.Component.Dcs("container mx-auto px-4 py-8",
this.NavbarSection.H(out),
this.TitleSection.H(out),
this.ShowSection.H(out),
this.actionsSection(out),
)
}
Layout Types
The framework provides several layout options:
Structured Layout
// Full page with sidebar, navigation, and structured content
func (this *SomeView) H(out *maes.SomeMaeOut) goc.HTML {
mod := &layouts.LayoutMod{
Title: "Page Title",
Messages: out.Extra.Messages,
Scope: out.Extra.Scope,
Ctx: out.Ctx,
BodyClasses: []string{"structured-page"},
}
return this.StructuredLayout.H(this.content(out), mod)
}
Tiled Layout
// Flexible grid-based layout
func (this *ListGardensView) H(out *maes.ListGardensMaeOut) goc.HTML {
mod := &layouts.LayoutMod{
Messages: out.Extra.Messages,
Scope: out.Extra.Scope,
Ctx: out.Ctx,
}
return this.TiledLayout.H(this.tiling(out), mod)
}
func (this *ListGardensView) tiling(out *maes.ListGardensMaeOut) goc.HTML {
return this.Kit.Component.Dcs("flex flex-col space-y-6",
this.NavbarSection.H(out),
this.TitleSection.H(out),
this.ListSection.H(out),
)
}
Vertical Layout
// Form-focused vertical layout
func (this *CreateGardenView) H(out *maes.CreateGardenMaeOut) goc.HTML {
return this.VerticalLayout.H(this.content(out), mod)
}
Empty Layout
// Minimal layout for simple pages
func (this *SimpleView) H(out *maes.SimpleMaeOut) goc.HTML {
return this.EmptyLayout.H(this.content(out), mod)
}
Component Usage
Views use the Kit component system to build UI elements:
Basic Components
func (this *SomeView) customSection(out *maes.SomeMaeOut) goc.HTML {
component := this.Kit.Component
return component.Dcs("bg-white rounded-lg p-6",
component.H1("Garden Details"),
component.P(out.Garden.Description),
component.Div(
component.Text("Location: "),
component.Strong(out.Garden.Location),
),
)
}
Form Components
func (this *CreateGardenView) formSection(out *maes.CreateGardenMaeOut) goc.HTML {
return this.Kit.Component.Form(
this.Kit.Component.FormGroup(
this.Kit.Component.Label("Name"),
this.Kit.Component.Input("text", "name", out.Form.Name),
),
this.Kit.Component.FormGroup(
this.Kit.Component.Label("Description"),
this.Kit.Component.Textarea("description", out.Form.Description),
),
this.Kit.Component.SubmitButton("Create Garden"),
)
}
Table Components
func (this *ListGardensView) tableSection(out *maes.ListGardensMaeOut) goc.HTML {
var rows []goc.HTML
for _, garden := range out.Gardens {
row := this.Kit.Component.TableRow(
this.Kit.Component.TableCell(garden.Name),
this.Kit.Component.TableCell(garden.Location),
this.Kit.Component.TableCell(garden.CreatedAt.Format("02/01/2006")),
this.actionsCell(garden),
)
rows = append(rows, row)
}
headers := []string{"Name", "Location", "Created", "Actions"}
return this.Kit.Component.Table(headers, rows)
}
Section Integration
Views coordinate multiple sections for complete page assembly:
func (this *ShowGardenView) content(out *maes.ShowGardenMaeOut) goc.HTML {
return this.Kit.Component.Dcs("space-y-6",
// Navigation
this.NavbarSection.H(out),
// Page header
this.TitleSection.H(out),
// Main content
this.ShowSection.H(out),
// Related data
this.plantsSection(out),
// Actions
this.actionsSection(out),
)
}
func (this *ShowGardenView) plantsSection(out *maes.ShowGardenMaeOut) goc.HTML {
if len(out.Garden.Plants) == 0 {
return this.Kit.Component.P("No plants in this garden yet.")
}
return this.Kit.Component.Div(
this.Kit.Component.H2("Plants"),
this.renderPlantsGrid(out.Garden.Plants),
)
}
Error and Message Handling
Views handle error display and user feedback:
func (this *SomeView) H(out *maes.SomeMaeOut) goc.HTML {
content := this.mainContent(out)
// Add error messages if present
if len(out.Extra.Messages.Errors) > 0 {
content = this.Kit.Component.Dcs("space-y-4",
this.errorSection(out.Extra.Messages.Errors),
content,
)
}
// Add success messages
if len(out.Extra.Messages.Success) > 0 {
content = this.Kit.Component.Dcs("space-y-4",
this.successSection(out.Extra.Messages.Success),
content,
)
}
return this.StructuredLayout.H(content, mod)
}
func (this *SomeView) errorSection(errors []string) goc.HTML {
var errorElements []goc.HTML
for _, error := range errors {
errorElements = append(errorElements,
this.Kit.Component.Dcs("bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded",
this.Kit.Component.Text(error),
),
)
}
return this.Kit.Component.Dcs("space-y-2", errorElements...)
}
JSON Views
JSON View Structure
JSON Views serialize data for API responses:
type ShowGardenJsonView struct {
GardenSerializer *serializers.GardenSerializer
}
func (this *ShowGardenJsonView) H(out *maes.ShowGardenMaeOut) (map[string]interface{}, error) {
garden, err := this.GardenSerializer.SerializeOne(out.Garden, "show")
if err != nil {
return nil, fmt.Errorf("failed to serialize garden: %w", err)
}
return map[string]interface{}{
"garden": garden,
}, nil
}
Collection JSON Views
func (this *ListGardensJsonView) H(out *maes.ListGardensMaeOut) (map[string]interface{}, error) {
gardens, err := this.GardenSerializer.SerializeMany(out.Gardens, "list")
if err != nil {
return nil, fmt.Errorf("failed to serialize gardens: %w", err)
}
response := map[string]interface{}{
"gardens": gardens,
}
if out.Pagination != nil {
response["pagination"] = this.serializePagination(out.Pagination)
}
return response, nil
}
func (this *ListGardensJsonView) serializePagination(pagination *common.Pagination) map[string]interface{} {
return map[string]interface{}{
"current_page": pagination.CurrentPage,
"per_page": pagination.PerPage,
"total": pagination.Total,
"total_pages": pagination.TotalPages,
"has_next": pagination.HasNext,
"has_prev": pagination.HasPrev,
}
}
Nested Data Serialization
func (this *ShowGardenJsonView) H(out *maes.ShowGardenMaeOut) (map[string]interface{}, error) {
garden, err := this.GardenSerializer.SerializeOne(out.Garden, "show")
if err != nil {
return nil, err
}
response := map[string]interface{}{
"garden": garden,
}
// Include related plants if hydrated
if len(out.Garden.Plants) > 0 {
plants, err := this.PlantSerializer.SerializeMany(out.Garden.Plants, "embedded")
if err != nil {
return nil, fmt.Errorf("failed to serialize plants: %w", err)
}
response["garden"].(map[string]interface{})["plants"] = plants
}
return response, nil
}
Controller Integration
Views are used in controllers to render responses:
HTML Response
func (gc *GardenController) Show(c *gin.Context) {
input := &maes.ShowGardenMaeIn{
GardenId: c.Param("id"),
}
output, err := gc.ShowGardenMae.Execute(c.Request.Context(), input)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
html := gc.ShowGardenView.H(output)
c.Data(200, "text/html; charset=utf-8", []byte(html))
}
JSON Response
func (gc *GardenController) ApiShow(c *gin.Context) {
input := &maes.ShowGardenMaeIn{
GardenId: c.Param("id"),
}
output, err := gc.ShowGardenMae.Execute(c.Request.Context(), input)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
response, err := gc.ShowGardenJsonView.H(output)
if err != nil {
c.JSON(500, gin.H{"error": "Failed to serialize response"})
return
}
c.JSON(200, response)
}
Styling with Tailwind CSS
The framework uses Tailwind CSS classes in components:
CSS Compilation
# Watch and compile Tailwind CSS
npx tailwindcss -i input.css -o ./assets/css/output.css --watch
Component Styling
func (this *SomeView) styledComponent() goc.HTML {
return this.Kit.Component.Dcs("bg-white rounded-lg shadow-md p-6 hover:shadow-lg transition-shadow",
this.Kit.Component.H2("Title"),
this.Kit.Component.Dcs("text-gray-600 mb-4",
this.Kit.Component.P("Content text"),
),
this.Kit.Component.Dcs("flex justify-between items-center",
this.Kit.Component.Button("Primary Action", "bg-blue-600 text-white px-4 py-2 rounded"),
this.Kit.Component.Link("Secondary", "text-blue-600 hover:text-blue-800"),
),
)
}
Best Practices
HTML Views
- Use appropriate layouts for different page types
- Compose views from reusable sections
- Handle errors and messages consistently
- Apply responsive design principles
JSON Views
- Use serializers for consistent data formatting
- Handle serialization errors gracefully
- Include appropriate metadata (pagination, etc.)
- Support different serialization contexts
Performance
- Minimize component nesting depth
- Reuse components across views
- Avoid expensive operations in view methods
- Use appropriate serialization contexts for JSON
Maintainability
- Keep views focused on presentation logic
- Extract common patterns into reusable components
- Use consistent naming conventions
- Document complex view compositions
The view system provides a powerful, component-based approach to generating both HTML pages and JSON API responses while maintaining clean separation of concerns and reusability.