XML

Using the HTC to Create Business Services Components

If you know that the client machines you'll be working with have Internet Explorer 5 installed on them, you will be able to build extremely powerful Web applications for these client machines using HTC and DHTML. The example code we created in Chapter 13 performed only a few simple tasks, but the DHTML code in these examples was becoming fairly long and complex. If we want to add more functionality to our Web-based applications without making the code too long and complicated, we need to remove the DHTML code from the HTML page and place it in a separate component that can be referenced by an HTML page. This removal also makes the HTML document easier to read and update.

Let's take a look at how to extend the capabilities of our Web-based application from Chapter 13 so that it allows the user to edit and review the XML data. If we allow the user to change the values of the XML data, the new values will need to be validated. If the data is validated on the server, this validation can be done as each field is typed in or after all the data has been input. Performing server-side validation as data is input often creates time delays on the client as each field is validated. On the other hand, sending data to the server when all the information is input makes it difficult for the user to find the exact fields that were incorrect. The best solution is to create business services components that run on the client that can validate the data after it has been entered.

NOTE
If you validate data on the client, you would still have to validate the data on the server a second time for security reasons, as a malicious hacker could submit invalid data.

HTC enables Web developers to implement behaviors that expose properties, methods, and custom events. As you will see in the example later in this chapter, properties placed inside an HTC will allow us to perform validation whenever the value of a property is changed.

Using an HTC for our validation code offers us several advantages. To begin with, the code we will use to validate a particular XML document could be reused in every Web application that uses XML based on the same schema or DTD. Also, the HTC will be located on the client, so this validation will not require any trips to the server. Finally, if the validation rules change, only the HTC needs to be changed, not the HTML application that uses the HTC.

Before we create our example, let's look at the different elements that can be used in an HTC document. These elements are listed in the following table:

HTC Elements

Element Description
COMPONENT Defines the document as being an HTC document; is the root element of an HTC document (HTC can also be used.)
ATTACH Attaches an event in the source document to a function in the component
METHOD Defines a method that can be called by the source document
EVENT Defines an event that can be raised by the HTC
PROPERTY Defines a property that can be accessed by the source document
GET Defines the function that will be called when retrieving the value of a property
PARAMETER Defines a parameter for a function
PUT Defines the function that will be called when setting the value of a property

The example we will create saves the values of each of the fields in properties in an HTC. The put functions associated with the properties in the HTC will validate each new value. Thus, we will use the HTC to validate any changes to a field. If the change is invalid, we will reset the value of the field back to its original value by using the value that is currently in the property. We'll prefix the elements with the public namespace to identify the properties in the HTC as public properties. Public properties can be get or put by external documents. Let's begin by creating a document called ValidatePO.htc that includes the following code:

  <public:COMPONENT>
     <public:PROPERTY NAME="PartNo"  PUT="putPartNo"
     GET="getPartNo"/>
     <public:PROPERTY NAME="Quantity"  PUT="putQuantity"
     GET="getQuantity"/>
     <public:PROPERTY NAME="UOM"  PUT="putUOM" GET="getUOM"/>
     <public:PROPERTY NAME="UnitPrice"  PUT="putUnitPrice"
     GET="getUnitPrice"/>
     <public:PROPERTY NAME="Discount"  PUT="putDiscount"
     GET="getDiscount"/>
     <public:PROPERTY NAME="Total"  PUT="putTotal" GET="getTotal"/>
     <public:PROPERTY NAME="Error"  PUT="putError" GET="getError"/>
  <script language="VBScript">
     Dim strPartNo
     Dim strQuantity
     Dim strUOM
     Dim strDiscount
     Dim strTotal
     Dim strUnitPrice
     Dim strError
     Function putPartNo (newValue)
        If newValue = "" Then
           strError = "The part number is invalid."
        Else
           strPartNo = newValue
        End If
     End function
     Function putQuantity (newValue)
        If newValue = "" Then
           strError = "The quantity is invalid."
        Else
           If IsNumeric(newValue ) Then
              strQuantity = newValue
           Else
              strError = "The quantity must be numeric."
           End If
        End If
     End function
     Function putUnitPrice (newValue)
        If newValue = "" Then
           strError = "The unit price is invalid."
        Else
           If IsNumeric(newValue) Then
              strUnitPrice = newValue
           Else
              strError = "The unit price must be numeric."
           End If
        End If
     End function
     Function putUOM (newValue)
        If newValue = "" Then
           strError = "The UOM is invalid."
        Else
           If (LCase(newValue) = "each") or _
              (LCase(newValue) = "case") Then
              strUOM = newValue
           Else
              strError = "The UOM can only be each or case."
           End If
        End If
     End function
     Function putDiscount (newValue)
        If newValue = "" Then
           strError = "The discount is invalid."
        Else
           If IsNumeric (newValue) Then
              strDiscount = newValue
           Else
              strError = "The discount must be numeric."
           End If
        End If
     End function
     Function putTotal (newValue)
        If newValue = "" Then
           strError = "The total is invalid."
        Else
           If IsNumeric (newValue) Then
              strTotal = newValue
           Else
              StrError = "The total must be numeric."
           End If
        End If
     End function
     Function putError (newValue)
        StrError = newValue
     End function
     Function getPartNo ()
        getPartNo = strPartNo
     End function
     Function getQuantity ()
        getQuantity = strQuantity
     End function
     Function getUOM ()
        getUOM = strUOM
     End function
     Function getDiscount ()
        getDiscount = strDiscount
     End function
     Function getTotal ()
        getTotal = strTotal
     End function
     Function getUnitPrice ()
        getUnitPrice = strUnitPrice
     End function
     Function getError ()
        getError = strError
     End function
  </script>
  </public:COMPONENT>

