CRUD

TsgcHTMLComponent_CRUD:一张表的列表、搜索、分页、创建与编辑表单、删除确认和写入路径,集于一个组件,基于 TDataSet 或您自己的 SQL,适用于 Delphi、C++ Builder 和 .NET。

TsgcHTMLComponent_CRUD

每个业务应用为每张表都要重写一遍的那个页面,现在是一个组件。给它一个键字段和一个路由前缀,然后绑定 TDataSet,或者交给它一个基于您自己 SQL 的六方法适配器。ShowList 返回标记,页面回传的内容则通过 ProcessAction 处理。

组件类

TsgcHTMLComponent_CRUD,以及同一单元中的 TsgcHTMLCRUDAdapterTsgcHTMLCRUDAdapter_DataSet

渲染为

带搜索和分页器的列表、创建与编辑表单,以及删除确认

语言

Delphi, C++ Builder, .NET

绑定数据集,或编写适配器

设置 KeyFieldRoutePrefix,绑定一个处于活动状态的 DataSet,然后调用 ShowList(search, page)。如果使用您自己的 SQL,则改用 SetAdapter 为组件提供一个适配器。页面回传的内容通过 ProcessAction 路由。

uses
  Classes, sgcHTML_Component_CRUD;

var
  oCRUD: TsgcHTMLComponent_CRUD;
  vHTML: string;
begin
  oCRUD := TsgcHTMLComponent_CRUD.Create(nil);
  try
    oCRUD.CRUDID := 'customers';
    oCRUD.Title := 'Customers';
    oCRUD.RoutePrefix := '/customers';
    oCRUD.KeyField := 'ID';
    oCRUD.PageSize := 15;
    oCRUD.Actions := [caCreate, caEdit, caDelete, caView, caExport];
    oCRUD.VersionField := 'UPDATED_AT';   // optimistic locking
    oCRUD.OnValidate := CustomersValidate;

    // an active dataset: the columns are read off it
    oCRUD.DataSet := qryCustomers;

    vHTML := oCRUD.ShowList('', 1);   // search text, page number
  finally
    oCRUD.Free;
  end;
end;

// The rules that are about your data. Add a line per problem: nothing is
// written and the form comes back with the messages.
procedure TServer.CustomersValidate(Sender: TObject;
  aOperation: TsgcHTMLCRUDOperation; const aKey: string;
  aValues, aErrors: TStrings);
begin
  if Length(Trim(aValues.Values['NAME'])) < 3 then
    aErrors.Add('The name needs at least three characters.');
end;

// A message from the browser carries action, crud and key with the form fields
if oCRUD.ProcessAction(vAction, vKey, oFields, vHTML) then
  aResponse := vHTML;   // the markup to swap into the page

// Or over your own SQL: an adapter of six methods
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;

oCRUD.SetAdapter(TCustomersAdapter.Create(oCRUD));   // the component owns it
// includes: sgcHTML_Component_CRUD.hpp

TsgcHTMLComponent_CRUD *oCRUD = new TsgcHTMLComponent_CRUD(NULL);
try
{
  oCRUD->CRUDID = "customers";
  oCRUD->Title = "Customers";
  oCRUD->RoutePrefix = "/customers";
  oCRUD->KeyField = "ID";
  oCRUD->PageSize = 15;
  oCRUD->Actions = TsgcHTMLCRUDActions() << caCreate << caEdit << caDelete
    << caView << caExport;
  oCRUD->VersionField = "UPDATED_AT";   // optimistic locking
  oCRUD->OnValidate = CustomersValidate;

  // an active dataset: the columns are read off it
  oCRUD->DataSet = qryCustomers;

  String html = oCRUD->ShowList("", 1);   // search text, page number
}
__finally
{
  delete oCRUD;
}

// The rules that are about your data. Add a line per problem: nothing is
// written and the form comes back with the messages.
void __fastcall TServer::CustomersValidate(TObject *Sender,
  TsgcHTMLCRUDOperation aOperation, const String aKey,
  TStrings *aValues, TStrings *aErrors)
{
  if (aValues->Values["NAME"].Trim().Length() < 3)
    aErrors->Add("The name needs at least three characters.");
}

