構造化データのための sgcHTML 新コンポーネント4種: TreeGrid、PivotTable、ActivityFeed、Splitter | eSeGeCe Blog

構造化データのための sgcHTML 新コンポーネント4種: TreeGrid、PivotTable、ActivityFeed、Splitter

· コンポーネント

sgcHTML に新たに25個のコンポーネントが加わりました。今回はテーマ別に紹介する全5回シリーズの第1弾です。まずは構造化された変化するデータを扱う4つのコンポーネントから始めます。階層行のためのTreeGrid、グループ集計のためのPivotTable、イベントのライブストリームのためのActivityFeed、そしてこれらすべてを収めることの多いサイズ変更可能な2ペインレイアウトのためのSplitterです。以下のコンポーネントはいずれも通常の Delphi/C++Builder/.NET クラスです。プロパティを設定して HTML を呼び出せば、Bootstrap 5 のマークアップが返ってきます。JavaScript フレームワークもビルドステップも不要です。

TreeGrid — 折りたたみ可能な階層テーブル

TsgcHTMLComponent_TreeGrid は、行が折りたたみ・展開できる Bootstrap テーブルを、単純な Id / ParentId のノードペアから描画します — 組織図やカテゴリツリー、入れ子になった部品表データですでにおなじみの形です。表示するフィールドごとに TsgcHTMLTreeGridColumn を宣言してからノードを追加すれば、親子関係からツリーとトグル矢印が自動的に描画されます。

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');

データがすでに自己参照テーブルにある場合は、手動でノードを追加する処理を省略し、親フィールド名と子フィールド名を LoadFromDataSet に渡すだけで構いません — データセットを走査し、一度でツリー全体を構築します。

PivotTable — グループ化・集計・合計

TsgcHTMLComponent_PivotTable はフラットな行データを受け取り、行合計・列合計・総合計を備えたグループ集計テーブルを生成します。本来であればスプレッドシートで手作業で作成するようなレポートです。グループ化の基準となる RowFieldsColumnFields、集計方法(合計・平均・件数・最小・最大)を指定する1つ以上の Measures、そして実際に読み込むソース列を指示する DataFields リストを宣言します。

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);

AddRow(または LoadFromDataSet)以降の処理はすべて自動で計算されます。コンポーネントが生データをグループ化し、選択した集計方法をセルごとに適用し、行合計・列合計・総合計の行と列を自動的に追加します。

ActivityFeed — ライブでプッシュ可能なイベントストリーム

TsgcHTMLComponent_ActivityFeed は「誰が何をしたか」というエントリを新しい順に一覧表示し、相対的なタイムスタンプ(「3分前」のようにクライアント側でリアルタイムに更新されます)を付けます。また、一度描画して終わりではなく、最初から WebSocket 経由でプッシュされることを前提に設計されています。MaxItems によってリングバッファとして機能し — 上限に達すると最も古いエントリが自動的に削除されます。

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);

興味深いのは最後の行です。GetLastItemFragmentHTML は最新のエントリだけをアウトオブバンドの htmx フラグメントとして描画し、BroadcastFragment がすでに稼働している sgcWebSockets サーバー経由で、開いているすべてのブラウザにそれをプッシュします。ポーリングも、別途リアルタイム層を組み込む必要もありません。

Splitter — すべてを収める2ペインシェル

TsgcHTMLComponent_Splitter はレイアウトを担うコンポーネントです。ドラッグ可能なガター(仕切り)で区切られた、サイズ変更可能な2つのペインを水平または垂直に配置し、それぞれのペインにピクセル単位の最小サイズを設定できるため、どちらのペインもゼロまでドラッグされることはありません。TreeGrid と詳細パネル、あるいはファイル一覧とエディタといった画面構成にまさに必要なシェルです。

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 は特筆に値します。これを設定すると、訪問者がドラッグしたガターの位置がクライアント側で記憶され、次回訪問時に復元されます。状態管理のコードを一切書かなくても、ワークスペースのレイアウトがセッションをまたいで維持されます。

試してみる

この4つはすべて現行の sgcWebSockets リリースに含まれており、それぞれの専用ページに完全なドキュメント、主要プロパティのリファレンス、Delphi/C++Builder/.NET のコードサンプルが用意されています。TreeGridPivotTableActivityFeedSplitter機能一覧マトリクスには現在、9つのファミリーにわたる83個のコンポーネントが掲載されています — このシリーズの次回は、新しいチャートとビジュアルコードを取り上げます。

ご質問、ご意見、移行に関するサポートが必要な場合はお問い合わせください — コードを書いた本人から返信が届きます。