As you can see, public properties are used in the document. The advantage of using properties is they allow us to validate values when a user attempts to change the value of the property. If the value is invalid, we do not allow the property's value to change and raise an error. In the case of the HTC above, we do not actually raise an error but instead set the error property to a string that explains the error.

The put functions in this code are used to check that the new values are valid. If they are not valid, the functions set strError to a value that explains the error. If the value is valid, the functions set a private variable to the value. Each put function is associated with the PUT attribute of the corresponding property. For example, the putPartNo function is associated with the PUT attribute of the PartNo property. When you assign a value to the PartNo property, the function putPartNo is called.

The get functions will be used when a certain value is set equal to the property. For example, when you use the partNo property, the getPartNo function will be called. PartNo will return the private variable. Although we do not perform any validation in the get functions, you can add validation to these functions, too.

In the source document that will use the HTC, we will not use data binding for our text boxes. Instead, we will use DHTML to give us full control of the form as we did in the example in >Chapter 13. We will write script to fill the text boxes with user input, to move through the records, and to set and get the properties in the HTC document. Here is the first part of the HTML document, showing the FillText function and the ondatasetcomplete event:

  <html>
  <head>
  <xml src="NorthwindPO2.xml" id="NorthwindDSO"></xml>
  <style type="text/css">
     .FieldName  {font-family:Arial,sans-serif; font-size:12px;
                  font-weight:normal}
     .POValidate {behavior:url (ValidatePO.htc);}
  </style>
  <script language="JScript">
  function FillText()
     {
     txtPartNo.value=NorthwindDSO.recordset.fields("partno").value;
     txtQuantity.value=NorthwindDSO.recordset.fields("qty").value;
     txtUOM.value=NorthwindDSO.recordset.fields("uom").value;
     txtDiscount.value=NorthwindDSO.recordset.fields
        ("discount").value;
     txtUnitPrice.value=NorthwindDSO.recordset.fields
        ("unitPrice").value;
     txtTotal.value=NorthwindDSO.recordset.fields
        ("totalAmount").value;
     }
  function NorthwindDSO.ondatasetcomplete()
     {
     htcSpan.PartNo=NorthwindDSO.recordset.fields("partno").value;
     if (!htcSpan.Error=="")
        {
        alert(htcSpan.Error);
        }
     htcSpan.Quantity=NorthwindDSO.recordset.fields("qty").value;
     if (!htcSpan.Error=="")
        {
        alert(htcSpan.Error);
        }
     htcSpan.UOM=NorthwindDSO.recordset.fields("uom").value;
     if (!htcSpan.Error=="")
        {
        alert(htcSpan.Error);
        }
     htcSpan.UnitPrice=NorthwindDSO.recordset.fields
        ("unitPrice").value;
     if (!htcSpan.Error=="")
        {
        alert(htcSpan.Error);
        }
     htcSpan.Discount=NorthwindDSO.recordset.fields
        ("discount").value;
     if (!htcSpan.Error=="")
        {
        alert(htcSpan.Error);
        }
     htcSpan.Total=NorthwindDSO.recordset.fields
        ("totalAmount").value;
     if (!htcSpan.Error=="")
        {
        alert(htcSpan.Error);
        }
        htcSpan.Error="";
        FillText();
     }

