Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pagination #21

Merged
merged 11 commits into from
Feb 20, 2022
16 changes: 10 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
.PHONY: features
features:
.PHONY: example-features
example-features:
@go run ./examples/features/main.go

.PHONY: dimensions
dimensions:
.PHONY: example-dimensions
example-dimensions:
@go run ./examples/dimensions/main.go

.PHONY: updates
updates:
.PHONY: example-updates
example-updates:
@go run ./examples/updates/*.go

.PHONY: example-updates
example-pagination:
@go run ./examples/pagination/*.go

.PHONY: test
test:
@go test -race -cover ./table
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ keys can be customized with a KeyMap.

Can make rows selectable, and fetch the current selections.

Pagination can be set with a given page size, which automatically generates a
simple footer to show the current page and total pages.

## Defining table data

Each `Column` is associated with a unique string key. Each `Row` contains a
Expand Down Expand Up @@ -91,10 +94,10 @@ To run the examples, clone this repo and run:
make

# Run dimensions example to see multiple sizes of simple tables in action
make dimensions
make example-dimensions

# Or run any of them directly
go run ./examples/features/main.go
go run ./examples/pagination/main.go
```

## Contributing
Expand Down
29 changes: 26 additions & 3 deletions examples/features/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,18 @@ func NewModel() Model {
columnKeyDescription: "This is a really, really, really long description that will get cut off",
columnKeyCount: "N/A",
}),
table.NewRow(table.RowData{
columnKeyID: "2pg",
columnKeyName: "Page 2",
columnKeyDescription: "Second page",
columnKeyCount: 2,
}),
table.NewRow(table.RowData{
columnKeyID: "2pg2",
columnKeyName: "Page 2.1",
columnKeyDescription: "Second page again",
columnKeyCount: 4,
}),
}

// Start with the default key map and change it slightly, just for demoing
Expand All @@ -87,7 +99,8 @@ func NewModel() Model {
Focused(true).
Border(customBorder).
WithKeyMap(keys).
WithStaticFooter("Footer!"),
WithStaticFooter("Footer!").
WithPageSize(3),
}

model.updateFooter()
Expand All @@ -101,7 +114,15 @@ func (m Model) Init() tea.Cmd {

func (m *Model) updateFooter() {
highlightedRow := m.tableModel.HighlightedRow()
m.tableModel = m.tableModel.WithStaticFooter(fmt.Sprintf("Currently looking at ID: %s", highlightedRow.Data[columnKeyID]))

footerText := fmt.Sprintf(
"Pg. %d/%d - Currently looking at ID: %s",
m.tableModel.CurrentPage(),
m.tableModel.MaxPages(),
highlightedRow.Data[columnKeyID],
)

m.tableModel = m.tableModel.WithStaticFooter(footerText)
}

func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
Expand Down Expand Up @@ -130,7 +151,9 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
func (m Model) View() string {
body := strings.Builder{}

body.WriteString("Table demo with all features enabled!\nPress space/enter to select a row, q or ctrl+c to quit\n")
body.WriteString("Table demo with all features enabled!\n")
body.WriteString("Press left/right or page up/down to move pages\n")
body.WriteString("Press space/enter to select a row, q or ctrl+c to quit\n")

selectedIDs := []string{}

Expand Down
113 changes: 113 additions & 0 deletions examples/pagination/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package main

import (
"fmt"
"log"
"strings"

tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/evertras/bubble-table/table"
)

type Model struct {
tableDefault table.Model
tableWithRowIndices table.Model
}

func genTable(columnCount int, rowCount int) table.Model {
columns := []table.Column{}

for column := 0; column < columnCount; column++ {
columnStr := fmt.Sprintf("%d", column+1)
columns = append(columns, table.NewColumn(columnStr, columnStr, 8))
}

rows := []table.Row{}

for row := 1; row <= rowCount; row++ {
rowData := table.RowData{}

for column := 0; column < columnCount; column++ {
columnStr := fmt.Sprintf("%d", column+1)
rowData[columnStr] = fmt.Sprintf("%d - %d", column+1, row)
}

rows = append(rows, table.NewRow(rowData))
}

return table.New(columns).WithRows(rows).HeaderStyle(lipgloss.NewStyle().Bold(true))
}

func NewModel() Model {
return Model{
tableDefault: genTable(3, 105).WithPageSize(10).Focused(true),
tableWithRowIndices: genTable(3, 105).WithPageSize(10).Focused(false),
}
}

func (m Model) Init() tea.Cmd {
return nil
}

func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var (
cmd tea.Cmd
cmds []tea.Cmd
)

m.tableDefault, cmd = m.tableDefault.Update(msg)
cmds = append(cmds, cmd)

m.tableWithRowIndices, cmd = m.tableWithRowIndices.Update(msg)
cmds = append(cmds, cmd)

// Write a custom footer
start, end := m.tableWithRowIndices.VisibleIndices()
m.tableWithRowIndices = m.tableWithRowIndices.WithStaticFooter(
fmt.Sprintf("%d-%d of %d", start+1, end+1, m.tableWithRowIndices.TotalRows()),
)

switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "esc", "q":
cmds = append(cmds, tea.Quit)

case "a":
m.tableDefault = m.tableDefault.Focused(true)
m.tableWithRowIndices = m.tableWithRowIndices.Focused(false)

case "b":
m.tableDefault = m.tableDefault.Focused(false)
m.tableWithRowIndices = m.tableWithRowIndices.Focused(true)
}
}

return m, tea.Batch(cmds...)
}

func (m Model) View() string {
body := strings.Builder{}

body.WriteString("Table demo with pagination! Press left/right to move pages, or use page up/down\nPress 'a' for left table, 'b' for right table\nPress q or ctrl+c to quit\n\n")

pad := lipgloss.NewStyle().Padding(1)

tables := []string{
lipgloss.JoinVertical(lipgloss.Center, "A", pad.Render(m.tableDefault.View())),
lipgloss.JoinVertical(lipgloss.Center, "B", pad.Render(m.tableWithRowIndices.View())),
}

body.WriteString(lipgloss.JoinHorizontal(lipgloss.Top, tables...))

return body.String()
}

func main() {
p := tea.NewProgram(NewModel())

if err := p.Start(); err != nil {
log.Fatal(err)
}
}
16 changes: 14 additions & 2 deletions table/footer.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
package table

import "fmt"

func (m Model) hasFooter() bool {
return m.staticFooter != ""
return m.staticFooter != "" || m.pageSize != 0
}

func (m Model) renderFooter() string {
if !m.hasFooter() {
return ""
}

return m.border.styleFooter.Width(m.totalWidth).Render(m.staticFooter)
var footerText string

switch {
case m.staticFooter != "":
footerText = m.staticFooter

case m.pageSize != 0:
footerText = fmt.Sprintf("%d/%d", m.CurrentPage(), m.MaxPages())
}

return m.border.styleFooter.Width(m.totalWidth).Render(footerText)
}
9 changes: 9 additions & 0 deletions table/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ type KeyMap struct {
RowUp key.Binding

RowSelectToggle key.Binding

PageDown key.Binding
PageUp key.Binding
}

// DefaultKeyMap returns a set of sensible defaults for controlling a focused table.
Expand All @@ -22,5 +25,11 @@ func DefaultKeyMap() KeyMap {
RowSelectToggle: key.NewBinding(
key.WithKeys(" ", "enter"),
),
PageDown: key.NewBinding(
key.WithKeys("right", "l", "pgdown"),
),
PageUp: key.NewBinding(
key.WithKeys("left", "h", "pgup"),
),
}
}
28 changes: 16 additions & 12 deletions table/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,30 @@ var (

// Model is the main table model. Create using New().
type Model struct {
keyMap KeyMap
columns []Column
headerStyle lipgloss.Style

staticFooter string

rows []Row
// Data
columns []Column
rows []Row

// Interaction
focused bool
keyMap KeyMap
selectableRows bool

selectedRows []Row
rowCursorIndex int

focused bool

// Styles
highlightStyle lipgloss.Style
headerStyle lipgloss.Style
border Border

selectedRows []Row
// Footers
staticFooter string

border Border
// Pagination
pageSize int
currentPage int

// Internal cached calculations for reference
totalWidth int
}

Expand Down
14 changes: 14 additions & 0 deletions table/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,17 @@ func (m Model) WithStaticFooter(footer string) Model {

return m
}

// WithPageSize enables pagination using the given page size.
func (m Model) WithPageSize(pageSize int) Model {
m.pageSize = pageSize

return m
}

// WithNoPagination disable pagination in the table.
func (m Model) WithNoPagination() Model {
m.pageSize = 0

return m
}
Loading