Step 7 - Dynamic Editing with Templates

In the previous example I showed you how to create an HTML form and capture input directly. This is alright, except that in the previous example the form was completely static and stored in a .htm file. This works fine for blank forms you want to display, but is not so nice if you need to display dynamic data in the form.

Enter HTML Templates in Web Connection. Templates provide a mechanism for storing HTML content externally. So rather than writing HTML output in code we can embed content such as expressions (fields, variables, properties, function and method calls, UDF()s etc) into the templates. Let me demonstrate this point with two examples.

Let's start with a very simple example by once again redoing the customer list to support for editing records. To do this we'll change the SQL statement to add another column that has the Edit link. I'll also use a template to display the base page, into which I will then embed the wwShowCursor generated HTML list of customers:

FUNCTION CustomerList() lcCompany = Request.QUeryString("Company") IF EMPTY(lcCompany) lcCompany = Request.Form("Company") ENDIF *** Run a static query - this time with the Company parameter SELECT [<a href="ShowCustomer.wp?ID=] +TRANSFORM(pk) + [">] + Company + [</a>] as Company,; careof as Customer_Name, ; [<a href="EditCustomer.wp?ID=] +TRANSFORM(pk) + [">Edit</a>] as Action ; from tt_cust ; WHERE UPPER(company) = UPPER(lcCompany) ; INTO CURSOR TQuery ; ORDER BY Company loSC = CREATEOBJECT("wwShowCursor",Response) loSC.lAlternateRows = .T. loSC.cExtraTableTags = [style="font:normal normal 8pt Tahoma"] loSC.ShowCursor() *** Vars in template must be PRIVATE - they cannot be LOCAL!!! * PRIVATE pcCompany, pcCustomerList && not really required pcCompany = lcCompany pcCustomerList = loSC.GetOutput() *** This will cause rendering of a CustomerList.wp script file Response.ExpandTemplate( Request.GetPhysicalPath() ) RETURN ENDFUNC

Note that this time the company field is a dynamic HREF link to EditCustomer.wp and we're passing that request a parameter of our customer number that we want to edit. When you run this it should look like this:

Where is the HTML coming from here? It's not being generated in code, but rather comes from template HTML page that lives in the Web directory (it can live anywhere however). Response.ExpandTemplate() basically loads this template page and merges any expressions that are in scope into it. ExpandTemplate finds the template via a OS path like d:\inetput\wwwroot\webdemo\CustomerList.wp, which in this case is represented by Request.GetPhysicalPath(). GetPhysicalPath translates the current URL into a physical path which is the same as the one just shown. This works well as long as the Process method and external page name are the same which is not always the case especially if you use pages that are used in multiple places.

You can also do this:

Response.ExpandTemplate(Server.oConfig.oWebProcess.cHTMLPagePath + "CustomerList.wp")

which uses Web Connection's configuration settings from the INI file for the currently active Process class. oWebProcess is the config object for the project we created here and it's always 'o' plus the name of the Process class. Each of the properties of the class exist in the application's INI file in the WebProcess section in this case. HTMLPath will be set to d:\inetpub\wwwroot\webprocess\ and this value is retrieved from the Config object.

Hint:
At this point you'd normally create your own templates. But for this example, you can cheat and copy the templates from the html/wwDevregistry directory into your WebDemo project and work with these tempaltes to start with. They may require a little adjustment for some variables but overall they should be close.

So, once we know where to place the template how do we edit it? Any way you like to edit HTML. Use your favorite HTML editor or if you choose use Notepad or even the VFP editor. I like to use FrontPage for visual pages. Here's what this page looks like:

Notice that you can't see the customer list - that's because the customer list is embedded as an expression:

... Header stuff above
<h2>Customer List</h2>
<hr>
<form action="CustomerList.wp" method="POST" >
Company Name: 
<INPUT TYPE="INPUT" NAME="Company" VALUE="<%= pcCompany %>" SIZE="20">
<INPUT TYPE="SUBMIT" NAME="btnSubmit" VALUE="Search" SIZE="20"></form>
<%=  pcCustomerList %>
<p> 
... footer stuff below

The key to make this template work are the use of the <%= Expression %> tags. Here I'm simply embedding two variables pcCompany for the value of the search textbox and the actual HTML string of the customer list embedded into this page. Sweet and simple, yes? You now can visually design your HTML layout and generate the dynamic pieces into - a perfect way to isolate your HTML generation and Fox code cleanly.

Editing the data

This is a very basic example of template usage - it becomes much more useful once you start editing data. The following code shows the EditCustomer method that handles both display of the customer data as well as posting the data back to the server for editing:

FUNCTION EditCustomer()

lnPK = VAL(  Request.QUeryString("ID") )

