fptemplate.txt 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. REMARK:
  2. fptemplate.pp has been moved to fcl-base.
  3. fptemplate.pp
  4. implements template support
  5. Default behaviour:
  6. In the basic default version the TFPTemplate object can handle simple template
  7. tags ex. {templatetagname} and requires the replacement strings in a Values
  8. array before the parsing starts. An OnGetParam:TGetParamEvent event can be
  9. triggered if it is set, when a value is not found in the Values list.
  10. The template tag start and end delimiters can be set with the StartDelimiter
  11. and EndDelimiter properties (defaults are '{' and '}' for now).
  12. The parsing happens recursively so a replace text string can contain further
  13. tags in it.
  14. --------------------
  15. Recent improvements:
  16. With the recent improvements the template tag handling got more close to the
  17. traditional Delphi way of handling templates. The rest of this file is about
  18. these additions.
  19. By setting the AllowTagParams property to True this new parsing method will be
  20. activated and it is possible to pass parameters to the processing program from
  21. the template tags.
  22. Other than the two original StartDelimiter and EndDelimiter properties to
  23. specify the boundaries of a template tag, there are 3 more delimiters to
  24. define these parameters.
  25. ParamStartDelimiter (default is '[-')
  26. ParamEndDelimiter (default is '-]')
  27. ParamValueSeparator (default is '=')
  28. Some examples for tags with these above, StartDelimiter:='{+' and
  29. EndDelimiter:='+}'
  30. (the default '{' and '}' is not good when processing HTML templates with
  31. JavaScript in them):
  32. {+ATagHere+} //Tag name: ATagHere
  33. {+AnotherTagHere [-paramname1=paramvalue1-]+}
  34. <!--Tag name: AnotherTagHere ; Tag param name=paramname1;
  35. Tag param value=paramvalue1-->
  36. {+HereIsATagToo //Tag name=HereIsATagToo; with 3 paramname-paramvalue pairs
  37. [-param1=param1value-] //some text here to ignore
  38. //this text is ignored too
  39. [-param2=param2value which
  40. is multi line something
  41. text ending here
  42. -]
  43. [-param3=param3value-]
  44. +}
  45. If we want something close to the Delphi tag delimiters, we can set the
  46. StartDelimiter := '<#';
  47. EndDelimiter := '>';
  48. ParamStartDelimiter := ' ';
  49. ParamEndDelimiter := '"';
  50. ParamValueSeparator := '="';
  51. This allows the use of Dephi-like tags like these:
  52. <#input type="text" name="foo1" value="" caption="bar" checked="false">
  53. <#input type="RadioButton" name="foo2"
  54. value=""
  55. caption="bar" checked="false" >
  56. <#fieldvalue fieldname="FIRSTNAME">
  57. Of course, the above setting requires at least one space before the parameter
  58. names. Cannot just use tabs for example to separate them. Also, Delphi (and its
  59. emulation here) cannot handle any HTML code within the tag parameters because
  60. some might contain characters indicating tag-param-end or tag-end (see below
  61. to overcome this).
  62. When the tags are processed, for each tag a
  63. TReplaceTagEvent = Procedure(Sender : TObject; Const TagString : String;
  64. TagParams:TStringList; Out ReplaceText : String) Of Object;
  65. will be called with the parameters passed in TagParams:TStringList so it has
  66. to be assigned to such a procedure.
  67. Example:
  68. procedure TFPWebModule1.func1callRequest(Sender: TObject; ARequest: TRequest;
  69. AResponse: TResponse; var Handled: Boolean);
  70. var s:String;
  71. begin //Template:TFPTemplate is a property of the web module
  72. Template.FileName := 'pathtotemplate\mytemplate.html';
  73. Template.AllowTagParams := true;
  74. Template.StartDelimiter := '{+';
  75. Template.EndDelimiter := '+}';
  76. Template.OnReplaceTag := @func1callReplaceTag;
  77. s := Template.GetContent;
  78. //lets use some Delphi style tags too and re-run the parser
  79. Template.StartDelimiter := '<#';
  80. Template.EndDelimiter := '>';
  81. Template.ParamStartDelimiter := ' ';
  82. Template.ParamEndDelimiter := '"';
  83. Template.ParamValueSeparator := '="';
  84. Template.FileName := '';
  85. Template.Template := s;
  86. AResponse.Content := Template.GetContent;
  87. Handled := true;
  88. end;
  89. procedure TFPWebModule1.func1callReplaceTag(Sender: TObject; const TagString:
  90. String; TagParams: TStringList; Out ReplaceText: String);
  91. begin
  92. if AnsiCompareText(TagString, 'ATagHere') = 0 then
  93. begin
  94. ReplaceText := 'text to replace this tag, using the TagParams if needed';
  95. end else begin
  96. .
  97. .snip
  98. .
  99. //Not found value for tag -> TagString
  100. end;
  101. end;
  102. With these improvements it is easily possible to separate the web page design
  103. and the web server side programming.
  104. For example to generate a table record list the web designer can use the
  105. following Tag in a template:
  106. .
  107. .snip
  108. .
  109. <table class="beautify1"><tr class="beautify2"><td class="beautify3">
  110. {+REPORTRESULT
  111. [-HEADER=
  112. <table bordercolorlight="#6699CC" bordercolordark="#E1E1E1" class="Label">
  113. <tr class="Label" align=center bgcolor="#6699CC">
  114. <th><font color="white">~Column1</font></th>
  115. <th><font color="white">~Column2</font></th>
  116. </tr>
  117. -]
  118. [- ONEROW =
  119. <tr bgcolor="#F2F2F2" class="Label3" align="center">
  120. <td>~Column1Value</td><td>~Column2value</td>
  121. </tr>
  122. -]
  123. .
  124. .snip, and so on more parameters if needed
  125. .
  126. [- NOTFOUND=
  127. <tr class="Error"><td>There are no entries found.</td></tr>
  128. -]
  129. [-FOOTER=</table>-]
  130. +}
  131. </table>
  132. .
  133. .snip
  134. .
  135. I know, I know its ugly html progamming and who uses tables and font html tags
  136. nowadays, etc. ... but you get the idea.
  137. The OnReplaceTag event handler just need to replace the whole REPORTRESULT
  138. template tag with the ~Column1, ~Column2 for the HEADER parameter, and the
  139. ~Column1Value, ~Column2Value in the ONEROW parameter while looping through a
  140. sql query result set.
  141. Or if there is nothing to list, just use the NOTFOUND parameter as a replace
  142. text for the whole REPORTRESULT template tag.
  143. Sample code for the above template snippet (see below for simpler examples
  144. in the Step By Step list):
  145. procedure TFPWebModule1.func2callRequest(Sender: TObject; ARequest: TRequest;
  146. AResponse: TResponse; var Handled: Boolean);
  147. var s:String;
  148. begin //Template:TFPTemplate is a property of the web module
  149. Template.FileName := 'pathtotemplate\mytemplate.html';{template file with
  150. the above template tag -> REPORTRESULT}
  151. Template.AllowTagParams := true;
  152. Template.StartDelimiter := '{+';
  153. Template.EndDelimiter := '+}';
  154. Template.OnReplaceTag := @func2callReplaceTag;
  155. AResponse.Content := Template.GetContent;
  156. Handled := true;
  157. end;
  158. procedure TFPWebModule1.func2callReplaceTag(Sender: TObject; const TagString:
  159. String; TagParams: TStringList; Out ReplaceText: String);
  160. var
  161. header, footer, onerow, notfound:String;
  162. NoRecordsToShow, EndOfRecords:Boolean;
  163. begin//HTML template tag handling for an html template file
  164. //Replace the REPORTRESULT html tag using it's tag parameters
  165. if AnsiCompareText(TagString, 'REPORTRESULT') = 0 then
  166. begin
  167. //NoRecordsToShow could be something like SQL1.IsEmpty , etc.
  168. if NoRecordsToShow then
  169. begin //if there's nothing to list, just replace the whole tag with the
  170. //Not Found message that the template contains
  171. ReplaceText := TagParams.Values['NOTFOUND'];
  172. Exit;
  173. end;
  174. header := TagParams.Values['HEADER'];
  175. //insert header parameters
  176. //1st column title
  177. header := StringReplace(header, '~Column1', '1st column', []);
  178. //2nd column title
  179. header := StringReplace(header, '~Column2', '2nd column', []);
  180. ReplaceText := header;//done with the header (could have been looping
  181. //through table field names also)
  182. //insert the rows
  183. onerow := TagParams.Values['ONEROW'];//template for 1 row
  184. //loop through the rows, it could be someting like "while not SQL1.EOF do"
  185. while not EndOfRecords do
  186. begin
  187. ReplaceText := ReplaceText + StringReplace(StringReplace(onerow
  188. ,'~Column1Value', '$14.56', [])
  189. ,'~Column2Value', '$12.00', []);
  190. //get the next record, it could be:
  191. //SQL1.Next
  192. end;
  193. //insert the footer
  194. footer := TagParams.Values['FOOTER'];
  195. //replace footer parameters if needed
  196. //...
  197. ReplaceText := ReplaceText + footer;
  198. end else begin
  199. //Not found value for tag -> TagString
  200. ReplaceText := 'Template tag {' + TagString + '} is not implemented yet.';
  201. end;
  202. end;
  203. full example code at /lazarus/components/fpweb/demo/fptemplate/listrecords/
  204. ===============================================================================
  205. Step by Step:
  206. Creating CGI or Apache applications with WebModule in Lazarus, using HTML
  207. templates (FPTemplate)
  208. I. Hello World first
  209. II. Using templates
  210. III. More complicated HTML template design notes
  211. IV. Passing tag parameters
  212. ===============================================================================
  213. I. Hello World first:
  214. 1. File -> New -> CGI application or Apache module
  215. 2. Delete the httpd20 and httpd13 directories (we are making Apache 2.2 modules)
  216. from the fpc directory (ex: C:\pp\units\i386-win32\httpd20\ and
  217. C:\pp\units\i386-win32\httpd13\)
  218. Need to recompile FPC and then Lazarus if FPC was earlier compiled with these
  219. older httpd13 or httpd20 files.
  220. To avoid this recompilation you can also just copy all the files from the
  221. /packages/fcl-web/src/ directory into your project directory so they will
  222. be recompiled as needed.
  223. 3. Click inside the webmodule if not already selected
  224. 4. In the Object Inspector double click on the "Actions"
  225. 5. Click on +Add to create a new action for your web module
  226. 6. Change Default to True if you wish this one to be the default action
  227. 7. Change the action name to "func1call" (this will be the calling identifier
  228. of this action from the web browser. Something like
  229. http://localhost/mod_apache1/func1call?param1=... )
  230. 8. Inside the Events tab, double click on the "OnRequest" to create the
  231. procedure called "func1callRequest" that handles this action
  232. 9. Enter the following into the procedure body:
  233. procedure TFPWebModule1.func1callRequest(Sender: TObject; ARequest: TRequest;
  234. AResponse: TResponse; var Handled: Boolean);
  235. begin
  236. AResponse.Content := '<html><body>Hello World!</body></html>';
  237. Handled := true;
  238. end;
  239. 10. Save all, compile, configure the apache server to load the module:
  240. in your apache httpd.conf you can put
  241. LoadModule mod_apache1 "/<path to the mod>/mod_apache1.dll" #or mod_apache1.so
  242. <Location /myapache>
  243. SetHandler mod_apache1
  244. Order allow,deny
  245. Allow from all
  246. </Location>
  247. 11. Call your module action from your web browser
  248. ex: http://localhost/myapache/func1call?param1=paramvalue1
  249. 12. See "Hello World!" in your browser
  250. 13. Repeat from step 4 for other web actions
  251. full example code at /lazarus/components/fpweb/demo/fptemplate/helloworld/
  252. ===============================================================================
  253. II. Using templates:
  254. 1. Lets make a simple html template and save it as mytemplate1.html :
  255. <html>
  256. <body>
  257. This is a replaced template tag: {TagName1}
  258. </body>
  259. </html>
  260. 2. Save it and put it somewhere your apache module can access it (ex: below the
  261. apache module .dll or .so in a directory called "templates/")
  262. 3. Declare a procedure for your web module to handle the template tags
  263. private
  264. { private declarations }
  265. procedure func1callReplaceTag(Sender: TObject; const TagString:String;
  266. TagParams: TStringList; Out ReplaceText: String);
  267. 4. Create the body of the procedure
  268. procedure TFPWebModule1.func1callReplaceTag(Sender: TObject; const TagString:
  269. String; TagParams: TStringList; Out ReplaceText: String);
  270. begin
  271. if AnsiCompareText(TagString, 'TagName1') = 0 then
  272. begin
  273. ReplaceText := 'Here I am from the web module!';
  274. end else begin
  275. //Not found value for tag -> TagString
  276. ReplaceText := 'Template tag {' + TagString + '} is not implemented yet.';
  277. end;
  278. end;
  279. 5. In step 9 above in the fist example change the procedure body to:
  280. procedure TFPWebModule1.func1callRequest(Sender: TObject; ARequest: TRequest;
  281. AResponse: TResponse; var Handled: Boolean);
  282. begin //Template:TFPTemplate is a property of the web module
  283. Template.FileName := 'pathtotemplate/mytemplate1.html';
  284. Template.AllowTagParams := true;
  285. Template.OnReplaceTag := @func1callReplaceTag;
  286. AResponse.Content := Template.GetContent;
  287. Handled := true;
  288. end;
  289. 6. Compile, etc. and call it. Should show
  290. This is a replaced template tag: Here I am from the web module!
  291. full example code at /lazarus/components/fpweb/demo/fptemplate/simpletemplate/
  292. ===============================================================================
  293. III. More complicated HTML template design notes:
  294. 1. Template tag delimiters.
  295. Template.StartDelimiter := '{+';
  296. Template.EndDelimiter := '+}';
  297. should be used if there are { or } characters in the HTML template
  298. (ex: Javascript exist in the template)
  299. 2. For "same as Delphi" template tag handling, use
  300. Template.StartDelimiter := '<#';
  301. Template.EndDelimiter := '>';
  302. Template.ParamStartDelimiter := ' ';
  303. Template.ParamEndDelimiter := '"';
  304. Template.ParamValueSeparator := '="';
  305. ex: <#TagName1 param1="value1" param2="value2">
  306. ===============================================================================
  307. IV. Passing tag parameters:
  308. You can pass parameters to your CGI/Apache web module from the templates.
  309. Example HTML template tag:
  310. {+HereIsATag
  311. [-param1=param1value-] //some text here to ignore
  312. -]
  313. [-param3=param3value-]
  314. +}
  315. ex: {+DATETIME [-FORMAT=MM/DD hh:mm:ss-]+}
  316. Code:
  317. procedure TFPWebModule1.func1callRequest(Sender: TObject; ARequest: TRequest;
  318. AResponse: TResponse; var Handled: Boolean);
  319. var s:String;
  320. begin //Template:TFPTemplate is a property of the web module
  321. Template.FileName := 'pathtotemplate\templatename.html';
  322. Template.AllowTagParams := true;
  323. Template.StartDelimiter := '{+';
  324. Template.EndDelimiter := '+}';
  325. Template.OnReplaceTag := @func1callReplaceTag;
  326. AResponse.Content := Template.GetContent;
  327. Handled := true;
  328. end;
  329. procedure TFPWebModule1.func1callReplaceTag(Sender: TObject; const TagString:
  330. String; TagParams: TStringList; Out ReplaceText: String);
  331. begin
  332. if AnsiCompareText(TagString, 'DATETIME') = 0 then
  333. begin
  334. ReplaceText := FormatDateTime(TagParams.Values['FORMAT'], Now);
  335. end else begin
  336. //Not found value for tag -> TagString
  337. ReplaceText := 'Template tag {' + TagString + '} is not implemented yet.';
  338. end;
  339. end;
  340. For example, this way if the web designer changes the look of a page, - in this
  341. case the format of the date/time on the page - no changes are needed in the
  342. apache module code, therefore no recompiling or apache restart is needed. The
  343. best way is to make the project such, that the web/html design is separated
  344. from the back end apache module as much as possible.
  345. full example code at /lazarus/components/fpweb/demo/fptemplate/tagparam/
  346. ===============================================================================