Skip to content

Pagination ​

By default all rows are rendered in one scrollable (virtualized) list. Enable pagination if your users prefer pages:

Source
vue
<template>
  <div class="demo">
    <Datagrid
      :columns="columns"
      :rows="rows"
      :options="options"
      v-model:pageIndex="page"
    />
    <div class="demo-toolbar">Current page index: {{ page }}</div>
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";
import { Datagrid, type ColumnConfig, type GridOptions } from "@datagrid/vue-ui";
import { createEmployees, type Employee } from "./data";

const rows = createEmployees(1234);
const page = ref(0);

const columns: Array<ColumnConfig<Employee>> = [
  { field: "id", text: "ID", type: "number", width: 80 },
  { field: "firstName", text: "First name" },
  { field: "lastName", text: "Last name" },
  { field: "department", text: "Department" },
  { field: "salary", text: "Salary", type: "number" },
];

const options: GridOptions<Employee> = {
  pagination: { enabled: true, pageSize: 20, pageSizes: [20, 50, 100] },
  defaultColumn: { sortable: true, filterable: true, flex: 1 },
};
</script>
tsx
import { useState } from "react";
import { Datagrid, type ColumnConfig, type GridOptions } from "@datagrid/react-ui";
import { createEmployees, type Employee } from "../data";

const rows = createEmployees(1234);

const columns: Array<ColumnConfig<Employee>> = [
  { field: "id", text: "ID", type: "number", width: 80 },
  { field: "firstName", text: "First name" },
  { field: "lastName", text: "Last name" },
  { field: "department", text: "Department" },
  { field: "salary", text: "Salary", type: "number" },
];

const options: GridOptions<Employee> = {
  pagination: { enabled: true, pageSize: 20, pageSizes: [20, 50, 100] },
  defaultColumn: { sortable: true, filterable: true, flex: 1 },
};

export default function Pagination() {
  const [page, setPage] = useState(0);

  return (
    <div className="demo">
      <Datagrid columns={columns} rows={rows} options={options} pageIndex={page} onPageIndexChange={setPage} />
      <div className="demo-toolbar">Current page index: {page}</div>
    </div>
  );
}

Options ​

ts
pagination: {
  enabled: true,
  pageSize: 25,
  pageSizes: [10, 25, 50, 100], // [] hides the page size selector
}

Pagination is applied after filtering, sorting and grouping. With server side data the page is loaded from the server.

Switching pagination on and off ​

The pagination options can be changed at any time, e.g. to let the user choose between pages and one scrollable list. pageSizes: [] hides the page size selector of the pagination bar – here the page size is chosen in the toolbar instead.

Source
vue
<template>
  <div class="demo">
    <div class="demo-toolbar">
      <label><input type="checkbox" v-model="paged" /> Pages</label>
      <label>
        Rows per page
        <select v-model.number="pageSize" :disabled="!paged">
          <option :value="10">10</option>
          <option :value="15">15</option>
          <option :value="30">30</option>
        </select>
      </label>
    </div>
    <Datagrid :columns="columns" :rows="rows" :options="options" />
  </div>
</template>

<script setup lang="ts">
import { computed, ref } from "vue";
import { Datagrid, formatters, type ColumnConfig, type GridOptions } from "@datagrid/vue-ui";
import { createOrders, type Order } from "./data";

const rows = createOrders(300);
const paged = ref(true);
const pageSize = ref(10);

const columns: Array<ColumnConfig<Order>> = [
  { field: "id", text: "Order", type: "number", width: 90 },
  { field: "customer", text: "Customer", flex: 1 },
  { field: "date", text: "Date", type: "date" },
  { field: "status", text: "Status" },
  { field: "total", text: "Total", type: "number", valueFormatter: formatters.currency("EUR") },
];

const options = computed<GridOptions<Order>>(() => ({
  // the page size is chosen outside of the grid, [] hides the selector in the pagination bar
  pagination: { enabled: paged.value, pageSize: pageSize.value, pageSizes: [] },
  rowId: (data) => data.id,
  defaultColumn: { sortable: true, width: 130 },
}));
</script>
tsx
import { useMemo, useState } from "react";
import { Datagrid, formatters, type ColumnConfig, type GridOptions } from "@datagrid/react-ui";
import { createOrders, type Order } from "../data";

const rows = createOrders(300);

const columns: Array<ColumnConfig<Order>> = [
  { field: "id", text: "Order", type: "number", width: 90 },
  { field: "customer", text: "Customer", flex: 1 },
  { field: "date", text: "Date", type: "date" },
  { field: "status", text: "Status" },
  { field: "total", text: "Total", type: "number", valueFormatter: formatters.currency("EUR") },
];

