mirror of
https://gitlab.com/freepascal.org/fpc/pas2js.git
synced 2025-08-28 21:40:15 +02:00
* Add extjsdataset
This commit is contained in:
parent
3dac9ac047
commit
e24a9c6b04
364
packages/fcl-db/extjsdataset.pas
Normal file
364
packages/fcl-db/extjsdataset.pas
Normal file
@ -0,0 +1,364 @@
|
||||
unit ExtJSDataset;
|
||||
|
||||
{$mode objfpc}{$H+}
|
||||
|
||||
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
|
||||
// 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;
|
||||
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;
|
||||
|
||||
procedure TExtJSJSONDataSet.MetaDataToFieldDefs;
|
||||
|
||||
Var
|
||||
A : TJSArray;
|
||||
F : TJSObject;
|
||||
I,J,FS : Integer;
|
||||
N,idf : 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,J : 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.
|
||||
|
@ -35,7 +35,7 @@
|
||||
<UnitName Value="RestConnection"/>
|
||||
</Item4>
|
||||
<Item5>
|
||||
<Filename Value="extjsdataset.pp"/>
|
||||
<Filename Value="extjsdataset.pas"/>
|
||||
<UnitName Value="extjsdataset"/>
|
||||
</Item5>
|
||||
</Files>
|
||||
|
Loading…
Reference in New Issue
Block a user