Vanilla Example: Aggregation

import './index.css'
import {
  aggregationFeature,
  aggregationFn_count,
  aggregationFn_extent,
  aggregationFn_mean,
  aggregationFn_sum,
  columnFilteringFeature,
  constructTable,
  createColumnHelper,
  createFilteredRowModel,
  createPaginatedRowModel,
  filterFn_includesString,
  metaHelper,
  rowPaginationFeature,
  rowSelectionFeature,
  tableFeatures,
} from '@tanstack/table-core'
import { FlexRender } from '@tanstack/table-core/flex-render'
import { storeReactivityBindings } from '@tanstack/table-core/store-reactivity-bindings'
import { makeData } from './makeData'
import type { Sale } from './makeData'
import type { Table } from '@tanstack/table-core'

type RowSource = 'all' | 'custom' | 'filtered' | 'page' | 'selected'
type AggregationTableMeta = { rowSource: RowSource }
let data = makeData(10_000)
let rowSource: RowSource = 'filtered'
const features = tableFeatures({
  aggregationFeature,
  columnFilteringFeature,
  rowPaginationFeature,
  rowSelectionFeature,
  coreReactivityFeature: storeReactivityBindings(),
  filteredRowModel: createFilteredRowModel(),
  paginatedRowModel: createPaginatedRowModel(),
  filterFns: { includesString: filterFn_includesString },
  aggregationFns: {
    count: aggregationFn_count,
    extent: aggregationFn_extent,
    mean: aggregationFn_mean,
    sum: aggregationFn_sum,
  },
  tableMeta: metaHelper<AggregationTableMeta>(),
})
const columnHelper = createColumnHelper<typeof features, Sale>()
function getAggregationRows(table: Table<typeof features, Sale>) {
  const source = table.options.meta?.rowSource
  if (source === 'all') return table.getCoreRowModel().rows
  if (source === 'page') return table.getRowModel().rows
  if (source === 'selected') return table.getFilteredSelectedRowModel().rows
  if (source === 'custom') return table.getCoreRowModel().rows.slice(0, 3)
  return undefined
}
function formatValue(value: unknown): string {
  if (Array.isArray(value)) return value.map(formatValue).join(' – ')
  if (value && typeof value === 'object')
    return Object.entries(value)
      .map(([key, entry]) => `${key}: ${formatValue(entry)}`)
      .join(', ')
  if (typeof value === 'number')
    return value.toLocaleString(undefined, { maximumFractionDigits: 2 })
  return String(value ?? '—')
}
const columns = columnHelper.columns([
  columnHelper.display({ id: 'select' }),
  columnHelper.accessor('category', {
    header: 'Category',
    filterFn: 'includesString',
  }),
  columnHelper.accessor('item', {
    header: 'Item',
    footer: ({ table }) => `${table.options.meta?.rowSource} total`,
  }),
  columnHelper.accessor('amount', {
    header: 'Amount',
    aggregationFn: 'sum',
    cell: ({ getValue }) => getValue<number>().toLocaleString(),
    footer: ({ column, table }) =>
      formatValue(column.getAggregationValue(getAggregationRows(table))),
  }),
  columnHelper.accessor('score', {
    header: 'Score',
    aggregationFn: ['count', 'mean', { id: 'range', aggregationFn: 'extent' }],
    footer: ({ column, table }) =>
      formatValue(column.getAggregationValue(getAggregationRows(table))),
  }),
])
const el = <K extends keyof HTMLElementTagNameMap>(tag: K, text?: string) => {
  const node = document.createElement(tag)
  if (text !== undefined) node.textContent = text
  return node
}
function button(text: string, action: () => void, disabled = false) {
  const node = el('button', text)
  node.className = 'demo-button demo-button-sm'
  node.disabled = disabled
  node.onclick = action
  return node
}
function renderTable(table: Table<typeof features, Sale>) {
  const wrapper = document.getElementById('wrapper') as HTMLDivElement
  wrapper.innerHTML = ''
  wrapper.className = 'demo-root'
  wrapper.append(el('h1', 'Aggregation without grouping'))
  const description = el('p')
  description.innerHTML =
    'Amount uses a scalar <code>sum</code>. Score runs count, mean, and range together and returns a keyed object.'
  wrapper.append(description)
  const actions = el('div')
  actions.append(
    button('Regenerate Data', () => {
      data = makeData(10_000)
      table.setOptions((prev) => ({ ...prev, data }))
      renderTable(table)
    }),
    button('Stress Test (200k rows)', () => {
      data = makeData(200_000)
      table.setOptions((prev) => ({ ...prev, data }))
      renderTable(table)
    }),
  )
  wrapper.append(actions, Object.assign(el('div'), { className: 'spacer-sm' }))
  const controls = el('div')
  controls.className = 'controls'
  const filterLabel = el('label', 'Category filter: ')
  const filter = el('input')
  filter.value = String(table.getColumn('category')?.getFilterValue() ?? '')
  filter.oninput = () =>
    table.getColumn('category')?.setFilterValue(filter.value)
  filterLabel.append(filter)
  const sourceLabel = el('label', 'Total rows: ')
  const source = el('select')
  const sources: Array<[RowSource, string]> = [
    ['filtered', 'Filtered rows'],
    ['all', 'All rows'],
    ['page', 'Visible page'],
    ['selected', 'Filtered selected rows'],
    ['custom', 'First three core rows'],
  ]
  for (const [value, label] of sources) {
    const option = el('option', label)
    option.value = value
    option.selected = rowSource === value
    source.append(option)
  }
  source.onchange = () => {
    rowSource = source.value as RowSource
    table.setOptions((prev) => ({ ...prev, meta: { rowSource } }))
    renderTable(table)
  }
  sourceLabel.append(source)
  controls.append(filterLabel, sourceLabel)
  wrapper.append(controls, Object.assign(el('div'), { className: 'spacer-sm' }))
  const tableElement = el('table'),
    thead = el('thead'),
    tbody = el('tbody'),
    tfoot = el('tfoot')
  tableElement.append(thead, tbody, tfoot)
  for (const group of table.getHeaderGroups()) {
    const tr = el('tr')
    for (const header of group.headers) {
      const th = el('th')
      if (header.column.id === 'select') {
        const input = el('input')
        input.type = 'checkbox'
        input.checked = table.getIsAllPageRowsSelected()
        input.onchange = () => table.toggleAllPageRowsSelected()
        th.append(input)
      } else if (!header.isPlaceholder)
        th.innerHTML = String(FlexRender({ header }) ?? '')
      tr.append(th)
    }
    thead.append(tr)
  }
  for (const row of table.getRowModel().rows) {
    const tr = el('tr')
    for (const cell of row.getAllCells()) {
      const td = el('td')
      if (cell.column.id === 'amount') td.className = 'numeric'
      if (cell.column.id === 'select') {
        const input = el('input')
        input.type = 'checkbox'
        input.checked = row.getIsSelected()
        input.onchange = () => row.toggleSelected()
        td.append(input)
      } else td.innerHTML = String(FlexRender({ cell }) ?? '')
      tr.append(td)
    }
    tbody.append(tr)
  }
  for (const group of table.getFooterGroups()) {
    const tr = el('tr')
    for (const header of group.headers) {
      const th = el('th')
      th.colSpan = header.colSpan
      if (!header.isPlaceholder)
        th.innerHTML = String(FlexRender({ footer: header }) ?? '')
      tr.append(th)
    }
    tfoot.append(tr)
  }
  wrapper.append(
    tableElement,
    Object.assign(el('div'), { className: 'spacer-sm' }),
  )
  const pagination = el('div')
  pagination.className = 'controls'
  pagination.append(
    button('<<', () => table.firstPage(), !table.getCanPreviousPage()),
    button('<', () => table.previousPage(), !table.getCanPreviousPage()),
    button('>', () => table.nextPage(), !table.getCanNextPage()),
    button('>>', () => table.lastPage(), !table.getCanNextPage()),
  )
  const info = el('span')
  info.className = 'inline-controls'
  info.innerHTML = `<div>Page</div><strong>${table.store.state.pagination.pageIndex + 1} of ${table.getPageCount().toLocaleString()}</strong>`
  pagination.append(info)
  const pageLabel = el('span', '| Go to page:')
  pageLabel.className = 'inline-controls'
  const page = el('input')
  page.type = 'number'
  page.min = '1'
  page.max = String(table.getPageCount())
  page.value = String(table.store.state.pagination.pageIndex + 1)
  page.className = 'page-size-input'
  page.oninput = () =>
    table.setPageIndex(page.value ? Number(page.value) - 1 : 0)
  pageLabel.append(page)
  pagination.append(pageLabel)
  const size = el('select')
  for (const value of [10, 20, 30, 40, 50]) {
    const option = el('option', `Show ${value}`)
    option.value = String(value)
    option.selected = table.store.state.pagination.pageSize === value
    size.append(option)
  }
  size.onchange = () => table.setPageSize(Number(size.value))
  pagination.append(size)
  wrapper.append(pagination)
  wrapper.append(
    el(
      'div',
      `Showing ${table.getRowModel().rows.length.toLocaleString()} of ${table.getRowCount().toLocaleString()} Rows`,
    ),
    Object.assign(el('pre'), {
      textContent: JSON.stringify(table.store.state, null, 2),
    }),
  )
}
const table = constructTable({
  features,
  data,
  columns,
  meta: { rowSource },
  initialState: { pagination: { pageIndex: 0, pageSize: 10 } },
  debugTable: true,
  debugColumns: true,
})
table.store.subscribe(() => renderTable(table))
renderTable(table)