Data Pushing or Pulling
Control data loading with the server data source and use hybrid patterns where row data comes from both the client and the server.
Pulling Data
Use the pushRequests method on the server data source to manually trigger server data fetches.
Calling this method bypasses existing request tracking and processes these requests as new.
The grid resolves row data conflicts and cancels pending requests if
the grid resets before the server responds.
Click the Request Data button in the demo below to manually refresh the grid, simulating a user-triggered polling update.
Server Data Pulling
1"use client";2import "@1771technologies/lytenyte-pro/light-dark.css";3import "@1771technologies/lytenyte-pro/components.css";4import { useMemo } from "react";5import { Server } from "./server.js";6import type { DataEntry } from "./data";7import { HeaderCell, NumberCell } from "./components.js";8import { Grid, useServerDataSource } from "@1771technologies/lytenyte-pro";9import { logos } from "@1771technologies/grid-sample-data/stock-data-smaller";10import { RowGroupCell } from "@1771technologies/lytenyte-pro/components";11
12export interface GridSpec {13 readonly data: DataEntry;14 readonly column: { agg?: string };15}16
17const columns: Grid.Column<GridSpec>[] = [18 {19 id: "bid",20 name: "Bid",21 type: "number",22 cellRenderer: NumberCell,23 width: 120,24 widthFlex: 1,25 agg: "avg",26 },27 {28 id: "ask",29 name: "Ask",30 type: "number",31 cellRenderer: NumberCell,32 width: 120,33 widthFlex: 1,34 agg: "avg",35 },36 {37 id: "spread",38 name: "Spread",39 type: "number",40 cellRenderer: NumberCell,41 width: 120,42 widthFlex: 1,43 agg: "avg",44 },45
46 {47 id: "volatility",48 name: "Volatility",49 type: "number",50 cellRenderer: NumberCell,51 width: 120,52 widthFlex: 1,53 agg: "first",54 },55 {56 id: "latency",57 name: "Latency",58 type: "number",59 cellRenderer: NumberCell,60 width: 120,61 widthFlex: 1,62 agg: "first",63 },64 {65 id: "symbol",66 name: "Symbol",67 hide: true,68 type: "number",69 agg: "first",70 },71];72
73const base: Grid.ColumnBase<GridSpec> = { headerRenderer: HeaderCell };74const group: Grid.RowGroupColumn<GridSpec> = {75 cellRenderer: (p) => {76 return (77 <RowGroupCell78 {...p}79 groupLabel={(row) => {80 const symbol = row.key;81
82 return (83 <div className="flex h-full w-full items-center gap-2 overflow-hidden text-nowrap">84 <div className="flex h-8 min-h-8 w-8 min-w-8 items-center justify-center overflow-hidden rounded-full">85 <img86 src={logos[symbol!]}87 alt={`Logo of ${symbol}`}88 className="h-6.5 min-h-6.5 w-6.5 min-w-[26] rounded-full bg-black p-1"89 />90 </div>91 <div className="symbol-cell min-w-15 flex items-center justify-center rounded-2xl bg-teal-600/20 px-1 py-0.5 text-xs">92 {symbol}93 </div>94 </div>95 );96 }}97 />98 );99 },100 pin: "start",101 width: 170,102};103
104const groupModel = ["symbol"];105const aggModel = {106 time: { fn: "first" },107 volume: { fn: "group" },108 bid: { fn: "avg" },109 ask: { fn: "avg" },110 spread: { fn: "avg" },111 volatility: { fn: "first" },112 latency: { fn: "first" },113 pnl: { fn: "first" },114 symbol: { fn: "first" },115};116
117export default function ServerDataDemo() {118 const ds = useServerDataSource<DataEntry>({119 queryFn: async (params) => {120 return await Server(params.requests, groupModel, aggModel);121 },122 hasRowBranches: true,123 queryKey: [],124 blockSize: 50,125 });126
127 const isLoading = ds.isLoading.useValue();128
129 return (130 <>131 <div className="px-2 py-1">132 <button133 data-ln-button="website"134 data-ln-size="md"135 onClick={() => {136 ds.pushRequests(ds.requestsForView.get());137 }}138 >139 Request Data140 </button>141 </div>142 <div className="ln-grid" style={{ height: 500 }}>143 <Grid144 rowSource={ds}145 columns={columns}146 columnBase={base}147 rowGroupColumn={group}148 styles={useMemo(() => {149 return { viewport: { style: { scrollbarGutter: "stable" } } };150 }, [])}151 slotViewportOverlay={152 isLoading && (153 <div className="bg-ln-gray-20/40 absolute left-0 top-0 z-20 h-full w-full animate-pulse"></div>154 )155 }156 />157 </div>158 </>159 );160}1import "./component.css";2import { logos } from "@1771technologies/grid-sample-data/stock-data-smaller";3import { useEffect, useMemo, useRef } from "react";4
5import type { ClassValue } from "clsx";6import clsx from "clsx";7import { twMerge } from "tailwind-merge";8import type { Grid } from "@1771technologies/lytenyte-pro";9import type { GridSpec } from "./demo";10import { ArrowDownIcon, ArrowUpIcon } from "@radix-ui/react-icons";11
12export function tw(...c: ClassValue[]) {13 return twMerge(clsx(...c));14}15
16const formatter = new Intl.NumberFormat("en-US", {17 minimumFractionDigits: 2,18 maximumFractionDigits: 2,19});20
21export function NumberCell({ api, column, row }: Grid.T.CellRendererParams<GridSpec>) {22 const field = api.columnField(column, row) as number;23
24 const prevValue = useRef(field);25
26 const diff = useMemo(() => {27 if (prevValue.current === field) return 0;28
29 const diff = field - prevValue.current;30 prevValue.current = field;31
32 return diff;33 }, [field]);34
35 const value = typeof field === "number" ? formatter.format(field) : "-";36 const ref = useRef<HTMLDivElement>(null);37
38 useEffect(() => {39 if (!ref.current || diff === 0) return;40
41 ref.current.style.animation = "none";42 void ref.current.clientHeight; // Forces layout changes to be applied43 requestAnimationFrame(() => {44 if (!ref.current) return;45
46 ref.current.style.animation = "fadeOut 3s ease-out forwards";47 });48 }, [diff]);49
50 return (51 <div className="flex h-full items-center justify-end gap-2 tabular-nums tracking-tighter">52 <div53 ref={ref}54 className={clsx(55 "flex items-center rounded px-1 text-[10px]",56
57 diff < 0 && "bg-red-800/20 text-red-500",58 diff > 0 && "bg-green-500/20 text-green-500",59 )}60 >61 {diff < 0 && <ArrowDownIcon width={12} height={12} />}62 {diff > 0 && <ArrowUpIcon width={12} height={12} />}63 <span>{diff !== 0 && diff.toFixed(2)}</span>64 </div>65
66 {value}67 </div>68 );69}70
71function CaretRight() {72 return (73 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentcolor" viewBox="0 0 256 256">74 <path d="M181.66,133.66l-80,80A8,8,0,0,1,88,208V48a8,8,0,0,1,13.66-5.66l80,80A8,8,0,0,1,181.66,133.66Z"></path>75 </svg>76 );77}78
79export function GroupCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {80 if (api.rowIsGroup(row)) {81 const symbol = row.key;82
83 const isExpanded = row.expanded;84
85 return (86 <div className="flex h-full w-full items-center gap-2 overflow-hidden text-nowrap px-3">87 <button88 className={tw(89 "hover:bg-ln-gray-10 w-5 cursor-pointer rounded transition-colors",90 isExpanded && "rotate-90",91 )}92 onClick={() => {93 api.rowGroupToggle(row);94 }}95 >96 <span className="sr-only">Toggle the row group</span>97 <CaretRight />98 </button>99 <div className="flex h-8 min-h-8 w-8 min-w-8 items-center justify-center overflow-hidden rounded-full">100 <img101 src={logos[symbol!]}102 alt={`Logo of `}103 className="h-6.5 min-h-6.5 w-6.5 min-w-[26] rounded-full bg-black p-1"104 />105 </div>106 <div className="symbol-cell min-w-15 flex items-center justify-center rounded-2xl bg-teal-600/20 px-1 py-0.5 text-xs">107 {symbol}108 </div>109 </div>110 );111 }112 if (!api.rowIsLeaf(row)) return null;113
114 const symbol = row.data?.["symbol"] as string;115
116 return (117 <div className="flex h-full w-full items-center justify-end gap-3 overflow-hidden text-nowrap px-3">118 <div className="symbol-cell min-w-15 flex items-center justify-center rounded-2xl px-1 py-0.5 text-xs opacity-50">119 {symbol}120 </div>121 </div>122 );123}124
125export const HeaderCell = ({ column }: Grid.T.HeaderParams<GridSpec>) => {126 const aggName = column.agg;127
128 return (129 <div130 className={clsx(131 "flex h-full w-full items-center gap-2 text-sm",132 column.type === "number" && "flex-row-reverse",133 )}134 >135 <div>{column.name ?? column.id}</div>136 {aggName && (137 <span className="focus-visible:ring-ln-primary-50 text-ln-primary-50 rounded px-1 py-1 text-xs focus:outline-none focus-visible:ring-1">138 ({aggName as string})139 </span>140 )}141 </div>142 );143};1import type { DataRequest, DataResponse } from "@1771technologies/lytenyte-pro";2import { data, nextData, type DataEntry } from "./data.js";3
4const sleep = () => new Promise((res) => setTimeout(res, 500));5
6export async function Server(7 reqs: DataRequest[],8 groupModel: string[],9 aggModel: { [columnId: string]: { fn: string } },10) {11 // Simulate latency and server work.12 await sleep();13
14 // Tick the data so it changes in the UI15 nextData();16
17 return reqs.map((c) => {18 // Return flat items if there are no row groups19 if (!groupModel.length) {20 return {21 asOfTime: Date.now(),22 data: data.slice(c.start, c.end).map((x) => {23 return {24 kind: "leaf",25 id: x.id,26 data: x,27 };28 }),29 start: c.start,30 end: c.end,31 kind: "center",32 path: c.path,33 size: data.length,34 } satisfies DataResponse;35 }36
37 const groupLevel = c.path.length;38 const groupKeys = groupModel.slice(0, groupLevel + 1);39
40 const filteredForGrouping = data.filter((row) => {41 return c.path.every((v, i) => {42 const groupKey = groupModel[i];43 return `${row[groupKey as keyof DataEntry]}` === v;44 });45 });46
47 // This is the leaf level of the grouping48 if (groupLevel === groupModel.length) {49 return {50 kind: "center",51 asOfTime: Date.now(),52 start: c.start,53 end: c.end,54 path: c.path,55 data: filteredForGrouping.slice(c.start, c.end).map((x) => {56 return {57 kind: "leaf",58 id: x.id,59 data: x,60 };61 }),62 size: filteredForGrouping.length,63 } satisfies DataResponse;64 }65
66 const groupedData = Object.groupBy(filteredForGrouping, (r) => {67 const groupPath = groupKeys.map((g) => {68 if (typeof g !== "string")69 throw new Error("Non-string groups are not supported by this dummy implementation");70
71 return r[g as keyof DataEntry];72 });73
74 return groupPath.join(" / ");75 });76
77 const rows = Object.entries(groupedData);78
79 return {80 kind: "center",81 asOfTime: Date.now(),82 data: rows.slice(c.start, c.end).map((x) => {83 const childRows = x[1]!;84
85 const nextGroup = groupLevel + 1;86 let childCnt: number;87 if (nextGroup === groupModel.length) childCnt = childRows.length;88 else {89 childCnt = Object.keys(90 Object.groupBy(childRows, (x) => {91 const groupKey = groupModel[nextGroup];92 return `${x[groupKey as keyof DataEntry]}`;93 }),94 ).length;95 }96
97 const aggData = Object.fromEntries(98 Object.entries(aggModel)99 .map(([column, m]) => {100 if (typeof m.fn !== "string")101 throw new Error("Non-string aggregations are not supported by this dummy implementation");102
103 const id = column as keyof DataEntry;104
105 if (m.fn === "first") return [column, childRows[0][id]];106 if (m.fn === "last") return [column, childRows.at(-1)![id]];107
108 if (m.fn === "avg")109 return [column, childRows.reduce((acc, x) => acc + (x[id] as number), 0) / childRows.length];110
111 if (m.fn === "sum") return [column, childRows.reduce((acc, x) => acc + (x[id] as number), 0)];112
113 if (m.fn === "min") return [column, Math.min(...childRows.map((x) => x[id] as number))];114 if (m.fn === "max") return [column, Math.max(...childRows.map((x) => x[id] as number))];115 })116 .filter(Boolean) as [string, number | string][],117 );118
119 return {120 kind: "branch",121 childCount: childCnt,122 data: aggData,123 id: x[0],124 key: x[0].split(" / ").at(-1)!,125 };126 }),127
128 path: c.path,129 start: c.start,130 end: c.end,131 size: rows.length,132 } satisfies DataResponse;133 });134}The Request Data button’s onClick callback handles the server data fetch:
1<button2 onClick={() => {3 ds.pushRequests(ds.requestsForView.get());4 }}5>6 Request Data7</button>The demo uses the requestsForView atom to fetch data for the current view,
though any request set can be used. If you’re unfamiliar with the request interface,
see the Data Interface guide for more details.
Pushing Data
Use pushResponses on the server data source to push data into LyteNyte Grid.
It accepts complete data responses and applies them to the data source. You
can also generate client-side updates and push them as simulated
server responses, as demonstrated below.
Server Data Pushing
1"use client";2import "@1771technologies/lytenyte-pro/light-dark.css";3import "@1771technologies/lytenyte-pro/components.css";4import { useMemo } from "react";5import { getResponses, Server } from "./server.js";6import type { DataEntry } from "./data";7import { HeaderCell, NumberCell } from "./components.js";8import { Grid, useServerDataSource } from "@1771technologies/lytenyte-pro";9import { logos } from "@1771technologies/grid-sample-data/stock-data-smaller";10import { RowGroupCell } from "@1771technologies/lytenyte-pro/components";11
12export interface GridSpec {13 readonly data: DataEntry;14 readonly column: { agg?: string };15}16
17const columns: Grid.Column<GridSpec>[] = [18 {19 id: "bid",20 name: "Bid",21 type: "number",22 cellRenderer: NumberCell,23 width: 120,24 widthFlex: 1,25 agg: "avg",26 },27 {28 id: "ask",29 name: "Ask",30 type: "number",31 cellRenderer: NumberCell,32 width: 120,33 widthFlex: 1,34 agg: "avg",35 },36 {37 id: "spread",38 name: "Spread",39 type: "number",40 cellRenderer: NumberCell,41 width: 120,42 widthFlex: 1,43 agg: "avg",44 },45
46 {47 id: "volatility",48 name: "Volatility",49 type: "number",50 cellRenderer: NumberCell,51 width: 120,52 widthFlex: 1,53 agg: "first",54 },55 {56 id: "latency",57 name: "Latency",58 type: "number",59 cellRenderer: NumberCell,60 width: 120,61 widthFlex: 1,62 agg: "first",63 },64 {65 id: "symbol",66 name: "Symbol",67 hide: true,68 type: "number",69 agg: "first",70 },71];72
73const base: Grid.ColumnBase<GridSpec> = { headerRenderer: HeaderCell };74const group: Grid.RowGroupColumn<GridSpec> = {75 cellRenderer: (p) => {76 return (77 <RowGroupCell78 {...p}79 groupLabel={(row) => {80 const symbol = row.key;81
82 return (83 <div className="flex h-full w-full items-center gap-2 overflow-hidden text-nowrap">84 <div className="flex h-8 min-h-8 w-8 min-w-8 items-center justify-center overflow-hidden rounded-full">85 <img86 src={logos[symbol!]}87 alt={`Logo of ${symbol}`}88 className="h-6.5 min-h-6.5 w-6.5 min-w-[26] rounded-full bg-black p-1"89 />90 </div>91 <div className="symbol-cell min-w-15 flex items-center justify-center rounded-2xl bg-teal-600/20 px-1 py-0.5 text-xs">92 {symbol}93 </div>94 </div>95 );96 }}97 />98 );99 },100 pin: "start",101 width: 170,102};103
104const groupModel = ["symbol"];105const aggModel = {106 time: { fn: "first" },107 volume: { fn: "group" },108 bid: { fn: "avg" },109 ask: { fn: "avg" },110 spread: { fn: "avg" },111 volatility: { fn: "first" },112 latency: { fn: "first" },113 pnl: { fn: "first" },114 symbol: { fn: "first" },115};116
117export default function ServerDataDemo() {118 const ds = useServerDataSource<DataEntry>({119 queryFn: async (params) => {120 return await Server(params.requests, groupModel, aggModel);121 },122 hasRowBranches: true,123 queryKey: [],124 blockSize: 50,125 });126
127 const isLoading = ds.isLoading.useValue();128
129 return (130 <>131 <div className="px-2 py-1">132 <button133 data-ln-button="website"134 data-ln-size="md"135 onClick={() => {136 const res = getResponses(ds.requestsForView.get(), groupModel, aggModel);137 ds.pushResponses(res);138 }}139 >140 Push Data141 </button>142 </div>143 <div className="ln-grid" style={{ height: 500 }}>144 <Grid145 rowSource={ds}146 columns={columns}147 columnBase={base}148 rowGroupColumn={group}149 styles={useMemo(() => {150 return { viewport: { style: { scrollbarGutter: "stable" } } };151 }, [])}152 slotViewportOverlay={153 isLoading && (154 <div className="bg-ln-gray-20/40 absolute left-0 top-0 z-20 h-full w-full animate-pulse"></div>155 )156 }157 />158 </div>159 </>160 );161}1import "./component.css";2import { logos } from "@1771technologies/grid-sample-data/stock-data-smaller";3import { useEffect, useMemo, useRef } from "react";4
5import type { ClassValue } from "clsx";6import clsx from "clsx";7import { twMerge } from "tailwind-merge";8import type { Grid } from "@1771technologies/lytenyte-pro";9import type { GridSpec } from "./demo";10import { ArrowDownIcon, ArrowUpIcon } from "@radix-ui/react-icons";11
12export function tw(...c: ClassValue[]) {13 return twMerge(clsx(...c));14}15
16const formatter = new Intl.NumberFormat("en-US", {17 minimumFractionDigits: 2,18 maximumFractionDigits: 2,19});20
21export function NumberCell({ api, column, row }: Grid.T.CellRendererParams<GridSpec>) {22 const field = api.columnField(column, row) as number;23
24 const prevValue = useRef(field);25
26 const diff = useMemo(() => {27 if (prevValue.current === field) return 0;28
29 const diff = field - prevValue.current;30 prevValue.current = field;31
32 return diff;33 }, [field]);34
35 const value = typeof field === "number" ? formatter.format(field) : "-";36 const ref = useRef<HTMLDivElement>(null);37
38 useEffect(() => {39 if (!ref.current || diff === 0) return;40
41 ref.current.style.animation = "none";42 void ref.current.clientHeight; // Forces layout changes to be applied43 requestAnimationFrame(() => {44 if (!ref.current) return;45
46 ref.current.style.animation = "fadeOut 3s ease-out forwards";47 });48 }, [diff]);49
50 return (51 <div className="flex h-full items-center justify-end gap-2 tabular-nums tracking-tighter">52 <div53 ref={ref}54 className={clsx(55 "flex items-center rounded px-1 text-[10px]",56
57 diff < 0 && "bg-red-800/20 text-red-500",58 diff > 0 && "bg-green-500/20 text-green-500",59 )}60 >61 {diff < 0 && <ArrowDownIcon width={12} height={12} />}62 {diff > 0 && <ArrowUpIcon width={12} height={12} />}63 <span>{diff !== 0 && diff.toFixed(2)}</span>64 </div>65
66 {value}67 </div>68 );69}70
71function CaretRight() {72 return (73 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentcolor" viewBox="0 0 256 256">74 <path d="M181.66,133.66l-80,80A8,8,0,0,1,88,208V48a8,8,0,0,1,13.66-5.66l80,80A8,8,0,0,1,181.66,133.66Z"></path>75 </svg>76 );77}78
79export function GroupCell({ api, row }: Grid.T.CellRendererParams<GridSpec>) {80 if (api.rowIsGroup(row)) {81 const symbol = row.key;82
83 const isExpanded = row.expanded;84
85 return (86 <div className="flex h-full w-full items-center gap-2 overflow-hidden text-nowrap px-3">87 <button88 className={tw(89 "hover:bg-ln-gray-10 w-5 cursor-pointer rounded transition-colors",90 isExpanded && "rotate-90",91 )}92 onClick={() => {93 api.rowGroupToggle(row);94 }}95 >96 <span className="sr-only">Toggle the row group</span>97 <CaretRight />98 </button>99 <div className="flex h-8 min-h-8 w-8 min-w-8 items-center justify-center overflow-hidden rounded-full">100 <img101 src={logos[symbol!]}102 alt={`Logo of `}103 className="h-6.5 min-h-6.5 w-6.5 min-w-[26] rounded-full bg-black p-1"104 />105 </div>106 <div className="symbol-cell min-w-15 flex items-center justify-center rounded-2xl bg-teal-600/20 px-1 py-0.5 text-xs">107 {symbol}108 </div>109 </div>110 );111 }112 if (!api.rowIsLeaf(row)) return null;113
114 const symbol = row.data?.["symbol"] as string;115
116 return (117 <div className="flex h-full w-full items-center justify-end gap-3 overflow-hidden text-nowrap px-3">118 <div className="symbol-cell min-w-15 flex items-center justify-center rounded-2xl px-1 py-0.5 text-xs opacity-50">119 {symbol}120 </div>121 </div>122 );123}124
125export const HeaderCell = ({ column }: Grid.T.HeaderParams<GridSpec>) => {126 const aggName = column.agg;127
128 return (129 <div130 className={clsx(131 "flex h-full w-full items-center gap-2 text-sm",132 column.type === "number" && "flex-row-reverse",133 )}134 >135 <div>{column.name ?? column.id}</div>136 {aggName && (137 <span className="focus-visible:ring-ln-primary-50 text-ln-primary-50 rounded px-1 py-1 text-xs focus:outline-none focus-visible:ring-1">138 ({aggName as string})139 </span>140 )}141 </div>142 );143};1import type { DataRequest, DataResponse } from "@1771technologies/lytenyte-pro";2import { data, nextData, type DataEntry } from "./data.js";3
4const sleep = () => new Promise((res) => setTimeout(res, 50));5
6export async function Server(7 reqs: DataRequest[],8 groupModel: string[],9 aggModel: { [columnId: string]: { fn: string } },10) {11 // Simulate latency and server work.12 await sleep();13
14 return getResponses(reqs, groupModel, aggModel);15}16
17export function getResponses(18 reqs: DataRequest[],19 groupModel: string[],20 aggModel: { [columnId: string]: { fn: string } },21) {22 // Tick the data so it changes in the UI23 nextData();24 return reqs.map((c) => {25 // Return flat items if there are no row groups26 if (!groupModel.length) {27 return {28 asOfTime: Date.now(),29 data: data.slice(c.start, c.end).map((x) => {30 return {31 kind: "leaf",32 id: x.id,33 data: x,34 };35 }),36 start: c.start,37 end: c.end,38 kind: "center",39 path: c.path,40 size: data.length,41 } satisfies DataResponse;42 }43
44 const groupLevel = c.path.length;45 const groupKeys = groupModel.slice(0, groupLevel + 1);46
47 const filteredForGrouping = data.filter((row) => {48 return c.path.every((v, i) => {49 const groupKey = groupModel[i];50 return `${row[groupKey as keyof DataEntry]}` === v;51 });52 });53
54 // This is the leaf level of the grouping55 if (groupLevel === groupModel.length) {56 return {57 kind: "center",58 asOfTime: Date.now(),59 start: c.start,60 end: c.end,61 path: c.path,62 data: filteredForGrouping.slice(c.start, c.end).map((x) => {63 return {64 kind: "leaf",65 id: x.id,66 data: x,67 };68 }),69 size: filteredForGrouping.length,70 } satisfies DataResponse;71 }72
73 const groupedData = Object.groupBy(filteredForGrouping, (r) => {74 const groupPath = groupKeys.map((g) => {75 if (typeof g !== "string")76 throw new Error("Non-string groups are not supported by this dummy implementation");77
78 return r[g as keyof DataEntry];79 });80
81 return groupPath.join(" / ");82 });83
84 const rows = Object.entries(groupedData);85
86 return {87 kind: "center",88 asOfTime: Date.now(),89 data: rows.slice(c.start, c.end).map((x) => {90 const childRows = x[1]!;91
92 const nextGroup = groupLevel + 1;93 let childCnt: number;94 if (nextGroup === groupModel.length) childCnt = childRows.length;95 else {96 childCnt = Object.keys(97 Object.groupBy(childRows, (x) => {98 const groupKey = groupModel[nextGroup];99 return `${x[groupKey as keyof DataEntry]}`;100 }),101 ).length;102 }103
104 const aggData = Object.fromEntries(105 Object.entries(aggModel)106 .map(([column, m]) => {107 if (typeof m.fn !== "string")108 throw new Error("Non-string aggregations are not supported by this dummy implementation");109
110 const id = column as keyof DataEntry;111
112 if (m.fn === "first") return [column, childRows[0][id]];113 if (m.fn === "last") return [column, childRows.at(-1)![id]];114
115 if (m.fn === "avg")116 return [column, childRows.reduce((acc, x) => acc + (x[id] as number), 0) / childRows.length];117
118 if (m.fn === "sum") return [column, childRows.reduce((acc, x) => acc + (x[id] as number), 0)];119
120 if (m.fn === "min") return [column, Math.min(...childRows.map((x) => x[id] as number))];121 if (m.fn === "max") return [column, Math.max(...childRows.map((x) => x[id] as number))];122 })123 .filter(Boolean) as [string, number | string][],124 );125
126 return {127 kind: "branch",128 childCount: childCnt,129 data: aggData,130 id: x[0],131 key: x[0].split(" / ").at(-1)!,132 };133 }),134
135 path: c.path,136 start: c.start,137 end: c.end,138 size: rows.length,139 } satisfies DataResponse;140 });141}This example is intentionally simple to highlight the core functionality. Like
the data-pulling example, the main logic runs in the Push Data button’s onClick
handler. However, instead of calling pushRequests, it calls pushResponses
and provides complete responses.
1<button2 onClick={() => {3 const res = getResponses(4 ds.requestsForView.get(),5 grid.state.rowGroupModel.get(),6 grid.state.aggModel.get(),7 );8 ds.pushResponses(res);9 }}10>11 Push Data12</button>Pushing responses requires understanding the server data source response model and how responses form the data tree. For details, see the Data Interface guide.
Next Steps
- Server Row Sorting: Sort rows on the server using a defined sort model.
- Server Row Filtering: Filter server-side rows using the server data source.
- Server Row Grouping: Use the server data source to load and manage group slices.
- Handling Load Failures: Recover from failed data requests and retry loads.
