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