The FillText function is used to place the values of the recordset into the textboxes. It is called when the data has been loaded and when the user moves to another row. The DSO object raises the ondatasetcomplete event when all the data has arrived on the client. When this event is raised, we first set all the properties equal to the field values. Because we are setting the properties, the put functions in the HTC are used. As mentioned previously, the put functions also validate the new values. If there is an error, the property isn't set to the new value and the user gets an error message. This code doesn't fix the error, it only alerts the user to the problem.

To use the HTC document, we need to bind it to a tag on the form. We use the span tag called htcSpan to bind the HTC document. The htcSpan is bound by setting its class attribute to POValidate, the style linked to the HTC document. Once the span tag is bound to the HTC document, it inherits the properties and methods contained in the HTC document. Thus, the properties can be referenced by using the name for the tag followed by a dot followed by the name of the property. For example, htcSpan.PartNo refers to the PartNo property in the document.

In the second part of the HTML document, we'll use functions that will be associated with the onBlur event of all the text boxes. When the user tabs out of a text box, the onBlur event will be called. If the value in the text box is different from the value of the field in the recordset, the user has changed the value. If the value has been changed, we will set the field to this new value. Changing the value of the field will result in the oncellchange event being raised by the DSO object. We will validate the change in the oncellchange event.

  function partNoChange()
     {
     if(NorthwindDSO.recordset.fields("partno").value!=
        txtPartNo.value)
        {
        NorthwindDSO.recordset.fields("partno").value=
           txtPartNo.value;
        }
     }
  function QuantityChange()
     {
     if (NorthwindDSO.recordset.fields("qty").value!=
        txtQuantity.value)
        {
        NorthwindDSO.recordset.fields("qty").value=
           txtQuantity.value;
        }
     }
  function UOMChange()
     {
     if (NorthwindDSO.recordset.fields("uom").value!=txtUOM.value)
        {
        NorthwindDSO.recordset.fields("uom").value=txtUOM.value;
        }
     }
  function UnitPriceChange()
     {
     if (NorthwindDSO.recordset.fields("unitPrice").value!=
        txtUnitPrice.value)
        {
        NorthwindDSO.recordset.fields("unitPrice").value=
           txtUnitPrice.value;
        }
     }
  function DiscountChange()
     {
     if (NorthwindDSO.recordset.fields("discount").value!=
        txtDiscount.value)
        {
        NorthwindDSO.recordset.fields("discount").value=
           txtDiscount.value;
        }
     }
  function TotalChange()
     {
     if (NorthwindDSO.recordset.fields("totalAmount").value!=
        txtTotal.value)
        {
        NorthwindDSO.recordset.fields("totalAmount").value=
           txtTotal.value;
        }
     }

The third part of the HTML document shows how to use the oncellchange event that is fired whenever a field is changed. We will use the dataFld property of the event object to get the name of the field that has changed. Using a switch statement, we will find which field has been changed and set the property for that field equal to the new value. If the change is valid, the property will be set to the new value. If the value is not valid, the property will not be changed and the error property will be set to a string describing the error. If the change is invalid, we will also reset the value in the text box to the original value that is still stored in the property and then use the select method of the text box to move focus back to the field that was in error.

  function NorthwindDSO.oncellchange()
     {
     switch (event.dataFld)
        {
        case "partno":
           htcSpan.PartNo=txtPartNo.value;
           if (!htcSpan.Error=="")
              {
              txtPartNo.value=htcSpan.PartNo;
              txtPartNo.select();
              }
        break;
        case "qty":
           htcSpan.Quantity=txtQuantity.value;
           if (!htcSpan.Error=="")
              {
              txtQuantity.value=htcSpan.Quantity;
              txtQuantity.select();
              }
        break;
        case "uom":
           htcSpan.UOM=txtUOM.value;
           if (!htcSpan.Error=="")
              {
              txtUOM.value=htcSpan.UOM;
              txtUOM.select();
              }
        break;
        case "unitPrice":
           htcSpan.UnitPrice=txtUnitPrice.value;
           if (!htcSpan.Error=="")
              {
              txtUnitPrice.value=htcSpan.UnitPrice;
              txtUnitPrice.select();
              }
        break;
        case "discount":
           htcSpan.Discount=txtDiscount.value;
           if (!htcSpan.Error=="")
              {
              txtDiscount.value=htcSpan.Discount;
              txtDiscount.select();
              }
        break;
        case "totalAmount":
           htcSpan.Total=txtTotal.value;
           if (!htcSpan.Error=="")
              {
              txtTotal.value=htcSpan.Total;
              txtTotal.select();
              }
               break;
        default:
           htcSpan.Error = "Invalid element text";
        }
        if (!htcSpan.Error=="")
           {
           alert (htcSpan.Error);
           htcSpan.Error="";
           }
     }

