Blog

All Blog Posts  |  Next Post  |  Previous Post



Next Generation Data Grid for Delphi: Headless Data Layer

Friday, May 15, 2026

Intro

Not every data operation belongs on screen. Sometimes you need to load a data set, filter it, sort it, group it, calculate summaries, and export the result without showing a grid at all.

That is where the headless data layer in TMS FNC Data Grid becomes especially useful. With TTMSFNCDataGridData, the same engine that powers the visual TTMSFNCDataGrid can be used independently in services, background jobs, console tools, tests, batch exports, or startup data preparation routines.

In other words: you can use the grid's data intelligence without needing the grid's visual surface.


Why Use a Headless Data Layer?

A visual grid is perfect when users need to inspect and interact with data. But many application workflows happen before, after, or completely outside the UI:

  • Background processing: load data, transform it, and prepare results while the UI remains responsive

  • Batch exports: generate filtered CSV files without creating a form or control

  • Automated tests: verify sorting, filtering, grouping, and aggregation logic without UI automation

  • Services and console apps: reuse familiar grid data operations in non-visual applications

The important part is that TTMSFNCDataGridData owns the data operations. Methods such as Filter.Add, ApplyFilter, Sort, Group, SaveToCSVData, and the typed accessors like Strings[], Floats[], and Ints[] are available directly on the data layer.


Creating the Data Object

Because TTMSFNCDataGridData is a regular Delphi object, it can be created anywhere you would normally create an object. There is no need for a form, parent control, or rendering context.

type
  TMyForm = class(TForm)
  private
    FData: TTMSFNCDataGridData;
  end;

procedure TMyForm.FormCreate(Sender: TObject);
begin
  FData := TTMSFNCDataGridData.Create;
end;

procedure TMyForm.FormDestroy(Sender: TObject);
begin
  FData.Free;
end;

That small setup gives you a reusable in-memory data layer with the same core operations you would expect from the visual grid.


Loading Data

The headless layer supports the same practical loading scenarios as the grid. You can load JSON, load CSV, or fill cells manually when you want full control.

// From JSON
FData.Options.IO.StartRow := 1;
FData.LoadFromJSONData(
  JSONPath,
  '', '',
  ['id', 'name', 'category', 'price', 'stock', 'is_organic', 'is_vegan']);

// From CSV
FData.LoadFromCSVData('products.csv', ',');

Setting Options.IO.StartRow to 1 before loading JSON is a useful detail: row 0 can receive the JSON key names as column headers, while the actual records start below that fixed header row.

Manual population is equally direct:

FData.ColumnCount   := 3;
FData.FixedRowCount := 1;
FData.RowCount      := 4;

FData.Cells[0, 0] := 'ID';
FData.Cells[1, 0] := 'Name';
FData.Cells[2, 0] := 'Price';

FData.Cells[0, 1] := 1;
FData.Cells[1, 1] := 'Widget A';
FData.Cells[2, 1] := 9.99;


Filtering Without a Grid

Filtering works the same way it does with the visual grid. Add one or more filter conditions, apply them, and then work with the displayed rows.

FData.Filter.Clear;
FData.Filter.Add(COL_CATEGORY, gftEqual, 'Meat');
FData.ApplyFilter;

After calling ApplyFilter, rows that do not match are hidden from the active view. You can use IsRowDisplayed when iterating the data:

var Count := 0;

for var R := FData.FixedRowCount to FData.RowCount - 1 do
  if FData.IsRowDisplayed(R) then
    Inc(Count);

Log(Format('%d matching rows', [Count]));

To return to the full data set, call RemoveFilter.

FData.RemoveFilter;


Sorting the Active Result

Sorting is just as compact. The data layer can sort a column in ascending or descending order without requiring any visual repaint cycle.

FData.Sort(COL_PRICE, gsdDescending);

Sorting and filtering work together. When a filter is active, the sort operation reorders the displayed rows while keeping filtered-out rows hidden.


Grouping and Aggregations

The headless layer is also useful when you need summary data. You can group by a column and calculate values such as count, sum, average, minimum, or maximum.

FData.Group(COL_CATEGORY);
FData.GroupCount(COL_CATEGORY);
FData.GroupSum(COL_PRICE);

Once grouped, the result contains group header rows and summary rows that can be inspected programmatically:

for var R := FData.FixedRowCount to FData.RowCount - 1 do
begin
  if FData.IsRowNode(R) then
    Log(FData.Strings[FData.FixedColumnCount, R])
  else if FData.IsRowSummary(R) then
    Log(Format('  count=%.0f   sum=$%.2f',
      [FData.Floats[COL_CATEGORY, R], FData.Floats[COL_PRICE, R]]));
end;

FData.Ungroup;

This makes it convenient to build reporting features where the final output is a log, CSV file, dashboard feed, or another data structure rather than an interactive grid.


Exporting the Result

After loading, filtering, sorting, and grouping, you can export the processed result directly to CSV.

FData.SaveToCSVData('output.csv', ',');

This is a practical pattern for server-side export buttons, scheduled reporting tasks, or command-line tools that need the same data behavior as your application's UI.


Reading Typed Values

When looping over rows, use the typed accessors to keep your code clear and efficient. Instead of reading everything as text and converting manually, read the expected data type directly from the layer.

var Name  := FData.Strings[COL_NAME,  Row];
var Price := FData.Floats [COL_PRICE, Row];
var Stock := FData.Ints   [COL_STOCK, Row];

For tight processing loops, this keeps the intent of the code obvious: names are strings, prices are floating point values, stock counts are integers.


Showing the Data Later

Using the headless layer does not lock you into a non-visual workflow. If the processed data later needs to be shown in a TTMSFNCDataGrid, you can load the same source into the grid or copy values into the visual component.

