1unit WebPage;
2
3{$mode objfpc}{$H+}
4
5interface
6
7uses
8  Classes, SysUtils, fphtml, htmlelements, htmlwriter, HTTPDefs, fpweb, contnrs, dom;
9
10type
11  TRequestResponseEvent = procedure(Sender: TObject; ARequest: TRequest; AResponse: TResponse) of object;
12  TRequestEvent = procedure(Sender: TObject; ARequest: TRequest) of object;
13  THandleAjaxRequest = procedure(Sender: TObject; ARequest: TRequest; AnAjaxResponse: TAjaxResponse; var handled: boolean) of object;
14  TAjaxRequestResponseEvent = procedure(Sender: TObject; ARequest: TRequest; AResponse: TAjaxResponse) of object;
15
16type
17
18  { IWebPageDesigner }
19
20  IWebPageDesigner = interface(IUnknown)
21  ['{25629DEA-79D5-4165-A0A3-BE6E2BA74442}']
22    procedure Invalidate;
23  end;
24
25  { IHTMLDesignable }
26
27  IHTMLDesignable = interface(IUnknown)
28  ['{C75546D6-9C93-49F0-809F-D29C52CD306D}']
29    function GetDesigner: IWebPageDesigner;
30    procedure SetDesigner(const AValue: IWebPageDesigner);
31    property Designer: IWebPageDesigner read GetDesigner write SetDesigner;
32  end;
33
34  IHTMLIterationGroup = interface(IUnknown)
35  ['{95575CB6-7D96-4F72-AF72-D2EAF0BECE71}']
36    procedure SetIDSuffix(const AHTMLContentProducer: THTMLContentProducer);
37    procedure SetAjaxIterationID(AValue: String);
38  end;
39
40
41  { TStandardWebController }
42
43  TStandardWebController = class(TWebController)
44  private
45    FScriptFileReferences: TStringList;
46    FScripts: TFPObjectList;
47    FStyleSheetReferences: TContainerStylesheets;
48  protected
49    function GetScriptFileReferences: TStringList; override;
50    function GetScripts: TFPObjectList; override;
51    function GetStyleSheetReferences: TContainerStylesheets; override;
52  public
53    constructor Create(AOwner: TComponent); override;
54    destructor Destroy; override;
55    function CreateNewJavascriptStack(AJavaType: TJavaType): TJavaScriptStack; override;
56    function GetUrl(ParamNames, ParamValues, KeepParams: array of string; Action: string = ''): string; override;
57    procedure BindJavascriptCallstackToElement(AComponent: TComponent; AnElement: THtmlCustomElement; AnEvent: string); override;
58    procedure AddScriptFileReference(AScriptFile: String); override;
59    procedure AddStylesheetReference(Ahref, Amedia: String); override;
60    function DefaultMessageBoxHandler(Sender: TObject; AText: String; Buttons: TWebButtons; ALoaded: string = ''): string; override;
61    function CreateNewScript: TStringList; override;
62    procedure ShowRegisteredScript(ScriptID: integer); override;
63    procedure FreeScript(var AScript: TStringList); override;
64  published
65    property OnGetURL;
66  end;
67
68  { TWebPage }
69
70  TWebPage = class(TDataModule, IHTMLContentProducerContainer, IHTMLDesignable)
71  private
72    FAfterAjaxRequest: TAjaxRequestResponseEvent;
73    FBaseURL: string;
74    FBeforeRequest: TRequestEvent;
75    FBeforeShowPage: TRequestEvent;
76    FDesigner: IWebPageDesigner;
77    FOnAjaxRequest: THandleAjaxRequest;
78    FRequest: TRequest;
79    FWebController: TWebController;
80    FWebModule: TFPWebModule;
81    FContentProducers: TFPList; // list of THTMLContentProducer
82    function GetContentProducer(Index: integer): THTMLContentProducer;
83    function GetContentProducerList: TFPList;
84    function GetContentProducers(Index: integer): THTMLContentProducer;
85    function GetDesigner: IWebPageDesigner;
86    function GetHasWebController: boolean;
87    function GetWebController: TWebController;
88    procedure SetDesigner(const AValue: IWebPageDesigner);
89  protected
90    procedure DoAfterAjaxRequest(ARequest: TRequest; AnAjaxResponse: TAjaxResponse); virtual;
91    procedure DoHandleAjaxRequest(ARequest: TRequest; AnAjaxResponse: TAjaxResponse; var Handled: boolean); virtual;
92    procedure DoBeforeRequest(ARequest: TRequest); virtual;
93    procedure DoBeforeShowPage(ARequest: TRequest); virtual;
94    procedure DoCleanupAfterRequest(const AContentProducer: THTMLContentProducer);
95    procedure SetRequest(ARequest: TRequest); virtual;
96    procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override;
97    property ContentProducerList: TFPList read GetContentProducerList;
98  public
99    destructor Destroy; override;
100    function ContentProducerCount: integer;
101
102    function ProduceContent : string;
103    procedure AddContentProducer(AContentProducer: THTMLContentProducer);
104    procedure RemoveContentProducer(AContentProducer: THTMLContentProducer);
105    function ExchangeContentProducers(Child1, Child2: THTMLContentProducer) : boolean;
106    function MoveContentProducer(MoveElement, MoveBeforeElement: THTMLContentProducer) : boolean;
107    procedure ForeachContentProducer(AForeachChildsProc: TForeachContentProducerProc; Recursive: boolean);
108    function IsAjaxCall: boolean; virtual;
109
110    procedure HandlePage(ARequest: TRequest; AResponse: TResponse; AWriter: THTMLwriter; AWebModule: TFPWebModule = nil); virtual;
111    procedure DoBeforeGenerateXML; virtual;
112    procedure CleanupAfterRequest; virtual;
113    property Designer: IWebPageDesigner read GetDesigner write SetDesigner;
114    property Request: TRequest read FRequest;
115    property ContentProducers[Index: integer]: THTMLContentProducer read GetContentProducer;
116    property HasWebController: boolean read GetHasWebController;
117    property WebController: TWebController read GetWebController write FWebController;
118    property WebModule: TFPWebModule read FWebModule;
119  published
120    property BeforeRequest: TRequestEvent read FBeforeRequest write FBeforeRequest;
121    property BeforeShowPage: TRequestEvent read FBeforeShowPage write FBeforeShowPage;
122    property AfterAjaxRequest: TAjaxRequestResponseEvent read FAfterAjaxRequest write FAfterAjaxRequest;
123    property OnAjaxRequest: THandleAjaxRequest read FOnAjaxRequest write FOnAjaxRequest;
124    property BaseURL: string read FBaseURL write FBaseURL;
125  end;
126
127  function RegisterScript(AScript: string) : integer;
128
129implementation
130
131uses typinfo, strutils;
132
133var RegisteredScriptList : TStrings;
134
135function RegisterScript(AScript: string) : integer;
136begin
137  if not Assigned(RegisteredScriptList) then
138    begin
139    RegisteredScriptList := TStringList.Create;
140    end;
141  result := RegisteredScriptList.Add(AScript);
142end;
143
144{ TWebPage }
145
146function TWebPage.ProduceContent: string;
147var i : integer;
148begin
149  result := '';
150  for i := 0 to ContentProducerCount-1 do
151    result := result + THTMLContentProducer(ContentProducers[i]).ProduceContent;
152end;
153
154procedure TWebPage.AddContentProducer(AContentProducer: THTMLContentProducer);
155begin
156  ContentProducerList.Add(AContentProducer);
157end;
158
159procedure TWebPage.RemoveContentProducer(AContentProducer: THTMLContentProducer);
160begin
161  ContentProducerList.Remove(AContentProducer);
162end;
163
164function TWebPage.ExchangeContentProducers(Child1, Child2: THTMLContentProducer): boolean;
165var ChildIndex1, ChildIndex2: integer;
166begin
167  result := false;
168  ChildIndex1:=GetContentProducerList.IndexOf(Child1);
169  if (ChildIndex1=-1) then
170    Exit;
171  ChildIndex2:=GetContentProducerList.IndexOf(Child2);
172  if (ChildIndex2=-1) then
173    Exit;
174  GetContentProducerList.Exchange(ChildIndex1,ChildIndex2);
175  result := true;
176end;
177
178function TWebPage.MoveContentProducer(MoveElement, MoveBeforeElement: THTMLContentProducer): boolean;
179var ChildIndex1, ChildIndex2: integer;
180begin
181  result := false;
182  ChildIndex1:=GetContentProducerList.IndexOf(MoveElement);
183  if (ChildIndex1=-1) then
184    Exit;
185  ChildIndex2:=GetContentProducerList.IndexOf(MoveBeforeElement);
186  if (ChildIndex2=-1) then
187    Exit;
188  GetContentProducerList.Move(ChildIndex1,ChildIndex2);
189  result := true;
190end;
191
192procedure TWebPage.ForeachContentProducer(AForeachChildsProc: TForeachContentProducerProc; Recursive: boolean);
193var i : integer;
194    tmpChild: THTMLContentProducer;
195begin
196  for i := 0 to ContentProducerCount -1 do
197    begin
198    tmpChild := ContentProducers[i];
199    AForeachChildsProc(tmpChild);
200    if recursive then
201      tmpChild.ForeachContentProducer(AForeachChildsProc,Recursive);
202    end;
203end;
204
205procedure TWebPage.HandlePage(ARequest: TRequest; AResponse: TResponse; AWriter: THTMLwriter; AWebModule: TFPWebModule=nil);
206var Handled: boolean;
207    CompName: string;
208    AComponent: TComponent;
209    AnAjaxResponse: TAjaxResponse;
210    i: integer;
211    ASuffixID: string;
212    AIterationGroup: IHTMLIterationGroup;
213    AIterComp: TComponent;
214    wc: TWebController;
215    Iterationlevel: integer;
216
217  procedure SetIdSuffixes(AComp: THTMLContentProducer);
218  var
219    i: integer;
220    s: string;
221  begin
222    if assigned(AComp.parent) and (acomp.parent is THTMLContentProducer) then
223      SetIdSuffixes(THTMLContentProducer(AComp.parent));
224    if supports(AComp,IHTMLIterationGroup,AIterationGroup) then
225      begin
226        if assigned(FWebController) then
227          begin
228          iterationlevel := FWebController.IncrementIterationLevel;
229          assert(length(ASuffixID)>0);
230          i := PosEx('_',ASuffixID,2);
231          if i > 0 then
232            s := copy(ASuffixID,2,i-2)
233          else
234            s := copy(ASuffixID,2,length(ASuffixID)-1);
235
236          acomp.IDSuffix := s;
237          AIterationGroup.SetAjaxIterationID(s);
238          FWebController.SetIterationIDSuffix(iterationlevel,s);
239          acomp.ForeachContentProducer(@AIterationGroup.SetIDSuffix,true);
240          ASuffixID := copy(ASuffixID,i,length(ASuffixID)-i+1);
241          end;
242      end;
243  end;
244begin
245  SetRequest(ARequest);
246  FWebModule := AWebModule;
247  try
248    try
249      DoBeforeRequest(ARequest);
250      if IsAjaxCall then
251        begin
252        AnAjaxResponse := TAjaxResponse.Create(GetWebController, AResponse);
253        try
254          try
255            if HasWebController then
256              WebController.InitializeAjaxRequest;
257            Handled := false;
258            DoHandleAjaxRequest(ARequest, AnAjaxResponse, Handled);
259            if not Handled then
260              begin
261              CompName := Request.QueryFields.Values['AjaxID'];
262              if CompName='' then CompName := Request.GetNextPathInfo;
263
264              i := pos('$',CompName);
265              AComponent:=self;
266              while (i > 0) and (assigned(AComponent)) do
267                begin
268                AComponent := AComponent.FindComponent(copy(CompName,1,i-1));
269                CompName := copy(compname,i+1,length(compname)-i);
270                i := pos('$',CompName);
271                end;
272              if assigned(AComponent) then
273                AComponent := AComponent.FindComponent(CompName);
274
275              if assigned(AComponent) and (AComponent is THTMLContentProducer) then
276                begin
277                // Handle the SuffixID, search for iteration-groups and set their iteration-id-values
278                ASuffixID := ARequest.QueryFields.Values['IterationID'];
279                if ASuffixID<>'' then
280                  begin
281                  SetIdSuffixes(THTMLContentProducer(AComponent));
282                  webcontroller.ResetIterationLevel;
283                  end;
284                THTMLContentProducer(AComponent).HandleAjaxRequest(ARequest, AnAjaxResponse);
285                end;
286              end;
287            DoAfterAjaxRequest(ARequest, AnAjaxResponse);
288          except on E: Exception do
289            AnAjaxResponse.SetError(e.HelpContext, e.Message);
290          end;
291          AnAjaxResponse.BindToResponse;
292        finally
293          AnAjaxResponse.Free;
294        end;
295        end
296      else
297        begin
298        if HasWebController then
299          WebController.InitializeShowRequest;
300        DoBeforeShowPage(ARequest);
301        AResponse.Content := ProduceContent;
302        if HasWebController then
303          WebController.CleanupShowRequest;
304        end;
305    finally
306      CleanupAfterRequest;
307    end;
308  finally
309    SetRequest(nil);
310    AWebModule := nil;
311  end;
312end;
313
314procedure TWebPage.DoBeforeGenerateXML;
315begin
316  // Do Nothing
317end;
318
319procedure TWebPage.CleanupAfterRequest;
320begin
321  ForeachContentProducer(@DoCleanupAfterRequest, True);
322  if HasWebController then
323    WebController.CleanupAfterRequest;
324end;
325
326procedure TWebPage.DoCleanupAfterRequest(const AContentProducer: THTMLContentProducer);
327begin
328  AContentProducer.CleanupAfterRequest;
329end;
330
331procedure TWebPage.SetRequest(ARequest: TRequest);
332begin
333  FRequest := ARequest;
334end;
335
336procedure TWebPage.GetChildren(Proc: TGetChildProc; Root: TComponent);
337var i : integer;
338begin
339  inherited GetChildren(Proc, Root);
340  if (Root=Self) then
341    for I:=0 to ContentProducerCount-1 do
342      Proc(ContentProducers[i]);
343end;
344
345destructor TWebPage.Destroy;
346begin
347  inherited Destroy;
348  if assigned(FContentProducers) then
349    FreeAndNil(FContentProducers);
350end;
351
352function TWebPage.ContentProducerCount: integer;
353begin
354  if assigned(FContentProducers) then
355    result := FContentProducers.Count
356  else
357    result := 0;
358end;
359
360function TWebPage.GetContentProducers(Index: integer): THTMLContentProducer;
361begin
362  Result:=THTMLContentProducer(ContentProducerList[Index]);
363end;
364
365function TWebPage.GetDesigner: IWebPageDesigner;
366begin
367  result := FDesigner;
368end;
369
370function TWebPage.GetHasWebController: boolean;
371begin
372  result := assigned(FWebController);
373end;
374
375function TWebPage.GetWebController: TWebController;
376begin
377  if not assigned(FWebController) then
378    raise EHTTP.create('No webcontroller available');
379  result := FWebController;
380end;
381
382procedure TWebPage.SetDesigner(const AValue: IWebPageDesigner);
383begin
384  FDesigner := AValue;
385end;
386
387function TWebPage.GetContentProducerList: TFPList;
388begin
389  if not assigned(FContentProducers) then
390    FContentProducers := tfplist.Create;
391  Result := FContentProducers;
392end;
393
394function TWebPage.GetContentProducer(Index: integer): THTMLContentProducer;
395begin
396  Result := THTMLContentProducer(ContentProducerList[Index]);
397end;
398
399procedure TWebPage.DoAfterAjaxRequest(ARequest: TRequest; AnAjaxResponse: TAjaxResponse);
400begin
401  if assigned(AfterAjaxRequest) then
402    AfterAjaxRequest(Self,ARequest,AnAjaxResponse);
403end;
404
405procedure TWebPage.DoHandleAjaxRequest(ARequest: TRequest; AnAjaxResponse: TAjaxResponse; var Handled: boolean);
406begin
407  if assigned(OnAjaxRequest) then
408    OnAjaxRequest(Self,ARequest,AnAjaxResponse, Handled);
409end;
410
411procedure TWebPage.DoBeforeRequest(ARequest: TRequest);
412begin
413  if assigned(BeforeRequest) then
414    BeforeRequest(Self,ARequest);
415end;
416
417procedure TWebPage.DoBeforeShowPage(ARequest: TRequest);
418begin
419  if assigned(BeforeShowPage) then
420    BeforeShowPage(Self,ARequest);
421end;
422
423function TWebPage.IsAjaxCall: boolean;
424var s : string;
425begin
426  if assigned(request) then
427    begin
428    s := Request.HTTPXRequestedWith;
429    result := sametext(s,'XmlHttpRequest');
430    end
431  else
432    result := false;
433end;
434
435{ TStandardWebController }
436
437function TStandardWebController.GetScriptFileReferences: TStringList;
438begin
439  Result:=FScriptFileReferences;
440end;
441
442function TStandardWebController.GetScripts: TFPObjectList;
443begin
444  if not assigned(FScripts) then
445    begin
446    FScripts:=TFPObjectList.Create;
447    FScripts.OwnsObjects:=true;
448    end;
449  Result:=FScripts;
450end;
451
452function TStandardWebController.GetStyleSheetReferences: TContainerStylesheets;
453begin
454  Result:=FStyleSheetReferences;
455end;
456
457function TStandardWebController.CreateNewScript: TStringList;
458begin
459  Result:=TStringList.Create;
460  GetScripts.Add(result);
461end;
462
463procedure TStandardWebController.ShowRegisteredScript(ScriptID: integer);
464var
465  i: Integer;
466  s: string;
467begin
468  s := '// ' + inttostr(ScriptID);
469  for i := 0 to GetScripts.Count -1 do
470    if tstrings(GetScripts.Items[i]).Strings[0]=s then
471      Exit;
472  with CreateNewScript do
473    begin
474    Append(s);
475    Append(RegisteredScriptList.Strings[ScriptID]);
476    end;
477end;
478
479procedure TStandardWebController.FreeScript(var AScript: TStringList);
480begin
481  with GetScripts do
482    GetScripts.Delete(IndexOf(AScript));
483  AScript := nil;
484end;
485
486function TStandardWebController.DefaultMessageBoxHandler(Sender: TObject;
487  AText: String; Buttons: TWebButtons; ALoaded: string = ''): string;
488var i : integer;
489    HasCancel: boolean;
490    OnOk: string;
491    OnCancel: string;
492begin
493  HasCancel:=false;
494  OnOk:='';
495  OnCancel:='';
496  for i := low(Buttons) to High(Buttons) do
497    begin
498    if Buttons[i].ButtonType=btOk then
499      OnOk := Buttons[i].OnClick;
500    if Buttons[i].ButtonType=btCancel then
501      begin
502      HasCancel := True;
503      OnCancel := Buttons[i].OnClick;
504      end;
505    end;
506
507  if HasCancel then
508    result := 'if (confirm('''+AText+''')==true) {'+OnOk+'} else {'+OnCancel+'}'
509  else
510    result := 'alert('''+AText+''');'+OnOk;
511end;
512
513constructor TStandardWebController.Create(AOwner: TComponent);
514begin
515  inherited Create(AOwner);
516  FStyleSheetReferences := TContainerStylesheets.Create(TContainerStylesheet);
517  FScriptFileReferences := TStringList.Create;
518  // For some reason the Duplicates property does not work when sorted is true,
519  // But we don't want a sorted list so do a manual check in AddScriptFileReference
520  //FScriptFileReferences.Sorted:=true;
521  FScriptFileReferences.Duplicates:=dupIgnore;
522end;
523
524destructor TStandardWebController.Destroy;
525begin
526  FScriptFileReferences.Free;
527  FScripts.Free;
528  FStyleSheetReferences.Free;
529  inherited Destroy;
530end;
531
532function TStandardWebController.CreateNewJavascriptStack(AJavaType: TJavaType): TJavaScriptStack;
533begin
534  Result:=TJavaScriptStack.Create(self, AJavaType);
535end;
536
537function TStandardWebController.GetUrl(ParamNames, ParamValues,
538  KeepParams: array of string; Action: string): string;
539
540var qs,p : String;
541    i,j  : integer;
542    found: boolean;
543    FancyTitle: boolean;
544    ConnectChar: char;
545    CGIScriptName: string;
546    ActionVar: string;
547    ARequest: TRequest;
548    WebMod: TFPWebModule;
549
550begin
551  FancyTitle:=false;
552  qs := '';
553  result := Action;
554  ARequest := GetRequest;
555  ActionVar := '';
556  if assigned(owner) then
557    begin
558    if (owner is TWebPage) then
559      WebMod := TWebPage(Owner).WebModule
560    else if (owner is TFPWebModule) then
561      WebMod := TFPWebModule(Owner);
562
563    if assigned(WebMod) then
564      begin
565      ActionVar := WebMod.ActionVar;
566      if (action = '') and assigned(WebMod.Actions) and assigned(WebMod.Actions.CurrentAction) then
567        result := WebMod.Actions.CurrentAction.Name;
568      end;
569    end;
570  if ActionVar='' then FancyTitle:=true;
571  if Assigned(ARequest) then
572    begin
573    if  (high(KeepParams)>=0) and (KeepParams[0]='*') then
574      begin
575      for i := 0 to ARequest.QueryFields.Count-1 do
576        begin
577        p := ARequest.QueryFields.Names[i];
578        found := False;
579        for j := 0 to high(ParamNames) do if sametext(ParamNames[j],p) then
580          begin
581          found := True;
582          break;
583          end;
584        if not FancyTitle and SameText(ActionVar,p) then
585          found := true;
586        if not found then
587          qs := qs + p + '=' + ARequest.QueryFields.ValueFromIndex[i] + '&';
588        end;
589      end
590    else for i := 0 to high(KeepParams) do
591      begin
592      p := ARequest.QueryFields.Values[KeepParams[i]];
593      if p <> '' then
594        qs := qs + KeepParams[i] + '=' + p + '&';
595      end;
596    end;
597  for i := 0 to high(ParamNames) do
598    qs := qs + ParamNames[i] + '=' + ParamValues[i] + '&';
599
600  ConnectChar:='?';
601  if ScriptName='' then CGIScriptName:='.'
602  else
603    begin
604    CGIScriptName:=ScriptName;
605    if pos('?',ScriptName)>0 then ConnectChar := '&';
606    end;
607  if FancyTitle then // use ? or /
608    result := CGIScriptName + '/' + Result
609  else
610    begin
611    result := CGIScriptName + ConnectChar +ActionVar+'=' + Result;
612    ConnectChar:='&';
613    end;
614
615  p := copy(qs,1,length(qs)-1);
616  if p <> '' then
617    result := result + ConnectChar + p;
618  if assigned(OnGetURL) then
619    OnGetURL(ParamNames, ParamValues, KeepParams, Action, Result);
620end;
621
622procedure TStandardWebController.BindJavascriptCallstackToElement(AComponent: TComponent; AnElement: THtmlCustomElement; AnEvent: string);
623begin
624  if AnEvent='onclick' then
625    (AnElement as THTMLAttrsElement).onclick:=CurrentJavaScriptStack.GetScript
626  else if AnEvent='onchange' then
627    if AnElement is THTML_input then (AnElement as THTML_input).onchange:=CurrentJavaScriptStack.GetScript;
628end;
629
630procedure TStandardWebController.AddScriptFileReference(AScriptFile: String);
631begin
632  if FScriptFileReferences.IndexOf(AScriptFile)=-1 then
633    FScriptFileReferences.Add(AScriptFile);
634end;
635
636procedure TStandardWebController.AddStylesheetReference(Ahref, Amedia: String);
637begin
638  with FStyleSheetReferences.Add do
639    begin
640    href:=Ahref;
641    media:=Amedia;
642    end;
643end;
644
645initialization
646  RegisteredScriptList := nil;
647finalization
648  if assigned(RegisteredScriptList) then
649    RegisteredScriptList.Free;
650end.
651
652