* Added webdata

git-svn-id: trunk@15334 -
This commit is contained in:
michael 2010-05-27 15:22:02 +00:00
parent ef213e67d6
commit 81648b6fbf
9 changed files with 6211 additions and 0 deletions

8
.gitattributes vendored
View File

@ -2259,6 +2259,14 @@ packages/fcl-web/src/base/httpdefs.pp svneol=native#text/plain
packages/fcl-web/src/base/webpage.pp svneol=native#text/plain
packages/fcl-web/src/base/websession.pp svneol=native#text/plain
packages/fcl-web/src/base/webutil.pp svneol=native#text/plain
packages/fcl-web/src/webdata/Makefile svneol=native#text/plain
packages/fcl-web/src/webdata/Makefile.fpc svneol=native#text/plain
packages/fcl-web/src/webdata/extjsjson.pp svneol=native#text/plain
packages/fcl-web/src/webdata/extjsxml.pp svneol=native#text/plain
packages/fcl-web/src/webdata/fpextjs.pp svneol=native#text/plain
packages/fcl-web/src/webdata/fpwebdata.pp svneol=native#text/plain
packages/fcl-web/src/webdata/sqldbwebdata.pp svneol=native#text/plain
packages/fcl-web/src/webdata/webdata.txt svneol=native#text/plain
packages/fcl-web/tests/cgigateway.lpi svneol=native#text/plain
packages/fcl-web/tests/cgigateway.pp svneol=native#text/plain
packages/fcl-web/tests/testcgiapp.lpi svneol=native#text/plain

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,26 @@
#
# Makefile.fpc for FCL Web components
#
[package]
main=fcl-web
version=2.5.1
[target]
units=fpwebdata sqldbwebdata fpextjs extjsjson extjsxml
[require]
packages=fcl-base fcl-xml fcl-db fcl-json
[compiler]
options=-S2h
[install]
fpcpackage=y
[default]
fpcdir=../../../..
[rules]
.NOTPARALLEL:

View File

@ -0,0 +1,362 @@
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;
FIDValue : TJSONData;
FRowIndex : integer;
function CheckData: Boolean;
Public
Function GetNextBatch : Boolean; override;
Function TryFieldValue(Const AFieldName : String; out AValue : String) : Boolean; override;
Destructor destroy; override;
end;
{ TExtJSJSONDataFormatter }
TExtJSJSONDataFormatter = Class(TExtJSDataFormatter)
private
procedure SendSuccess(ResponseContent: TStream; AddIDValue : Boolean = False);
protected
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 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;
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';
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;
begin
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)];
else
Result:=O.Items[O.Add(AFieldName,F.DisplayText)];
end;
end;
function TExtJSJSONDataFormatter.RowToJSON: TJSONObject;
Var
F : TField;
I : Integer;
begin
Result:=TJSONObject.Create();
For I:=0 to Dataset.Fields.Count-1 do
begin
F:=Dataset.Fields[I];
AddFieldToJSON(Result,F.FieldName,F);
end;
end;
Function TExtJSJSONDataFormatter.GetJSONMetaData: TJSONObject;
Var
F : TJSONArray;
Fi : TField;
I : Integer;
O : TJSONObject;
SF : 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);
F.Add(O);
end;
O:=TJSONObject.Create();
O.Add(SDefFieldProperty,SF);
O.Add(SDefDirectionProperty,SDefAscDesc[SortDescending]);
Result.Add(SDefSortInfoProperty,O);
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();
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
Inc(RCount);
Dec(ACount);
Rows.Add(RowToJSON);
DS.Next;
end;
If (PageSize>0) then
While (not DS.EOF) do
begin
Inc(RCount);
DS.Next;
end;
Resp.Add(RowsProperty,Rows);
Resp.Add(SuccessProperty,True);
If (PageSize>0) then
Resp.Add(TotalProperty,RCount);
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));
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(Provider.IDFieldName,Provider.IDFieldValue);
L:=Resp.AsJSON;
ResponseContent.WriteBuffer(L[1],Length(L));
finally
Resp.Free;
end;
end;
procedure TExtJSJSONDataFormatter.DoInsertRecord(ResponseContent: TStream);
begin
Inherited;
SendSuccess(ResponseContent,True);
end;
procedure TExtJSJSONDataFormatter.DoUpdateRecord(ResponseContent: TStream);
begin
inherited DoUpdateRecord(ResponseContent);
SendSuccess(ResponseContent,False);
end;
procedure TExtJSJSONDataFormatter.DoDeleteRecord(ResponseContent: TStream);
begin
inherited DoDeleteRecord(ResponseContent);
SendSuccess(ResponseContent,False);
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
FIDValue:=D
else
begin
FreeAndNil(D);
Raise EFPHTTPError.CreateFmt(SErrWrongDataFormat,[D.ClassName]);
end;
Result:=True;
finally
P.Free;
end;
end;
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
If Assigned(FIDValue) and (0=CompareText(AFieldName,'ID')) then
begin
AValue:=FIDValue.AsString;
Result:=True;
end
else
begin
I:=FCurrentRow.IndexOfName(AFieldName);
Result:=I<>-1;
if result then
AValue:=FCurrentRow.Items[I].AsString;
end;
end;
end;
destructor TExtJSJSonWebdataInputAdaptor.destroy;
begin
If Assigned(FRows) then
FreeAndNil(FRows)
else if assigned(FCurrentRow) then
FreeAndNil(FCurrentRow)
else if Assigned(FIDValue) then
FreeAndNil(FIDValue);
inherited destroy;
end;
initialization
WebDataProviderManager.RegisterInputAdaptor('ExtJS - JSON',TExtJSJSONWebdataInputAdaptor);
WebDataProviderManager.RegisterDataProducer('ExtJS - JSON',TExtJSJSONDataFormatter);
finalization
WebDataProviderManager.UnRegisterInputAdaptor('ExtJS - JSON');
WebDataProviderManager.UnRegisterDataProducer('ExtJS - JSON')
end.