*** Are we posting back?
IF !EMPTY(Request.Form("btnSubmit"))
   IF !USED("TT_CUST")
      USE TT_CUST IN 0
   ENDIF
   SELECT TT_Cust
   
   LOCATE FOR pk = lnPK
   IF !FOUND()
      THIS.ErrorMsg("Customer not available","Get it right, man!")
      RETURN
   ENDIF
   
   REPLACE Company WITH Request.Form("Company"),;
           Careof WITH Request.Form("Name"),;
           Phone WITH Request.Form("Phone"),;
           Email WITH REquest.Form("Email")

   THIS.ShowCustomer(lnPK)
   RETURN
ENDIF

*** Show Entry
SELECT Company,careof,Phone, Address,pk ;
    from TT_CUST ;
   WHERE PK = lnPK ;
   INTO CURSOR TQUery 

SCATTER NAME poQuery MEMO

Response.ExpandTemplate( Request.GetPhysicalPath() )   

Two things are important here for displaying the data. Notice the query that runs to retrieve the single customer record and the SCATTER NAME to create an object. Although this isn't necessary I like the idea of having an object in the HTML code as opposed to working with a cursor directly. Inside of the HTML template the template expressions are bound to:

<input name="Company" value="<%= poQuery.Company %>">

and so on. I could have used TQuery.Company to acces the fields directly as well. This mechanism provides a basic implementation of data binding that makes it real easy to expose data from your VFP code to the HTML page. You can see in FrontPage that each of the fields is bound to the HTML text box fields for display. Here's what this looks like in FrontPage.

When the form is POSTed back to the server it's posted back to the same EditCustomer.wp page/request. The code checks to see if we're posting back the form with:

IF !EMPTY(Request.Form("btnSubmit"))

and then loads the customer and updates the data by retrieving each of the form variable fields with Request.Form(). The code to do all of this is minimal, but keep in mind the error checking code is also missing here.

If your field names match the name of the HTML form variables the save code can be simplified quite a bit by using code like the following:

   LOCATE FOR pk = lnPK
   IF !FOUND()
      THIS.ErrorMsg("Customer not available","Get it right, man!")
      RETURN
   ENDIF
   
   SCATTER NAME oRec MEMO
   
   Request.FormVarsToObject(oRec)
   
   GATHER NAME oRec MEMO

FormVarsToObject looks at the current object and tries to find matching HTML form variables and retrieves that data into an object that you provide. This object can be a record object as above or it can be a custom business object (see the Step By Step with the wwBusiness Object topic for more info).

Directly accessing templates

Ok, you can use a small code module to fire your logic, but you can also put the code directly into the template page! Believe it or not you can take the code we put into our server process method and stick it directly into the template page. Templates support the concept of code blocks which can execute as the page is evaluated. Each block is executed top to bottom alongside the expressions.

Start by editing the WebProcess and renaming the EditCustomer method to xEditCustomer, so that the method is no longer accessible. Also add a SET STEP ON at the top of the renamed method so you can see when and if it fires.

Then add the following to the template above:

<html>
<body>
<br>
<%
PUBLIC pcErrorMsg, poQuery
pcErrorMsg = ""

lnPK= VAL(Request.QueryString("ID"))

IF !USED("TT_CUST")
   USE TT_CUST IN 0
ENDIF
   
SELE TT_CUST   

LOCATE FOR PK = lnPK
IF !FOUND()
   pcErrorMsg = "Invalid Customer. Please select a customer from the list")
ENDIF

SCATTER NAME poQUery MEMO
%>

Then run the request again.

Wow! It still works and no debugger popped up which means the code fired in the template page. Don't believe me? Add the following as the last line in the code block we just added:

RETURN "Hello from the editcust.wp code block"

Returning a value from a code block causes that value to be displayed in the HTML document and you'll see it when you re-run the request.

Tip:
I consider running long blocks of code in templates and scripts bad coding practice as it mixes the User Interface with the business logic. It's really much cleaner to use a Process method using ExpandTemplate to call out to the HTML page to perform the computational part (as shown in the first example on this page) and then leave just the display issues to the template page. With the ability to embed expressions that can represent fields, properties of objects and PRIVATE variables it's easy to configure everything you need from within the process code so that all you have to is embed expressions in template pages. Another reason code in templates is not a good idea is that you can't debug it - if code fails it will show an error in the page, but will not give you a clear indication of what code actually failed. There's also no way to step through the script code in debug mode. All of these issues add up to a best practice of using Process code as much as possible and using templates only to display the results.

Template Summary
Powerful, isn't it? You have the basic ability to add code to HTML pages without having to compile anything. Change some text upload the file and the changes are there immediately. No other files to update, no code to recompile. Easy.

But there's a downside. Script pages are not trivial to debug if something goes wrong. You can't step through the code if an error occurs and you get no error information beyond the code block which failed. So, for complex code I wouldn't recommend using this mechanism, but for simple things it works great.

Codeblocks are also slow because they are evaluated line by line using macro-expansion and the actual code block has to be parsed first so there's a fair amount of overhead. Code like the above is no problem - but looping constructs running through a lot of records will be slow and better left for external code maintained in your server.

Expression expansion on the other hand is fairly fast so <%= %> tags are efficient.




  Last Updated: 2/11/2008 | © West Wind Technologies, 2008