extjsjson.pp 16 KB

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