export default function PaginationToggle() {
  const [paged, setPaged] = useState(true);
  const [pageSize, setPageSize] = useState(10);

  const options = useMemo<GridOptions<Order>>(
    () => ({
      // the page size is chosen outside of the grid, [] hides the selector in the pagination bar
      pagination: { enabled: paged, pageSize, pageSizes: [] },
      rowId: (data) => data.id,
      defaultColumn: { sortable: true, width: 130 },
    }),
    [paged, pageSize]
  );

  return (
    <div className="demo">
      <div className="demo-toolbar">
        <label>
          <input type="checkbox" checked={paged} onChange={(e) => setPaged(e.target.checked)} /> Pages
        </label>
        <label>
          Rows per page{" "}
          <select value={pageSize} disabled={!paged} onChange={(e) => setPageSize(Number(e.target.value))}>
            <option value={10}>10</option>
            <option value={15}>15</option>
            <option value={30}>30</option>
          </select>
        </label>
      </div>
      <Datagrid columns={columns} rows={rows} options={options} />
    </div>
  );
}

Pagination with grouping ​

With row grouping the grid paginates the top level groups: a page contains pageSize groups with all their rows, so a group is never split across two pages. The pagination bar counts groups instead of rows.

Source
vue
<template>
  <div class="demo">
    <div class="demo-toolbar">
      <button @click="api?.expandAll()">Expand all</button>
      <button @click="api?.collapseAll()">Collapse all</button>
    </div>
    <Datagrid
      :columns="columns"
      :rows="rows"
      :options="options"
      :columnGroups="groups"
      @ready="api = $event"
    />
  </div>
</template>

<script setup lang="ts">
import { shallowRef } from "vue";
import { Datagrid, formatters, type ColumnConfig, type Core, type GridOptions } from "@datagrid/vue-ui";
import { createEmployees, type Employee } from "./data";

const rows = createEmployees(300);
const api = shallowRef<Core>();
const groups = ["city"];

const columns: Array<ColumnConfig<Employee>> = [
  { field: "city", text: "City", groupable: true },
  { field: "lastName", text: "Name" },
  { field: "department", text: "Department" },
  {
    field: "salary",
    text: "Salary (avg)",
    type: "number",
    aggFunc: "avg",
    valueFormatter: formatters.currency("EUR", { maximumFractionDigits: 0 }),
  },
];

const options: GridOptions<Employee> = {
  // with row grouping a page contains 4 groups (with all their rows)
  pagination: { enabled: true, pageSize: 4, pageSizes: [] },
  groupShowCount: true,
  defaultColumn: { sortable: true, flex: 1 },
};
</script>
tsx
import { useRef } from "react";
import {
  Datagrid,
  formatters,
  type ColumnConfig,
  type DatagridHandle,
  type GridOptions,
} from "@datagrid/react-ui";
import { createEmployees, type Employee } from "../data";

const rows = createEmployees(300);
const groups = ["city"];

const columns: Array<ColumnConfig<Employee>> = [
  { field: "city", text: "City", groupable: true },
  { field: "lastName", text: "Name" },
  { field: "department", text: "Department" },
  {
    field: "salary",
    text: "Salary (avg)",
    type: "number",
    aggFunc: "avg",
    valueFormatter: formatters.currency("EUR", { maximumFractionDigits: 0 }),
  },
];

const options: GridOptions<Employee> = {
  // with row grouping a page contains 4 groups (with all their rows)
  pagination: { enabled: true, pageSize: 4, pageSizes: [] },
  groupShowCount: true,
  defaultColumn: { sortable: true, flex: 1 },
};

export default function PaginationGrouping() {
  const grid = useRef<DatagridHandle>(null);

  return (
    <div className="demo">
      <div className="demo-toolbar">
        <button onClick={() => grid.current?.expandAll()}>Expand all</button>
        <button onClick={() => grid.current?.collapseAll()}>Collapse all</button>
      </div>
      <Datagrid ref={grid} columns={columns} rows={rows} options={options} columnGroups={groups} />
    </div>
  );
}

Binding and API ​

Bind the page index to control the page from outside of the grid, e.g. with your own buttons or to show a specific row. The page of a row depends on the current sort and filter, so the demo looks up the position of the employee in api.getFilteredSortedItems() and divides it by the page size:

Source
vue
<template>
  <div class="demo">
    <div class="demo-toolbar">
      <button :disabled="page === 0" @click="page--">Previous</button>
      <span>Page {{ page + 1 }} of {{ pageCount }}</span>
      <button :disabled="page >= pageCount - 1" @click="page++">Next</button>
      <input type="text" v-model="search" placeholder="Employee ID, e.g. 345" @keydown.enter="show" />
      <button @click="show">Show employee</button>
      <span>{{ message }}</span>
    </div>
    <Datagrid
      :columns="columns"
      :rows="rows"
      :options="options"
      v-model:pageIndex="page"
      v-model:selectedItems="selected"
      @ready="api = $event"
      @page-change="pageCount = $event.pageCount"
    />
  </div>
