fptemplate.txt 14 KB

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