View File

@ -0,0 +1,362 @@
unit extjsxml;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, httpdefs, fpextjs, dom, xmlread, xmlwrite, fpwebdata, db;
Type
{ TExtJSXMLWebdataInputAdaptor }
TExtJSXMLWebdataInputAdaptor = CLass(TCustomWebdataInputAdaptor)
private
FDE: String;
FRE: String;
FREEL: String;
FXML : TXMLDocument;
FDocRoot : TDOMElement;
FRoot : TDOMElement;
FCurrentRow : TDOMElement;
FIDValue : TDOMElement;
function isDocumentStored: boolean;
function IsRecordStored: boolean;
function isRootStored: boolean;
function CheckData: Boolean;
protected
Public
Constructor Create(AOwner : TComponent); override;
Destructor destroy; override;
Function TryFieldValue(Const AFieldName : String; out AValue : String) : Boolean; override;
Property DocumentElement : String Read FDE Write FDE stored isDocumentStored;
Property RootElement : String Read FRE Write FRE stored isRootStored;
Property RecordElement : String Read FREEL Write FREEL stored IsRecordStored;
end;
{ TExtJSJSONDataFormatter }
{ TExtJSXMLDataFormatter }
TExtJSXMLDataFormatter = Class(TExtJSDataFormatter)
private
FDP: String;
FReP: String;
FRP: String;
function IsDocumentStored: boolean;
function IsRecordStored: boolean;
function IsRootStored: boolean;
protected
Function CreateAdaptor(ARequest : TRequest) : TCustomWebdataInputAdaptor; override;
Procedure DoExceptionToStream(E : Exception; ResponseContent : TStream); override;
Function GetDataContentType : String; override;
function RowToXML(Doc: TXMLDocument): TDOMelement;
procedure DatasetToStream(Stream: TStream); override;
public
Constructor Create(AOwner : TComponent); override;
published
Property RootProperty : String Read FRP Write FRP Stored IsRootStored;
Property RecordProperty : String Read FReP Write FReP Stored IsRecordStored;
Property DocumentProperty : String Read FDP Write FDP Stored IsDocumentStored;
end;
implementation
{ $define wmdebug}
{$ifdef wmdebug}
uses dbugintf;
{$endif wmdebug}
Resourcestring
SerrNoExceptionMessage = 'No exception to take error message from.';
Const
// For TExtJSXMLDataFormatter.
SDefDocumentProperty = 'xrequest';
SDefRecordProperty = 'row';
SDefRootProperty = 'dataset';
// Fpr TExtJSXMLWebdataInputAdaptor
SDefRootElement = SDefRootProperty;
SDefRecordElement = SDefRecordProperty;
SDefDocumentElement = SDefDocumentProperty;
function TExtJSXMLDataFormatter.IsRootStored: boolean;
begin
Result:=RootProperty<>SDefRootProperty;
end;
function TExtJSXMLDataFormatter.CreateAdaptor(ARequest: TRequest
): TCustomWebdataInputAdaptor;
Var
R : TExtJSXMLWebdataInputAdaptor;
begin
R:=TExtJSXMLWebdataInputAdaptor.Create(Self);
R.Request:=ARequest;
R.DocumentElement:=Self.DocumentProperty;
R.RootElement:=Self.RootProperty;
R.RecordElement:=Self.RecordProperty;
Result:=R;
end;
function TExtJSXMLDataFormatter.IsRecordStored: boolean;
begin
Result:=RecordProperty<>SDefRecordProperty;
end;
function TExtJSXMLDataFormatter.IsDocumentStored: boolean;
begin
Result:=DocumentProperty<>SDefDocumentProperty
end;
procedure TExtJSXMLDataFormatter.DoExceptionToStream(E: Exception;
ResponseContent: TStream);
Var
Xml : TXMLDocument;
El,C : TDOMElement;
begin
XML:=TXMLDocument.Create;
try
El:=XML.CreateElement(RootProperty);
XML.AppendChild(El);
El[SuccessProperty]:='false';
C:=XML.CreateElement(SuccessProperty);
C.AppendChild(XML.CreateTextNode('false'));
El.AppendChild(c);
C:=XML.CreateElement(MessageProperty);
El.AppendChild(C);
If Assigned(E) then
C.AppendChild(XML.CreateTextNode(E.Message))
else
C.AppendChild(XML.CreateTextNode(SerrNoExceptionMessage));
WriteXMLFile(XML,ResponseContent);
Finally
XML.Free;
end;
end;
function TExtJSXMLDataFormatter.GetDataContentType: String;
begin
Result:='text/xml';
end;
Function TExtJSXMLDataFormatter.RowToXML(Doc : TXMLDocument) : TDOMelement;
Var
E : TDOMElement;
F : TField;
I : Integer;
begin
Result:=Doc.CreateElement(RecordProperty);
For I:=0 to Dataset.Fields.Count-1 do
begin
F:=Dataset.Fields[i];
E:=Doc.CreateElement(F.FieldName);
E.AppendChild(Doc.CreateTextNode(F.DisplayText));
Result.AppendChild(E);
end;
end;
procedure TExtJSXMLDataFormatter.DatasetToStream(Stream: TStream);
Var
Xml : TXMLDocument;
E,C : TDOMElement;
i,RCount,ACount : Integer;
DS : TDataset;
begin
RCount:=0;
ACount:=0;
DS:=Dataset;
XML:=TXMLDocument.Create;
try
E:=XML.CreateElement(RootProperty);
XML.AppendChild(E);
// 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
Inc(RCount);
Dec(ACount);
E.AppendChild(RowToXML(XML));
DS.Next;
end;
If (PageSize>0) then
While (not DS.EOF) do
begin
Inc(RCount);
DS.Next;
end;
C:=XML.CreateElement(TotalProperty);
C.AppendChild(XML.CreateTextNode(IntToStr(RCount)));
E.AppendChild(C);
C:=XML.CreateElement(SuccessProperty);
C.AppendChild(XML.CreateTextNode('true'));
E.AppendChild(C);
WriteXMLFile(XML,Stream);
finally
XML.Free;
end;
end;
constructor TExtJSXMLDataFormatter.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
RootProperty:=SDefRootProperty;
RecordProperty:=SDefRecordProperty;
DocumentProperty:=SDefDocumentProperty
end;
{ TExtJSXMLWebdataInputAdaptor }
function TExtJSXMLWebdataInputAdaptor.isDocumentStored: boolean;
begin
Result:=DocumentElement<>SDefDocumentElement;
end;
function TExtJSXMLWebdataInputAdaptor.IsRecordStored: boolean;
begin
Result:=RecordElement<>SDefRecordElement;
end;
function TExtJSXMLWebdataInputAdaptor.isRootStored: boolean;
begin
Result:=RootElement<>SDefRootElement;
end;
function TExtJSXMLWebdataInputAdaptor.CheckData: Boolean;
Var
S : String;
T : TStringSTream;
E : TDomElement;
P : Integer;
begin
{$ifdef wmdebug}senddebug('Check data: '+Request.Content);{$endif}
Result:=Assigned(FXML);
If Not (Result) then
begin
S:=Request.ContentType;
P:=Pos(';',S);
If (P<>0) then
S:=Copy(S,1,P-1);
{$ifdef wmdebug}senddebug('Check data: '+S);{$endif}
Result:=CompareText(S,'application/x-www-form-urlencoded')=0;
If not Result then
begin
T:=TStringStream.Create(Request.Content);
try
XmlRead.ReadXMLFile(FXML,T);
If (DocumentElement<>'') and (FXML.DocumentElement.NodeName=DocumentElement) then
begin
{$ifdef wmdebug}senddebug('Document element is ExtJS DocumentElement');{$endif}
FDocRoot:=FXML.DocumentElement;
E:=FDocRoot;
end
else if (DocumentElement<>'') then
begin
//FXML.
{$ifdef wmdebug}senddebug('Looking for ExtJS Documentelement "'+DocumentElement+'" in XML.DocumentElement');{$endif}
FDocRoot:=FXML.DocumentElement.FindNode(DocumentElement) as TDOMElement;
E:=FDocRoot;
end;
{$ifdef wmdebug}senddebug('Looking for DocRoot element "'+RootElement+'" in FDocRoot');{$endif}
If Assigned(FDocRoot) then
FRoot:=FDocRoot
else
FRoot:=FXML.FindNode(RootElement) as TDomElement;
{$ifdef wmdebug}senddebug('Looking for current record element "'+RecordElement+'" in FRoot');{$endif}
If Assigned(FRoot) then
begin
FCurrentRow:=FRoot.FindNode(RecordElement) as TDomElement;
If Not Assigned(FCurrentRow) then
FIDValue:=FRoot.FindNode('ID') as TDomElement;
end
else
begin
{$ifdef wmdebug}senddebug('Looking for current record element "'+RecordElement+'" in document');{$endif}
FCurrentRow:=FXML.FindNode(RecordElement) as TDomElement;
end;
If (FCurrentRow=Nil) and (FXML.DocumentElement.NodeName=RecordElement) then
begin
{$ifdef wmdebug}senddebug('Documentelement is record element "'+RecordElement+'"');{$endif}
FCurrentRow:=FXML.DocumentElement;
end;
{$ifdef wmdebug}senddebug('Have current row: "'+IntToStr(Ord(Assigned(FCurrentRow)))+'"');{$endif}
Result:=True;
finally
T.free;
end;
end;
end;
end;
function TExtJSXMLWebdataInputAdaptor.TryFieldValue(const AFieldName: String;
out AValue: String): Boolean;
Var
I : Integer;
E : TDOMElement;
N : TDOMNode;
begin
Result:=False;
if CheckData then
begin
If Assigned(FIDValue) and (0=CompareText(AFieldName,'ID')) then
begin
AValue:=FIDValue.NodeValue;
Result:=True;
end
else if Assigned(FCurrentRow) then
begin
E:=FCurrentRow.FindNode(AFieldName) as TDomElement;
Result:=Assigned(E);
if result then
begin
N:=E.FirstChild;
If Assigned(N) then
AValue:=N.NodeValue;
end;
end;
end;
end;
constructor TExtJSXMLWebdataInputAdaptor.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
RootElement:=SDefRootElement;
RecordElement:=SDefRecordElement;
DocumentElement:=SDefDocumentElement;
end;
destructor TExtJSXMLWebdataInputAdaptor.destroy;
begin
FreeAndNil(FXML);
inherited destroy;
end;
initialization
WebDataProviderManager.RegisterInputAdaptor('ExtJS - XML',TExtJSXMLWebdataInputAdaptor);
WebDataProviderManager.RegisterDataProducer('ExtJS - XML',TExtJSXMLDataFormatter);
finalization
WebDataProviderManager.UnRegisterInputAdaptor('ExtJS - XML');
WebDataProviderManager.UnRegisterDataProducer('ExtJS - XML')
end.

