Rtl.TemplateLoader.pas 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. {
  2. This file is part of the Pas2JS run time library.
  3. Copyright (c) 2019 by Michael Van Canneyt
  4. This unit implements a HTML template loader.
  5. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************}
  11. unit Rtl.TemplateLoader;
  12. {$mode objfpc}
  13. interface
  14. uses
  15. Classes, SysUtils, JS, web;
  16. Type
  17. TFailData = Record
  18. message : string;
  19. code : Integer;
  20. end;
  21. TTemplateNotifyEvent = Reference to Procedure(Sender : TObject; Const aTemplate : String);
  22. TTemplateErrorNotifyEvent = Reference to Procedure(Sender : TObject; Const aTemplate,aError : String; aErrorcode : Integer);
  23. { TCustomTemplateLoader }
  24. TCustomTemplateLoader = Class(TComponent)
  25. Private
  26. FBaseURL: String;
  27. FOnLoad: TTemplateNotifyEvent;
  28. FOnLoadFail: TTemplateErrorNotifyEvent;
  29. FTemplates : TJSObject;
  30. function GetTemplate(aName : String): String;
  31. procedure SetTemplate(aName : String; AValue: String);
  32. Protected
  33. // Process an url before it is used to fetch data
  34. Function ProcessURL(const aURL : String) : String;
  35. Public
  36. Constructor Create (aOwner : TComponent); override;
  37. Destructor Destroy; override;
  38. // Remove a template
  39. Procedure RemoveRemplate(aName : String);
  40. // fetch a template using promise. Promise resolves to template name. On fail a TFailData record is passed on.
  41. // Note that the global OnLoad/OnLoadFail a
  42. Function FetchTemplate(Const aName,aURL : String) : TJSPromise;
  43. // procedural API.
  44. // If the aOnSuccess aOnFail event handlers are specified, they're called as well in addition to global handlers.
  45. Procedure LoadTemplate(Const aName,aURL : String; aOnSuccess : TTemplateNotifyEvent = Nil; AOnFail : TTemplateErrorNotifyEvent= Nil);
  46. // procedural API for multiple templates at once.
  47. // Form = name, URL, name URL.
  48. // If the aOnSuccess aOnFail event handlers are specified, they're called as well in addition to global handlers.
  49. Procedure LoadTemplates(Const Templates : Array of String; aOnSuccess : TTemplateNotifyEvent = Nil; AOnFail : TTemplateErrorNotifyEvent= nil);
  50. // URLs will be relative to this. Take care that you add a / at the end if needed !
  51. Property BaseURL : String Read FBaseURL Write FBaseURl;
  52. Property Templates[aName : String] : String Read GetTemplate Write SetTemplate; default;
  53. // Called when a template was loaded.
  54. Property OnLoad : TTemplateNotifyEvent Read FOnLoad Write FOnLoad;
  55. // Called when a template failed to load.
  56. Property OnLoadFail : TTemplateErrorNotifyEvent Read FOnLoadFail Write FOnLoadFail;
  57. end;
  58. TTemplateLoader = Class(TCustomTemplateLoader)
  59. Published
  60. Property BaseURL;
  61. Property OnLoad;
  62. Property OnLoadFail;
  63. end;
  64. // Global instance, for ease of use.
  65. Function GlobalTemplates : TCustomTemplateLoader;
  66. implementation
  67. { TCustomTemplateLoader }
  68. Var
  69. _loader : TCustomTemplateLoader;
  70. Function GlobalTemplates : TCustomTemplateLoader;
  71. begin
  72. if _loader=Nil then
  73. _loader:=TCustomTemplateLoader.Create(Nil);
  74. Result:=_Loader;
  75. end;
  76. Type
  77. { TURLLoader }
  78. TURLLoader = Class(TObject)
  79. private
  80. FLoader: TCustomTemplateLoader;
  81. FName: String;
  82. FURL: String;
  83. procedure dofetch(resolve, reject: TJSPromiseResolver);
  84. Public
  85. Constructor Create(aLoader : TCustomTemplateLoader; aName,aURL : String);
  86. Function fetch : TJSPromise;
  87. Property Name : String Read FName;
  88. Property URL : String Read FURL;
  89. Property Loader : TCustomTemplateLoader Read FLoader;
  90. end;
  91. { TURLLoader }
  92. constructor TURLLoader.Create(aLoader: TCustomTemplateLoader; aName, aURL: String);
  93. begin
  94. FLoader:=aLoader;
  95. FURL:=aURL;
  96. FName:=aName;
  97. end;
  98. procedure TURLLoader.dofetch(resolve,reject : TJSPromiseResolver);
  99. function doOK(response : JSValue) : JSValue;
  100. var
  101. Res : TJSResponse absolute response;
  102. F : TFailData;
  103. begin
  104. If (Res.status<>200) then
  105. begin
  106. F.Message:=res.StatusText;
  107. F.Code:=Res.Status;
  108. Result:=Reject(F);
  109. end
  110. else
  111. Res.text._then(
  112. function (value : JSValue) : JSValue
  113. begin
  114. Loader.Templates[FName]:=String(Value);
  115. if Assigned(Loader.FonLoad) then
  116. Loader.FOnLoad(FLoader,Name);
  117. Result:=Resolve(Name);
  118. end
  119. );
  120. end;
  121. function doFail(response : JSValue) : JSValue;
  122. Var
  123. F : TFailData;
  124. begin
  125. F.message:='unknown error';
  126. F.code:=999;
  127. Result:=Reject(F);
  128. end;
  129. begin
  130. Window.Fetch(URl)._then(@DoOK).catch(@DoFail);
  131. end;
  132. function TURLLoader.fetch : TJSPromise;
  133. begin
  134. Result:=TJSPromise.New(@Dofetch)
  135. end;
  136. function TCustomTemplateLoader.GetTemplate(aName : String): String;
  137. Var
  138. V : jsValue;
  139. begin
  140. V:=FTemplates[LowerCase(aName)];
  141. if isString(V) then
  142. Result:=String(V)
  143. else
  144. Result:='';
  145. end;
  146. procedure TCustomTemplateLoader.SetTemplate(aName : String; AValue: String);
  147. begin
  148. FTemplates[LowerCase(aName)]:=AValue;
  149. end;
  150. function TCustomTemplateLoader.ProcessURL(const aURL: String): String;
  151. Var
  152. R : TJSRegexp;
  153. begin
  154. R:=TJSRegexp.New('^https?://|^/','i');
  155. if R.Test(aURL) then
  156. Result:=aURL
  157. else
  158. Result:=BaseURL+aURL;
  159. end;
  160. constructor TCustomTemplateLoader.Create(aOwner: TComponent);
  161. begin
  162. inherited Create(aOwner);
  163. FTemplates:=TJSObject.New;
  164. end;
  165. destructor TCustomTemplateLoader.Destroy;
  166. begin
  167. FTemplates:=nil;
  168. inherited Destroy;
  169. end;
  170. procedure TCustomTemplateLoader.RemoveRemplate(aName: String);
  171. begin
  172. jsDelete(FTemplates,Lowercase(aName));
  173. end;
  174. function TCustomTemplateLoader.FetchTemplate(const aName, aURL: String): TJSPromise;
  175. begin
  176. Result:=TURLLoader.Create(Self,aName,ProcessURL(aURL)).fetch;
  177. end;
  178. procedure TCustomTemplateLoader.LoadTemplate(const aName, aURL: String; aOnSuccess: TTemplateNotifyEvent;
  179. AOnFail: TTemplateErrorNotifyEvent);
  180. function doOK(aValue : JSValue) : JSValue;
  181. begin
  182. if Assigned(aOnSuccess) then
  183. aOnSuccess(Self,aName);
  184. Result:=nil;
  185. end;
  186. function doFail(aValue : JSValue) : JSValue;
  187. Var
  188. F : TFailData absolute aValue;
  189. S : String;
  190. C : Integer;
  191. begin
  192. S:=F.message;
  193. C:=F.Code;
  194. if Assigned(FonLoadFail) then
  195. FOnLoadFail(Self,aName,S,C);
  196. if Assigned(aOnFail) then
  197. aOnFail(Self,aName,S,C);
  198. Result:=nil;
  199. end;
  200. begin
  201. FetchTemplate(aName,aURL)._then(@DoOK).catch(@doFail);
  202. end;
  203. procedure TCustomTemplateLoader.LoadTemplates(const Templates: array of String; aOnSuccess: TTemplateNotifyEvent;
  204. AOnFail: TTemplateErrorNotifyEvent);
  205. Var
  206. I,L : Integer;
  207. begin
  208. L:=Length(Templates);
  209. if (L mod 2)<>0 then
  210. Raise Exception.CreateFmt('Number of arguments (%d) must be even',[L]);
  211. I:=0;
  212. While I<L do
  213. begin
  214. LoadTemplate(Templates[I],Templates[I+1],aOnsuccess,aOnFail);
  215. Inc(I,2);
  216. end;
  217. end;
  218. end.