// Let the grid load its own visual copy
TMSFNCDataGrid1.LoadFromJSONData(
  JSONPath,
  '', '',
  ['id', 'name', 'price']);

For database-backed scenarios, TTMSFNCDataGridDatabaseAdapter can be used with a shared TDataSet. You can bind that adapter to a visual grid, or bind it directly to TTMSFNCDataGridData when the workflow is headless and the data operations matter more than immediate visual interaction.


Binding a Dataset

For a non-visual workflow, assign the database adapter to the data layer. The result is still a dataset-backed data engine, but without any renderer or control on screen.

procedure TMyService.LoadProducts;
begin
  FData := TTMSFNCDataGridData.Create;

  Adapter.DataSource := DataSource1;     // TDataSource attached to FDQuery1
  Adapter.AutoCreateColumns := True;

  FData.Adapter := Adapter;

  FDQuery1.Open;                         // records are loaded into FData

  FData.Filter.Clear;
  FData.Filter.Add(COL_CATEGORY, gftEqual, 'Meat');
  FData.ApplyFilter;

  FData.Sort(COL_PRICE, gsdDescending);
  FData.SaveToCSVData('filtered-products.csv', ',');
end;

This is the database equivalent of loading JSON or CSV into FData: once the records are in the data layer, the rest of the processing code can stay the same. For exports, reports, validation jobs, and tests, that can be cleaner than creating a visual grid just to reach the data operations.


Choosing Fields and Column Behavior

Automatic columns are convenient during prototyping, but production screens often need a more curated shape: hide technical fields, reorder columns, use friendly headers, or render specific field types with richer UI.

The adapter column collection lets you decide exactly which fields appear and how they are presented:

Adapter.AutoCreateColumns := False;

with Adapter.Columns.Add do
begin
  FieldName := 'ProductName';
  Header := 'Product';
end;

with Adapter.Columns.Add do
begin
  FieldName := 'Price';
  Header := 'Unit Price';
end;

Individual adapter columns can also be configured for more specialized rendering:

Adapter.Columns[2].HTMLTemplate    := '<b>{Name}</b>';
Adapter.Columns[3].PictureField    := True;   // BLOB field as image
Adapter.Columns[4].ProgressField   := True;   // numeric field as progress bar
Adapter.Columns[5].UseLookupEditor := True;   // lookup field as drop-down

For conversion logic, the adapter exposes OnFieldToData and OnDataToField. That gives you a clean place to format database values before they enter the grid, or to translate edited grid values before they are posted back to the dataset.


A Complete Processing Flow

A typical headless workflow can be surprisingly small: load records, apply business filters, sort the result, and export it.

FData.ClearData;
FData.FixedRowCount       := 1;
FData.Options.IO.StartRow := 1;

FData.LoadFromJSONData(
  JSONPath,
  '', '',
  ['id', 'name', 'category', 'price', 'stock']);

FData.Filter.Clear;
FData.Filter.Add(COL_CATEGORY, gftEqual, 'Meat');
FData.ApplyFilter;

FData.Sort(COL_PRICE, gsdDescending);
FData.SaveToCSVData('filtered-products.csv', ',');

No visual control is required, but the data behavior remains consistent with the grid users see elsewhere in the application.


Sample

https://www.tmssoftware.com/download/DataLayerDemo.zip


Conclusion

TTMSFNCDataGridData gives you the data-processing side of TTMSFNCDataGrid as a standalone layer. You can load JSON or CSV, apply filters, sort rows, group data, calculate aggregations, read typed values, and export the result without placing a grid on screen.

For applications that need background exports, automated tests, reporting pipelines, or non-visual data preparation, this is a clean way to reuse the same proven data layer that powers the visual grid. It keeps your logic testable, your UI optional, and your data workflow ready for both desktop and web applications.



Pieter Scheldeman


  1. Next Generation Data Grid for Delphi: Getting Started

  2. Next Generation Data Grid for Delphi: Adding, Formatting & Converting Data

  3. Next Generation Data Grid for Delphi: Filtering & Sorting

  4. Next Generation Data Grid for Delphi: Grouping

  5. Next Generation Data Grid for Delphi: Webinar Replay Available!

  6. Next Generation Data Grid for Delphi: Cell Controls

  7. Next Generation Data Grid for Delphi: Master-Detail

  8. Next Generation Data Grid for Delphi: Calculations

  9. Next Generation Data Grid for Delphi: Import & Export

  10. Next Generation Data Grid for Delphi: Template

  11. Next Generation Data Grid for Delphi: Filter Row

  12. Next Generation Data Grid for C++: Getting Started

  13. Freebie Friday: Next Generation Grid Quick Sample Data

  14. Next Generation Data Grid for Delphi: Columns Editor

  15. Next Generation Data Grid for Delphi: File Drag & Drop

  16. Next Generation Data Grid for Delphi: Visual Grouping

  17. Next Generation Data Grid for Delphi: FMX Linux Support

  18. Next Generation Data Grid for Delphi: Header & Footer Buttons

  19. Next Generation Data Grid for Delphi: Paging

  20. Next Generation Data Grid for Delphi: Cell Classes

  21. Next Generation Data Grid for Delphi: Excel Style Selection

  22. Next Generation Data Grid for Delphi: AutoFill

  23. Bridging FlexCel and Our Next-Gen Data Grid

  24. Next Generation Data Grid for Delphi: Headless Data Layer



This blog post has not received any comments yet.



Add a new comment

You will receive a confirmation mail with a link to validate your comment, please use a valid email address.
All fields are required.



All Blog Posts  |  Next Post  |  Previous Post