Presenters

Overview

Presenters are responsible for transforming entity data into display-ready formats for views. They handle column definitions, data formatting, filtering, and search presentation, providing a clean separation between data models and view presentation logic.

Purpose

Presenters serve several key functions: - Data Formatting - Convert raw entity data into display formats - Column Management - Define and manage table/list column configurations - Filter Integration - Provide filter options for entity lists - Search Presentation - Format entities for search results - View Abstraction - Abstract presentation logic from controllers

Structure

Each presenter typically follows this pattern:

type PlantPresenter struct {
    Kit                         *kit.Kit
    PlantPrimaryColumnIllustrer *illustrers.PlantPrimaryColumnIllustrer
    PlantSearchIllustrer        *illustrers.PlantSearchIllustrer
    Presenter                   *presenter.Presenter
}

Core Methods

Data Column Formatting

func (this *PlantPresenter) NameDataColumn(entity *mdl.Plant) goc.HTML {
    return this.Kit.Atoms.Cells.TextCell.TextCell(entity.Name)
}

func (this *PlantPresenter) CreatedAtDataColumn(entity *mdl.Plant) goc.HTML {
    return this.Kit.Atoms.Cells.TextCell.TextCell(entity.CreatedAt.Format("02/01/2006 15:04"))
}

func (this *PlantPresenter) HarvestedDataColumn(entity *mdl.Plant) goc.HTML {
    return this.Kit.Atoms.Cells.DecimalCell.DecimalCell(entity.Harvested)
}

func (this *PlantPresenter) PerennialDataColumn(entity *mdl.Plant) goc.HTML {
    return this.Kit.Atoms.Cells.BooleanCell.BooleanCell(entity.Perennial)
}

Dynamic Column Access

func (this *PlantPresenter) ColumnByKey(key string, entity *mdl.Plant) goc.HTML {
    switch key {
    case "name":
        return this.NameDataColumn(entity)
    case "created_at":
        return this.CreatedAtDataColumn(entity)
    case "harvested":
        return this.HarvestedDataColumn(entity)
    case "perennial":
        return this.PerennialDataColumn(entity)
    }
    return this.Kit.Organisms.Tables.Table.EmptyCellRow()
}

Column Configuration

Column Definition

func (this *PlantPresenter) Columns(filter string) []*presenter.Column {
    var columns []*presenter.Column

    columns = append(columns, &presenter.Column{
        Key:   "name",
        Label: "Name",
        Kind:  "data",
        Main:  true,     // Show in main view
        Sub:   true,     // Show in sub view
    })

    columns = append(columns, &presenter.Column{
        Key:   "created_at",
        Label: "Created At",
        Kind:  "data",
        Main:  false,    // Hide in main view
        Sub:   false,    // Hide in sub view
    })

    return this.Presenter.FilteredColumns(columns, filter)
}

Column Types

  • data - Standard data columns
  • action - Action/button columns
  • link - Link/navigation columns
  • image - Image display columns

Search Integration

Search Presentation

func (this *PlantPresenter) Search(ctx context.Context, value *searcher.SearchValue, entity *mdl.Plant) goc.HTML {
    return this.PlantSearchIllustrer.Act(ctx, &illustrers.PlantSearchIllustrerMod{
        Entity: entity,
        Value:  value,
    })
}

Primary Column Display

func (this *PlantPresenter) PrimaryColumn(ctx context.Context, entity *mdl.Plant) goc.HTML {
    return this.PlantPrimaryColumnIllustrer.Act(ctx, &illustrers.PlantPrimaryColumnIllustrerMod{
        Entity: entity,
    })
}

Filter Configuration

func (this *PlantPresenter) Filters(ctx context.Context) []filter.Filter {
    filters := filter.Filters{}

    filters.AddIdFilter("id")
    filters.AddStringFilter("name")
    filters.AddStringFilter("exposition")
    filters.AddBooleanFilter("perennial")
    filters.AddIntegerFilter("size")

    return filters
}

