Skip to content

Headless usage ​

The core of the grid does not depend on a UI framework or on the DOM. It can be used to process data (sorting, filtering, grouping, pagination, selection) for your own user interface.

useDatagrid ​

useDatagrid creates a grid instance without user interface and provides its state reactively:

vue
<template>
  <div class="demo">
    <div class="demo-toolbar">
      <input v-model="search" type="search" placeholder="Search…" />
      <button @click="api.sortColumn('salary', 'desc')">Highest salary</button>
      <button @click="api.sortColumn('lastName', 'asc')">By name</button>
      <button :disabled="!state.pagination?.hasPreviousPage" @click="api.prevPage()">‹</button>
      <span>page {{ (state.pagination?.pageIndex ?? 0) + 1 }} / {{ state.pagination?.pageCount }}</span>
      <button :disabled="!state.pagination?.hasNextPage" @click="api.nextPage()">›</button>
    </div>
    <div class="cards">
      <div v-for="employee in employees" :key="employee.id" class="card">
        <strong>{{ employee.firstName }} {{ employee.lastName }}</strong>
        <span>{{ employee.department }}</span>
        <span>{{ employee.salary.toLocaleString() }} €</span>
      </div>
    </div>
  </div>
</template>

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

const columns: Array<ColumnConfig<Employee>> = [
  { field: "firstName" },
  { field: "lastName", sortable: true },
  { field: "department" },
  { field: "salary", type: "number", sortable: true },
];

// sorting, filtering and pagination without the grid ui
const { state, api } = useDatagrid(columns, createEmployees(200), {
  pagination: { enabled: true, pageSize: 12 },
});

// state.items contains the rows of the current page (sorted and filtered)
const employees = computed(() =>
  state.value.items.filter((item) => item.isRow).map((item) => (item as Row<Employee>).data)
);

const search = ref("");
watch(search, (value) => api.setGlobalFilter(value));
</script>

<style>
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 8px;
  overflow: auto;
}

.card {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 10px;
  border: 1px solid var(--vp-c-divider);
  border-radius: 8px;
  font-size: 14px;
}
</style>
tsx
import { useDatagrid, type ColumnConfig, type GridOptions, type Row } from "@datagrid/react-ui";
import { createEmployees, type Employee } from "../data";

const rows = createEmployees(200);

const columns: Array<ColumnConfig<Employee>> = [
  { field: "firstName" },
  { field: "lastName", sortable: true },
  { field: "department" },
  { field: "salary", type: "number", sortable: true },
];

const options: GridOptions<Employee> = {
  pagination: { enabled: true, pageSize: 12 },
};

export default function Headless() {
  // sorting, filtering and pagination without the grid ui
  const { state, api } = useDatagrid(columns, rows, options);
  const { items, pagination } = state;

  const employees = items.filter((item) => item.isRow).map((item) => (item as Row<Employee>).data);

  return (
    <div className="demo">
      <div className="demo-toolbar">
        <input onChange={(e) => api?.setGlobalFilter(e.target.value)} type="search" placeholder="Search…" />
        <button onClick={() => api?.sortColumn("salary", "desc")}>Highest salary</button>
        <button onClick={() => api?.sortColumn("lastName", "asc")}>By name</button>
        <button disabled={!pagination?.hasPreviousPage} onClick={() => api?.prevPage()}>
          ‹
        </button>
        <span>
          page {(pagination?.pageIndex ?? 0) + 1} / {pagination?.pageCount}
        </span>
        <button disabled={!pagination?.hasNextPage} onClick={() => api?.nextPage()}>
          ›
        </button>
      </div>
      <div className="cards">
        {employees.map((employee) => (
          <div key={employee.id} className="card">
            <strong>
              {employee.firstName} {employee.lastName}
            </strong>
            <span>{employee.department}</span>
            <span>{employee.salary.toLocaleString()} €</span>
          </div>
        ))}
      </div>
      <style>{`
        .cards {
          display: grid;
          grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
          gap: 8px;
          overflow: auto;
        }

        .card {
          display: flex;
          flex-direction: column;
          gap: 2px;
          padding: 10px;
          border: 1px solid var(--vp-c-divider);
          border-radius: 8px;
          font-size: 14px;
        }
      `}</style>
    </div>
  );
}
ts
import { useDatagrid } from "@datagrid/vue-ui";

const { state, api } = useDatagrid(columns, rows, options);
  • columns, rows and options can be refs, changes are applied automatically.
  • state.value contains items (rows and group rows of the current view), columns, rowCount, selectedKeys, expandedKeys, pagination and the complete grid state.
  • api is the complete grid API.
ts
import { useDatagrid } from "@datagrid/react-ui";

const { state, api } = useDatagrid(columns, rows, options);
  • New values of columns, rows and options are applied when their reference changes (keep them stable with useMemo or module constants).
  • state contains items (rows and group rows of the current view), columns, rowCount, selectedKeys, expandedKeys, pagination and the complete grid state. The component re-renders when it changes.
  • api is the complete grid API. It is undefined during the first render.

Core without a framework ​

ts
import { Core } from "@datagrid/core";

const core = new Core(undefined, { pagination: { enabled: true, pageSize: 20 } }, {
  onRowsChanged: () => render(core.getAllVisibleItems()),
});

core.initialize(undefined, columns, rows);
core.sortColumn("name", "asc");
core.setColumnFilter("age", ">30");

The first parameter is an optional function to measure column widths for autosizing. Passing a DOM element to initialize (with .ut-header and .ut-body children) enables the virtualization and the mouse and keyboard handling, which is what the Vue and React components do. They are a good reference for building adapters for other frameworks.

Released under the ISC License.