Templates vs. Scripts

Web Connection supports two very similar mechanisms for 'scripting'. One uses templates and one uses scripts. The difference between the two are relatively subtle but the key points are:

Templates
Used with: Response.ExpandTemplate()

  • Are evaluated
  • Can be updated as text files and immediately reflect changes
  • Can only evaluate FoxPro expressions or CodeBlocks
  • Cannot use nested block code other then
  • Are fast to execute, especially if you only use expressions

Scripts
Used with: Response.ExpandScript()

  • Are (automatically) compiled as FoxPro code
  • Are turned into real PRG files that you can debug
  • Support complex logic including expressions, codeblocks and nested control flow blocks
  • Are more difficult to update in multi-instance applications due to FXP locking

In summary, templates work great if you don't have to execute code inside of your scripts. They are quick to execute, but most importantly you can update them and are guaranteed to see changes reflected immediately as soon as you update the file.

Scripts support the full gamut of FoxPro code, so you can create much more complex logic than with templates. If you need to build layout that loops over FoxPro data

but they are more complex to administer because the code has to be compiled on the server and FXP files have to be updated. And although Scripts run fully compiled FoxPro code, there's a bit of overhead involved in checking to ensure that compiled files exist and can execute. Scripts can also be compiled into an application optionally.

Scripting 101

Scripts are little programs that are based on VFP's TEXTMERGE mechanism. Web Connection converts the scripts you create into a real runnable PRG file that outputs into a TEXTMERGE file which is captured and returned to the Web browser.

Here's a simple example script page:

<html>
<body style="font:normal normal 10pt Verdana">
<h1>Customer Table Display</h1>
<hr>
<%
SELECT * FROM TT_CUST INTO Cursor TQuery 
lnReccount = RECCOUNT()
%>
Customer Table has <%= Reccount() %> records.<p>

<table border=1 style="font:normal normal 8pt Tahoma">
<%
SCAN
%>
<tr><td><%= TQuery.Company %></td><td><%= TQuery.Address %></td></tr>
<% ENDSCAN %>
</table>

</body></html>

To run this page save it to a .WCS (Web Connection Script) extension and call it via Web URL:

http://localhost/wconnect/CustomerTable.wcs

Notice that you use for blocks of script code that run like a program, and to output expressions that return a value. should only be used with a single expression while can host multiple commands (translated into TEXTMERGE terms are TextMerge expressions, while is considered pure code. Anything else (the static HTML) is what goes between TEXT...ENDTEXT directives).

If you have an error in your code the page will bring back an error. For example if I misspell Reccount() above as Recount() you get a Scripting Error Page that says:

Error:  File 'recount.prg' does not exist. 
Code: Recount() 
Error Number: 1 
Line No: 9 

If you're in debug mode (DEBUGMODE .T. in wconnect.h) Web Connection will also try to find the line of code that caused the error and position the cursor on it. This is not always possible due to the nature of the error, but in most cases and especially those were a simple typo occurred you can edit the change save and re-run the request immediately.

Fixing up the example
Let's change the example around a little more to allow a little more flexibility. Let's fix up the display by adding the WestWind stylesheet, clean up the table display and add another field (phone) and finally support for asking the user which records he wants to see:

<html>
<head>
<title>Customer Table Scripting Demo</title>
<LINK rel="stylesheet" type="text/css" href="westwind.css">
</head>
<body>
<h1>Customer Table Display</h1>
<hr>
<%
lcCompany = UPPER(Request.Form("txtCompany"))
lcWhere = ""
IF !EMPTY(lcCompany)
   lcWhere = "WHERE UPPER(company) = lcCompany"
ENDIF


SELECT * FROM TT_CUST &lcWhere ;
   INTO Cursor TQuery ;
   ORDER BY Company
   
lnReccount = RECCOUNT()
%>

<form method="POST" action="customertable.wcs">
Company Filter: <input type="text" name="txtCompany" value="<%= lcCompany %>">
<input type="Submit" value=" Get Customers">
</form>

<hr>
Selected  <b><%= Reccount() %></b> records.<p>

<table border="1">
<tr bgcolor="#EEEEEE"><th>Company</th><th>Address</th><th>Phone</th></tr>
<% SCAN %>
<tr>
   <td  valign="top"><%= TQuery.Company %></td>
    <td><%= IIF(EMPTY(TQuery.Address),[<br>],DisplayMemo(TQuery.Address)) %></td>
    <td><%= IIF(EMPTY(TQuery.Phone),[<br>],DisplayMemo(TQuery.Phone)) %></td>
</tr>
<% ENDSCAN %>
</table>
<hr>
<HR>
</body>
</html>

Note that this form has now become both an input and output form at the same time because we're using the form field (txtCompany) in a

