pas2js/packages/fcl-db/extjsdataset.pas
2018-05-17 13:22:48 +00:00

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.