Repository: Evertras/bubble-table
Pull request #127 (2022-08-28 08:19:03)

Add format string option to columns
Helps #126 

Adds a format string option to columns, so that users can provide a string used by `fmt.Sprintf` to display data.  Particularly useful for numeric values that can continue to be sorted.


Patch:
diff --git a/examples/sorting/main.go b/examples/sorting/main.go
index 5a065e9..e2d75c8 100644
--- a/examples/sorting/main.go
+++ b/examples/sorting/main.go
@@ -26,37 +26,38 @@ func NewModel() Model {
 		simpleTable: table.New([]table.Column{
 			table.NewColumn(columnKeyName, "Name", 13),
 			table.NewColumn(columnKeyType, "Type", 13),
-			table.NewColumn(columnKeyWins, "Wins", 5),
+			table.NewColumn(columnKeyWins, "Win %", 8).
+				WithFormatString("%.1f%%"),
 		}).WithRows([]table.Row{
 			table.NewRow(table.RowData{
 				columnKeyName: "ピカピカ",
 				columnKeyType: "Pikachu",
-				columnKeyWins: 4,
+				columnKeyWins: 78.3,
 			}),
 			table.NewRow(table.RowData{
 				columnKeyName: "Zapmouse",
 				columnKeyType: "Pikachu",
-				columnKeyWins: 3,
+				columnKeyWins: 3.3,
 			}),
 			table.NewRow(table.RowData{
 				columnKeyName: "Burninator",
 				columnKeyType: "Charmander",
-				columnKeyWins: 8,
+				columnKeyWins: 32.1,
 			}),
 			table.NewRow(table.RowData{
 				columnKeyName: "Alphonse",
 				columnKeyType: "Pikachu",
-				columnKeyWins: 13,
+				columnKeyWins: 13.8,
 			}),
 			table.NewRow(table.RowData{
 				columnKeyName: "Trogdor",
 				columnKeyType: "Charmander",
-				columnKeyWins: 9,
+				columnKeyWins: 99.9,
 			}),
 			table.NewRow(table.RowData{
 				columnKeyName: "Dihydrogen Monoxide",
 				columnKeyType: "Squirtle",
-				columnKeyWins: 31,
+				columnKeyWins: 31.348,
 			}),
 		}),
 	}
diff --git a/table/column.go b/table/column.go
index 4328a4f..800fde7 100644
--- a/table/column.go
+++ b/table/column.go
@@ -14,6 +14,8 @@ type Column struct {
 
 	filterable bool
 	style      lipgloss.Style
+
+	fmtString string
 }
 
 // NewColumn creates a new fixed-width column with the given information.
@@ -58,6 +60,19 @@ func (c Column) WithFiltered(filterable bool) Column {
 	return c
 }
 
+// WithFormatString sets the format string used by fmt.Sprintf to display the data.
+// If not set, the default is "%v" for all data types.  Intended mainly for
+// numeric formatting.
+//
+// Since data is of the interface{} type, make sure that all data in the column
+// is of the expected type or the format may fail.  For example, hardcoding '3'
+// instead of '3.0' and using '%.2f' will fail because '3' is an integer.
+func (c Column) WithFormatString(fmtString string) Column {
+	c.fmtString = fmtString
+
+	return c
+}
+
 func (c *Column) isFlex() bool {
 	return c.flexFactor != 0
 }
diff --git a/table/row.go b/table/row.go
index 2b08f3c..ac6dea5 100644
--- a/table/row.go
+++ b/table/row.go
@@ -71,12 +71,18 @@ func (m Model) renderRowColumnData(row Row, column Column, rowStyle lipgloss.Sty
 			data = ""
 		}
 
+		fmtString := "%v"
+
+		if column.fmtString != "" {
+			fmtString = column.fmtString
+		}
+
 		switch entry := data.(type) {
 		case StyledCell:
-			str = fmt.Sprintf("%v", entry.Data)
+			str = fmt.Sprintf(fmtString, entry.Data)
 			cellStyle = entry.Style.Copy().Inherit(cellStyle)
 		default:
-			str = fmt.Sprintf("%v", entry)
+			str = fmt.Sprintf(fmtString, entry)
 		}
 	}
 


Test patch:
diff --git a/table/view_test.go b/table/view_test.go
index 1a3f18a..bf8ff9c 100644
--- a/table/view_test.go
+++ b/table/view_test.go
@@ -1127,3 +1127,25 @@ func Test3x3WithRoundedBorder(t *testing.T) {
 
 	assert.Equal(t, expectedTable, rendered)
 }
+
+func TestSingleColumnViewSortedAndFormatted(t *testing.T) {
+	model := New([]Column{
+		NewColumn("name", "Name", 5),
+		NewColumn("val", "Value", 7).
+			WithFormatString("~%.2f"),
+	}).WithRows([]Row{
+		NewRow(RowData{"name": "π", "val": 3.14}),
+		NewRow(RowData{"name": "Φ", "val": 1.618}),
+	}).SortByAsc("val")
+
+	const expectedTable = `┏━━━━━┳━━━━━━━┓
+┃ Name┃  Value┃
+┣━━━━━╋━━━━━━━┫
+┃    Φ┃  ~1.62┃
+┃    π┃  ~3.14┃
+┗━━━━━┻━━━━━━━┛`
+
+	rendered := model.View()
+
+	assert.Equal(t, expectedTable, rendered)
+}