// A message from the browser carries action, crud and key with the form fields
String html;
if (oCRUD->ProcessAction(action, key, fields, html))
  response = html;   // the markup to swap into the page

// For your own SQL, derive from TsgcHTMLCRUDAdapter, override its six methods
// (Locate, Read, List, Insert, Update, Delete) and call oCRUD->SetAdapter().
using System.Collections.Generic;
using esegece.sgcWebSockets;

var crud = new TsgcHTMLComponent_CRUD();
crud.CRUDID = "customers";
crud.Title = "Customers";
crud.RoutePrefix = "/customers";
crud.KeyField = "ID";
crud.PageSize = 15;
crud.Actions = TsgcHTMLCRUDActions.caCreate | TsgcHTMLCRUDActions.caEdit
    | TsgcHTMLCRUDActions.caDelete | TsgcHTMLCRUDActions.caView
    | TsgcHTMLCRUDActions.caExport;
crud.VersionField = "UPDATED_AT";   // optimistic locking

// The key travels with the page: it is in neither the list nor the form
var id = crud.Columns.Add();
id.FieldName = "ID";
id.InList = false;
id.InForm = false;

var name = crud.Columns.Add();
name.FieldName = "NAME";
name.Caption = "Name";
name.Required = true;
name.MaxLength = 120;

// There is no TDataSet: read a DataTable, an IList<T> or a table behind a DbConnection
crud.SetAdapter(new TsgcHTMLCRUDAdapter_DataTable(crud, customersTable));

// The rules that are about your data
crud.OnValidate += (object sender, TsgcHTMLCRUDOperation op, string recordKey,
    List<string> values, List<string> errors) =>
{
    if (TsgcHTMLComponent_CRUD.ValueOf(values, "NAME").Trim().Length < 3)
        errors.Add("The name needs at least three characters.");
};

string html = crud.ShowList("", 1);   // search text, page number

// A message from the browser carries action, crud and key with the form fields
if (crud.ProcessAction(action, key, fields, out string reply))
    response = reply;   // the markup to swap into the page

关键属性与方法

您最常使用的成员。

适配器

TsgcHTMLCRUDAdapter 是写入路径,有六个虚方法:LocateReadListInsertUpdateDeleteTsgcHTMLCRUDAdapter_DataSet 驱动绑定的数据集,SetAdapter 则把您自己的适配器交给组件,并由组件接管其所有权。List 以制表符分隔的行返回一页数据,键放在最后一个单元格,并返回整个数据集共有多少行。

在 .NET 中

.NET 中没有 TDataSet,因此移植版提供了三个适配器:TsgcHTMLCRUDAdapter_DataTable(基于 DataTable)、TsgcHTMLCRUDAdapter_List<T>(基于任意 IList<T>)和 TsgcHTMLCRUDAdapter_DbConnection(基于 DbConnection 背后的表),每个值都作为参数发送。请声明 Columns:它们不会从表中读取。

页面

ShowList(search, page)ShowNewShowEdit(key)Save(key, values, html)DeleteRecord(key, html) 各自返回宿主换入页面的标记。ProcessAction 把回传的 crudListcrudNewcrudEditcrudSavecrudDelete 分别路由给它们,对任何其他操作则返回 False,因此一个页面可以容纳多个组件。

页面选项

Actions 是由 caCreatecaEditcaDeletecaViewcaExport 组成的集合,只有 caView 的页面就是一份报表。EditModecemModalcemPageConfirmDelete 在删除前进行询问,PageSize 默认为 25,最大不超过 500,SearchFields 列出搜索框查找的字段,以分号分隔。

TsgcHTMLCRUDColumnFieldNameCaptionVisibleReadOnlyRequiredInListInFormWidthMaxLength。如果没有声明任何列,组件会从活动数据集中读取它们:标题取自 DisplayLabel,还有必填标志和长度,键会显示但从不编辑。LoadColumnsFromDataSet 可按需执行此操作。

