123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413 |
- {
- This file is part of the Free Pascal run time library.
- Copyright (c) 2019 by Michael Van Canneyt, member of the
- Free Pascal development team
- Simple EXTJS JSON dataset component.
- See the file COPYING.FPC, included in this distribution,
- for details about the copyright.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- **********************************************************************}
- 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;
- Property Indexes;
- Property ActiveIndex;
- // 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;
- Property OwnsData;
- 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;
- for I:=0 to Fields.Count-1 do
- If SameText(Fields[i].FieldName,IDField) then
- Fields[i].ProviderFlags:=Fields[i].ProviderFlags+[pfInKey];
- 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
- FN:=Fields[i].FieldName;
- if O.hasOwnProperty(FN) 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.
|