Repository: Evertras/bubble-table
Pull request #9 (2022-02-13 16:17:49)

Rename header to column for clarity
'Header' was a mental shortcut name that is unclear.  These are actually columns, so call them columns.


Patch:
diff --git a/examples/dimensions/main.go b/examples/dimensions/main.go
index edbf432..3c7c182 100644
--- a/examples/dimensions/main.go
+++ b/examples/dimensions/main.go
@@ -19,11 +19,11 @@ type Model struct {
 }
 
 func genTable(columnCount int, rowCount int) table.Model {
-	headers := []table.Header{}
+	columns := []table.Column{}
 
 	for column := 0; column < columnCount; column++ {
 		columnStr := fmt.Sprintf("%d", column+1)
-		headers = append(headers, table.NewHeader(columnStr, columnStr, 4))
+		columns = append(columns, table.NewColumn(columnStr, columnStr, 4))
 	}
 
 	rows := []table.Row{}
@@ -39,7 +39,7 @@ func genTable(columnCount int, rowCount int) table.Model {
 		rows = append(rows, table.NewRow(rowData))
 	}
 
-	return table.New(headers).WithRows(rows).HeaderStyle(lipgloss.NewStyle().Bold(true))
+	return table.New(columns).WithRows(rows).HeaderStyle(lipgloss.NewStyle().Bold(true))
 }
 
 func NewModel() Model {
diff --git a/examples/features/main.go b/examples/features/main.go
index 4a6997a..99ea84c 100644
--- a/examples/features/main.go
+++ b/examples/features/main.go
@@ -46,11 +46,11 @@ type Model struct {
 }
 
 func NewModel() Model {
-	headers := []table.Header{
-		table.NewHeader(columnKeyID, "ID", 5),
-		table.NewHeader(columnKeyName, "Name", 10),
-		table.NewHeader(columnKeyDescription, "Description", 30),
-		table.NewHeader(columnKeyCount, "#", 5),
+	columns := []table.Column{
+		table.NewColumn(columnKeyID, "ID", 5),
+		table.NewColumn(columnKeyName, "Name", 10),
+		table.NewColumn(columnKeyDescription, "Description", 30),
+		table.NewColumn(columnKeyCount, "#", 5),
 	}
 
 	rows := []table.Row{
@@ -75,7 +75,7 @@ func NewModel() Model {
 	}
 
 	return Model{
-		tableModel: table.New(headers).
+		tableModel: table.New(columns).
 			WithRows(rows).
 			HeaderStyle(lipgloss.NewStyle().Foreground(lipgloss.Color("10")).Bold(true)).
 			SelectableRows(true).
diff --git a/table/border.go b/table/border.go
index c0a8388..572246a 100644
--- a/table/border.go
+++ b/table/border.go
@@ -242,6 +242,8 @@ func (b *Border) generateStyles() {
 	)
 }
 
+// BorderDefault uses the basic square border, useful to reset the border if
+// it was changed somehow
 func (m Model) BorderDefault() Model {
 	// Already generated styles
 	m.border = borderDefault
@@ -249,6 +251,7 @@ func (m Model) BorderDefault() Model {
 	return m
 }
 
+// Border uses the given border components to render the table
 func (m Model) Border(border Border) Model {
 	border.generateStyles()
 
diff --git a/table/header.go b/table/column.go
similarity index 51%
rename from table/header.go
rename to table/column.go
index 286e64c..3ce254b 100644
--- a/table/header.go
+++ b/table/column.go
@@ -4,7 +4,8 @@ import (
 	"fmt"
 )
 
-type Header struct {
+// Column is a column in the table
+type Column struct {
 	Title string
 	Key   string
 	Width int
@@ -12,8 +13,9 @@ type Header struct {
 	fmtString string
 }
 
-func NewHeader(key, title string, width int) Header {
-	return Header{
+// NewColumn creates a new column with the given information
+func NewColumn(key, title string, width int) Column {
+	return Column{
 		Key:       key,
 		Title:     title,
 		Width:     width,
diff --git a/table/row.go b/table/row.go
index 0e10558..13c1bcd 100644
--- a/table/row.go
+++ b/table/row.go
@@ -8,6 +8,8 @@ import (
 
 type RowData map[string]interface{}
 
+// Row represents a row in the table with some data keyed to the table columns>
+// Can have a style applied to it such as color/bold.  Create using NewRow()
 type Row struct {
 	Style lipgloss.Style
 	Data  RowData
@@ -15,6 +17,7 @@ type Row struct {
 	selected bool
 }
 
+// NewRow creates a new row and copies the given row data
 func NewRow(data RowData) Row {
 	d := Row{
 		Data: make(map[string]interface{}),
@@ -28,14 +31,15 @@ func NewRow(data RowData) Row {
 	return d
 }
 
+// WithStyle uses the given style for the text in the row
 func (r Row) WithStyle(style lipgloss.Style) Row {
-	r.Style = style
+	r.Style = style.Copy()
 
 	return r
 }
 
 func (m Model) renderRow(i int) string {
-	numHeaders := len(m.headers)
+	numColumns := len(m.columns)
 	row := m.rows[i]
 	last := i == len(m.rows)-1
 	highlighted := i == m.rowCursorIndex
@@ -58,7 +62,7 @@ func (m Model) renderRow(i int) string {
 		rowLastStyleRight lipgloss.Style
 	)
 
-	if numHeaders == 1 {
+	if numColumns == 1 {
 		rowStyleLeft = m.border.styleSingleColumnInner
 
 		rowLastStyleLeft = m.border.styleSingleColumnBottom
@@ -72,16 +76,16 @@ func (m Model) renderRow(i int) string {
 		rowLastStyleRight = m.border.styleMultiBottomRight
 	}
 
-	for i, header := range m.headers {
+	for i, column := range m.columns {
 		var str string
 
-		if header.Key == columnKeySelect {
+		if column.Key == columnKeySelect {
 			if row.selected {
 				str = "[x]"
 			} else {
 				str = "[ ]"
 			}
-		} else if entry, exists := row.Data[header.Key]; exists {
+		} else if entry, exists := row.Data[column.Key]; exists {
 			str = fmt.Sprintf("%v", entry)
 		}
 
@@ -90,7 +94,7 @@ func (m Model) renderRow(i int) string {
 		if !last {
 			if i == 0 {
 				cellStyle = cellStyle.Inherit(rowStyleLeft)
-			} else if i < numHeaders-1 {
+			} else if i < numColumns-1 {
 				cellStyle = cellStyle.Inherit(rowStyleInner)
 			} else {
 				cellStyle = cellStyle.Inherit(rowStyleRight)
@@ -98,14 +102,14 @@ func (m Model) renderRow(i int) string {
 		} else {
 			if i == 0 {
 				cellStyle = cellStyle.Inherit(rowLastStyleLeft)
-			} else if i < numHeaders-1 {
+			} else if i < numColumns-1 {
 				cellStyle = cellStyle.Inherit(rowLastStyleInner)
 			} else {
 				cellStyle = cellStyle.Inherit(rowLastStyleRight)
 			}
 		}
 
-		cellStr := cellStyle.Render(fmt.Sprintf(header.fmtString, limitStr(str, header.Width)))
+		cellStr := cellStyle.Render(fmt.Sprintf(column.fmtString, limitStr(str, column.Width)))
 
 		columnStrings = append(columnStrings, cellStr)
 	}
diff --git a/table/table.go b/table/table.go
index 6de10be..c5251d9 100644
--- a/table/table.go
+++ b/table/table.go
@@ -8,8 +8,6 @@ import (
 	"github.com/charmbracelet/lipgloss"
 )
 
-type MovementMode int
-
 const (
 	columnKeySelect = "___select___"
 )
@@ -18,8 +16,9 @@ var (
 	defaultHighlightStyle = lipgloss.NewStyle().Background(lipgloss.Color("#334"))
 )
 
+// Model is the main table model.  Create using New()
 type Model struct {
-	headers     []Header
+	columns     []Column
 	headerStyle lipgloss.Style
 
 	rows []Row
@@ -37,47 +36,53 @@ type Model struct {
 	border Border
 }
 
-func New(headers []Header) Model {
+// New creates a new table ready for further modifications
+func New(columns []Column) Model {
 	m := Model{
-		headers:        make([]Header, len(headers)),
+		columns:        make([]Column, len(columns)),
 		highlightStyle: defaultHighlightStyle.Copy(),
 		border:         borderDefault,
 	}
 
 	// Do a full deep copy to avoid unexpected edits
-	copy(m.headers, headers)
+	copy(m.columns, columns)
 
 	return m
 }
 
+// HeaderStyle sets the style to apply to the header text, such as color or bold
 func (m Model) HeaderStyle(style lipgloss.Style) Model {
 	m.headerStyle = style.Copy()
 	return m
 }
 
+// WithRows sets the rows to show as data in the table
 func (m Model) WithRows(rows []Row) Model {
 	m.rows = rows
 	return m
 }
 
+// SelectableRows sets whether or not rows are selectable.  If set, adds a column
+// in the front that acts as a checkbox and responds to controls if Focused
 func (m Model) SelectableRows(selectable bool) Model {
 	m.selectableRows = selectable
 
-	hasSelectHeader := m.headers[0].Key == columnKeySelect
+	hasSelectColumn := m.columns[0].Key == columnKeySelect
 
-	if hasSelectHeader != selectable {
+	if hasSelectColumn != selectable {
 		if selectable {
-			m.headers = append([]Header{
-				NewHeader(columnKeySelect, "[x]", 3),
-			}, m.headers...)
+			m.columns = append([]Column{
+				NewColumn(columnKeySelect, "[x]", 3),
+			}, m.columns...)
 		} else {
-			m.headers = m.headers[1:]
+			m.columns = m.columns[1:]
 		}
 	}
 
 	return m
 }
 
+// HighlightedRow returns the full Row that's currently highlighted by the user
 func (m Model) HighlightedRow() Row {
 	if len(m.rows) > 0 {
 		return m.rows[m.rowCursorIndex]
@@ -87,24 +92,31 @@ func (m Model) HighlightedRow() Row {
 	return Row{}
 }
 
+// SelectedRows returns all rows that have been set as selected by the user
 func (m Model) SelectedRows() []Row {
 	return m.selectedRows
 }
 
+// HighlightStyle sets a custom style to use when the row is being highlighted
+// by the cursor
 func (m Model) HighlightStyle(style lipgloss.Style) Model {
 	m.highlightStyle = style
 	return m
 }
 
+// Focused allows the table to show highlighted rows and take in controls of
+// up/down/space/etc to let the user navigate the table and interact with it
 func (m Model) Focused(focused bool) Model {
 	m.focused = focused
 	return m
 }
 
+// Init initializes the table per the Bubble Tea architecture
 func (m Model) Init() tea.Cmd {
 	return nil
 }
 
+// Update responds to input from the user or other messages from Bubble Tea
 func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
 	if !m.focused {
 		return m, nil
@@ -153,12 +165,14 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
 	return m, nil
 }
 
+// View renders the table.  It does not end in a newline, so that it can be
+// composed with other elements more consistently.
 func (m Model) View() string {
-	numHeaders := len(m.headers)
+	numColumns := len(m.columns)
 	hasRows := len(m.rows) > 0
 
 	// Safety valve for empty tables
-	if numHeaders == 0 {
+	if numColumns == 0 {
 		return ""
 	}
 
@@ -172,7 +186,7 @@ func (m Model) View() string {
 		headerStyleRight lipgloss.Style
 	)
 
-	if numHeaders == 1 {
+	if numColumns == 1 {
 		if hasRows {
 			headerStyleLeft = m.border.styleSingleColumnTop
 		} else {
@@ -196,13 +210,13 @@ func (m Model) View() string {
 		headerStyleRight = headerStyleRight.Copy().Inherit(m.headerStyle)
 	}
 
-	for i, header := range m.headers {
+	for i, header := range m.columns {
 		headerSection := fmt.Sprintf(header.fmtString, header.Title)
 		var borderStyle lipgloss.Style
 
 		if i == 0 {
 			borderStyle = headerStyleLeft
-		} else if i < len(m.headers)-1 {
+		} else if i < len(m.columns)-1 {
 			borderStyle = headerStyleInner
 		} else {
 			borderStyle = headerStyleRight


Test patch:
diff --git a/table/table_test.go b/table/table_test.go
index c82ec07..f03bb12 100644
--- a/table/table_test.go
+++ b/table/table_test.go
@@ -18,12 +18,12 @@ func TestBasicTableShowsAllHeaders(t *testing.T) {
 		secondWidth = 20
 	)
 
-	headers := []Header{
-		NewHeader(firstKey, firstTitle, firstWidth),
-		NewHeader(secondKey, secondTitle, secondWidth),
+	columns := []Column{
+		NewColumn(firstKey, firstTitle, firstWidth),
+		NewColumn(secondKey, secondTitle, secondWidth),
 	}
 
-	table := New(headers)
+	table := New(columns)
 
 	rendered := table.View()
 
@@ -33,7 +33,7 @@ func TestBasicTableShowsAllHeaders(t *testing.T) {
 	assert.False(t, strings.HasSuffix(rendered, "\n"), "Should not end in newline")
 }
 
-func TestNilHeadersSafelyReturnsEmptyView(t *testing.T) {
+func TestNilColumnsSafelyReturnsEmptyView(t *testing.T) {
 	table := New(nil)
 
 	assert.Equal(t, "", table.View())