Documentation

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.