mirror of
https://gitlab.com/freepascal.org/fpc/pas2js.git
synced 2025-04-16 03:19:22 +02:00
399 lines
10 KiB
ObjectPascal
399 lines
10 KiB
ObjectPascal
unit ExtJSDataset;
|
|
|
|
{$mode objfpc}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, db, JS, jsondataset;
|
|
|
|
type
|
|
{ TExtJSJSONDataSet }
|
|
|
|
// Base for ExtJS datasets. It handles MetaData conversion.
|
|
TExtJSJSONDataSet = Class(TBaseJSONDataset)
|
|
Private
|
|
FFields : TJSArray;
|
|
FIDField: String;
|
|
FRoot: String;
|
|
Protected
|
|
// Data proxy support
|
|
Procedure InternalOpen; override;
|
|
function DoResolveRecordUpdate(anUpdate: TRecordUpdateDescriptor): Boolean; override;
|
|
Function DataPacketReceived(ARequest: TDataRequest) : Boolean; override;
|
|
Function GenerateMetaData : TJSObject;
|
|
function ConvertDateFormat(S: String): String; virtual;
|
|
Procedure MetaDataToFieldDefs; override;
|
|
procedure InitDateTimeFields; override;
|
|
function StringToFieldType(S: String): TFieldType;virtual;
|
|
function GetStringFieldLength(F: TJSObject; AName: String; AIndex: Integer): integer; virtual;
|
|
Public
|
|
Constructor Create(AOwner : TComponent); override;
|
|
// Can be set directly if the dataset is closed.
|
|
Property MetaData;
|
|
// Can be set directly if the dataset is closed. If metadata is set, it must match the data.
|
|
Property Rows;
|
|
// Root of data array in data packet
|
|
property Root : String Read FRoot Write FRoot;
|
|
// property IDField
|
|
property IDField : String Read FIDField Write FIDField;
|
|
published
|
|
Property FieldDefs;
|
|
// redeclared data set properties
|
|
property Active;
|
|
property BeforeOpen;
|
|
property AfterOpen;
|
|
property BeforeClose;
|
|
property AfterClose;
|
|
property BeforeInsert;
|
|
property AfterInsert;
|
|
property BeforeEdit;
|
|
property AfterEdit;
|
|
property BeforePost;
|
|
property AfterPost;
|
|
property BeforeCancel;
|
|
property AfterCancel;
|
|
property BeforeDelete;
|
|
property AfterDelete;
|
|
property BeforeScroll;
|
|
property AfterScroll;
|
|
property OnCalcFields;
|
|
property OnDeleteError;
|
|
property OnEditError;
|
|
property OnFilterRecord;
|
|
property OnNewRecord;
|
|
property OnPostError;
|
|
end;
|
|
|
|
{ TExtJSJSONObjectDataSet }
|
|
// Use this dataset for data where the data is an array of objects.
|
|
TExtJSJSONObjectDataSet = Class(TExtJSJSONDataSet)
|
|
Protected
|
|
Function CreateFieldMapper : TJSONFieldMapper; override;
|
|
end;
|
|
|
|
{ TExtJSJSONArrayDataSet }
|
|
// Use this dataset for data where the data is an array of arrays.
|
|
TExtJSJSONArrayDataSet = Class(TExtJSJSONDataSet)
|
|
Protected
|
|
Function CreateFieldMapper : TJSONFieldMapper; override;
|
|
end;
|
|
|
|
implementation
|
|
|
|
{ TExtJSJSONDataSet }
|
|
|
|
function TExtJSJSONDataSet.StringToFieldType(S: String): TFieldType;
|
|
|
|
begin
|
|
if (s='int') then
|
|
Result:=ftLargeInt
|
|
else if (s='float') then
|
|
Result:=ftFloat
|
|
else if (s='boolean') then
|
|
Result:=ftBoolean
|
|
else if (s='date') then
|
|
Result:=ftDateTime
|
|
else if (s='string') or (s='auto') or (s='') then
|
|
Result:=ftString
|
|
else
|
|
if MapUnknownToStringType then
|
|
Result:=ftString
|
|
else
|
|
Raise EJSONDataset.CreateFmt('Unknown JSON data type : %s',[s]);
|
|
end;
|
|
|
|
function TExtJSJSONDataSet.GetStringFieldLength(F: TJSObject; AName: String;
|
|
AIndex: Integer): integer;
|
|
|
|
Var
|
|
I,L : Integer;
|
|
D : JSValue;
|
|
|
|
begin
|
|
Result:=0;
|
|
D:=F.Properties['maxlen'];
|
|
if Not jsIsNan(toNumber(D)) then
|
|
begin
|
|
Result:=Trunc(toNumber(D));
|
|
if (Result<=0) then
|
|
Raise EJSONDataset.CreateFmt('Invalid maximum length specifier for field %s',[AName])
|
|
end
|
|
else
|
|
begin
|
|
For I:=0 to Rows.Length-1 do
|
|
begin
|
|
D:=FieldMapper.GetJSONDataForField(Aname,AIndex,Rows[i]);
|
|
if isString(D) then
|
|
begin
|
|
l:=Length(String(D));
|
|
if L>Result then
|
|
Result:=L;
|
|
end;
|
|
end;
|
|
end;
|
|
if (Result=0) then
|
|
Result:=20;
|
|
end;
|
|
|
|
constructor TExtJSJSONDataSet.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create(AOwner);
|
|
UseDateTimeFormatFields:=True;
|
|
end;
|
|
|
|
procedure TExtJSJSONDataSet.MetaDataToFieldDefs;
|
|
|
|
Var
|
|
A : TJSArray;
|
|
F : TJSObject;
|
|
I,FS : Integer;
|
|
N: String;
|
|
ft: TFieldType;
|
|
D : JSValue;
|
|
|
|
begin
|
|
FieldDefs.Clear;
|
|
D:=Metadata.Properties['fields'];
|
|
if Not IsArray(D) then
|
|
Raise EJSONDataset.Create('Invalid metadata object');
|
|
A:=TJSArray(D);
|
|
For I:=0 to A.Length-1 do
|
|
begin
|
|
If Not isObject(A[i]) then
|
|
Raise EJSONDataset.CreateFmt('Field definition %d in metadata is not an object',[i]);
|
|
F:=TJSObject(A[i]);
|
|
D:=F.Properties['name'];
|
|
If Not isString(D) then
|
|
Raise EJSONDataset.CreateFmt('Field definition %d in has no or invalid name property',[i]);
|
|
N:=String(D);
|
|
D:=F.Properties['type'];
|
|
If IsNull(D) or isUndefined(D) then
|
|
ft:=ftstring
|
|
else If Not isString(D) then
|
|
begin
|
|
Raise EJSONDataset.CreateFmt('Field definition %d in has invalid type property',[i])
|
|
end
|
|
else
|
|
begin
|
|
ft:=StringToFieldType(String(D));
|
|
end;
|
|
if (ft=ftString) then
|
|
fs:=GetStringFieldLength(F,N,I)
|
|
else
|
|
fs:=0;
|
|
FieldDefs.Add(N,ft,fs);
|
|
end;
|
|
FFields:=A;
|
|
end;
|
|
|
|
procedure TExtJSJSONDataSet.InternalOpen;
|
|
|
|
Var
|
|
I : integer;
|
|
|
|
begin
|
|
inherited InternalOpen;
|
|
Writeln('Checking ID field ',IDField, ' as key field');
|
|
for I:=0 to Fields.Count-1 do
|
|
If SameText(Fields[i].FieldName,IDField) then
|
|
begin
|
|
Fields[i].ProviderFlags:=Fields[i].ProviderFlags+[pfInKey];
|
|
Writeln('Setting ID field ',IDField, ' as key field');
|
|
end;
|
|
end;
|
|
|
|
function TExtJSJSONDataSet.DoResolveRecordUpdate(anUpdate: TRecordUpdateDescriptor): Boolean;
|
|
|
|
Var
|
|
D : JSValue;
|
|
O : TJSObject;
|
|
A : TJSArray;
|
|
I,RecordIndex : Integer;
|
|
FN : String;
|
|
|
|
begin
|
|
Result:=True;
|
|
if anUpdate.OriginalStatus=usDeleted then
|
|
exit;
|
|
D:=anUpdate.ServerData;
|
|
If isNull(D) then
|
|
exit;
|
|
if not isNumber(AnUpdate.Bookmark.Data) then
|
|
exit(False);
|
|
RecordIndex:=Integer(AnUpdate.Bookmark.Data);
|
|
If isString(D) then
|
|
O:=TJSOBject(TJSJSON.Parse(String(D)))
|
|
else if isObject(D) then
|
|
O:=TJSOBject(D)
|
|
else
|
|
Exit(False);
|
|
if Not isArray(O[Root]) then
|
|
exit(False);
|
|
A:=TJSArray(O[Root]);
|
|
If A.Length=1 then
|
|
begin
|
|
O:=TJSObject(A[0]);
|
|
For I:=0 to Fields.Count-1 do
|
|
begin
|
|
if O.hasOwnProperty(Fields[i].FieldName) then
|
|
FieldMapper.SetJSONDataForField(Fields[i],Rows[RecordIndex],O[FN]);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TExtJSJSONDataSet.DataPacketReceived(ARequest: TDataRequest): Boolean;
|
|
|
|
Var
|
|
O : TJSObject;
|
|
A : TJSArray;
|
|
|
|
begin
|
|
Result:=False;
|
|
If isNull(aRequest.Data) then
|
|
exit;
|
|
If isString(aRequest.Data) then
|
|
O:=TJSOBject(TJSJSON.Parse(String(aRequest.Data)))
|
|
else if isObject(aRequest.Data) then
|
|
O:=TJSOBject(aRequest.Data)
|
|
else
|
|
DatabaseError('Cannot handle data packet');
|
|
if (Root='') then
|
|
root:='rows';
|
|
if (IDField='') then
|
|
idField:='id';
|
|
if O.hasOwnProperty('metaData') and isObject(o['metaData']) then
|
|
begin
|
|
if not Active then // Load fields from metadata
|
|
metaData:=TJSObject(o['metaData']);
|
|
// We must always check this one...
|
|
if metaData.hasOwnProperty('root') and isString(metaData['root']) then
|
|
Root:=string(metaData['root']);
|
|
if metaData.hasOwnProperty('idField') and isString(metaData['idField']) then
|
|
IDField:=string(metaData['idField']);
|
|
end;
|
|
if O.hasOwnProperty(Root) and isArray(o[Root]) then
|
|
begin
|
|
A:=TJSArray(o[Root]);
|
|
Result:=A.Length>0;
|
|
AddToRows(A);
|
|
end;
|
|
end;
|
|
|
|
function TExtJSJSONDataSet.GenerateMetaData: TJSObject;
|
|
|
|
Var
|
|
F : TJSArray;
|
|
O : TJSObject;
|
|
I,M : Integer;
|
|
T : STring;
|
|
|
|
begin
|
|
Result:=TJSObject.New;
|
|
F:=TJSArray.New;
|
|
Result.Properties['fields']:=F;
|
|
For I:=0 to FieldDefs.Count -1 do
|
|
begin
|
|
O:=New(['name',FieldDefs[i].name]);
|
|
F.push(O);
|
|
M:=0;
|
|
case FieldDefs[i].DataType of
|
|
ftfixedchar,
|
|
ftString:
|
|
begin
|
|
T:='string';
|
|
M:=FieldDefs[i].Size;
|
|
end;
|
|
ftBoolean: T:='boolean';
|
|
ftDate,
|
|
ftTime,
|
|
ftDateTime: T:='date';
|
|
ftFloat: t:='float';
|
|
ftInteger,
|
|
ftAutoInc,
|
|
ftLargeInt : t:='int';
|
|
else
|
|
Raise EJSONDataset.CreateFmt('Unsupported field type : %s',[Ord(FieldDefs[i].DataType)]);
|
|
end; // case
|
|
O.Properties['type']:=t;
|
|
if M<>0 then
|
|
O.Properties['maxlen']:=M;
|
|
end;
|
|
Result.Properties['root']:='rows';
|
|
end;
|
|
|
|
function TExtJSJSONDataSet.ConvertDateFormat(S: String): String;
|
|
|
|
{ Not handled: N S w z W t L o O P T Z c U MS }
|
|
|
|
begin
|
|
Result:=StringReplace(S,'y','yy',[rfReplaceall]);
|
|
Result:=StringReplace(Result,'Y','yyyy',[rfReplaceall]);
|
|
Result:=StringReplace(Result,'g','h',[rfReplaceall]);
|
|
Result:=StringReplace(Result,'G','hh',[rfReplaceall]);
|
|
Result:=StringReplace(Result,'F','mmmm',[rfReplaceall]);
|
|
Result:=StringReplace(Result,'M','mmm',[rfReplaceall]);
|
|
Result:=StringReplace(Result,'n','m',[rfReplaceall]);
|
|
Result:=StringReplace(Result,'D','ddd',[rfReplaceall]);
|
|
Result:=StringReplace(Result,'j','d',[rfReplaceall]);
|
|
Result:=StringReplace(Result,'l','dddd',[rfReplaceall]);
|
|
Result:=StringReplace(Result,'i','nn',[rfReplaceall]);
|
|
Result:=StringReplace(Result,'u','zzz',[rfReplaceall]);
|
|
Result:=StringReplace(Result,'a','am/pm',[rfReplaceall,rfIgnoreCase]);
|
|
Result:=LowerCase(Result);
|
|
end;
|
|
|
|
procedure TExtJSJSONDataSet.InitDateTimeFields;
|
|
|
|
Var
|
|
F : TJSObject;
|
|
FF : TField;
|
|
I: Integer;
|
|
Fmt : String;
|
|
D : JSValue;
|
|
|
|
begin
|
|
If (FFields=Nil) then
|
|
Exit;
|
|
For I:=0 to FFields.Length-1 do
|
|
begin
|
|
F:=TJSObject(FFields[i]);
|
|
D:=F.Properties['type'];
|
|
if isString(D) and (String(D)='date') then
|
|
begin
|
|
D:=F.Properties['dateFormat'];
|
|
if isString(D) then
|
|
begin
|
|
FMT:=ConvertDateFormat(String(D));
|
|
FF:=FindField(String(F.Properties['name']));
|
|
if (FF<>Nil) and (FF.DataType in [ftDate,ftTime,ftDateTime]) and (FF.FieldKind=fkData) then
|
|
begin
|
|
if FF is TJSONDateField then
|
|
TJSONDateField(FF).DateFormat:=Fmt
|
|
else if FF is TJSONTimeField then
|
|
TJSONTimeField(FF).TimeFormat:=Fmt
|
|
else if FF is TJSONDateTimeField then
|
|
TJSONDateTimeField(FF).DateTimeFormat:=Fmt;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{ TExtJSJSONArrayDataSet }
|
|
|
|
function TExtJSJSONArrayDataSet.CreateFieldMapper: TJSONFieldMapper;
|
|
begin
|
|
Result:=TJSONArrayFieldMapper.Create;
|
|
end;
|
|
|
|
{ TExtJSJSONObjectDataSet }
|
|
|
|
function TExtJSJSONObjectDataSet.CreateFieldMapper: TJSONFieldMapper;
|
|
begin
|
|
Result:=TJSONObjectFieldMapper.Create;
|
|
end;
|
|
|
|
end.
|
|
|