Four New sgcHTML Components for Structured Data: TreeGrid, PivotTable, ActivityFeed and Splitter | eSeGeCe Blog

Four New sgcHTML Components for Structured Data: TreeGrid, PivotTable, ActivityFeed and Splitter

· Components
Four New sgcHTML Components for Structured Data: TreeGrid, PivotTable, ActivityFeed and Splitter

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.