tag to ask the user to enter the company filter for the list to create. On each request we then retrieve the user's input using the Request.Form method into a variable (which must be PRIVATE not LOCAL in order to be visible anywhere but in the current block) lcCompany. We use the variable to also echo it back in the edit box by using to set the VALUE= attribute of the txtCompany field. This way the field always displays the user's last choice.

Script Modes
Web Connection's scripting engine can use 2 modes to run scripts:

  • Interactive
    In interactive mode pages are run entirely through Randy Pearson's CodeBlock utility that manually parses and executes each line of code. As you might expect this is fairly slow. However, it does provide the ability to make changes to the pages on the fly and see the page changes immediately reflected on the site. In addition, you can pop up the VFP editor on code failures. CodeBlock performs well for non-looping pages, but can be real slow for pages that need to loop through large amounts of data. This is the default setting for Web Connection.

  • Compiled
    As the name implies compiled pages are real VFP programs that are executed at native VFP speed. They run much faster than interactive pages, but because they are compiled cannot be changed without recompiling. You can compile pages through the Web interface on the Admin.asp page.

Objects available in scripts
Scripts have the following Web Connection objects available:

  • Request
  • Response
  • Server
  • Process
  • Session These objects are the standard Web Connection objects and you can look up their properties and methods separately in the various class references.

For additional examples of how to use scripting see the scripting samples on the Web Connection Demo page.

Templates 101

Templates are similar to Scripts using very similar ASP like syntax with and tags. The main difference between scripts and templates is that templates are not programs but are evaluated. Think of Templates like fancy FoxPro TextMerge blocks.

Templates work best for displaying 'Views' - objects or values of data that was previous created and formatted in the code preceding the call to ExpandTemplate and you can then embed expressions into the page to display the view:

<div class="item">
    <div><%= TQuery.Company %>
    <div><%= TQuery.CareOf %>
    <hr />
     <div class="errordisplay">
         <%= pcErrorMsg %>
     </div>
</div>

Web Connection uses a set of MergeText functions to walk through the document top to bottom and evaluate each expression or codeblock. Each codeblock or expression is evaluated exactly once. You can embed code blocks like:

<%
  LOCAL lcOutput
  lcOutput =   "Hello World"
  lcOutput = lcOutput + ", Rick!"
  RETURN lcOutput   && rendered into the output
%>

The above works as it's a self contained block of code. Think of the block as an inline, but fully self-contained function that executes and can't directly pass values out of itself.

Unlike scripts you cannot do things like this:

<% SELECT TQuery
      SCAN %>
   Company: <%= Company %>
<% ENDSCAN %>

Splitting up structured statements into multiple code blocks is not supported in templates, with the exception of the IF/ENDIF. The following does work:

<% if (plIsActive) %>
    Balance: <% loCustomer.oData.Balance %>
<% endif %>

Note that the syntax must include spaces before the if and endif clauses! There's no else clause, but you can use multiple if statements to accomplish the same.

Templates work best with expressions rather than CodeBlocks. CodeBlocks must be compiled each time, while expressions are simply parsed and evaluated which is fairly efficient. If you find yourself with a bunch of code in a template, it's usually better to create a UDF or class method to handle that operation, rather than performing this logic in the template in a code block.

You can use any combination of expressions, UDF calls, object properties and methods and any PRIVATE variables that are in scope from the calling code.

<HTML>
<BODY>
<%
PUBLIC oCust
oCust = CREATE("cCustomer")
oCust.Load( VAL(Request.QueryString("PK")) )
%>

Welcome back, <%= oCust.cFirstName %>. Your credit limit is <%= oCust.GetCreditLimit() %>.

<hr>
Time created: <%= TIME() %>

<%
RELEASE oCust
%>
</BODY>
</HTML>

Notice that you can use code blocks, but because templates are not self-contained programs any variables that you want to use throughout the page must be declared as PUBLIC (oCust here). However, any regular expression can be accessed as needed.

Objects available in Templates
Templates have the following Web Connection objects available to them:

  • Request
  • Server
  • Process
  • Session
  • (Response) Note that the Response object is in parenthesis here! You can use the Response object, but only if and only if you use the llNoOutput flag on its various methods. Templates are evaluated into a string and that string is then sent to output all at once after parsing is complete, so using the Response object directly would cause output sent to go before any content in the actual template.

If you need to modify the behavior of the Response object such as changing the HTTP header you need to first clear the object, and then reassign the custom header:




test value

(note the use of the .T. parameter (llNoOutput) on the Response.Write method call! Most response method include this parameter)

If you do this frequently you should consider using Process class methods to handle the header.

oHeader = CREATE("wwHTTPHeader")
oHeader.DefaultHeader()
oHeader.AddCookie("TestCookie","Rick")

Response.ExpandTemplate(Request.GetPhysicalPath(), oHeader)