验证

首先会拒绝为空的必填列,以及长度超过 MaxLength 的值。然后触发 OnValidate:为每个问题向 aErrors 添加一行,就不会写入任何内容,表单会带着这些消息返回。OnApplyUpdates 在适配器之前运行,设置 aHandled 会让写入由您来完成。OnApplied 在写入完成之后触发,用于记录日志,或刷新正在查看该列表的其他人。

乐观锁

VersionField 中指定一个版本字段。保存时会把浏览器发送的内容与已存储的内容进行比较,如果期间有其他人修改了该记录,就拒绝写入,因此第二个点击 Save 的人不会覆盖第一个人的修改。

主从关系

Details 保存记录的子列表。每个条目指定另一个 TsgcHTMLComponent_CRUD(放在 CRUD 中)、子表中保存主键的字段(放在 MasterField 中)和一个 Caption。编辑记录时,它们会显示在表单下方。SetMaster(field, value) 把列表限定到一条主记录。

CSV 导出

有了 caExport,工具栏会显示一个指向 RoutePath('export.csv') 的 Export 链接。用 GetExportCSV(search) 响应该路由:它导出列表当前显示的搜索结果,最多 500 行,而不是整张表。每个值都带引号,因此逗号或换行不会破坏文件。

先定位主键

每个键都来自浏览器。ShowEditSaveDeleteRecord 在编辑或删除任何内容之前,都会要求适配器对它执行 Locate,因此更改 URL 中的数字不会触及另一条记录。Actions 同样会强制执行:没有 caDelete 的页面,即使伪造的表单提交了删除请求也会拒绝。单元格和字段都经过转义,因此包含标记的记录会显示为文本。

Socket 与 HTTP

搜索框、New、Edit、Delete 以及编辑表单本身,都是标有 data-sgc-ws-send 的表单,每个表单带有隐藏的 actioncrudkey 字段,宿主把它们交给 ProcessAction。通过 socket 发送的消息不带路径,也永远不会到达路由器,因此请自行进行授权。在 Delphi 中,TsgcHTMX_Engine_Server.MessageSession 返回握手 cookie 所对应的会话,或者返回 nil。对于 HTTP,RoutePath 会构建宿主在引擎路由器上声明的路径:/customers/customers/new/customers/{id}/edit/customers/save/customers/{id}/delete

起步方式

可以像上面那样在代码中创建它,也可以使用 Delphi IDE 中的向导:Tools › New sgcHTML CRUD page 会列出当前打开的窗体上的数据集,读取您选中的数据集的字段,并向窗体添加一个已配置好的组件,同时把两行连接代码放到剪贴板中。它需要 Delphi 10.4 或更高版本。在 IDE 之外,可以使用 sgcHTMLGen 命令行工具,根据表、路由前缀和字段,生成该页面的 Object Pascal 或 C# 源代码,每个字段写成 name:flag,例如 ID:keyNAME:req:60

可用性

属于 sgcHTML,后者独立于 sgcWebSockets 单独销售。该单元在定义了 SGC_HTML 的地方编译,sgcVer.inc 会在除 Android 和 iOS 之外的所有平台上为 HTML 包定义它。演示位于 Demos\60.HTML\01.RunTime\02.AdminCRUD,其中客户区域基于此组件构建,旁边是它所取代的手写页面。

继续探索

在线帮助CRUD 页面、适配器和写入路径的使用指南。
所有 sgcHTML 组件浏览 80 多个组件的完整功能矩阵。
下载免费试用版30 天试用版附带 60.HTML 演示项目。
价格Single、Team 和 Site 授权,均含完整源代码。
超值之选:All-AccesseSeGeCe 全部产品,含高级支持,每年 €1,059 起。
查看 All-Access 价格

准备好开始了吗?

下载免费试用版,开始在 Delphi、C++ Builder 和 .NET 中构建 Web 界面。