123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602 |
- unit extjsjson;
- {$mode objfpc}{$H+}
- interface
- uses
- Classes, SysUtils, httpdefs, fphttp, fpwebdata, fpextjs, fpjson, db, jsonparser;
- type
- { TExtJSJSonWebdataInputAdaptor }
- TExtJSJSonWebdataInputAdaptor = CLass(TCustomWebdataInputAdaptor)
- private
- FRows : TJSONArray;
- FCurrentRow : TJSONObject;
- FRowIndex : integer;
- function CheckData: Boolean;
- Public
- procedure reset; override;
- Function GetNextBatch : Boolean; override;
- Function TryFieldValue(Const AFieldName : String; out AValue : String) : Boolean; override;
- Destructor destroy; override;
- end;
- { TExtJSJSONDataFormatter }
- TJSONObjectEvent = Procedure(Sender : TObject; AObject : TJSONObject) of Object;
- TJSONExceptionObjectEvent = Procedure(Sender : TObject; E : Exception; AResponse : TJSONObject) of Object;
- TJSONObjectAllowRowEvent = Procedure(Sender : TObject; Dataset : TDataset; Var Allow : Boolean) of Object;
- TJSONObjectAllowEvent = Procedure(Sender : TObject; AObject : TJSONObject; Var Allow : Boolean) of Object;
- TExtJSJSONDataFormatter = Class(TExtJSDataFormatter)
- private
- FAfterDataToJSON: TJSONObjectEvent;
- FAfterDelete: TJSONObjectEvent;
- FAfterInsert: TJSONObjectEvent;
- FAfterRowToJSON: TJSONObjectEvent;
- FAfterUpdate: TJSONObjectEvent;
- FBeforeDataToJSON: TJSONObjectEvent;
- FBeforeDelete: TNotifyEvent;
- FBeforeInsert: TNotifyEvent;
- FBeforeRowToJSON: TJSONObjectEvent;
- FBeforeUpdate: TNotifyEvent;
- FOnAllowRow: TJSONObjectAllowRowEvent;
- FOnErrorResponse: TJSONExceptionObjectEvent;
- FOnMetaDataToJSON: TJSONObjectEvent;
- FBatchResult : TJSONArray;
- Function AddIdToBatch : TJSONObject;
- procedure SendSuccess(ResponseContent: TStream; AddIDValue : Boolean = False);
- protected
- function AllowRow(ADataset : TDataset) : Boolean; virtual;
- Procedure StartBatch(ResponseContent : TStream); override;
- Procedure NextBatchItem(ResponseContent : TStream); override;
- Procedure EndBatch(ResponseContent : TStream); override;
- Function CreateAdaptor(ARequest : TRequest) : TCustomWebdataInputAdaptor; override;
- Function AddFieldToJSON(O: TJSONObject; AFieldName: String; F: TField): TJSONData;
- function GetDataContentType: String; override;
- Function GetJSONMetaData: TJSONObject;
- function RowToJSON: TJSONObject;
- Procedure DoBeforeRow(ARow : TJSONObject); virtual;
- Procedure DoAfterRow(ARow : TJSONObject); virtual;
- Procedure DoBeforeData(AResponse : TJSONObject); virtual;
- Procedure DoAfterData(AResponse : TJSONObject); virtual;
- Procedure DoOnMetaData(AMetadata : TJSONObject); virtual;
- procedure DatasetToStream(Stream: TStream); override;
- Procedure DoExceptionToStream(E : Exception; ResponseContent : TStream); override;
- Procedure DoInsertRecord(ResponseContent : TStream); override;
- Procedure DoUpdateRecord(ResponseContent : TStream); override;
- Procedure DoDeleteRecord(ResponseContent : TStream); override;
- Public
- Destructor destroy; override;
- Published
- // Called before any fields are added to row object (passed to handler).
- Property AfterRowToJSON : TJSONObjectEvent Read FAfterRowToJSON Write FAfterRowToJSON;
- // Called After all fields are added to row object (passed to handler).
- Property BeforeRowToJSON : TJSONObjectEvent Read FBeforeRowToJSON Write FBeforeRowToJSON;
- // Called when metadata object has been created (passed to handler).
- Property OnMetaDataToJSON : TJSONObjectEvent Read FOnMetaDataToJSON Write FOnMetaDataToJSON;
- // Called when response object has been created, and has Rows property (response passed to handler).
- Property AfterDataToJSON : TJSONObjectEvent Read FAfterDataToJSON Write FAfterDataToJSON;
- // Called just before response object will be streamed (response passed to handler).
- Property BeforeDataToJSON : TJSONObjectEvent Read FBeforeDataToJSON Write FBeforeDataToJSON;
- // Called when an exception is caught and formatted.
- Property OnErrorResponse : TJSONExceptionObjectEvent Read FOnErrorResponse Write FOnErrorResponse;
- // Called to decide whether a record is sent to the client;
- Property OnAllowRow : TJSONObjectAllowRowEvent Read FOnAllowRow Write FOnAllowRow;
- // After a record was succesfully updated
- Property AfterUpdate : TJSONObjectEvent Read FAfterUpdate Write FAfterUpdate;
- // After a record was succesfully inserted.
- Property AfterInsert : TJSONObjectEvent Read FAfterInsert Write FAfterInsert;
- // After a record was succesfully inserted.
- Property AfterDelete : TJSONObjectEvent Read FAfterDelete Write FAfterDelete;
- // From TCustomHTTPDataContentProducer
- Property BeforeUpdate;
- Property BeforeInsert;
- Property BeforeDelete;
- end;
- implementation
- { $define wmdebug}
- {$ifdef wmdebug}
- uses dbugintf;
- {$endif wmdebug}
- Resourcestring
- SErrWrongDataFormat = 'Post ROWS data has wrong value type. Expected array or object, got : %s.';
- SerrNoExceptionMessage = 'No exception to take error message from.';
- Const
- // Do not localize these strings
- SDefMetaDataProperty = 'metaData';
- SDefFieldsProperty = 'fields';
- SDefFieldProperty = 'field';
- SDefFieldNameProperty = 'name';
- SDefDirectionProperty = 'direction';
- SDefSortInfoProperty = 'sortInfo';
- SIdProperty = 'idProperty';
- SSuccessProperty = 'successProperty';
- SRootProperty = 'root';
- STotalProperty = 'totalProperty';
- SDefAscDesc : Array[Boolean] of string = ('ASC','DESC');
- function TExtJSJSONDataFormatter.GetDataContentType: String;
- begin
- Result:='text/html';
- end;
- function TExtJSJSONDataFormatter.CreateAdaptor(ARequest: TRequest
- ): TCustomWebdataInputAdaptor;
- begin
- Result:=TExtJSJSonWebdataInputAdaptor.Create(Self);
- Result.Request:=ARequest;
- end;
- function TExtJSJSONDataFormatter.AddFieldToJSON(O : TJSONObject; AFieldName : String; F : TField): TJSONData;
- Var
- S : String;
- begin
- if F.IsNull then
- Result:=O.Items[O.Add(AFieldName)]
- else
- Case F.DataType of
- ftSmallint,
- ftInteger,
- ftAutoInc,
- ftWord:
- Result:=O.Items[O.Add(AFieldName,F.AsInteger)];
- ftBoolean:
- Result:=O.Items[O.Add(AFieldName,F.AsBoolean)];
- ftLargeint:
- Result:=O.Items[O.Add(AFieldName,F.AsLargeInt)];
- ftDate:
- Result:=O.Items[O.Add(AFieldName,FormatDateTime('yyyy-mm-dd',F.AsDateTime))];
- ftDateTime:
- Result:=O.Items[O.Add(AFieldName,FormatDateTime('yyyy-mm-dd hh":"nn":"ss',F.AsDateTime))];
- ftTime:
- Result:=O.Items[O.Add(AFieldName,FormatDateTime('hh":"nn":"ss',F.AsDateTime))];
- ftMemo,
- ftFmtMemo,
- ftWideMemo,
- ftBlob :
- begin
- S:=F.AsString;
- If (OnTranscode<>Nil) then
- OnTranscode(Self,F,S,True);
- Result:=O.Items[O.Add(AFieldName,S)];
- end;
- else
- S:=F.DisplayText;
- If (OnTranscode<>Nil) then
- OnTranscode(Self,F,S,True);
- Result:=O.Items[O.Add(AFieldName,S)];
- end;
- end;
- function TExtJSJSONDataFormatter.RowToJSON: TJSONObject;
- Var
- F : TField;
- I : Integer;
- begin
- Result:=TJSONObject.Create();
- try
- DobeforeRow(Result);
- For I:=0 to Dataset.Fields.Count-1 do
- begin
- F:=Dataset.Fields[I];
- AddFieldToJSON(Result,F.FieldName,F);
- end;
- DoAfterRow(Result);
- except
- Result.Free;
- Raise;
- end;
- end;
- procedure TExtJSJSONDataFormatter.DoBeforeRow(ARow: TJSONObject);
- begin
- If Assigned(FBeforeRowToJSON) then
- FBeforeRowToJSON(Self,ARow);
- end;
- procedure TExtJSJSONDataFormatter.DoAfterRow(ARow: TJSONObject);
- begin
- If Assigned(FAfterRowToJSON) then
- FAfterRowToJSON(Self,ARow);
- end;
- procedure TExtJSJSONDataFormatter.DoBeforeData(AResponse: TJSONObject);
- begin
- If Assigned(FBeforeDataToJSON) then
- FBeforeDataToJSON(Self,AResponse);
- end;
- procedure TExtJSJSONDataFormatter.DoAfterData(AResponse: TJSONObject);
- begin
- If Assigned(FAfterDataToJSON) then
- FAfterDataToJSON(Self,AResponse);
- end;
- procedure TExtJSJSONDataFormatter.DoOnMetaData(AMetadata: TJSONObject);
- begin
- If Assigned(FOnMetaDataToJSON) then
- FOnMetaDataToJSON(Self,AMetaData);
- end;
- Function TExtJSJSONDataFormatter.GetJSONMetaData: TJSONObject;
- Function DefReplace(S : String) : String;
- begin
- Result:=StringReplace(Result,'/',DateSeparator,[rfReplaceAll]);
- Result:=StringReplace(Result,':',TimeSeparator,[rfReplaceAll]);
- Result:=StringReplace(Result,'hh','H',[rfReplaceAll]);
- Result:=StringReplace(Result,'nn','i',[rfReplaceAll]);
- Result:=StringReplace(S,'n','i',[rfReplaceAll]);
- end;
- Var
- F : TJSONArray;
- Fi : TField;
- I : Integer;
- O : TJSONObject;
- SF,FT : String;
- begin
- If (SortField='') then
- SF:=Dataset.Fields[0].FieldName
- else
- SF:=SortField;
- Result:=TJSonObject.Create;
- try
- F:=TJSONArray.Create;
- Result.add(SDefFieldsProperty,F);
- For I:=0 to Dataset.Fields.Count-1 do
- begin
- Fi:=Dataset.Fields[i];
- O:=TJSONObject.Create();
- O.Add(SDefFieldNameProperty,Fi.FieldName);
- Ft:='';
- Case Fi.DataType of
- ftInteger,
- ftSmallint,
- ftWord,
- ftLargeInt : FT:='int';
- ftCurrency,
- ftFloat,
- ftBCD : FT:='float';
- ftBoolean : ft:='boolean';
- ftDate,
- ftDateTime,
- ftTimeStamp,
- ftTime : ft:='date';
- ftString,
- ftMemo,
- ftFmtMemo,
- ftFixedChar,
- ftWideString,
- ftWideMemo : ft:='string'
- end;
- if (FT<>'') then
- begin
- O.Add('type',FT);
- if (FT='date') then
- // Needs improving
- Case Fi.DataType of
- ftDate : O.Add('dateFormat','Y-m-d');
- ftTime : O.Add('dateFormat','H:i:s');
- ftDateTime,
- ftTimeStamp : O.Add('dateFormat','Y-m-d H:i:s');
- end;
- end;
- F.Add(O);
- end;
- O:=TJSONObject.Create();
- O.Add(SDefFieldProperty,SF);
- O.Add(SDefDirectionProperty,SDefAscDesc[SortDescending]);
- Result.Add(SDefSortInfoProperty,O);
- {$ifdef wmdebug}senddebug('ID property: '+Provider.IDFieldName);{$endif}
- Result.Add(SIdProperty,Provider.IDFieldName);
- Result.Add(SSuccessProperty, SuccessProperty);
- Result.Add(SRootProperty, RowsProperty);
- Result.Add(STotalProperty, totalProperty);
- DoOnMetaData(Result);
- except
- Result.free;
- Raise;
- end;
- end;
- procedure TExtJSJSONDataFormatter.DatasetToStream(Stream: TStream);
- Var
- Rows : TJSONArray;
- Meta,Resp : TJSONObject;
- L : String;
- DS : TDataset;
- i,RCount,ACount : Integer;
- begin
- Rows:=Nil;
- Resp:=TJSONObject.Create;
- try
- Rows:=TJSONArray.Create();
- Resp.Add(RowsProperty,Rows);
- DoBeforeData(Resp);
- DS:=Dataset;
- DS.First;
- RCount:=0;
- If MetaData then
- begin
- Meta:=GetJSONMetaData;
- Resp.Add(SDefMetaDataProperty,Meta);
- end;
- // Go to start
- ACount:=PageStart;
- While (Not DS.EOF) and (ACount>0) do
- begin
- DS.Next;
- Dec(ACount);
- Inc(RCount);
- end;
- ACount:=PageSize;
- While (not DS.EOF) and ((PageSize=0) or (ACount>0)) do
- begin
- If AllowRow(DS) then
- begin
- Inc(RCount);
- Dec(ACount);
- Rows.Add(RowToJSON);
- end;
- DS.Next;
- end;
- If (PageSize>0) then
- While (not DS.EOF) do
- begin
- Inc(RCount);
- DS.Next;
- end;
- Resp.Add(SuccessProperty,True);
- If (PageSize>0) then
- Resp.Add(TotalProperty,RCount);
- DoAfterData(Resp);
- L:=Resp.AsJSON;
- Stream.WriteBuffer(L[1],Length(L));
- finally
- Resp.Free;
- end;
- end;
- procedure TExtJSJSONDataFormatter.DoExceptionToStream(E: Exception;
- ResponseContent: TStream);
- Var
- Resp : TJSonObject;
- L : String;
- begin
- Resp:=tjsonObject.Create();
- try
- Resp.Add(SuccessProperty,False);
- If Assigned(E) then
- Resp.Add(MessageProperty,E.Message)
- else
- Resp.Add(MessageProperty,SerrNoExceptionMessage);
- L:=Resp.AsJSON;
- If Length(L)>0 then
- ResponseContent.WriteBuffer(L[1],Length(L));
- Resp.Add('root',RowsProperty);
- Resp.Add(RowsProperty,TJSONArray.Create());
- If Assigned(FOnErrorResponse) then
- FOnErrorResponse(Self,E,Resp);
- finally
- Resp.Free;
- end;
- end;
- procedure TExtJSJSONDataFormatter.SendSuccess(ResponseContent: TStream; AddIDValue : Boolean = False);
- Var
- Resp : TJSonObject;
- L : String;
- begin
- try
- Resp:=TJsonObject.Create;
- Resp.Add(SuccessProperty,True);
- Resp.Add('root',Self.RowsProperty);
- If Assigned(FBatchResult) and (FBatchResult.Count>0) then
- begin
- Resp.Add(Self.RowsProperty,FBatchResult);
- FBatchResult:=Nil;
- end
- else
- Resp.Add(Self.RowsProperty,TJSONNull.Create());
- L:=Resp.AsJSON;
- ResponseContent.WriteBuffer(L[1],Length(L));
- finally
- Resp.Free;
- end;
- end;
- function TExtJSJSONDataFormatter.AllowRow(ADataset: TDataset): Boolean;
- begin
- Result:=True;
- If Assigned(FOnAllowRow) then
- FOnAllowRow(Self,Dataset,Result);
- end;
- procedure TExtJSJSONDataFormatter.StartBatch(ResponseContent: TStream);
- begin
- If Assigned(FBatchResult) then
- FBatchResult.Clear
- else
- FBatchResult:=TJSONArray.Create();
- end;
- procedure TExtJSJSONDataFormatter.NextBatchItem(ResponseContent: TStream);
- begin
- end;
- procedure TExtJSJSONDataFormatter.EndBatch(ResponseContent: TStream);
- begin
- SendSuccess(Responsecontent,True);
- end;
- Function TExtJSJSONDataFormatter.AddIdToBatch : TJSONObject;
- begin
- Result:=TJSONObject.Create([Provider.IDFieldName,Provider.IDFieldValue]);
- FBatchResult.Add(Result);
- end;
- procedure TExtJSJSONDataFormatter.DoInsertRecord(ResponseContent: TStream);
- Var
- D : TJSONObject;
- begin
- Inherited;
- D:=AddIDToBatch;
- If Assigned(FAfterInsert) then
- FAfterInsert(Self,D);
- end;
- procedure TExtJSJSONDataFormatter.DoUpdateRecord(ResponseContent: TStream);
- Var
- D : TJSONObject;
- begin
- inherited DoUpdateRecord(ResponseContent);
- D:=AddIDToBatch;
- If Assigned(FAfterUpdate) then
- FAfterUpdate(Self,D);
- end;
- procedure TExtJSJSONDataFormatter.DoDeleteRecord(ResponseContent: TStream);
- begin
- inherited DoDeleteRecord(ResponseContent);
- If Assigned(FAfterDelete) then
- FAfterDelete(Self,Nil);
- end;
- destructor TExtJSJSONDataFormatter.destroy;
- begin
- FreeAndNil(FBatchResult);
- inherited destroy;
- end;
- { TExtJSJSonWebdataInputAdaptor }
- function TExtJSJSonWebdataInputAdaptor.CheckData : Boolean;
- Var
- D : TJSONData;
- P : TJSONParser;
- S : String;
- begin
- Result:=Assigned(FCurrentRow);
- If Not (Result) and TryParamValue('rows',S) then
- begin
- {$ifdef wmdebug}senddebug('Check data: '+GetParamValue('rows'));{$endif}
- P:=TJSONParser.Create(S);
- try
- D:=P.Parse;
- {$ifdef wmdebug}senddebug('Classname : '+D.ClassName);{$endif}
- If D is TJSONArray then
- begin
- FRows:=TJSONArray(D);
- FRowIndex:=0;
- FCurrentRow:=FRows.Items[0] as TJSONObject;
- end
- else If D is TJSONObject then
- begin
- FRows:=Nil;
- FCurrentRow:=TJSONObject(D);
- end
- else if D is TJSONInt64Number then
- begin
- FRows:=nil;
- FCurrentRow:=TJSONObject.Create(['ID',D]);
- end
- else
- begin
- FreeAndNil(D);
- Raise EFPHTTPError.CreateFmt(SErrWrongDataFormat,[D.ClassName]);
- end;
- Result:=True;
- finally
- P.Free;
- end;
- end;
- end;
- procedure TExtJSJSonWebdataInputAdaptor.reset;
- begin
- If (FRows=Nil) then
- FreeAndNil(FCurrentRow)
- else
- FreeAndNil(FRows);
- FRowIndex:=0;
- inherited reset;
- end;
- function TExtJSJSonWebdataInputAdaptor.GetNextBatch: Boolean;
- begin
- If (FRows=Nil) then
- Result:=inherited GetNextBatch
- else
- begin
- Result:=FRowindex<FRows.Count-1;
- Inc(FRowIndex);
- If Result then
- FCurrentRow:=FRows.Items[FRowIndex] as TJSONObject
- else
- FCurrentRow:=Nil;
- end;
- end;
- function TExtJSJSonWebdataInputAdaptor.TryFieldValue(const AFieldName: String;
- out AValue: String): Boolean;
- Var
- I : Integer;
- begin
- Result:=False;
- if CheckData then
- begin
- I:=FCurrentRow.IndexOfName(AFieldName);
- Result:=I<>-1;
- if result and (FCurrentRow.Items[I].JSONType<>jtNull) then
- AValue:=FCurrentRow.Items[I].AsString;
- end;
- end;
- destructor TExtJSJSonWebdataInputAdaptor.destroy;
- begin
- If Assigned(FRows) then
- FreeAndNil(FRows)
- else if assigned(FCurrentRow) then
- FreeAndNil(FCurrentRow);
- inherited destroy;
- end;
- initialization
- WebDataProviderManager.RegisterInputAdaptor('ExtJS - JSON',TExtJSJSONWebdataInputAdaptor);
- WebDataProviderManager.RegisterDataProducer('ExtJS - JSON',TExtJSJSONDataFormatter);
- finalization
- WebDataProviderManager.UnRegisterInputAdaptor('ExtJS - JSON');
- WebDataProviderManager.UnRegisterDataProducer('ExtJS - JSON')
- end.
|