I had some problems with synchronization between dataset editing (using TXDataWebDataSet) and manual editing. I have now switched everything back to manual. Without using TXDataWebDataSet.
The only thing that still bothers me is that I can't work with XData for the images. But the way I did it now, it should work.
Here is my base class for the XData communication with the individual servers. This works well and I have centralized editing of headers, errors, etc.
Below are the functions that work with the TJSXmlHttpRequest.
Please take a look at the load and save image functions.
TdmSngWebModel = class(TWebDataModule)
XDataWebDataSet: TXDataWebDataSet;
private
FXConnection : TXDataWebConnection;
FXClient : TXDataWebClient;
FIsConnected : Boolean;
FModelname : String;
FTempToken : String;
FExpandString : String;
FSelectString : String;
FOrderByString : String;
FAddQueryString : Boolean;
FUnauthorizedProc : TUnauthorizedProc;
FConnectionErrorProc : TConnectionErrorProc;
//FWaitFrameElementId : String;
function GetQueryString: String;
protected
FLastConnectionError : TErrorRec;
FLastError : TErrorRec;
FLastSngResultHeader : TSngResultHeader;
FConnectedModel : String;
procedure XConnectionRequest(Args: TXDataWebConnectionRequest); virtual;
procedure XConnectionResponse(Args: TXDataWebConnectionResponse); virtual;
procedure XConnectionError(Error: TXDataWebConnectionError); virtual;
procedure XConnectionConnect(Sender: TObject); virtual;
procedure XClientRequest(Request: TXDataClientRequest); virtual;
procedure XClientError(Error: TXDataClientError); virtual;
procedure XClientLoad(Response: TXDataClientResponse); virtual;
public // Modulfunktionen und Properties
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
/// SetConnection in Fehlerfall
/// Die Rückmeldung 401:Unauthorized erzeugt in der Connect Routine KEIN Error, sodern durchläuft
/// nur das Event XDataWebConnectionResponse. Daher wird zwischen "Unauthorized" und "Error" unterschieden
procedure SetConnection( const AServicePath, AToken, AModel: String;
ASuccess : TSuccessProc;
AUnauthorized : TUnauthorizedProc = nil;
AConnectionError : TConnectionErrorProc = nil ); virtual;
function MakeSngParameter( AFunction, AVersion: String; APageSize, APageIndex: Integer; AData: TJSObject): TJSObject; overload;
function MakeSngParameter( AFunction, AVersion: String; AData: TJSObject): TJSObject; overload;
function MakeFilter( AFilter: TsngFilterGroup; APageSize, APageIndex: Integer): String; overload;
function MakeFilter( AFilter: TsngFilterGroup): String; overload;
/// Setzt den QueryString. ASelect ist die Selectliste (kommagetrennt), AExpand die Eypandliste (kommagetrennt)
procedure SetQueryString( ASelect, AExpand, AOrderBy: String); overload;
procedure SetQueryString( ASelect, AExpand: String); overload;
procedure SetQueryOrder( AOrderBy: String);
function GetResultValue ( AResultValue: JSValue) : JSValue;
function GetResultValueAsArray ( AResultValue: JSValue) : TJSArray;
function GetSngResultAsObject ( AResultValue: JSValue) : TJSObject;
function GetSngResultDataAsObject ( AResultValue: JSValue) : TJSObject;
function GetSngResultDataField ( AResultValue: JSValue; AField: String) : JSValue;
procedure GetBlobURL( AXDataBlobValue: JSValue; ALoadDataProc: TLoadDataProc; ARequestId: String = ''); overload;
procedure GetBlobURL( AJSObject: TJSObject; AXDataBlobFieldname: String; ALoadDataProc: TLoadDataProc; ARequestId: String = ''); overload;
procedure LoadImage( const AWebImageControl: TWebImageControl; AJSObject: TJSObject; AXDataBlobFieldname: String);
procedure SaveImage( const ABase64Image: String; AJSObject: TJSObject; AXDataBlobFieldname: String);
property XConnection: TXDataWebConnection read FXConnection;
property XClient: TXDataWebClient read FXClient;
property LastConnectionError : TErrorRec read FLastConnectionError;
property LastSngResultHeader : TSngResultHeader read FLastSngResultHeader;
property IsConnected : Boolean read FIsConnected;
property ConnectedModel : String read FConnectedModel;
property QueryString : String read GetQueryString;
property AddQueryString : Boolean read FAddQueryString write FAddQueryString;
//property WaitFrameElementId : String read FWaitFrameElementId write FWaitFrameElementId;
public // direkte Aufrufe der sng REST-API
//function Callback ( const CallbackFunction: string; const Param: TJSONObject): TJSONObject;
// function CloseModule ( const AKill: Boolean = false) : Boolean;
[async] function Signal : String;
[async] function Version ( out AVersion: String) : Boolean; overload;
[async] function Version : String; overload;
// function DBVersion : String;
// function InstallDB ( out AMessage: String) : Boolean;
// function RegisterService ( out AMessage: String) : Boolean;
// function Uninstall ( out AMessage: String) : Boolean;
// function Update ( out AMessage: String) : Boolean;
// function Backup ( out AMessage: String) : Boolean;
[async] function GetSettingList ( const AFilter: TJSObject) : TJSArray;
[async] function GetSetting ( const AData: TJSObject) : String; overload;
[async] function GetSettingDef ( const AData: TJSObject; ADefault: String) : String; overload;
[async] function GetSetting ( const AGroup, ASubgroup, AName: String) : String; overload;
[async] function GetSettingDef ( const AGroup, ASubgroup, AName, ADefault: String): String; overload;
[async] function GetSettingJSON ( const AData: TJSObject) : TJSObject;
[async] function SetSetting ( const AData: TJSObject) : Boolean; overload;
[async] function SetSetting ( const AGroup, ASubgroup, AName, AValue: String) : Boolean; overload;
[async] function SetSetting ( const AGroup, ASubgroup, AName: String; AValue: TJSObject) : Boolean; overload;
[async] function GetLookupDic : TJSArray;
[async] function GetLookupList ( const AName: String) : TJSArray;
[async] function AddLookup ( const AName, AValue: String) : String;
[async] function ChangeLookup ( const ALupID, AValue: String) : Boolean;
[async] function DelLookup ( const ALupID: String) : Boolean;
[async] function DelLookupAll ( const AName: String) : Boolean;
end;
Here is the code for load and save. Please check this out.
{-----------------------------------------------------------------------------}
{ TdmSngWebModel GetBlobURL }
{-----------------------------------------------------------------------------}
procedure TdmSngWebModel.GetBlobURL( AXDataBlobValue: JSValue; ALoadDataProc: TLoadDataProc; ARequestId: String = '');
var
xhr: TJSXmlHttpRequest;
procedure _Load;
begin
if xhr.Status = _http_200_Ok then
ALoadDataProc( ARequestId, TJSURL.createObjectURL(xhr.response));
end;
begin
xhr := TJSXMLHttpRequest.new;
xhr.open('GET', XConnection.URL + '/' + string( AXDataBlobValue));
xhr.setRequestHeader( 'Authorization', 'Bearer ' + Env.Token);
xhr.responseType := 'blob';
xhr.addEventListener('load', @_Load);
xhr.send;
end;
procedure TdmSngWebModel.GetBlobURL( AJSObject: TJSObject; AXDataBlobFieldname: String; ALoadDataProc: TLoadDataProc; ARequestId: String = '');
begin
GetBlobURL( AJSObject[ AXDataBlobFieldname+'@xdata.proxy'], ALoadDataProc, ARequestId);
end;
procedure TdmSngWebModel.LoadImage( const AWebImageControl: TWebImageControl; AJSObject: TJSObject; AXDataBlobFieldname: String);
procedure OnLoad( ARequestId: String; AData: JSValue);
begin
AWebImageControl.URL := JS.toString( AData);
end;
begin
GetBlobURL( AJSObject, AXDataBlobFieldname, TLoadDataProc( @OnLoad));
end;
procedure TdmSngWebModel.SaveImage( const ABase64Image: String; AJSObject: TJSObject; AXDataBlobFieldname: String);
var
xhr: TJSXmlHttpRequest;
function Base64ToArrayBuffer(str: string): TJSArrayBuffer;
var
BufView: TJSUInt8Array;
BinaryString: string;
I: Integer;
begin
BinaryString := window.atob(str);
Result := TJSArrayBuffer.new(Length(BinaryString));
BufView := TJSUInt8Array.new(Result);
for I := 0 to Length(BinaryString) - 1 do
BufView[I] := TJSString(BinaryString).charCodeAt(I);
end;
begin
xhr := TJSXMLHttpRequest.new;
xhr.open( 'PUT', XConnection.URL + '/' + string( AJSObject[ AXDataBlobFieldname+'@xdata.proxy']));
xhr.setRequestHeader( 'Authorization', 'Bearer ' + Env.Token);
xhr.send( Base64ToArrayBuffer( ABase64Image));
end;