extjsjson.pp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  1. {
  2. This file is part of the Free Pascal run time library.
  3. Copyright (c) 2019 by the Free Pascal development team
  4. extjs json
  5. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************}
  11. unit extjsjson;
  12. {$mode objfpc}{$H+}
  13. interface
  14. uses
  15. Classes, SysUtils, httpdefs, fphttp, fpwebdata, fpextjs, fpjson, db, jsonparser;
  16. type
  17. { TExtJSJSonWebdataInputAdaptor }
  18. TExtJSJSonWebdataInputAdaptor = CLass(TCustomWebdataInputAdaptor)
  19. private
  20. FRows : TJSONArray;
  21. FCurrentRow : TJSONObject;
  22. FRowIndex : integer;
  23. function CheckData: Boolean;
  24. Public
  25. procedure reset; override;
  26. Function GetNextBatch : Boolean; override;
  27. Function TryFieldValue(Const AFieldName : String; out AValue : String) : Boolean; override;
  28. Destructor destroy; override;
  29. end;
  30. { TExtJSJSONDataFormatter }
  31. TJSONObjectEvent = Procedure(Sender : TObject; AObject : TJSONObject) of Object;
  32. TJSONExceptionObjectEvent = Procedure(Sender : TObject; E : Exception; AResponse : TJSONObject) of Object;
  33. TJSONObjectAllowRowEvent = Procedure(Sender : TObject; Dataset : TDataset; Var Allow : Boolean) of Object;
  34. TJSONObjectAllowEvent = Procedure(Sender : TObject; AObject : TJSONObject; Var Allow : Boolean) of Object;
  35. TExtJSJSONDataFormatter = Class(TExtJSDataFormatter)
  36. private
  37. FAfterDataToJSON: TJSONObjectEvent;
  38. FAfterDelete: TJSONObjectEvent;
  39. FAfterInsert: TJSONObjectEvent;
  40. FAfterRowToJSON: TJSONObjectEvent;
  41. FAfterUpdate: TJSONObjectEvent;
  42. FBeforeDataToJSON: TJSONObjectEvent;
  43. FBeforeDelete: TNotifyEvent;
  44. FBeforeInsert: TNotifyEvent;
  45. FBeforeRowToJSON: TJSONObjectEvent;
  46. FBeforeUpdate: TNotifyEvent;
  47. FOnAllowRow: TJSONObjectAllowRowEvent;
  48. FOnErrorResponse: TJSONExceptionObjectEvent;
  49. FOnMetaDataToJSON: TJSONObjectEvent;
  50. FBatchResult : TJSONArray;
  51. Function AddIdToBatch : TJSONObject;
  52. procedure SendSuccess(ResponseContent: TStream; AddIDValue : Boolean = False);
  53. protected
  54. function AllowRow(ADataset : TDataset) : Boolean; virtual;
  55. Procedure StartBatch(ResponseContent : TStream); override;
  56. Procedure NextBatchItem(ResponseContent : TStream); override;
  57. Procedure EndBatch(ResponseContent : TStream); override;
  58. Function CreateAdaptor(ARequest : TRequest) : TCustomWebdataInputAdaptor; override;
  59. Function AddFieldToJSON(O: TJSONObject; const AFieldName: String; F: TField): TJSONData;
  60. function GetDataContentType: String; override;
  61. Function GetJSONMetaData: TJSONObject;
  62. function RowToJSON: TJSONObject;
  63. Procedure DoBeforeRow(ARow : TJSONObject); virtual;
  64. Procedure DoAfterRow(ARow : TJSONObject); virtual;
  65. Procedure DoBeforeData(AResponse : TJSONObject); virtual;
  66. Procedure DoAfterData(AResponse : TJSONObject); virtual;
  67. Procedure DoOnMetaData(AMetadata : TJSONObject); virtual;
  68. procedure DatasetToStream(Stream: TStream); override;
  69. Procedure DoExceptionToStream(E : Exception; ResponseContent : TStream); override;
  70. Procedure DoInsertRecord(ResponseContent : TStream); override;
  71. Procedure DoUpdateRecord(ResponseContent : TStream); override;
  72. Procedure DoDeleteRecord(ResponseContent : TStream); override;
  73. Public
  74. Destructor destroy; override;
  75. Published
  76. // Called before any fields are added to row object (passed to handler).
  77. Property AfterRowToJSON : TJSONObjectEvent Read FAfterRowToJSON Write FAfterRowToJSON;
  78. // Called After all fields are added to row object (passed to handler).
  79. Property BeforeRowToJSON : TJSONObjectEvent Read FBeforeRowToJSON Write FBeforeRowToJSON;
  80. // Called when metadata object has been created (passed to handler).
  81. Property OnMetaDataToJSON : TJSONObjectEvent Read FOnMetaDataToJSON Write FOnMetaDataToJSON;
  82. // Called when response object has been created, and has Rows property (response passed to handler).
  83. Property AfterDataToJSON : TJSONObjectEvent Read FAfterDataToJSON Write FAfterDataToJSON;
  84. // Called just before response object will be streamed (response passed to handler).
  85. Property BeforeDataToJSON : TJSONObjectEvent Read FBeforeDataToJSON Write FBeforeDataToJSON;
  86. // Called when an exception is caught and formatted.
  87. Property OnErrorResponse : TJSONExceptionObjectEvent Read FOnErrorResponse Write FOnErrorResponse;
  88. // Called to decide whether a record is sent to the client;
  89. Property OnAllowRow : TJSONObjectAllowRowEvent Read FOnAllowRow Write FOnAllowRow;
  90. // After a record was succesfully updated
  91. Property AfterUpdate : TJSONObjectEvent Read FAfterUpdate Write FAfterUpdate;
  92. // After a record was succesfully inserted.
  93. Property AfterInsert : TJSONObjectEvent Read FAfterInsert Write FAfterInsert;
  94. // After a record was succesfully inserted.
  95. Property AfterDelete : TJSONObjectEvent Read FAfterDelete Write FAfterDelete;
  96. // From TCustomHTTPDataContentProducer
  97. Property BeforeUpdate;
  98. Property BeforeInsert;
  99. Property BeforeDelete;
  100. end;
  101. implementation
  102. { $define wmdebug}
  103. {$ifdef wmdebug}
  104. uses dbugintf;
  105. {$endif wmdebug}
  106. Resourcestring
  107. SErrWrongDataFormat = 'Post ROWS data has wrong value type. Expected array or object, got : %s.';
  108. SerrNoExceptionMessage = 'No exception to take error message from.';
  109. Const
  110. // Do not localize these strings
  111. SDefMetaDataProperty = 'metaData';
  112. SDefFieldsProperty = 'fields';
  113. SDefFieldProperty = 'field';
  114. SDefFieldNameProperty = 'name';
  115. SDefDirectionProperty = 'direction';
  116. SDefSortInfoProperty = 'sortInfo';
  117. SIdProperty = 'idProperty';
  118. SSuccessProperty = 'successProperty';
  119. SRootProperty = 'root';
  120. STotalProperty = 'totalProperty';
  121. SDefAscDesc : Array[Boolean] of string = ('ASC','DESC');
  122. function TExtJSJSONDataFormatter.GetDataContentType: String;
  123. begin
  124. Result:='text/html';
  125. end;
  126. function TExtJSJSONDataFormatter.CreateAdaptor(ARequest: TRequest
  127. ): TCustomWebdataInputAdaptor;
  128. begin
  129. Result:=TExtJSJSonWebdataInputAdaptor.Create(Self);
  130. Result.Request:=ARequest;
  131. end;
  132. function TExtJSJSONDataFormatter.AddFieldToJSON(O : TJSONObject; const AFieldName : String; F : TField): TJSONData;
  133. Var
  134. S : String;
  135. begin
  136. if F.IsNull then
  137. Result:=O.Items[O.Add(AFieldName)]
  138. else
  139. Case F.DataType of
  140. ftSmallint,
  141. ftInteger,
  142. ftAutoInc,
  143. ftWord:
  144. Result:=O.Items[O.Add(AFieldName,F.AsInteger)];
  145. ftBoolean:
  146. Result:=O.Items[O.Add(AFieldName,F.AsBoolean)];
  147. ftLargeint:
  148. Result:=O.Items[O.Add(AFieldName,F.AsLargeInt)];
  149. ftDate:
  150. Result:=O.Items[O.Add(AFieldName,FormatDateTime('yyyy-mm-dd',F.AsDateTime))];
  151. ftDateTime:
  152. Result:=O.Items[O.Add(AFieldName,FormatDateTime('yyyy-mm-dd hh":"nn":"ss',F.AsDateTime))];
  153. ftTime:
  154. Result:=O.Items[O.Add(AFieldName,FormatDateTime('hh":"nn":"ss',F.AsDateTime))];
  155. ftMemo,
  156. ftFmtMemo,
  157. ftWideMemo,
  158. ftBlob :
  159. begin
  160. S:=F.AsString;
  161. If (OnTranscode<>Nil) then
  162. OnTranscode(Self,F,S,True);
  163. Result:=O.Items[O.Add(AFieldName,S)];
  164. end;
  165. else
  166. S:=F.DisplayText;
  167. If (OnTranscode<>Nil) then
  168. OnTranscode(Self,F,S,True);
  169. Result:=O.Items[O.Add(AFieldName,S)];
  170. end;
  171. end;
  172. function TExtJSJSONDataFormatter.RowToJSON: TJSONObject;
  173. Var
  174. F : TField;
  175. I : Integer;
  176. begin
  177. Result:=TJSONObject.Create();
  178. try
  179. DobeforeRow(Result);
  180. For I:=0 to Dataset.Fields.Count-1 do
  181. begin
  182. F:=Dataset.Fields[I];
  183. AddFieldToJSON(Result,F.FieldName,F);
  184. end;
  185. DoAfterRow(Result);
  186. except
  187. Result.Free;
  188. Raise;
  189. end;
  190. end;
  191. procedure TExtJSJSONDataFormatter.DoBeforeRow(ARow: TJSONObject);
  192. begin
  193. If Assigned(FBeforeRowToJSON) then
  194. FBeforeRowToJSON(Self,ARow);
  195. end;
  196. procedure TExtJSJSONDataFormatter.DoAfterRow(ARow: TJSONObject);
  197. begin
  198. If Assigned(FAfterRowToJSON) then
  199. FAfterRowToJSON(Self,ARow);
  200. end;
  201. procedure TExtJSJSONDataFormatter.DoBeforeData(AResponse: TJSONObject);
  202. begin
  203. If Assigned(FBeforeDataToJSON) then
  204. FBeforeDataToJSON(Self,AResponse);
  205. end;
  206. procedure TExtJSJSONDataFormatter.DoAfterData(AResponse: TJSONObject);
  207. begin
  208. If Assigned(FAfterDataToJSON) then
  209. FAfterDataToJSON(Self,AResponse);
  210. end;
  211. procedure TExtJSJSONDataFormatter.DoOnMetaData(AMetadata: TJSONObject);
  212. begin
  213. If Assigned(FOnMetaDataToJSON) then
  214. FOnMetaDataToJSON(Self,AMetaData);
  215. end;
  216. Function TExtJSJSONDataFormatter.GetJSONMetaData: TJSONObject;
  217. Var
  218. F : TJSONArray;
  219. Fi : TField;
  220. I : Integer;
  221. O : TJSONObject;
  222. SF,FT : String;
  223. begin
  224. If (SortField='') then
  225. SF:=Dataset.Fields[0].FieldName
  226. else
  227. SF:=SortField;
  228. Result:=TJSonObject.Create;
  229. try
  230. F:=TJSONArray.Create;
  231. Result.add(SDefFieldsProperty,F);
  232. For I:=0 to Dataset.Fields.Count-1 do
  233. begin
  234. Fi:=Dataset.Fields[i];
  235. O:=TJSONObject.Create();
  236. O.Add(SDefFieldNameProperty,Fi.FieldName);
  237. Ft:='';
  238. Case Fi.DataType of
  239. ftInteger,
  240. ftSmallint,
  241. ftWord,
  242. ftLargeInt : FT:='int';
  243. ftCurrency,
  244. ftFloat,
  245. ftBCD : FT:='float';
  246. ftBoolean : ft:='boolean';
  247. ftDate,
  248. ftDateTime,
  249. ftTimeStamp,
  250. ftTime : ft:='date';
  251. ftString,
  252. ftMemo,
  253. ftFmtMemo,
  254. ftFixedChar,
  255. ftWideString,
  256. ftWideMemo : ft:='string'
  257. end;
  258. if (FT<>'') then
  259. begin
  260. O.Add('type',FT);
  261. if (FT='date') then
  262. // Needs improving
  263. Case Fi.DataType of
  264. ftDate : O.Add('dateFormat','Y-m-d');
  265. ftTime : O.Add('dateFormat','H:i:s');
  266. ftDateTime,
  267. ftTimeStamp : O.Add('dateFormat','Y-m-d H:i:s');
  268. end;
  269. end;
  270. F.Add(O);
  271. end;
  272. O:=TJSONObject.Create();
  273. O.Add(SDefFieldProperty,SF);
  274. O.Add(SDefDirectionProperty,SDefAscDesc[SortDescending]);
  275. Result.Add(SDefSortInfoProperty,O);
  276. {$ifdef wmdebug}senddebug('ID property: '+Provider.IDFieldName);{$endif}
  277. Result.Add(SIdProperty,Provider.IDFieldName);
  278. Result.Add(SSuccessProperty, SuccessProperty);
  279. Result.Add(SRootProperty, RowsProperty);
  280. Result.Add(STotalProperty, totalProperty);
  281. DoOnMetaData(Result);
  282. except
  283. Result.free;
  284. Raise;
  285. end;
  286. end;
  287. procedure TExtJSJSONDataFormatter.DatasetToStream(Stream: TStream);
  288. Var
  289. Rows : TJSONArray;
  290. Meta,Resp : TJSONObject;
  291. L : String;
  292. DS : TDataset;
  293. i,RCount,ACount : Integer;
  294. begin
  295. Rows:=Nil;
  296. Resp:=TJSONObject.Create;
  297. try
  298. Rows:=TJSONArray.Create();
  299. Resp.Add(RowsProperty,Rows);
  300. DoBeforeData(Resp);
  301. DS:=Dataset;
  302. DS.First;
  303. RCount:=0;
  304. If MetaData then
  305. begin
  306. Meta:=GetJSONMetaData;
  307. Resp.Add(SDefMetaDataProperty,Meta);
  308. end;
  309. // Go to start
  310. ACount:=PageStart;
  311. While (Not DS.EOF) and (ACount>0) do
  312. begin
  313. DS.Next;
  314. Dec(ACount);
  315. Inc(RCount);
  316. end;
  317. ACount:=PageSize;
  318. While (not DS.EOF) and ((PageSize=0) or (ACount>0)) do
  319. begin
  320. If AllowRow(DS) then
  321. begin
  322. Inc(RCount);
  323. Dec(ACount);
  324. Rows.Add(RowToJSON);
  325. end;
  326. DS.Next;
  327. end;
  328. If (PageSize>0) then
  329. While (not DS.EOF) do
  330. begin
  331. Inc(RCount);
  332. DS.Next;
  333. end;
  334. Resp.Add(SuccessProperty,True);
  335. If (PageSize>0) then
  336. Resp.Add(TotalProperty,RCount);
  337. DoAfterData(Resp);
  338. L:=Resp.AsJSON;
  339. Stream.WriteBuffer(L[1],Length(L));
  340. finally
  341. Resp.Free;
  342. end;
  343. end;
  344. procedure TExtJSJSONDataFormatter.DoExceptionToStream(E: Exception;
  345. ResponseContent: TStream);
  346. Var
  347. Resp : TJSonObject;
  348. L : String;
  349. begin
  350. Resp:=tjsonObject.Create();
  351. try
  352. Resp.Add(SuccessProperty,False);
  353. If Assigned(E) then
  354. Resp.Add(MessageProperty,E.Message)
  355. else
  356. Resp.Add(MessageProperty,SerrNoExceptionMessage);
  357. L:=Resp.AsJSON;
  358. If Length(L)>0 then
  359. ResponseContent.WriteBuffer(L[1],Length(L));
  360. Resp.Add('root',RowsProperty);
  361. Resp.Add(RowsProperty,TJSONArray.Create());
  362. If Assigned(FOnErrorResponse) then
  363. FOnErrorResponse(Self,E,Resp);
  364. finally
  365. Resp.Free;
  366. end;
  367. end;
  368. procedure TExtJSJSONDataFormatter.SendSuccess(ResponseContent: TStream; AddIDValue : Boolean = False);
  369. Var
  370. Resp : TJSonObject;
  371. L : String;
  372. begin
  373. try
  374. Resp:=TJsonObject.Create;
  375. Resp.Add(SuccessProperty,True);
  376. Resp.Add('root',Self.RowsProperty);
  377. If Assigned(FBatchResult) and (FBatchResult.Count>0) then
  378. begin
  379. Resp.Add(Self.RowsProperty,FBatchResult);
  380. FBatchResult:=Nil;
  381. end
  382. else
  383. Resp.Add(Self.RowsProperty,TJSONNull.Create());
  384. L:=Resp.AsJSON;
  385. ResponseContent.WriteBuffer(L[1],Length(L));
  386. finally
  387. Resp.Free;
  388. end;
  389. end;
  390. function TExtJSJSONDataFormatter.AllowRow(ADataset: TDataset): Boolean;
  391. begin
  392. Result:=True;
  393. If Assigned(FOnAllowRow) then
  394. FOnAllowRow(Self,Dataset,Result);
  395. end;
  396. procedure TExtJSJSONDataFormatter.StartBatch(ResponseContent: TStream);
  397. begin
  398. If Assigned(FBatchResult) then
  399. FBatchResult.Clear
  400. else
  401. FBatchResult:=TJSONArray.Create();
  402. end;
  403. procedure TExtJSJSONDataFormatter.NextBatchItem(ResponseContent: TStream);
  404. begin
  405. end;
  406. procedure TExtJSJSONDataFormatter.EndBatch(ResponseContent: TStream);
  407. begin
  408. SendSuccess(Responsecontent,True);
  409. end;
  410. Function TExtJSJSONDataFormatter.AddIdToBatch : TJSONObject;
  411. begin
  412. Result:=TJSONObject.Create([Provider.IDFieldName,Provider.IDFieldValue]);
  413. FBatchResult.Add(Result);
  414. end;
  415. procedure TExtJSJSONDataFormatter.DoInsertRecord(ResponseContent: TStream);
  416. Var
  417. D : TJSONObject;
  418. begin
  419. Inherited;
  420. D:=AddIDToBatch;
  421. If Assigned(FAfterInsert) then
  422. FAfterInsert(Self,D);
  423. end;
  424. procedure TExtJSJSONDataFormatter.DoUpdateRecord(ResponseContent: TStream);
  425. Var
  426. D : TJSONObject;
  427. begin
  428. inherited DoUpdateRecord(ResponseContent);
  429. D:=AddIDToBatch;
  430. If Assigned(FAfterUpdate) then
  431. FAfterUpdate(Self,D);
  432. end;
  433. procedure TExtJSJSONDataFormatter.DoDeleteRecord(ResponseContent: TStream);
  434. begin
  435. inherited DoDeleteRecord(ResponseContent);
  436. If Assigned(FAfterDelete) then
  437. FAfterDelete(Self,Nil);
  438. end;
  439. destructor TExtJSJSONDataFormatter.destroy;
  440. begin
  441. FreeAndNil(FBatchResult);
  442. inherited destroy;
  443. end;
  444. { TExtJSJSonWebdataInputAdaptor }
  445. function TExtJSJSonWebdataInputAdaptor.CheckData : Boolean;
  446. Var
  447. D : TJSONData;
  448. P : TJSONParser;
  449. S : String;
  450. begin
  451. Result:=Assigned(FCurrentRow);
  452. If Not (Result) and TryParamValue('rows',S) then
  453. begin
  454. {$ifdef wmdebug}senddebug('Check data: '+GetParamValue('rows'));{$endif}
  455. P:=TJSONParser.Create(S);
  456. try
  457. D:=P.Parse;
  458. {$ifdef wmdebug}senddebug('Classname : '+D.ClassName);{$endif}
  459. If D is TJSONArray then
  460. begin
  461. FRows:=TJSONArray(D);
  462. FRowIndex:=0;
  463. FCurrentRow:=FRows.Items[0] as TJSONObject;
  464. end
  465. else If D is TJSONObject then
  466. begin
  467. FRows:=Nil;
  468. FCurrentRow:=TJSONObject(D);
  469. end
  470. else if D is TJSONInt64Number then
  471. begin
  472. FRows:=nil;
  473. FCurrentRow:=TJSONObject.Create(['ID',D]);
  474. end
  475. else
  476. begin
  477. FreeAndNil(D);
  478. Raise EFPHTTPError.CreateFmt(SErrWrongDataFormat,[D.ClassName]);
  479. end;
  480. Result:=True;
  481. finally
  482. P.Free;
  483. end;
  484. end;
  485. end;
  486. procedure TExtJSJSonWebdataInputAdaptor.reset;
  487. begin
  488. If (FRows=Nil) then
  489. FreeAndNil(FCurrentRow)
  490. else
  491. FreeAndNil(FRows);
  492. FRowIndex:=0;
  493. inherited reset;
  494. end;
  495. function TExtJSJSonWebdataInputAdaptor.GetNextBatch: Boolean;
  496. begin
  497. If (FRows=Nil) then
  498. Result:=inherited GetNextBatch
  499. else
  500. begin
  501. Result:=FRowindex<FRows.Count-1;
  502. Inc(FRowIndex);
  503. If Result then
  504. FCurrentRow:=FRows.Items[FRowIndex] as TJSONObject
  505. else
  506. FCurrentRow:=Nil;
  507. end;
  508. end;
  509. function TExtJSJSonWebdataInputAdaptor.TryFieldValue(const AFieldName: String;
  510. out AValue: String): Boolean;
  511. Var
  512. I : Integer;
  513. begin
  514. Result:=False;
  515. if CheckData then
  516. begin
  517. I:=FCurrentRow.IndexOfName(AFieldName);
  518. Result:=I<>-1;
  519. if result and (FCurrentRow.Items[I].JSONType<>jtNull) then
  520. AValue:=FCurrentRow.Items[I].AsString;
  521. end;
  522. end;
  523. destructor TExtJSJSonWebdataInputAdaptor.destroy;
  524. begin
  525. If Assigned(FRows) then
  526. FreeAndNil(FRows)
  527. else if assigned(FCurrentRow) then
  528. FreeAndNil(FCurrentRow);
  529. inherited destroy;
  530. end;
  531. initialization
  532. WebDataProviderManager.RegisterInputAdaptor('ExtJS - JSON',TExtJSJSONWebdataInputAdaptor);
  533. WebDataProviderManager.RegisterDataProducer('ExtJS - JSON',TExtJSJSONDataFormatter);
  534. finalization
  535. WebDataProviderManager.UnRegisterInputAdaptor('ExtJS - JSON');
  536. WebDataProviderManager.UnRegisterDataProducer('ExtJS - JSON')
  537. end.