</template>

<script setup lang="ts">
import { ref, shallowRef } from "vue";
import { Datagrid, type ColumnConfig, type Core, type GridOptions, type ItemUid, type Row } from "@datagrid/vue-ui";
import { createEmployees, type Employee } from "./data";

const rows = createEmployees(500);
const api = shallowRef<Core>();
const page = ref(0);
const pageCount = ref(1);
const selected = ref<Array<ItemUid>>([]);
const search = ref("");
const message = ref("");

function show() {
  const id = Number(search.value);
  // position of the row after sorting and filtering (the page depends on the current sort)
  const index = api.value!.getFilteredSortedItems().findIndex((row) => (row as Row<Employee>).data.id === id);
  if (index === -1) {
    message.value = `Employee ${search.value} not found`;
    return;
  }
  page.value = Math.floor(index / api.value!.getPaginationState().pageSize);
  selected.value = [id];
  message.value = `Employee ${id} is on page ${page.value + 1}`;
}

const columns: Array<ColumnConfig<Employee>> = [
  { field: "id", text: "ID", type: "number", width: 80 },
  { field: "firstName", text: "First name" },
  { field: "lastName", text: "Last name", sort: "asc" },
  { field: "department", text: "Department" },
  { field: "city", text: "City" },
];

const options: GridOptions<Employee> = {
  rowId: (data) => data.id,
  selection: "Row",
  pagination: { enabled: true, pageSize: 10, pageSizes: [10, 20, 50] },
  defaultColumn: { sortable: true, flex: 1 },
};
</script>
tsx
import { useRef, useState } from "react";
import {
  Datagrid,
  type ColumnConfig,
  type DatagridHandle,
  type GridOptions,
  type ItemUid,
  type Row,
} from "@datagrid/react-ui";
import { createEmployees, type Employee } from "../data";

const rows = createEmployees(500);

const columns: Array<ColumnConfig<Employee>> = [
  { field: "id", text: "ID", type: "number", width: 80 },
  { field: "firstName", text: "First name" },
  { field: "lastName", text: "Last name", sort: "asc" },
  { field: "department", text: "Department" },
  { field: "city", text: "City" },
];

const options: GridOptions<Employee> = {
  rowId: (data) => data.id,
  selection: "Row",
  pagination: { enabled: true, pageSize: 10, pageSizes: [10, 20, 50] },
  defaultColumn: { sortable: true, flex: 1 },
};

export default function PaginationJump() {
  const grid = useRef<DatagridHandle>(null);
  const [page, setPage] = useState(0);
  const [pageCount, setPageCount] = useState(1);
  const [selected, setSelected] = useState<Array<ItemUid>>([]);
  const [search, setSearch] = useState("");
  const [message, setMessage] = useState("");

  function show() {
    const api = grid.current!.api;
    const id = Number(search);
    // position of the row after sorting and filtering (the page depends on the current sort)
    const index = api.getFilteredSortedItems().findIndex((row) => (row as Row<Employee>).data.id === id);
    if (index === -1) {
      setMessage(`Employee ${search} not found`);
      return;
    }
    const newPage = Math.floor(index / api.getPaginationState().pageSize);
    setPage(newPage);
    setSelected([id]);
    setMessage(`Employee ${id} is on page ${newPage + 1}`);
  }

  return (
    <div className="demo">
      <div className="demo-toolbar">
        <button disabled={page === 0} onClick={() => setPage(page - 1)}>
          Previous
        </button>
        <span>
          Page {page + 1} of {pageCount}
        </span>
        <button disabled={page >= pageCount - 1} onClick={() => setPage(page + 1)}>
          Next
        </button>
        <input
          type="text"
          value={search}
          placeholder="Employee ID, e.g. 345"
          onChange={(e) => setSearch(e.target.value)}
          onKeyDown={(e) => e.key === "Enter" && show()}
        />
        <button onClick={show}>Show employee</button>
        <span>{message}</span>
      </div>
      <Datagrid
        ref={grid}
        columns={columns}
        rows={rows}
        options={options}
        pageIndex={page}
        onPageIndexChange={setPage}
        onPageChange={(state) => setPageCount(state.pageCount)}
        selectedItems={selected}
        onSelectedItemsChange={setSelected}
      />
    </div>
  );
}
vue
<Datagrid v-model:pageIndex="page" ... />
tsx
<Datagrid pageIndex={page} onPageIndexChange={setPage} ... />
ts
api.setPageIndex(3);   // zero based
api.nextPage();
api.prevPage();
api.setPageSize(50);
api.getPaginationState();
// { active, pageIndex, pageCount, pageSize, pageSizes, hasPreviousPage, hasNextPage, rowCount }

@page-change is emitted when the page or the page size changes.

Released under the ISC License.