sgcHTML just grew by 25 components, and this is the first of five posts walking through them by theme. We start with four components built around structured, changing data: TreeGrid for hierarchical rows, PivotTable for grouped aggregates, ActivityFeed for a live stream of events, and Splitter for the resizable two-pane layout that so often has to hold all of the above. Every component below is a plain Delphi/C++Builder/.NET class: set its properties, call HTML, get Bootstrap 5 markup back. No JavaScript framework, no build step.
TreeGrid — collapsible hierarchical tables
TsgcHTMLComponent_TreeGrid renders a Bootstrap table whose rows collapse and expand, built from plain Id / ParentId node pairs — the same shape you already use for org charts, category trees, or nested bill-of-materials data. Declare a TsgcHTMLTreeGridColumn per visible field, then add nodes; the parent/child relationship draws the tree and the toggle arrows for you.
uses
sgcHTML_Component_TreeGrid;
var
oTreeGrid: TsgcHTMLComponent_TreeGrid;
oColumn: TsgcHTMLTreeGridColumn;
begin
oTreeGrid := TsgcHTMLComponent_TreeGrid.Create(nil);
try
oTreeGrid.TreeGridID := 'orgchart';
oTreeGrid.Sortable := True;
oColumn := oTreeGrid.Columns.Add;
oColumn.Caption := 'Department';
oColumn.FieldName := 'Name';
oColumn := oTreeGrid.Columns.Add;
oColumn.Caption := 'Headcount';
oColumn.FieldName := 'Headcount';
oColumn.Align := tgaRight;
oTreeGrid.AddNode('eng', '', ['Engineering', '42']);
oTreeGrid.AddNode('eng-be', 'eng', ['Backend', '18']);
oTreeGrid.AddNode('eng-fe', 'eng', ['Frontend', '15']);
WebModule.Response := oTreeGrid.HTML; // hierarchical table + scoped CSS + toggle script
finally
oTreeGrid.Free;
end;
end;
// Or bind it straight to a self-referencing dataset (Id / ParentId columns):
oTreeGrid.LoadFromDataSet(qryDepartments, 'Id', 'ParentId');
If your data already lives in a self-referencing table, skip the manual node calls entirely and hand LoadFromDataSet the parent and child field names — it walks the dataset and builds the whole tree in one pass.
PivotTable — group, aggregate, total
TsgcHTMLComponent_PivotTable takes flat rows and produces a grouped, aggregated table with row totals, column totals and a grand total, the report you'd otherwise build by hand in a spreadsheet. You declare RowFields and ColumnFields to group by, one or more Measures to aggregate (sum, average, count, min, max), and a DataFields list telling it which source columns to actually read.
uses
sgcHTML_Component_PivotTable;
var
oPivot: TsgcHTMLComponent_PivotTable;
oMeasure: TsgcHTMLPivotMeasure;
begin
oPivot := TsgcHTMLComponent_PivotTable.Create(nil);
try
oPivot.Caption := 'Revenue by Region and Quarter';
oPivot.RowFields.Add.FieldName := 'Region';
oPivot.ColumnFields.Add.FieldName := 'Quarter';
oMeasure := oPivot.Measures.Add;
oMeasure.SourceField := 'Amount';
oMeasure.Caption := 'Revenue';
oMeasure.Aggregation := paSum;
oMeasure.Format := '#,##0.00';
oPivot.DataFields.Add('Region');
oPivot.DataFields.Add('Quarter');
oPivot.DataFields.Add('Amount');
oPivot.AddRow(['EMEA', 'Q1', '12000']);
oPivot.AddRow(['EMEA', 'Q2', '15500']);
oPivot.AddRow(['APAC', 'Q1', '9800']);
WebModule.Response := oPivot.HTML; // aggregated table + row/column/grand totals
finally
oPivot.Free;
end;
end;
// Or bind it straight to a dataset (reads only RowFields/ColumnFields/Measures fields):
oPivot.LoadFromDataSet(qrySales);
Everything downstream of AddRow (or LoadFromDataSet) is computed for you: the component groups the raw rows, applies the chosen aggregation per cell, and appends the row/column/grand-total rows and columns automatically.
ActivityFeed — a live, pushable event stream
TsgcHTMLComponent_ActivityFeed renders a newest-first list of "who did what" entries with relative timestamps ("3 min ago", kept live client-side), and it's designed from the start to be pushed to over WebSockets, not just rendered once. MaxItems keeps it a ring buffer — the oldest entry drops off automatically once you're at the limit.
uses
sgcHTML_Enums, sgcHTML_Component_ActivityFeed;
var
oFeed: TsgcHTMLComponent_ActivityFeed;
begin
oFeed := TsgcHTMLComponent_ActivityFeed.Create(nil);
try
oFeed.FeedID := 'timeline';
oFeed.Title := 'Recent Activity';
oFeed.MaxItems := 30;
oFeed.AddActivity('Ana', 'closed', 'ticket #482', hcSuccess, 'bi bi-check-circle');
oFeed.AddActivity('Marc', 'commented on', 'invoice #1190', hcPrimary);
WebModule.Response := oFeed.HTML; // feed list + scoped CSS + live relative-time script
finally
oFeed.Free;
end;
end;
// Push a new item live to connected clients (out-of-band htmx fragment):
oFeed.AddActivity('Ana', 'approved', 'purchase order #77', hcSuccess);
Engine.BroadcastFragment(oFeed.GetLastItemFragmentHTML);
That last line is the interesting part: GetLastItemFragmentHTML renders just the newest entry as an out-of-band htmx fragment, and BroadcastFragment pushes it to every open browser over the sgcWebSockets server you're already running. No polling, no separate real-time layer to wire up.
Splitter — the two-pane shell that holds it all
TsgcHTMLComponent_Splitter is the layout piece: two resizable panes with a draggable gutter between them, horizontal or vertical, with pixel-floor minimums on each side so neither pane can be dragged to nothing. It's exactly the shell a TreeGrid-plus-detail-panel or a file-list-plus-editor screen needs.
uses
sgcHTML_Component_Splitter;
var
oSplitter: TsgcHTMLComponent_Splitter;
begin
oSplitter := TsgcHTMLComponent_Splitter.Create(nil);
try
oSplitter.SplitterID := 'workspace';
oSplitter.Orientation := soHorizontal;
oSplitter.InitialSplit := 30;
oSplitter.MinSizeA := 180;
oSplitter.MinSizeB := 320;
oSplitter.GutterSize := 8;
oSplitter.Height := '560px';
oSplitter.PersistKey := 'workspace-split';
oSplitter.AddPaneA('<div class="p-3"><h5>Files</h5>...</div>');
oSplitter.AddPaneB('<div class="p-3"><h5>Editor</h5>...</div>');
WebModule.Response := oSplitter.HTML; // two panes + gutter + scoped CSS + drag script
finally
oSplitter.Free;
end;
end;
PersistKey is worth calling out: set it and the gutter position a visitor drags to is remembered client-side and restored on their next visit, so a workspace layout stays put across sessions without you writing any state-management code.
Try them
All four ship in the current sgcWebSockets release, with full documentation, key-property references and Delphi/C++Builder/.NET code samples on their own pages: TreeGrid, PivotTable, ActivityFeed and Splitter. The full feature matrix now lists 83 components across nine families — the next post in this series covers the new charts and visual codes.
Questions, feedback or migration help? Get in touch — you will get a reply from the people who wrote the code.
