Every business application contains the same page, written again for every table: a list with a search box and a pager, a form to create and to edit, a confirmation to delete, and the code that writes. Our own admin demo had four request handlers and about four hundred lines of page builders for customers alone, and then the same again for providers, and again for invoices.
sgcWebSockets 2026.10 adds TsgcHTMLComponent_CRUD, which owns that page. The demo now builds the customers area with an adapter of six methods, and keeps the hand written version next to it so you can read both.
The Page
uses
sgcHTML_Component_CRUD;
FCRUD := TsgcHTMLComponent_CRUD.Create(Self);
FCRUD.CRUDID := 'customers';
FCRUD.RoutePrefix := '/customers';
FCRUD.Title := 'Customers';
FCRUD.KeyField := 'ID';
FCRUD.PageSize := 15;
FCRUD.Actions := [caCreate, caEdit, caDelete, caView, caExport];
FCRUD.DataSet := FDQueryCustomers;
Bound to a dataset it reads its own columns, and that is the whole page: list, search, paging, the create and edit form, the delete confirmation and a CSV export of whatever the list is showing.
Or Your Own SQL
A dataset is one way to store records, not the only one. Everything that changes a record goes through an adapter, so the component never sees SQL, a record or a database driver:
type
TCustomersAdapter = class(TsgcHTMLCRUDAdapter)
public
function Locate(const aKey: string): Boolean; override;
function Read(const aKey: string; aValues: TStrings): Boolean; override;
function List(const aSearch: string; aPage, aPageSize: Integer;
aRows: TStrings): Integer; override;
function Insert(aValues: TStrings; var aKey: string;
aErrors: TStrings): Boolean; override;
function Update(const aKey: string; aValues, aErrors: TStrings)
: Boolean; override;
function Delete(const aKey: string; aErrors: TStrings): Boolean; override;
end;
FCRUD.SetAdapter(TCustomersAdapter.Create(FCRUD));
Six methods over whatever you already have. In the demo each one is a single call on the application's database pool.
The Parts You Would Have Written Next
Optimistic locking. Name a version column and a save compares what the browser sends with what is stored, so the second person to press Save is told the record changed instead of silently overwriting the first:
FCRUD.VersionField := 'UPDATED_AT';
Master and detail. A record's child lists are other CRUD components, rendered under the form while the record is being edited:
with FCRUD.Details.Add do
begin
CRUD := FCRUDOrderLines;
MasterField := 'ORDER_ID';
Caption := 'Lines';
end;
Validation that is about your data, not about forms. Required columns are the component's job; OnValidate is where the rules that are about customers live. Add a line per problem and the write is refused and the form comes back with them.
Two Ways to Start
In the IDE there is now a wizard: Tools › New sgcHTML CRUD page. Name the table, the key and the columns and the component is created on the form being designed, with the two lines that wire it up on the clipboard.
Outside the IDE there is a command line tool, sgcHTMLGen, which writes the source of the page in Object Pascal or in C#:
sgcHTMLGen crud --table Customers --prefix /customers ^
--fields "ID:key CODE:req:32 NAME:req:120 CITY:80 NOTES:noform"
How the Page Talks Back
Every control of the page posts the same way: the search box, the New button, the Edit and Delete of a row and the form itself carry three hidden fields, action, crud and, where it means something, key. The host reads them and calls ProcessAction, which answers the markup to swap in.
A message sent over the socket carries no path, so it never reaches the router and never gets the router's own check. Authorising it is the application's job, and the engine now hands you what that takes:
oSession := FHTMXEngine.MessageSession(aConnection, FAuth);
try
if not Assigned(oSession) then
Exit; // a write with nobody behind it is not a write
oPage.ProcessAction(vAction, oParams.Values['key'], oParams, aResponse);
finally
oSession.Free;
end;
The key that arrives from the browser is located before any write runs, so a key that belongs to nobody writes nothing.
See Both Versions
In Demos\60.HTML\01.RunTime\02.AdminCRUD, /customers is the component and /customers-classic is the page it replaced. Same database, same data, same look. One of them is four hundred lines shorter.
Upgrading
The component is part of sgcHTML and is additive: nothing changes in an existing page until you drop one on a form.
Read Next
- Ask Your Delphi Grid a Question in Plain English
- Reach a Browser That Is Closed, From Delphi
- sgcWebSockets 2026.10, everything else in this release
Watch It
There is a short video of this on the eSeGeCe channel.
Questions, feedback or migration help? Get in touch — you will get a reply from the people who wrote the code.