View File

@ -0,0 +1,143 @@
unit fpextjs;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, fphttp, db, httpdefs, fpwebdata;
Type
{ TExtJSDataFormatter }
TExtJSDataFormatter = Class(TCustomHTTPDataContentProducer)
private
FMP: String;
FPLP: String;
FPSP: String;
FRP: String;
FSP: String;
FTP: String;
function IsMessageStored: boolean;
function IsPageLimitStored: boolean;
function IsPageStartStored: boolean;
function IsRowsStored: boolean;
Function IsSuccessStored : Boolean;
function IsTotalStored: boolean;
Public
Constructor Create(AOwner : TComponent); override;
Procedure DoReadRecords(Stream : TStream); override;
Function ProduceContent : String; override;
Property SuccessProperty : String Read FSP Write FSP stored IsSuccessStored;
Property PageStartProperty : String Read FPSP Write FPSP stored IsPageStartStored;
Property PageLimitProperty : String Read FPLP Write FPLP stored IsPageLimitStored;
Property RowsProperty : String Read FRP Write FRP stored IsRowsStored;
Property MessageProperty : String Read FMP Write FMP stored IsMessageStored;
Property TotalProperty : String Read FTP Write FTP stored IsTotalStored;
end;
Const
// Do not localize these constants.
DefRowsProperty = 'rows';
DefPageLimitProperty = 'limit';
DefPageStartProperty = 'start';
DefSuccessProperty = 'success';
DefMessageProperty = 'message';
DefTotalProperty = 'total';
implementation
Resourcestring
SErrNoAdaptor = 'No adaptor available';
{ TExtJSDataFormatter }
function TExtJSDataFormatter.IsSuccessStored: Boolean;
begin
Result:=(SuccessProperty<>DefSuccessProperty);
end;
function TExtJSDataFormatter.IsTotalStored: boolean;
begin
Result:=(TotalProperty<>DefTotalProperty);
end;
function TExtJSDataFormatter.IsMessageStored: boolean;
begin
Result:=(MessageProperty<>DefMessageProperty);
end;
function TExtJSDataFormatter.IsPageLimitStored: boolean;
begin
Result:=(PageLimitProperty<>DefPageLimitProperty);
end;
function TExtJSDataFormatter.IsPageStartStored: boolean;
begin
Result:=(PageStartProperty<>DefPageStartProperty);
end;
function TExtJSDataFormatter.IsRowsStored: boolean;
begin
Result:=(RowsProperty<>DefRowsProperty);
end;
constructor TExtJSDataFormatter.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
RowsProperty:=DefRowsProperty;
PageLimitProperty:=DefPageLimitProperty;
PageStartProperty:=DefPageStartProperty;
SuccessProperty:=DefSuccessProperty;
MessageProperty:=DefMessageProperty;
TotalProperty:=DefTotalProperty;
end;
procedure TExtJSDataFormatter.DoReadRecords(Stream: TStream);
Var
I : Integer;
L : TStrings;
S : String;
begin
If AllowPageSize then
begin
If Not Assigned(Adaptor) then
Raise EFPHTTPError.Create(SErrNoAdaptor);
if Adaptor.TryFieldValue(PageStartProperty,S) then
begin
I:=StrToIntDef(S,-1);
If I<>-1 then
PageStart:=I;
end;
if Adaptor.TryFieldValue(PageLimitProperty,S) then
begin
I:=StrToIntDef(S,-1);
If I<>-1 then
PageSize:=I;
end;
end;
Inherited DoReadRecords(Stream);
end;
function TExtJSDataFormatter.ProduceContent: String;
Var
S : TStringStream;
begin
S:=TStringStream.Create('');
try
ContentToStream(S);
Result:=S.DataString;
finally
FreeAndNil(S);
end;
end;
end.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,405 @@
unit sqldbwebdata;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, fphttp, fpwebdata, DB, SQLDB;
Type
{ TCustomSQLDBWebDataProvider }
TNewIDEvent = Procedure(Sender : TObject; Out AID : String) of object;
TGetParamTypeEvent = Procedure (Sender : TObject; Const ParamName,AValue : String; Var AType : TFieldtype) of object;
TCustomSQLDBWebDataProvider = Class(TFPCustomWebDataProvider)
private
FIDFieldName: String;
FOnGetNewID: TNewIDEvent;
FSQLS : Array[0..3] of TStringList;
FConnection: TSQLConnection;
FQuery : TSQLQuery;
FLastNewID : String;
FOnGetParamType : TGetParamTypeEvent;
procedure CheckDataset;
function GetS(AIndex: integer): TStrings;
procedure SetConnection(const AValue: TSQLConnection);
procedure SetS(AIndex: integer; const AValue: TStrings);
Protected
function CreateQuery(AOwner: TComponent; ATransaction: TSQLTransaction; ASQL: Tstrings): TSQLQuery;
function GetParamType(P: TParam; const AValue: String): TFieldType; virtual;
procedure SetTypedParam(P: TParam; Const AValue: String); virtual;
procedure ExecuteSQL(ASQL: TStrings; Msg: String=''; DoNewID : Boolean = False); virtual;
procedure ApplySQLParams(AQuery: TSQLQuery; DoNewID : Boolean = False); virtual;
Procedure SQLChanged(Sender : TObject); virtual;
Procedure DoUpdate; override;
Procedure DoDelete; override;
Procedure DoInsert; override;
Procedure DoApplyParams; override;
Function GetDataset : TDataset; override;
Function GetNewID : String;
Function IDFieldValue : String; override;
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
Property SelectSQL : TStrings Index 0 Read GetS Write SetS;
Property UpdateSQL : TStrings Index 1 Read GetS Write SetS;
Property DeleteSQL : TStrings Index 2 Read GetS Write SetS;
Property InsertSQL : TStrings Index 3 Read GetS Write SetS;
Property Connection : TSQLConnection Read FConnection Write SetConnection;
Property OnGetNewID : TNewIDEvent Read FOnGetNewID Write FOnGetNewID;
property OnGetParameterType : TGetParamTypeEvent Read FOnGetParamType Write FOnGetParamType;
Public
Constructor Create(AOwner : TComponent); override;
Destructor Destroy; override;
end;
TSQLDBWebDataProvider = Class(TCustomSQLDBWebDataProvider)
Published
Property SelectSQL;
Property UpdateSQL;
Property DeleteSQL;
Property InsertSQL;
Property Connection;
Property IDFieldName;
Property OnGetNewID;
property OnGetParameterType;
end;
implementation
{ $define wmdebug}
{$ifdef wmdebug}
uses dbugintf;
{$endif}
resourcestring
SErrNoSelectSQL = '%s: No select SQL statement provided.';
SErrNoUpdateSQL = '%s: No update SQL statement provided.';
SErrNoInsertSQL = '%s: No insert SQL statement provided.';
SErrNoDeleteSQL = '%s: No delete SQL statement provided.';
SErrUpdating = '%s: An error occurred during the update operation: %s';
SErrDeleting = '%s: An error occurred during the delete operation: %s';
SErrInserting = '%s: An error occurred during the insert operation: %s';
SErrNoNewIDEvent = '%s : Cannot generate ID: No OnGetNewID event assigned.';
{ TCustomSQLDBWebDataProvider }
function TCustomSQLDBWebDataProvider.GetS(AIndex: integer): TStrings;
begin
Result:=FSQLS[AIndex];
end;
procedure TCustomSQLDBWebDataProvider.SetConnection(const AValue: TSQLConnection
);
begin
if (FConnection=AValue) then exit;
If Assigned(FConnection) then
FConnection.RemoveFreeNotification(Self);
FConnection:=AValue;
If Assigned(FConnection) then
FConnection.FreeNotification(Self);
end;
procedure TCustomSQLDBWebDataProvider.SetS(AIndex: integer;
const AValue: TStrings);
begin
FSQLS[AIndex].Assign(AValue);
end;
procedure TCustomSQLDBWebDataProvider.SQLChanged(Sender: TObject);
begin
If (Sender=SelectSQL) and Assigned(FQuery) then
begin
FQuery.Close;
FQuery.SQL.Assign(SelectSQL);
end;
end;
procedure TCustomSQLDBWebDataProvider.ExecuteSQL(ASQL : TStrings; Msg : String = ''; DoNewID : Boolean = False);
Var
Q : TSQLQuery;
begin
{$ifdef wmdebug}SendDebug('Entering TCustomSQLDBWebDataProvider.ExecuteSQL');{$endif}
Q:=CreateQuery(Nil,Nil,ASQL);
try
Q.Transaction.Active:=True;
try
ApplySQLParams(Q,DoNewID);
Q.ExecSQL;
(Q.Transaction as TSQLTransaction).Commit;
except
On E : Exception do
begin
(Q.Transaction as TSQLTransaction).Rollback;
If (Msg<>'') then
E.Message:=Format(Msg,[Self.Name,E.Message]);
Raise;
end;
end
finally
Q.Free;
end;
{$ifdef wmdebug}SendDebug('Exiting TCustomSQLDBWebDataProvider.ExecuteSQL');{$endif}
end;
procedure TCustomSQLDBWebDataProvider.DoUpdate;
begin
{$ifdef wmdebug}SendDebug('Entering TCustomSQLDBWebDataProvider.DoUpdate');{$endif}
If (Trim(UpdateSQL.Text)='') then
Raise EFPHTTPError.CreateFmt(SErrNoUpdateSQL,[Self.Name]);
FLastNewID:='';
ExecuteSQL(UpdateSQL,SErrUpdating);
{$ifdef wmdebug}SendDebug('Exiting TCustomSQLDBWebDataProvider.DoUpdate');{$endif}
end;
procedure TCustomSQLDBWebDataProvider.DoDelete;
begin
{$ifdef wmdebug}SendDebug('Entering TCustomSQLDBWebDataProvider.DoDelete');{$endif}
If (Trim(DeleteSQL.Text)='') then
Raise EFPHTTPError.CreateFmt(SErrNoDeleteSQL,[Self.Name]);
FLastNewID:='';
ExecuteSQL(DeleteSQL,SErrDeleting);
{$ifdef wmdebug}SendDebug('Exiting TCustomSQLDBWebDataProvider.DoDelete');{$endif}
end;
procedure TCustomSQLDBWebDataProvider.DoInsert;
begin
{$ifdef wmdebug}SendDebug('Entering TCustomSQLDBWebDataProvider.DoInsert');{$endif}
If (Trim(InsertSQL.Text)='') then
Raise EFPHTTPError.CreateFmt(SErrNoInsertSQL,[Self.Name]);
FLastNewID:='';
ExecuteSQL(InsertSQL,SErrInserting,(IDFieldName<>''));
{$ifdef wmdebug}SendDebug('Exiting TCustomSQLDBWebDataProvider.DoInsert');{$endif}
end;
procedure TCustomSQLDBWebDataProvider.Notification(AComponent: TComponent;
Operation: TOperation);
begin
If (Operation=opRemove) then
begin
If (AComponent=FQuery) then
FQuery:=Nil
else if (AComponent=FConnection) then
FConnection:=Nil;
end;
end;
Function TCustomSQLDBWebDataProvider.CreateQuery(AOwner : TComponent; ATransaction : TSQLTransaction; ASQL : Tstrings) : TSQLQuery;
begin
Result:=TSQLQuery.Create(AOwner);
If (AOwner<>Self) then
Result.FreeNotification(Self);
Result.DataBase:=Connection;
If ATransaction=Nil then
begin
ATransaction:=TSQLTransaction.Create(Result);
ATransaction.DataBase:=Connection;
end;
Result.Transaction:=ATransaction;
Result.SQL.Assign(ASQL);
end;
procedure TCustomSQLDBWebDataProvider.CheckDataset;
begin
{$ifdef wmdebug}SendDebug('Entering CheckDataset');{$endif}
If (Trim(SelectSQL.Text)='') then
Raise EFPHTTPError.CreateFmt(SErrNoSelectSQL,[Self.Name]);
If (FQuery=Nil) then
FQuery:=CreateQuery(Nil,Nil,SelectSQL)
else if not FQuery.Active then
FQuery.SQL.Assign(SelectSQL);
{$ifdef wmdebug}SendDebug('Exiting CheckDataset');{$endif}
end;
Function TCustomSQLDBWebDataProvider.GetParamType(P : TParam; Const AValue : String) : TFieldType;
begin
Result:=ftunknown;
If Assigned(FOnGetParamType) then
FOnGetParamType(Self,P.Name,AValue,Result);
end;
procedure TCustomSQLDBWebDataProvider.SetTypedParam(P : TParam; Const AValue : String);
Var
I : Integer;
Q : Int64;
D : TDateTime;
ft : TFieldType;
F : Double;
B : Boolean;
C : Currency;
begin
ft:=GetParamtype(P,AValue);
If ft<>ftUnknown then
begin
try
case ft of
ftInteger,
ftword,
ftsmallint : I:=StrToInt(AValue);
ftDate : D:=StrToDate(AValue);
ftDateTime,
ftTimestamp : D:=StrToDateTime(AValue);
ftBoolean : B:=StrToBool(AValue);
ftTime : D:=StrToTime(AValue);
ftLargeint : Q:=StrToInt64(AValue);
ftCurrency : C:=StrToCurr(Avalue);
else
ft:=ftString
end
except
ft:=ftUnknown
end;
end;
If (ft=ftUnknown) and (Length(AValue)<30) then
begin
if TryStrToInt(Avalue,I) then
ft:=ftInteger
else if TryStrToInt64(Avalue,Q) then
ft:=ftInteger
else if (Pos(DateSeparator,AValue)<>0) then
begin
if (Pos(TimeSeparator,AValue)<>0) and TryStrToDateTime(Avalue,D) then
ft:=ftDateTime
else if TryStrToDate(Avalue,D) then
ft:=ftDate
end
else If (Pos(TimeSeparator,AValue)<>0) and TryStrToTime(Avalue,D) then
ft:=ftTime
else if (Pos(DecimalSeparator,AValue)<>0) then
begin
if trystrtofloat(AValue,F) then
ft:=ftFloat
else if TryStrToCurr(Avalue,C) then
ft:=ftCurrency
end
else if TryStrToBool(Avalue,B) then
ft:=ftBoolean
end;
Case ft of
ftInteger,
ftword,
ftsmallint : P.AsInteger:=I;
ftBoolean : P.AsBoolean:=B;
ftLargeInt : P.AsLargeInt:=Q;
ftDate : P.AsDate:=D;
ftDateTime,
ftTimestamp : P.AsDateTime:=D;
ftTime : P.AsTime:=D;
ftFloat,
ftBCD,
ftFMTBCD : P.AsFloat:=F;
ftCurrency : P.AsCurrency:=F;
else
P.AsString:=AValue;
end;
end;
procedure TCustomSQLDBWebDataProvider.ApplySQLParams(AQuery : TSQLQuery; DoNewID : Boolean = False);
var
I: Integer;
P : TParam;
S : String;
begin
{$ifdef wmdebug}SendDebug('Entering ApplySQLPArams');{$endif}
For I:=0 to AQuery.Params.Count-1 do
begin
P:=AQuery.Params[i];
If (P.Name=IDFieldName) and DoNewID then
SetTypedParam(P,GetNewID)
else If Adaptor.TryFieldValue(P.Name,S) then
SetTypedParam(P,S)
else If Adaptor.TryParamValue(P.Name,S) then
SetTypedParam(P,S)
else
P.Clear;
end;
{$ifdef wmdebug}SendDebug('Exiting ApplySQLPArams');{$endif}
end;
procedure TCustomSQLDBWebDataProvider.DoApplyParams;
begin
CheckDataset;
ApplySQLParams(FQuery);
end;
function TCustomSQLDBWebDataProvider.GetDataset: TDataset;
begin
{$ifdef wmdebug}SendDebug('Get dataset: checking dataset');{$endif}
CheckDataset;
FLastNewID:='';
Result:=FQuery;
{$ifdef wmdebug}SendDebug('Get dataset: activating transaction');{$endif}
If Not FQuery.Transaction.Active then
FQuery.Transaction.Active:=True;
{$ifdef wmdebug}SendDebug('Get dataset: done');{$endif}
end;
function TCustomSQLDBWebDataProvider.GetNewID: String;
begin
If Not Assigned(FOnGetNewID) then
Raise EFPHTTPError.CreateFmt(SErrNoNewIDEvent,[Self.Name]);
FOnGetNewID(Self,Result);
FLastNewID:=Result;
end;
function TCustomSQLDBWebDataProvider.IDFieldValue: String;
begin
{$ifdef wmdebug}SendDebug('Entering IDFieldValue');{$endif}
If (FLastNewID<>'') then
Result:=FLastNewID
else If (IDFieldName<>'') then
begin
If not Adaptor.TryParamValue(IDFieldName,Result) then
If not Adaptor.TryFieldValue(IDFieldName,Result) then
Result:=inherited IDFieldValue;
end
else
Result:=inherited IDFieldValue;
{$ifdef wmdebug}SendDebug('Exiting IDFieldValue : '+Result);{$endif}
end;
constructor TCustomSQLDBWebDataProvider.Create(AOwner: TComponent);
Var
I : Integer;
L : TStringList;
begin
inherited Create(AOwner);
For I:=0 to 3 do
begin
L:=TStringList.Create;
L.OnChange:=@SQLChanged;
FSQLS[i]:=L;
end;
end;
destructor TCustomSQLDBWebDataProvider.Destroy;
Var
I: Integer;
begin
For I:=0 to 3 do
FreeAndNil(FSQLS[i]);
Connection:=Nil;
FreeAndNil(FQuery);
inherited Destroy;
end;
end.