Integration with Views

Presenters are used by views to format data for display:

// In a view template
func (this *PlantListView) renderTable(plants []*mdl.Plant) goc.HTML {
    var rows []goc.HTML

    for _, plant := range plants {
        row := this.Kit.Organisms.Tables.TableRow.TableRow(
            this.PlantPresenter.NameDataColumn(plant),
            this.PlantPresenter.PerennialDataColumn(plant),
            this.PlantPresenter.HarvestedDataColumn(plant),
        )
        rows = append(rows, row)
    }

    return this.Kit.Organisms.Tables.Table.Table(rows...)
}

Cell Types

Text Cells

func (this *SomePresenter) TextColumn(entity *mdl.SomeEntity) goc.HTML {
    return this.Kit.Atoms.Cells.TextCell.TextCell(entity.TextValue)
}

Numeric Cells

func (this *SomePresenter) IntegerColumn(entity *mdl.SomeEntity) goc.HTML {
    return this.Kit.Atoms.Cells.IntegerCell.IntegerCell(entity.IntValue)
}

func (this *SomePresenter) DecimalColumn(entity *mdl.SomeEntity) goc.HTML {
    return this.Kit.Atoms.Cells.DecimalCell.DecimalCell(entity.DecimalValue)
}

Boolean Cells

func (this *SomePresenter) BooleanColumn(entity *mdl.SomeEntity) goc.HTML {
    return this.Kit.Atoms.Cells.BooleanCell.BooleanCell(entity.BoolValue)
}

Date/Time Cells

func (this *SomePresenter) DateColumn(entity *mdl.SomeEntity) goc.HTML {
    formatted := entity.Date.Format("02/01/2006")
    return this.Kit.Atoms.Cells.TextCell.TextCell(formatted)
}

func (this *SomePresenter) DateTimeColumn(entity *mdl.SomeEntity) goc.HTML {
    formatted := entity.DateTime.Format("02/01/2006 15:04")
    return this.Kit.Atoms.Cells.TextCell.TextCell(formatted)
}

Custom Formatting

Conditional Formatting

func (this *PlantPresenter) StatusColumn(entity *mdl.Plant) goc.HTML {
    var cssClass string
    var text string

    if entity.Harvested > 0 {
        cssClass = "text-green-600"
        text = "Harvested"
    } else {
        cssClass = "text-yellow-600"
        text = "Growing"
    }

    return this.Kit.Component.Dc(cssClass, this.Kit.Component.Text(text))
}

Complex Display Logic

func (this *PlantPresenter) SizeDisplayColumn(entity *mdl.Plant) goc.HTML {
    switch {
    case entity.Size < 10:
        return this.Kit.Component.Text("Small")
    case entity.Size < 50:
        return this.Kit.Component.Text("Medium")
    default:
        return this.Kit.Component.Text("Large")
    }
}

Best Practices

Performance

  • Use appropriate cell types for data formatting
  • Avoid complex calculations in presentation methods
  • Cache formatted values when possible

Maintainability

  • Keep presentation logic separate from business logic
  • Use consistent naming conventions for column methods
  • Group related columns logically

Flexibility

  • Support different column filter sets
  • Allow for conditional column display
  • Provide fallbacks for missing data

Testing

func TestPlantPresenter_NameDataColumn(t *testing.T) {
    presenter := setupPlantPresenter()
    plant := &mdl.Plant{Name: "Tomato"}

    result := presenter.NameDataColumn(plant)

    assert.Contains(t, string(result), "Tomato")
}

func TestPlantPresenter_Columns(t *testing.T) {
    presenter := setupPlantPresenter()

    columns := presenter.Columns("")

    assert.Greater(t, len(columns), 0)
    assert.Contains(t, getColumnKeys(columns), "name")
}

Presenters provide a clean, consistent way to transform entity data into formatted display elements while maintaining separation of concerns between data models and view presentation.