extjsdataset.pas 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. {
  2. This file is part of the Free Pascal run time library.
  3. Copyright (c) 2019 by Michael Van Canneyt, member of the
  4. Free Pascal development team
  5. Simple EXTJS JSON dataset component.
  6. See the file COPYING.FPC, included in this distribution,
  7. for details about the copyright.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  11. **********************************************************************}
  12. unit ExtJSDataset;
  13. {$mode objfpc}
  14. interface
  15. uses
  16. Classes, SysUtils, db, JS, jsondataset;
  17. type
  18. { TExtJSJSONDataSet }
  19. // Base for ExtJS datasets. It handles MetaData conversion.
  20. TExtJSJSONDataSet = Class(TBaseJSONDataset)
  21. Private
  22. FFields : TJSArray;
  23. FIDField: String;
  24. FRoot: String;
  25. Protected
  26. // Data proxy support
  27. Procedure InternalOpen; override;
  28. function DoResolveRecordUpdate(anUpdate: TRecordUpdateDescriptor): Boolean; override;
  29. Function DataPacketReceived(ARequest: TDataRequest) : Boolean; override;
  30. Function GenerateMetaData : TJSObject;
  31. function ConvertDateFormat(S: String): String; virtual;
  32. Procedure MetaDataToFieldDefs; override;
  33. procedure InitDateTimeFields; override;
  34. function StringToFieldType(S: String): TFieldType;virtual;
  35. function GetStringFieldLength(F: TJSObject; AName: String; AIndex: Integer): integer; virtual;
  36. Public
  37. Constructor Create(AOwner : TComponent); override;
  38. // Can be set directly if the dataset is closed.
  39. Property MetaData;
  40. // Can be set directly if the dataset is closed. If metadata is set, it must match the data.
  41. Property Rows;
  42. // Root of data array in data packet
  43. property Root : String Read FRoot Write FRoot;
  44. // property IDField
  45. property IDField : String Read FIDField Write FIDField;
  46. published
  47. Property FieldDefs;
  48. Property Indexes;
  49. Property ActiveIndex;
  50. // redeclared data set properties
  51. property Active;
  52. property BeforeOpen;
  53. property AfterOpen;
  54. property BeforeClose;
  55. property AfterClose;
  56. property BeforeInsert;
  57. property AfterInsert;
  58. property BeforeEdit;
  59. property AfterEdit;
  60. property BeforePost;
  61. property AfterPost;
  62. property BeforeCancel;
  63. property AfterCancel;
  64. property BeforeDelete;
  65. property AfterDelete;
  66. property BeforeScroll;
  67. property AfterScroll;
  68. property OnCalcFields;
  69. property OnDeleteError;
  70. property OnEditError;
  71. property OnFilterRecord;
  72. property OnNewRecord;
  73. property OnPostError;
  74. end;
  75. { TExtJSJSONObjectDataSet }
  76. // Use this dataset for data where the data is an array of objects.
  77. TExtJSJSONObjectDataSet = Class(TExtJSJSONDataSet)
  78. Protected
  79. Function CreateFieldMapper : TJSONFieldMapper; override;
  80. end;
  81. { TExtJSJSONArrayDataSet }
  82. // Use this dataset for data where the data is an array of arrays.
  83. TExtJSJSONArrayDataSet = Class(TExtJSJSONDataSet)
  84. Protected
  85. Function CreateFieldMapper : TJSONFieldMapper; override;
  86. end;
  87. implementation
  88. { TExtJSJSONDataSet }
  89. function TExtJSJSONDataSet.StringToFieldType(S: String): TFieldType;
  90. begin
  91. if (s='int') then
  92. Result:=ftLargeInt
  93. else if (s='float') then
  94. Result:=ftFloat
  95. else if (s='boolean') then
  96. Result:=ftBoolean
  97. else if (s='date') then
  98. Result:=ftDateTime
  99. else if (s='string') or (s='auto') or (s='') then
  100. Result:=ftString
  101. else
  102. if MapUnknownToStringType then
  103. Result:=ftString
  104. else
  105. Raise EJSONDataset.CreateFmt('Unknown JSON data type : %s',[s]);
  106. end;
  107. function TExtJSJSONDataSet.GetStringFieldLength(F: TJSObject; AName: String;
  108. AIndex: Integer): integer;
  109. Var
  110. I,L : Integer;
  111. D : JSValue;
  112. begin
  113. Result:=0;
  114. D:=F.Properties['maxlen'];
  115. if Not jsIsNan(toNumber(D)) then
  116. begin
  117. Result:=Trunc(toNumber(D));
  118. if (Result<=0) then
  119. Raise EJSONDataset.CreateFmt('Invalid maximum length specifier for field %s',[AName])
  120. end
  121. else
  122. begin
  123. For I:=0 to Rows.Length-1 do
  124. begin
  125. D:=FieldMapper.GetJSONDataForField(Aname,AIndex,Rows[i]);
  126. if isString(D) then
  127. begin
  128. l:=Length(String(D));
  129. if L>Result then
  130. Result:=L;
  131. end;
  132. end;
  133. end;
  134. if (Result=0) then
  135. Result:=20;
  136. end;
  137. constructor TExtJSJSONDataSet.Create(AOwner: TComponent);
  138. begin
  139. inherited Create(AOwner);
  140. UseDateTimeFormatFields:=True;
  141. end;
  142. procedure TExtJSJSONDataSet.MetaDataToFieldDefs;
  143. Var
  144. A : TJSArray;
  145. F : TJSObject;
  146. I,FS : Integer;
  147. N: String;
  148. ft: TFieldType;
  149. D : JSValue;
  150. begin
  151. FieldDefs.Clear;
  152. D:=Metadata.Properties['fields'];
  153. if Not IsArray(D) then
  154. Raise EJSONDataset.Create('Invalid metadata object');
  155. A:=TJSArray(D);
  156. For I:=0 to A.Length-1 do
  157. begin
  158. If Not isObject(A[i]) then
  159. Raise EJSONDataset.CreateFmt('Field definition %d in metadata is not an object',[i]);
  160. F:=TJSObject(A[i]);
  161. D:=F.Properties['name'];
  162. If Not isString(D) then
  163. Raise EJSONDataset.CreateFmt('Field definition %d in has no or invalid name property',[i]);
  164. N:=String(D);
  165. D:=F.Properties['type'];
  166. If IsNull(D) or isUndefined(D) then
  167. ft:=ftstring
  168. else If Not isString(D) then
  169. begin
  170. Raise EJSONDataset.CreateFmt('Field definition %d in has invalid type property',[i])
  171. end
  172. else
  173. begin
  174. ft:=StringToFieldType(String(D));
  175. end;
  176. if (ft=ftString) then
  177. fs:=GetStringFieldLength(F,N,I)
  178. else
  179. fs:=0;
  180. FieldDefs.Add(N,ft,fs);
  181. end;
  182. FFields:=A;
  183. end;
  184. procedure TExtJSJSONDataSet.InternalOpen;
  185. Var
  186. I : integer;
  187. begin
  188. inherited InternalOpen;
  189. for I:=0 to Fields.Count-1 do
  190. If SameText(Fields[i].FieldName,IDField) then
  191. Fields[i].ProviderFlags:=Fields[i].ProviderFlags+[pfInKey];
  192. end;
  193. function TExtJSJSONDataSet.DoResolveRecordUpdate(anUpdate: TRecordUpdateDescriptor): Boolean;
  194. Var
  195. D : JSValue;
  196. O : TJSObject;
  197. A : TJSArray;
  198. I,RecordIndex : Integer;
  199. FN : String;
  200. begin
  201. Result:=True;
  202. if anUpdate.OriginalStatus=usDeleted then
  203. exit;
  204. D:=anUpdate.ServerData;
  205. If isNull(D) then
  206. exit;
  207. if not isNumber(AnUpdate.Bookmark.Data) then
  208. exit(False);
  209. RecordIndex:=Integer(AnUpdate.Bookmark.Data);
  210. If isString(D) then
  211. O:=TJSOBject(TJSJSON.Parse(String(D)))
  212. else if isObject(D) then
  213. O:=TJSOBject(D)
  214. else
  215. Exit(False);
  216. if Not isArray(O[Root]) then
  217. exit(False);
  218. A:=TJSArray(O[Root]);
  219. If A.Length=1 then
  220. begin
  221. O:=TJSObject(A[0]);
  222. For I:=0 to Fields.Count-1 do
  223. begin
  224. if O.hasOwnProperty(Fields[i].FieldName) then
  225. FieldMapper.SetJSONDataForField(Fields[i],Rows[RecordIndex],O[FN]);
  226. end;
  227. end;
  228. end;
  229. function TExtJSJSONDataSet.DataPacketReceived(ARequest: TDataRequest): Boolean;
  230. Var
  231. O : TJSObject;
  232. A : TJSArray;
  233. begin
  234. Result:=False;
  235. If isNull(aRequest.Data) then
  236. exit;
  237. If isString(aRequest.Data) then
  238. O:=TJSOBject(TJSJSON.Parse(String(aRequest.Data)))
  239. else if isObject(aRequest.Data) then
  240. O:=TJSOBject(aRequest.Data)
  241. else
  242. DatabaseError('Cannot handle data packet');
  243. if (Root='') then
  244. root:='rows';
  245. if (IDField='') then
  246. idField:='id';
  247. if O.hasOwnProperty('metaData') and isObject(o['metaData']) then
  248. begin
  249. if not Active then // Load fields from metadata
  250. metaData:=TJSObject(o['metaData']);
  251. // We must always check this one...
  252. if metaData.hasOwnProperty('root') and isString(metaData['root']) then
  253. Root:=string(metaData['root']);
  254. if metaData.hasOwnProperty('idField') and isString(metaData['idField']) then
  255. IDField:=string(metaData['idField']);
  256. end;
  257. if O.hasOwnProperty(Root) and isArray(o[Root]) then
  258. begin
  259. A:=TJSArray(o[Root]);
  260. Result:=A.Length>0;
  261. AddToRows(A);
  262. end;
  263. end;
  264. function TExtJSJSONDataSet.GenerateMetaData: TJSObject;
  265. Var
  266. F : TJSArray;
  267. O : TJSObject;
  268. I,M : Integer;
  269. T : STring;
  270. begin
  271. Result:=TJSObject.New;
  272. F:=TJSArray.New;
  273. Result.Properties['fields']:=F;
  274. For I:=0 to FieldDefs.Count -1 do
  275. begin
  276. O:=New(['name',FieldDefs[i].name]);
  277. F.push(O);
  278. M:=0;
  279. case FieldDefs[i].DataType of
  280. ftfixedchar,
  281. ftString:
  282. begin
  283. T:='string';
  284. M:=FieldDefs[i].Size;
  285. end;
  286. ftBoolean: T:='boolean';
  287. ftDate,
  288. ftTime,
  289. ftDateTime: T:='date';
  290. ftFloat: t:='float';
  291. ftInteger,
  292. ftAutoInc,
  293. ftLargeInt : t:='int';
  294. else
  295. Raise EJSONDataset.CreateFmt('Unsupported field type : %s',[Ord(FieldDefs[i].DataType)]);
  296. end; // case
  297. O.Properties['type']:=t;
  298. if M<>0 then
  299. O.Properties['maxlen']:=M;
  300. end;
  301. Result.Properties['root']:='rows';
  302. end;
  303. function TExtJSJSONDataSet.ConvertDateFormat(S: String): String;
  304. { Not handled: N S w z W t L o O P T Z c U MS }
  305. begin
  306. Result:=StringReplace(S,'y','yy',[rfReplaceall]);
  307. Result:=StringReplace(Result,'Y','yyyy',[rfReplaceall]);
  308. Result:=StringReplace(Result,'g','h',[rfReplaceall]);
  309. Result:=StringReplace(Result,'G','hh',[rfReplaceall]);
  310. Result:=StringReplace(Result,'F','mmmm',[rfReplaceall]);
  311. Result:=StringReplace(Result,'M','mmm',[rfReplaceall]);
  312. Result:=StringReplace(Result,'n','m',[rfReplaceall]);
  313. Result:=StringReplace(Result,'D','ddd',[rfReplaceall]);
  314. Result:=StringReplace(Result,'j','d',[rfReplaceall]);
  315. Result:=StringReplace(Result,'l','dddd',[rfReplaceall]);
  316. Result:=StringReplace(Result,'i','nn',[rfReplaceall]);
  317. Result:=StringReplace(Result,'u','zzz',[rfReplaceall]);
  318. Result:=StringReplace(Result,'a','am/pm',[rfReplaceall,rfIgnoreCase]);
  319. Result:=LowerCase(Result);
  320. end;
  321. procedure TExtJSJSONDataSet.InitDateTimeFields;
  322. Var
  323. F : TJSObject;
  324. FF : TField;
  325. I: Integer;
  326. Fmt : String;
  327. D : JSValue;
  328. begin
  329. If (FFields=Nil) then
  330. Exit;
  331. For I:=0 to FFields.Length-1 do
  332. begin
  333. F:=TJSObject(FFields[i]);
  334. D:=F.Properties['type'];
  335. if isString(D) and (String(D)='date') then
  336. begin
  337. D:=F.Properties['dateFormat'];
  338. if isString(D) then
  339. begin
  340. FMT:=ConvertDateFormat(String(D));
  341. FF:=FindField(String(F.Properties['name']));
  342. if (FF<>Nil) and (FF.DataType in [ftDate,ftTime,ftDateTime]) and (FF.FieldKind=fkData) then
  343. begin
  344. if FF is TJSONDateField then
  345. TJSONDateField(FF).DateFormat:=Fmt
  346. else if FF is TJSONTimeField then
  347. TJSONTimeField(FF).TimeFormat:=Fmt
  348. else if FF is TJSONDateTimeField then
  349. TJSONDateTimeField(FF).DateTimeFormat:=Fmt;
  350. end;
  351. end;
  352. end;
  353. end;
  354. end;
  355. { TExtJSJSONArrayDataSet }
  356. function TExtJSJSONArrayDataSet.CreateFieldMapper: TJSONFieldMapper;
  357. begin
  358. Result:=TJSONArrayFieldMapper.Create;
  359. end;
  360. { TExtJSJSONObjectDataSet }
  361. function TExtJSJSONObjectDataSet.CreateFieldMapper: TJSONFieldMapper;
  362. begin
  363. Result:=TJSONObjectFieldMapper.Create;
  364. end;
  365. end.