View File

@ -0,0 +1,114 @@
FPC WebData architecture
========================
The aim of this set of components is to be able to easily send data
to a webapplication, and to handle updates of this data, all in a
webserver module.
The following components are used
- TFPWebDataProvider
The central component, forming a bridge between TDataset and web content.
- TCustomWebdataInputAdaptor
A class that transforms the input of a web request to something that
TFPWebDataProvider understands. Example implementations are provided
for ExtJS, XML and JSON.
- TWebdataInputAdaptor
A descendent of TCustomWebdataInputAdaptor that allows to select the
input format from a list of known formats.
- TCustomHTTPDataContentProducer
This class produces the response for the webapplication. It is an
abstract class: descendents need to be made for the various expected
outputs. Example implementations are provided for ExtJS, XML and JSON.
- THTTPDataContentProducer
A descendent of TCustomHTTPDataContentProducer that allows to select the
output format from a list of known formats.
- TFPWebProviderDataModule
A THTTPSessionDatamodule descendent that can be used to handle data
requests from a webclient. It functions as a container for
TFPWebDataProvider components, InputAdaptors and Content producers.
A module is registered in the Lazarus IDE package under File/New.
Typically, one will do the following
- Create a TFPWebProviderDataModule from the IDE.
- Drop some dataset components on it, and set them up for use with some
datasources
- For each dataset, drop a TFPWebDataProvider component on the module,
and connect it to the datasource. The name of this component is exposed
to the client.
- Drop a suitable input adaptor.
The data can then typically be read through the URL:
baseurl/modulename/providername/read
Or updated through the URLs
baseurl/modulename/providername/update
baseurl/modulename/providername/create
baseurl/modulename/providername/delete
where baseurl is the base URL for the web-application.
Large applications: factory support
For large-scale applications with lots of different datasets, there is
support for registering dataproviders in a central factory system:
The WebDataProviderManager function returns an instance of
TFPWebDataProviderManager.
It must be used to register WebDataProvider names and classes:
Function RegisterProvider(Const AProviderName : String; AClass : TFPCustomWebDataProviderClass) : TWebDataProviderDef; overload;
The first form registers a class: an instance of this class will
be created by the factory whenever a provider of name AProviderName is
requested.
The TFPWebProviderDataModule class is aware of the WebDataProviderManager
factory class, and will look there for a TFPCustomWebDataProvider instance
if none is found in the webmodule instance itself and the
'UseProviderManager' property is 'True'.
The WebDataProviderManager factory can also Register a complete datamodule:
Procedure RegisterDatamodule(Const AClass : TDatamoduleClass);
This will register all WebDataProvider instances on the datamodule:
An instance will be created, all TFPCustomWebDataProvider instances
will be registered with their component names.
When a provider belonging to such a datamodule is requested, then
the module will be created, and the requested TFPCustomWebDataProvider
instance is returned.
A provider instance can be requested with the following factory methods:
Function GetProvider(Const ADef : TWebDataProviderDef; AOwner : TComponent;out AContainer : TComponent): TFPCustomWebDataProvider;
Function GetProvider(Const AProviderName : String; AOwner : TComponent; Out AContainer : TComponent): TFPCustomWebDataProvider;
The result is the provider instance. All instances are created using a
container module: either this is the module class used in RegisterDatamodule
or a vanilla TDatamodule class. This instance is returned in AContainer.
The container must be freed by the caller.
In practise, this means that one creates a datamodule, drops some
TFPWebDataProvider instances on it, and adds the following call
to the initialization section of the unit:
WebDataProviderManager.RegisterDatamodule(TMyDataModule);
The TFPWebProviderDataModule that handles web requests will then be able
to handle requests for the TFPWebDataProvider instances on the datamodule.
Note that the RegisterDataModule routine will create an instance of the
datamodule to get a list of provider components (it uses the component.name
property). The WebDataProviderManager's 'registering' property will be set
to true: this way one can avoid connecting to a database during registration.
The WebDataProviderManager also handles the registration of inputadataptors
and output contents producers.