[ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ]

Figure 4 - The same URL with the XML parameter to render the object as an XML document

Now, when the data is returned, the HTML template is not called, and the data is generated using wwXML's ObjectToXML method instead:

    FUNCTION Item 
    LOCAL loItem 
    PRIVATE oItem, pcSKU 

    loItem = CREATE([cItem]) 
    pcSKU = UPPER(Request.QueryString("Sku")) 

    IF EMPTY(pcSKU) OR ; 

      THIS.ErrorMsg("Invalid Item") 

    oItem = loItem.oData 

    IF THIS.nResultMode = 2 

      loXML = CREATE("wwXML") 
      loXML.cDocRootName = "wwstore" 
      lcXML = loXML.ObjectToXML(oItem,"item") 


*** Call External ASP-like Template
Response.ExpandTemplate( Request.GetPhysicalPath() )

Everything runs through the cItem business object. The decision to generate XML is set in the nResultMode flag, and is checked in the startup of the of the Process object, which handles the Web request (in the Process method):

*** Support XML retrieval of data on select request
THIS.nResultMode = IIF(Request.QueryString("Display") = "XML",2,0)

nResultmode is then forwarded to all request methods. If it's set to 2, wwXML is used to create XML from the object. Otherwise, HTML is requested and the ExpandTemplate method of the Response object is used to evaluate the HTML template containing the <% %> script tags. If you used ASP instead of Web Connection, this business logic, as well as the check for the display mode, would be embedded inside the ASP page or a COM component that returns the result either in HTML or XML formats.

The wwXML syntax is very simple: You create the object and call the ObjectToXML method. The XML generated is shown in Figure 3. Note that we decided to return only the oData member (oItem = loItem.oData) rather than the entire item object, since the main object's (the generic business object properties) data is not appropriate here. 

If I want to retrieve this data from a VFP client application that has this item object available, I can now run the following:


    loItem = CREATE("cItem") 
    loItem.GetBlankRecord() && Init oData member 


    ? loItem.oData.Descript ? 

Note that I pass loItem.oData as a second parameter to XMLToObject, as the object that is to be populated with the data from the XML. The passed object is used as a template for pulling the data from the XML, by walking through the object properties and looking for matching elements in the XML document. You can also optionally have the server side generate a DTD and let the client side assemble a generic container object that only contains the object's properties. This may be useful in some scenarios, but most commonly an object that receives the incoming data will likely exist on the client application.

Pretty slick isn't it? With a handful of lines of code, you can persist an object from the server down to a client very, very easily! Note also that the XML is version-independent - the client application determines which data is used, so that if the server decides to add a new fields it won't break our client application. We've essentially decoupled the client and server through the intermediary XML. You can swap either end of the transaction with a different client or server and the other side would never know the difference!

Complex objects and hierarchical XML

If I take object persistence a step further, I can have more complex objects that combine multiple objects together with object composition. For example, this same Web Store application has an invoice object that has its own oData member containing the invoice header information, and also contains an oCustomer and oLineItems object for the detail info. When I load an invoice object, I can now access each of the subobjects through the invoice object, since it internally manages the subobjects as part of the load operation:

    loInv = CREATEOBJECT("cInvoice") 
    loInv.Load( 100 ) && Invoice PK 
    ? loInv.oData.InvDate && Invoice Data 
    ? loInv.oData.InvTotal 

    ? loInv.oCustomer.oData.LastName && Customer Data 
    ? loInv.oCustomer.oData.Company 

    ? loInv.oLineItems.aRows[1].SKU && LineItem Data 
    ? loInv.oLineItems.aRows[1].Description 
    ? loInv.oLineItems.aRows[1].ItemTotal

The Invoice object has its own oData member, as does the oCustomer member object. loInv is simply a container that holds this object. The oLineItems member contains all of the lineitem detail in an object that has a member array of all the lineitem objects containing the actual lineitem data, which again simply maps to fields in the lineitems table.

Exporting an object into XML on the server, then downloading the XML and reassembling it on the client, becomes a matter of only a few lines of code

This object is very portable! You can take the entire object and simply pass it as a reference to a method or function, and let that code access all of the properties. The calling application (in this case an online e-Commerce app and an offline invoice manager application using the same object) can simply use the business object methods such as Load, Save, and New to manage the object.

Because of this structured object approach, it's also possible to use this object on a Web page in an HTML template. In my application, template pages use syntax like:

    <%= oInv.oCustomer.oData.LastName %>

to embed the actual data into the HTML form as shown in Figure 5. Note that all data in this HTML form is served from this single compound object.

Figure 5 - The invoice business object displayed in HTML format. All the elements displayed on this HTML form are using template expansion with ASP syntax to render the business object properties.

On the server side, the invoice is rendered either in HTML format or in XML format, according to the QueryString display mechanism discussed earlier. There's one difference, though: This object is considerably more complex and requires a hierarchical XML document. To generate the XML on the server the following code is used: 

    lnPK = VAL( Request.QueryString("PK") ) 
    IF lnPK = 0 
      THIS.ErrorMsg("Invalid PK") 

    loInv = CREATE("cInvoice") 
    IF !loInv.Load(lnPK) 

      THIS.ErrorMsg("Invalid Invoice") 
    loXML = CREATE("wwXML") 
    loXML.lRecurseObjects = .T. 
    loXML.cDocRootName = "wwstore" 
    lcXML = loXML.ObjectToXML(loInv,"invoice") 


Note the lRecurseObjects property, which is used to make sure that all the surrogate objects are also created into the following XML:

    <?xml version="1.0"?>
                <address>32 Kaiea Place</address>
                <company>West Wind Technologies</company>
             <cc>4123 1234 1234 1231</cc>
                   <descript>Web Connection</descript>
                   <descript>HTML Help Builder</descript>


This time around, the object is generated with all of the base properties of the business objects - we get properties that are not required to be transferred over the wire, such as the data filename, the Alias, the error message property, and so on. These are base business object properties, which although superfluous, don't hurt anything (other than making the result a little bigger). Incidentally, you can hide these properties during export by using the wwXML::cPropertyExclusions property.

Take a moment to think about how powerful this is: With a couple of lines of code you've generically persisted a rather complex and nested business object to XML! You can now transfer this object over the wire and - you guessed it - reassemble it on the client side just as easily. In fact, the Web store application includes an offline invoice manager, which retrieves orders placed online. The offline application can then be used to process credit card transactions (also using HTTP and XML) and send order confirmations to customers.

[ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ]