Repository: Evertras/bubble-table
Pull request #57 (2022-04-06 12:54:28)

Add programmatic options for pagination controls
Adds various programmatic calls to control pagination.

* PageDown
* PageUp
* PageFirst
* PageLast
* WithCurrentPage

Evertras: Helps #55 

Patch:
diff --git a/examples/pagination/main.go b/examples/pagination/main.go
index defc02a..93bece1 100644
--- a/examples/pagination/main.go
+++ b/examples/pagination/main.go
@@ -3,6 +3,7 @@ package main
 import (
 	"fmt"
 	"log"
+	"math/rand"
 	"strings"
 
 	tea "github.com/charmbracelet/bubbletea"
@@ -90,6 +91,10 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 			m.tableDefault = m.tableDefault.Focused(false)
 			m.tableWithRowIndices = m.tableWithRowIndices.Focused(true)
 
+		case "r":
+			m.tableDefault = m.tableDefault.WithCurrentPage(rand.Intn(m.tableDefault.MaxPages()) + 1)
+			m.tableWithRowIndices = m.tableWithRowIndices.WithCurrentPage(rand.Intn(m.tableWithRowIndices.MaxPages()) + 1)
+
 		case "z":
 			if m.rowCount < 10 {
 				break
@@ -122,7 +127,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 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 'z' to reduce rows by 10, 'y' to increase rows by 10\nPress q or ctrl+c to quit\n\n")
+	body.WriteString("Table demo with pagination! Press left/right to move pages, or use page up/down, or 'r' to jump to a random page\nPress 'a' for left table, 'b' for right table\nPress 'z' to reduce rows by 10, 'y' to increase rows by 10\nPress q or ctrl+c to quit\n\n")
 
 	pad := lipgloss.NewStyle().Padding(1)
 
diff --git a/table/options.go b/table/options.go
index ad24c34..439c40f 100644
--- a/table/options.go
+++ b/table/options.go
@@ -176,3 +176,57 @@ func (m Model) WithTargetWidth(totalWidth int) Model {
 
 	return m
 }
+
+// PageDown goes to the next page of a paginated table, wrapping to the first
+// page if the table is already on the last page.
+func (m Model) PageDown() Model {
+	m.pageDown()
+
+	return m
+}
+
+// PageUp goes to the previous page of a paginated table, wrapping to the
+// last page if the table is already on the first page.
+func (m Model) PageUp() Model {
+	m.pageUp()
+
+	return m
+}
+
+// PageLast goes to the last page of a paginated table.
+func (m Model) PageLast() Model {
+	m.pageLast()
+
+	return m
+}
+
+// PageFirst goes to the first page of a paginated table.
+func (m Model) PageFirst() Model {
+	m.pageFirst()
+
+	return m
+}
+
+// WithCurrentPage sets the current page (1 as the first page) of a paginated
+// table, bounded to the total number of pages.  The current selected row will
+// be set to the top row of the page if the page changed.
+func (m Model) WithCurrentPage(currentPage int) Model {
+	if m.pageSize == 0 || currentPage == m.CurrentPage() {
+		return m
+	}
+
+	if currentPage < 1 {
+		currentPage = 1
+	} else {
+		maxPages := m.MaxPages()
+
+		if currentPage > maxPages {
+			currentPage = maxPages
+		}
+	}
+
+	m.currentPage = currentPage - 1
+	m.rowCursorIndex = m.currentPage * m.pageSize
+
+	return m
+}


Test patch:
diff --git a/table/options_test.go b/table/options_test.go
index 24035b6..1e173d9 100644
--- a/table/options_test.go
+++ b/table/options_test.go
@@ -62,3 +62,71 @@ func TestWithHighlightedRowSetTooHigh(t *testing.T) {
 
 	assert.Equal(t, model.rows[1], model.HighlightedRow())
 }
+
+// This is long only because it's a lot of repetitive test cases
+// nolint: funlen
+func TestPageOptions(t *testing.T) {
+	const (
+		pageSize = 5
+		rowCount = 30
+	)
+
+	cols := []Column{
+		NewColumn("id", "ID", 3),
+	}
+
+	rows := make([]Row, rowCount)
+
+	model := New(cols).WithRows(rows).WithPageSize(pageSize)
+	assert.Equal(t, 1, model.CurrentPage())
+
+	model = model.PageDown()
+	assert.Equal(t, 2, model.CurrentPage())
+
+	model = model.PageDown()
+	model = model.PageUp()
+	assert.Equal(t, 2, model.CurrentPage())
+
+	model = model.PageLast()
+	assert.Equal(t, 6, model.CurrentPage())
+
+	model = model.PageLast()
+	model = model.PageLast()
+	assert.Equal(t, 6, model.CurrentPage())
+
+	model = model.PageFirst()
+	assert.Equal(t, 1, model.CurrentPage())
+
+	model = model.PageFirst()
+	model = model.PageFirst()
+	assert.Equal(t, 1, model.CurrentPage())
+
+	model = model.PageUp()
+	assert.Equal(t, 6, model.CurrentPage())
+
+	model = model.PageDown()
+	assert.Equal(t, 1, model.CurrentPage())
+
+	model = model.WithCurrentPage(3)
+	model = model.WithCurrentPage(3)
+	model = model.WithCurrentPage(3)
+	assert.Equal(t, 3, model.CurrentPage())
+	assert.Equal(t, 10, model.rowCursorIndex)
+
+	model = model.WithCurrentPage(-1)
+	assert.Equal(t, 1, model.CurrentPage())
+	assert.Equal(t, 0, model.rowCursorIndex)
+
+	model = model.WithCurrentPage(0)
+	assert.Equal(t, 1, model.CurrentPage())
+	assert.Equal(t, 0, model.rowCursorIndex)
+
+	model = model.WithCurrentPage(7)
+	assert.Equal(t, 6, model.CurrentPage())
+	assert.Equal(t, 25, model.rowCursorIndex)
+
+	model.rowCursorIndex = 26
+	model = model.WithCurrentPage(6)
+	assert.Equal(t, 6, model.CurrentPage())
+	assert.Equal(t, 26, model.rowCursorIndex)
+}