The last part of the HTML document shows the move functions and the rest of the HTML code. We will code the move functions using JScript instead of using VBScript as we did in Chapter 13 so that we can see how to code in both scripting languages. First the DSO object is moved, and then the FillText function is called to set the values in the text boxes.

  function MoveNext()
     {
     NorthwindDSO.recordset.moveNext();
     if (NorthwindDSO.recordset.eof)
        {
        NorthwindDSO.recordset.moveFirst();
        }
     FillText();
     }
  function MovePrevious()
     {
     NorthwindDSO.recordset.movePrevious();
     if (NorthwindDSO.recordset.bof)
        {
        NorthwindDSO.recordset.MoveLast();
        }
     FillText();
     }
  function MoveLast()
     {
     if (!NorthwindDSO.recordset.eof && !NorthwindDSO.recordset.bof)
        {
        NorthwindDSO.recordset.moveLast();
        FillText();
        }
     }
  function MoveFirst()
     {
     if (!NorthwindDSO.recordset.eof && !NorthwindDSO.recordset.bof)
        {
        NorthwindDSO.recordset.moveFirst();
        FillText();
        }
     }
  </script>
  </head>
  <body>
     <!--This is the span element that gives the reference to the
        HTC component.-->
        <span id="htcSpan" class="POValidate"> </span>
        <!--We place the values in a table to make them look
           neater.-->
        <table  cellpadding="5">
        <tr>
           <td>
           <div class="FieldName">Part No</div>
           </td>
           <td>
           <!--The onBlur event is associated with the partNoChange
              listed above.-->
           <div class="FieldName"><input type="text" id="txtPartNo"
              onBlur="partNoChange()"></div>
           </td>
        </tr>
        <tr>
           <td>
           <div class="FieldName">Quantity</div>
           </td>
           <td>
           <div class="FieldName"><input id=txtQuantity
              name=txtQuantity onBlur="QuantityChange()"></div>
           </td>
        </tr>
        <tr>
           <td>
           <div class="FieldName">UOM</div>
           </td>
           <td>
           <div class="FieldName"><input id=txtUOM name=txtUOM onBlur="UOMChange()"></div>
           </td>
        </tr>
        <tr>
           <td>
           <div class="FieldName">Unit Price</div>
           </td>
           <td>
           <div class="FieldName"><input id=txtUnitPrice
              name=txtUnitPrice onBlur="UnitPriceChange()"></div>
           </td>
        </tr>
        <tr>
           <td>
           <div class="FieldName">Discount</div>
           </td>
           <td>
           <div class="FieldName"><input id=txtDiscount
              name=txtDiscount onBlur="DiscountChange()"></div>
           </td>
        </tr>
        <tr>
           <td>
           <div class="FieldName">Total</div>
           </td>
           <td>
           <div class="FieldName"><input id=txtTotal name=txtTotal
              onBlur="TotalChange()"></div>
           </td>
        </tr>
        </table>
        <!--The buttons are also placed in a table so that
           they appear neater in the document.-->
        <table>
        <td><input id=cmdMoveNext name=cmdMoveNext type=button
           value="Move Next" onClick="MoveNext()"></input></td>
        <td><input id=cmdMovePrevious name=cmdMovePrevious
           type=button value="Move Previous"
           onClick="MovePrevious()"></input></td>
        <td><input id=cmdMoveFirst name=cmdMoveFirst type=button
           value="Move First" onClick="MoveFirst()"></input></td>
        <td><input id=cmdMoveLast name=cmdMoveNext type=button
           value="Move Last" onClick="MoveLast()"></input></td>
        </table>
  </body>
  </html>