extjsdataset.pas 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  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. Property OwnsData;
  75. end;
  76. { TExtJSJSONObjectDataSet }
  77. // Use this dataset for data where the data is an array of objects.
  78. TExtJSJSONObjectDataSet = Class(TExtJSJSONDataSet)
  79. Protected
  80. Function CreateFieldMapper : TJSONFieldMapper; override;
  81. end;
  82. { TExtJSJSONArrayDataSet }
  83. // Use this dataset for data where the data is an array of arrays.
  84. TExtJSJSONArrayDataSet = Class(TExtJSJSONDataSet)
  85. Protected
  86. Function CreateFieldMapper : TJSONFieldMapper; override;
  87. end;
  88. implementation
  89. { TExtJSJSONDataSet }
  90. function TExtJSJSONDataSet.StringToFieldType(S: String): TFieldType;
  91. begin
  92. if (s='int') then
  93. Result:=ftLargeInt
  94. else if (s='float') then
  95. Result:=ftFloat
  96. else if (s='boolean') then
  97. Result:=ftBoolean
  98. else if (s='date') then
  99. Result:=ftDateTime
  100. else if (s='string') or (s='auto') or (s='') then
  101. Result:=ftString
  102. else
  103. if MapUnknownToStringType then
  104. Result:=ftString
  105. else
  106. Raise EJSONDataset.CreateFmt('Unknown JSON data type : %s',[s]);
  107. end;
  108. function TExtJSJSONDataSet.GetStringFieldLength(F: TJSObject; AName: String;
  109. AIndex: Integer): integer;
  110. Var
  111. I,L : Integer;
  112. D : JSValue;
  113. begin
  114. Result:=0;
  115. D:=F.Properties['maxlen'];
  116. if Not jsIsNan(toNumber(D)) then
  117. begin
  118. Result:=Trunc(toNumber(D));
  119. if (Result<=0) then
  120. Raise EJSONDataset.CreateFmt('Invalid maximum length specifier for field %s',[AName])
  121. end
  122. else
  123. begin
  124. For I:=0 to Rows.Length-1 do
  125. begin
  126. D:=FieldMapper.GetJSONDataForField(Aname,AIndex,Rows[i]);
  127. if isString(D) then
  128. begin
  129. l:=Length(String(D));
  130. if L>Result then
  131. Result:=L;
  132. end;
  133. end;
  134. end;
  135. if (Result=0) then
  136. Result:=20;
  137. end;
  138. constructor TExtJSJSONDataSet.Create(AOwner: TComponent);
  139. begin
  140. inherited Create(AOwner);
  141. UseDateTimeFormatFields:=True;
  142. end;
  143. procedure TExtJSJSONDataSet.MetaDataToFieldDefs;
  144. Var
  145. A : TJSArray;
  146. F : TJSObject;
  147. I,FS : Integer;
  148. N: String;
  149. ft: TFieldType;
  150. D : JSValue;
  151. begin
  152. FieldDefs.Clear;
  153. D:=Metadata.Properties['fields'];
  154. if Not IsArray(D) then
  155. Raise EJSONDataset.Create('Invalid metadata object');
  156. A:=TJSArray(D);
  157. For I:=0 to A.Length-1 do
  158. begin
  159. If Not isObject(A[i]) then
  160. Raise EJSONDataset.CreateFmt('Field definition %d in metadata is not an object',[i]);
  161. F:=TJSObject(A[i]);
  162. D:=F.Properties['name'];
  163. If Not isString(D) then
  164. Raise EJSONDataset.CreateFmt('Field definition %d in has no or invalid name property',[i]);
  165. N:=String(D);
  166. D:=F.Properties['type'];
  167. If IsNull(D) or isUndefined(D) then
  168. ft:=ftstring
  169. else If Not isString(D) then
  170. begin
  171. Raise EJSONDataset.CreateFmt('Field definition %d in has invalid type property',[i])
  172. end
  173. else
  174. begin
  175. ft:=StringToFieldType(String(D));
  176. end;
  177. if (ft=ftString) then
  178. fs:=GetStringFieldLength(F,N,I)
  179. else
  180. fs:=0;
  181. FieldDefs.Add(N,ft,fs);
  182. end;
  183. FFields:=A;
  184. end;
  185. procedure TExtJSJSONDataSet.InternalOpen;
  186. Var
  187. I : integer;
  188. begin
  189. inherited InternalOpen;
  190. for I:=0 to Fields.Count-1 do
  191. If SameText(Fields[i].FieldName,IDField) then
  192. Fields[i].ProviderFlags:=Fields[i].ProviderFlags+[pfInKey];
  193. end;
  194. function TExtJSJSONDataSet.DoResolveRecordUpdate(anUpdate: TRecordUpdateDescriptor): Boolean;
  195. Var
  196. D : JSValue;
  197. O : TJSObject;
  198. A : TJSArray;
  199. I,RecordIndex : Integer;
  200. FN : String;
  201. begin
  202. Result:=True;
  203. if anUpdate.OriginalStatus=usDeleted then
  204. exit;
  205. D:=anUpdate.ServerData;
  206. If isNull(D) then
  207. exit;
  208. if not isNumber(AnUpdate.Bookmark.Data) then
  209. exit(False);
  210. RecordIndex:=Integer(AnUpdate.Bookmark.Data);
  211. If isString(D) then
  212. O:=TJSOBject(TJSJSON.Parse(String(D)))
  213. else if isObject(D) then
  214. O:=TJSOBject(D)
  215. else
  216. Exit(False);
  217. if Not isArray(O[Root]) then
  218. exit(False);
  219. A:=TJSArray(O[Root]);
  220. If A.Length=1 then
  221. begin
  222. O:=TJSObject(A[0]);
  223. For I:=0 to Fields.Count-1 do
  224. begin
  225. FN:=Fields[i].FieldName;
  226. if O.hasOwnProperty(FN) then
  227. FieldMapper.SetJSONDataForField(Fields[i],Rows[RecordIndex],O[FN]);
  228. end;
  229. end;
  230. end;
  231. function TExtJSJSONDataSet.DataPacketReceived(ARequest: TDataRequest): Boolean;
  232. Var
  233. O : TJSObject;
  234. A : TJSArray;
  235. begin
  236. Result:=False;
  237. If isNull(aRequest.Data) then
  238. exit;
  239. If isString(aRequest.Data) then
  240. O:=TJSOBject(TJSJSON.Parse(String(aRequest.Data)))
  241. else if isObject(aRequest.Data) then
  242. O:=TJSOBject(aRequest.Data)
  243. else
  244. DatabaseError('Cannot handle data packet');
  245. if (Root='') then
  246. root:='rows';
  247. if (IDField='') then
  248. idField:='id';
  249. if O.hasOwnProperty('metaData') and isObject(o['metaData']) then
  250. begin
  251. if not Active then // Load fields from metadata
  252. metaData:=TJSObject(o['metaData']);
  253. // We must always check this one...
  254. if metaData.hasOwnProperty('root') and isString(metaData['root']) then
  255. Root:=string(metaData['root']);
  256. if metaData.hasOwnProperty('idField') and isString(metaData['idField']) then
  257. IDField:=string(metaData['idField']);
  258. end;
  259. if O.hasOwnProperty(Root) and isArray(o[Root]) then
  260. begin
  261. A:=TJSArray(o[Root]);
  262. Result:=A.Length>0;
  263. AddToRows(A);
  264. end;
  265. end;
  266. function TExtJSJSONDataSet.GenerateMetaData: TJSObject;
  267. Var
  268. F : TJSArray;
  269. O : TJSObject;
  270. I,M : Integer;
  271. T : STring;
  272. begin
  273. Result:=TJSObject.New;
  274. F:=TJSArray.New;
  275. Result.Properties['fields']:=F;
  276. For I:=0 to FieldDefs.Count -1 do
  277. begin
  278. O:=New(['name',FieldDefs[i].name]);
  279. F.push(O);
  280. M:=0;
  281. case FieldDefs[i].DataType of
  282. ftfixedchar,
  283. ftString:
  284. begin
  285. T:='string';
  286. M:=FieldDefs[i].Size;
  287. end;
  288. ftBoolean: T:='boolean';
  289. ftDate,
  290. ftTime,
  291. ftDateTime: T:='date';
  292. ftFloat: t:='float';
  293. ftInteger,
  294. ftAutoInc,
  295. ftLargeInt : t:='int';
  296. else
  297. Raise EJSONDataset.CreateFmt('Unsupported field type : %s',[Ord(FieldDefs[i].DataType)]);
  298. end; // case
  299. O.Properties['type']:=t;
  300. if M<>0 then
  301. O.Properties['maxlen']:=M;
  302. end;
  303. Result.Properties['root']:='rows';
  304. end;
  305. function TExtJSJSONDataSet.ConvertDateFormat(S: String): String;
  306. { Not handled: N S w z W t L o O P T Z c U MS }
  307. begin
  308. Result:=StringReplace(S,'y','yy',[rfReplaceall]);
  309. Result:=StringReplace(Result,'Y','yyyy',[rfReplaceall]);
  310. Result:=StringReplace(Result,'g','h',[rfReplaceall]);
  311. Result:=StringReplace(Result,'G','hh',[rfReplaceall]);
  312. Result:=StringReplace(Result,'F','mmmm',[rfReplaceall]);
  313. Result:=StringReplace(Result,'M','mmm',[rfReplaceall]);
  314. Result:=StringReplace(Result,'n','m',[rfReplaceall]);
  315. Result:=StringReplace(Result,'D','ddd',[rfReplaceall]);
  316. Result:=StringReplace(Result,'j','d',[rfReplaceall]);
  317. Result:=StringReplace(Result,'l','dddd',[rfReplaceall]);
  318. Result:=StringReplace(Result,'i','nn',[rfReplaceall]);
  319. Result:=StringReplace(Result,'u','zzz',[rfReplaceall]);
  320. Result:=StringReplace(Result,'a','am/pm',[rfReplaceall,rfIgnoreCase]);
  321. Result:=LowerCase(Result);
  322. end;
  323. procedure TExtJSJSONDataSet.InitDateTimeFields;
  324. Var
  325. F : TJSObject;
  326. FF : TField;
  327. I: Integer;
  328. Fmt : String;
  329. D : JSValue;
  330. begin
  331. If (FFields=Nil) then
  332. Exit;
  333. For I:=0 to FFields.Length-1 do
  334. begin
  335. F:=TJSObject(FFields[i]);
  336. D:=F.Properties['type'];
  337. if isString(D) and (String(D)='date') then
  338. begin
  339. D:=F.Properties['dateFormat'];
  340. if isString(D) then
  341. begin
  342. FMT:=ConvertDateFormat(String(D));
  343. FF:=FindField(String(F.Properties['name']));
  344. if (FF<>Nil) and (FF.DataType in [ftDate,ftTime,ftDateTime]) and (FF.FieldKind=fkData) then
  345. begin
  346. if FF is TJSONDateField then
  347. TJSONDateField(FF).DateFormat:=Fmt
  348. else if FF is TJSONTimeField then
  349. TJSONTimeField(FF).TimeFormat:=Fmt
  350. else if FF is TJSONDateTimeField then
  351. TJSONDateTimeField(FF).DateTimeFormat:=Fmt;
  352. end;
  353. end;
  354. end;
  355. end;
  356. end;
  357. { TExtJSJSONArrayDataSet }
  358. function TExtJSJSONArrayDataSet.CreateFieldMapper: TJSONFieldMapper;
  359. begin
  360. Result:=TJSONArrayFieldMapper.Create;
  361. end;
  362. { TExtJSJSONObjectDataSet }
  363. function TExtJSJSONObjectDataSet.CreateFieldMapper: TJSONFieldMapper;
  364. begin
  365. Result:=TJSONObjectFieldMapper.Create;
  366. end;
  367. end.