Scripts like templates can use expressions simply by embedding an expression into the HTML text with . Templates should use expressions as much as possible for optimal performance as these are simply evaluated on the fly. You can of course call UDF functions, as well as method of any class that's in scope.

**Tip: Editing .wcs and .wc files in FrontPage or Visual Interdev** By default these tools don't know about .wc and .wcs and won't allow you to edit these files as HTML files. In FrontPage you can select the file and right click and use the *Open With...* option to open the file manually. For a permanent mapping of the extensions to the FrontPage editor use the Tools|Options|Configure Editors dialog box to attach the extensions to the FrontPage editor. Simply copy the settings for the .HTM extension. In Visual Interdev you can open the file in the editor which will come up as an unknown (text) document at first. Close the file and then go to the Project Explorer pane and select the file still in the list. Right click and select *Open With...* and select the HTML Editor. The dialog that pops up also has a *Set As Default* button which creates a permanent mapping in VI to bring up the HTML editor for the templates.

FrontPage Themes and special Bot extensions can't be used with foreign extensions. However, you can fool FrontPage, but choosing a scriptmap other than WCS or WC that it does support. Since IIS uses a number of scriptmaps that you won't need (like the old IDC/HTC extensions) you can map script or templates to those instead. All of FrontPage's features will work then correctly. The same can be applied with custom scriptmaps you create.

How to call scripts and templates

Scripts and templates can be invoked in two ways:

  • Directly by using the scripting scriptmaps (.WC or .WCS)
  • Using the ExpandTemplate() and ExpandScript() methods of the Response object If you call scripts directly via scriptmap the pages must be fully self-contained and provide and handle all inputs and outputs.

But you can also run a Web Connection process method first and then from witin it call out to the script page. The advantage of this approach is that you can separate the user interface (the script page) from the business logic (the Process class method). In addition you can set up objects and create private variables that will also be in scope in the script page.

* Simple Process method that calls a template
Function CallTemplate
PRIVATE oCust


oCust = CREATE("cCust")
oCust.Load( VAL(Request.QueryString("PK")) )

... additional processing against customer object


*** Now display the template
Response.ExpandTemplate( "\inetpub\wwwroot\myapp\CallTemplate.wc" )

ENDFUNC

The template can now use the oCust object as part of the script as long as oCust was declared as PRIVATE (the default if you don't declare it).




Welcome back 

Feel free to shop around. Your current credit-limit is: 



Notice that any PRIVATE variables you declare will be in scope and can be called from the template or script. Any Classes or UDF functions that are in scope or the call stack also can be called directly.

Paths for ExpandTemplate and ExpandScript
Notice that in the example above I hard coded the path of the template into the call to ExpandTemplate. In general that's a very bad idea. Instead you should do one of two things:

  • Use a configuration setting for the path
    You can set up a configuration setting in the application INI file that points at the path. When you create a new Process class the Config object set up for your process automatically contains a cHTMLPath property which generally is set to the virtual directory you chose during setup. You can edit this value in the .ini file and by looking the [] section in that file. It should look like this:
[wwDemo]
datapath=wwDemo\
htmlpagepath=d:\inetpub\wwwroot\wconnect\

Once you've set this path you can access the template like this:

Response.ExpandTemplate( Server.oConfig.owwDemo.cHTMLPagePath + "CallTemplate.wc" )

Now if you end up moving the project, you simply change the setting in the INI file rather than changing code.

  • Using Scriptmaps for naming
    Web Connection has built in support for scriptmap naming of requests, so if you set up a new process it can automatically be assigned a script map of its own. Web Connection can automatically decide whether to call a Process method (if one exists) or template directly as part of the Process class's execution. If you do have a process method and you rename your script to use the same extension as the process class (in this case wwd for my wwDemo process) you can re-write the ExpandTemplate call as follows:
Response.ExpandTemplate( Request.GetPhysicalPath() )

GetPhysicalPath returns a mapped disk path to the scriptpage that may or may not exist based on the URL. IIS provides this info. If we called:

http://localhost/wconnect/CallTemplate.wwd

It would return:

d:\inetpub\wwwroot\wconnect\CallTemplate.wwd

Using this mechanism provides consistency in your applications, because if you do this you're always mapping a Process method to the template/script page, which makes it easy to correlate where the template is fired from.

**Note** Be careful with this however if you create pages that transfer control to other Process methods. GetPhysicalPath will be accurate only for the actual request method that maps to the URL, not any transferred method calls. So if CallMethod() call THIS.CallAnotherMethod() to perform tasks, and then it calls into GetPhysicalPath() it'll still return the path to CallMethod.wwd not CallAnotherMethod.wwd. If you do this a lot you'll have to use the Config pathing or use explicit filename overrides.

© West Wind Technologies, 1996-2019 • Updated: 08/14/17
Comment or report problem with topic