pas2js/packages/rtl/Rtl.TemplateLoader.pas
2019-09-12 20:38:59 +00:00

276 lines
6.9 KiB
ObjectPascal

{
This file is part of the Pas2JS run time library.
Copyright (c) 2019 by Michael Van Canneyt
This unit implements a HTML template loader.
See the file COPYING.FPC, included in this distribution,
for details about the copyright.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
**********************************************************************}
unit Rtl.TemplateLoader;
{$mode objfpc}
interface
uses
Classes, SysUtils, JS, web;
Type
TFailData = Record
message : string;
code : Integer;
end;
TTemplateNotifyEvent = Reference to Procedure(Sender : TObject; Const aTemplate : String);
TTemplateErrorNotifyEvent = Reference to Procedure(Sender : TObject; Const aTemplate,aError : String; aErrorcode : Integer);
{ TCustomTemplateLoader }
TCustomTemplateLoader = Class(TComponent)
Private
FBaseURL: String;
FOnLoad: TTemplateNotifyEvent;
FOnLoadFail: TTemplateErrorNotifyEvent;
FTemplates : TJSObject;
function GetTemplate(aName : String): String;
procedure SetTemplate(aName : String; AValue: String);
Protected
// Process an url before it is used to fetch data
Function ProcessURL(const aURL : String) : String;
Public
Constructor Create (aOwner : TComponent); override;
Destructor Destroy; override;
// Remove a template
Procedure RemoveRemplate(aName : String);
// fetch a template using promise. Promise resolves to template name. On fail a TFailData record is passed on.
// Note that the global OnLoad/OnLoadFail a
Function FetchTemplate(Const aName,aURL : String) : TJSPromise;
// procedural API.
// If the aOnSuccess aOnFail event handlers are specified, they're called as well in addition to global handlers.
Procedure LoadTemplate(Const aName,aURL : String; aOnSuccess : TTemplateNotifyEvent = Nil; AOnFail : TTemplateErrorNotifyEvent= Nil);
// procedural API for multiple templates at once.
// Form = name, URL, name URL.
// If the aOnSuccess aOnFail event handlers are specified, they're called as well in addition to global handlers.
Procedure LoadTemplates(Const Templates : Array of String; aOnSuccess : TTemplateNotifyEvent = Nil; AOnFail : TTemplateErrorNotifyEvent= nil);
// URLs will be relative to this. Take care that you add a / at the end if needed !
Property BaseURL : String Read FBaseURL Write FBaseURl;
Property Templates[aName : String] : String Read GetTemplate Write SetTemplate; default;
// Called when a template was loaded.
Property OnLoad : TTemplateNotifyEvent Read FOnLoad Write FOnLoad;
// Called when a template failed to load.
Property OnLoadFail : TTemplateErrorNotifyEvent Read FOnLoadFail Write FOnLoadFail;
end;
TTemplateLoader = Class(TCustomTemplateLoader)
Published
Property BaseURL;
Property OnLoad;
Property OnLoadFail;
end;
// Global instance, for ease of use.
Function GlobalTemplates : TCustomTemplateLoader;
implementation
{ TCustomTemplateLoader }
Var
_loader : TCustomTemplateLoader;
Function GlobalTemplates : TCustomTemplateLoader;
begin
if _loader=Nil then
_loader:=TCustomTemplateLoader.Create(Nil);
Result:=_Loader;
end;
Type
{ TURLLoader }
TURLLoader = Class(TObject)
private
FLoader: TCustomTemplateLoader;
FName: String;
FURL: String;
procedure dofetch(resolve, reject: TJSPromiseResolver);
Public
Constructor Create(aLoader : TCustomTemplateLoader; aName,aURL : String);
Function fetch : TJSPromise;
Property Name : String Read FName;
Property URL : String Read FURL;
Property Loader : TCustomTemplateLoader Read FLoader;
end;
{ TURLLoader }
constructor TURLLoader.Create(aLoader: TCustomTemplateLoader; aName, aURL: String);
begin
FLoader:=aLoader;
FURL:=aURL;
FName:=aName;
end;
procedure TURLLoader.dofetch(resolve,reject : TJSPromiseResolver);
function doOK(response : JSValue) : JSValue;
var
Res : TJSResponse absolute response;
F : TFailData;
begin
If (Res.status<>200) then
begin
F.Message:=res.StatusText;
F.Code:=Res.Status;
Result:=Reject(F);
end
else
Res.text._then(
function (value : JSValue) : JSValue
begin
Loader.Templates[FName]:=String(Value);
if Assigned(Loader.FonLoad) then
Loader.FOnLoad(FLoader,Name);
Result:=Resolve(Name);
end
);
end;
function doFail(response : JSValue) : JSValue;
Var
F : TFailData;
begin
F.message:='unknown error';
F.code:=999;
Result:=Reject(F);
end;
begin
Window.Fetch(URl)._then(@DoOK).catch(@DoFail);
end;
function TURLLoader.fetch : TJSPromise;
begin
Result:=TJSPromise.New(@Dofetch)
end;
function TCustomTemplateLoader.GetTemplate(aName : String): String;
Var
V : jsValue;
begin
V:=FTemplates[LowerCase(aName)];
if isString(V) then
Result:=String(V)
else
Result:='';
end;
procedure TCustomTemplateLoader.SetTemplate(aName : String; AValue: String);
begin
FTemplates[LowerCase(aName)]:=AValue;
end;
function TCustomTemplateLoader.ProcessURL(const aURL: String): String;
Var
R : TJSRegexp;
begin
R:=TJSRegexp.New('^https?://|^/','i');
if R.Test(aURL) then
Result:=aURL
else
Result:=BaseURL+aURL;
end;
constructor TCustomTemplateLoader.Create(aOwner: TComponent);
begin
inherited Create(aOwner);
FTemplates:=TJSObject.New;
end;
destructor TCustomTemplateLoader.Destroy;
begin
FTemplates:=nil;
inherited Destroy;
end;
procedure TCustomTemplateLoader.RemoveRemplate(aName: String);
begin
jsDelete(FTemplates,Lowercase(aName));
end;
function TCustomTemplateLoader.FetchTemplate(const aName, aURL: String): TJSPromise;
begin
Result:=TURLLoader.Create(Self,aName,ProcessURL(aURL)).fetch;
end;
procedure TCustomTemplateLoader.LoadTemplate(const aName, aURL: String; aOnSuccess: TTemplateNotifyEvent;
AOnFail: TTemplateErrorNotifyEvent);
function doOK(aValue : JSValue) : JSValue;
begin
if Assigned(aOnSuccess) then
aOnSuccess(Self,aName);
Result:=nil;
end;
function doFail(aValue : JSValue) : JSValue;
Var
F : TFailData absolute aValue;
S : String;
C : Integer;
begin
S:=F.message;
C:=F.Code;
if Assigned(FonLoadFail) then
FOnLoadFail(Self,aName,S,C);
if Assigned(aOnFail) then
aOnFail(Self,aName,S,C);
Result:=nil;
end;
begin
FetchTemplate(aName,aURL)._then(@DoOK).catch(@doFail);
end;
procedure TCustomTemplateLoader.LoadTemplates(const Templates: array of String; aOnSuccess: TTemplateNotifyEvent;
AOnFail: TTemplateErrorNotifyEvent);
Var
I,L : Integer;
begin
L:=Length(Templates);
if (L mod 2)<>0 then
Raise Exception.CreateFmt('Number of arguments (%d) must be even',[L]);
I:=0;
While I<L do
begin
LoadTemplate(Templates[I],Templates[I+1],aOnsuccess,aOnFail);
Inc(I,2);
end;
end;
end.