Steve Kirk
MSDN Content Development Group
November 20, 1997
Click to view or copy the sample files associated with this technical article.
In this article I'll develop a transaction-processing client application in Dynamic HTML (DHTML) for Internet Explorer 4.0. DHTML and Internet Explorer 4.0 provide an enhanced client-programming model that can simplify development and help you build more efficient web applications. With DHTML you can replace many HTML documents containing large graphics and components with fewer documents containing small scripts and components that service the interface through this enhanced programming model. Client/server web applications can benefit by using data binding with local data source objects to replace larger data access components or to eliminate network round trips for pages of server-generated HTML.
With DHTML data binding, you can create an interface to tabular data with very little code. A navigable DHTML table that is bound to live data requires only that you specify a data source object for the table and that you specify a template for a single row with data fields for each column.
Data bound elements require a data source object, which provides a standard OLE DB interface to your data. Although general-purpose data-access components provide this interface, they are designed to connect directly to a database or data file rather than to work with a services model where client data is provided through a COM API. I'll begin by using Microsoft® Visual Basic® version 5.0 (with Service Pack 2) to develop a component that provides data through this interface, and then I'll build the user interface around this data provider using DHTML and data binding.
Like the other client applications in the HelpDesk sample, this client is based on the HelpDesk COM API that provides an object-oriented data model and application services. For a more complete description of the HelpDesk COM API see "Designing the HelpDesk Transaction Processing Object Model." You can also read about the HelpDesk HTML client I created for Microsoft Windows® CE in "A Web Application for Handheld Transaction Processing Clients."
A DHTML element can be bound to a data source object (DSO) so that it is repeated to represent multiple data records in the data source (A in Figure 1) or it can be bound to a single field in the currently selected record in the data source (B in Figure 1).
Figure 1. Interfaces types using data binding
In order to bind an HTML element to data, you add a data source (datasrc) or data field (datafld) attribute to the element. A table element can be bound to the DSO so that a row is created for each row in the DSO or a text input element can be bound to a field in the current record of the DSO so that it changes as the record pointer is moved through the DSO. For more information on data binding, see "Data Binding" in the DHTML section of the Internet Client SDK documentation on the MSDN Library (SDK Documentation, Internet Client SDK).
The data source object (DSO) can act as a client-side data cache that handles sorting, paging, validation, and updates for the user interface while protecting the rest of the application from repeated data requests to service user-interface navigation.
Figure 2 shows the DHTML client, the DSO, and the rest of the HelpDesk system architecture.
Figure 2. DHTML data binding and the HelpDesk architecture
The client side consists of a thin DHTML interface employing data binding, the DSO, and the HelpDesk client COM server. On the network beyond the client computer are additional HelpDesk API components and the SQL Server database.
The DSO provides the interfaces that DHTML data binding requires in order to render data. At the outermost level, the DSO provides methods for adding and removing a data source listener and it provides a property that exposes a data provider object. Note that these DSO procedures use reserved procedure ID attributes that identify them to data binding by DispID. The data-provider object implements an OLE DB Simple Provider interface and contains a private array that holds the data. Table 1 lists the properties and methods that are provided by the DSO, and Table 2 lists the properties and methods of the data provider.
Table 1. Properties and Methods of the Data Source Object
Property/Method | Description |
MsDataSourceObject | Returns a reference to the data provider object that implements the OLE DB interface. Requires procedure ID–3900 attribute |
AddDataSourceListner | Requires procedure ID–3901 attribute |
RemoveDataSourceListner | Requires procedure ID–3902 attribute |
Table 2. Properties and Methods of the Data Provider
Property/Method | Description |
OLEDBSimpleProvider_GetRowCount | Returns the number of rows of data in the data set |
OLEDBSimpleProvider_GetColumnCount | Returns the number of columns of data in the data set |
OLEDBSimpleProvider_GetRWStatus | Boolean true/false that indicates whether data provider is read or write |
OLEDBSimpleProvider_SetVariant | Write data in specified row and column with supplied value |
OLEDBSimpleProvider_GetVariant | Gets data from specified row and column |
OLEDBSimpleProvider_Find | Finds supplied data value |
OLEDBSimpleProvider_InsertRows | Inserts supplied rows into data |
OLEDBSimpleProvider_DeleteRows | Deletes specified data rows |
OLEDBSimpleProvider_GetLocale | Returns locale for internationalization |
OLEDBSimpleProvider_AddOLEDBSimpleProviderListener | Adds a reference to a listener object that receives notifications |
OLEDBSimpleProvider_ RemoveOLEDBSimpleProviderListener | Removes a reference to a listener object |
OLEDBSimpleProvider_ IsAsync | Boolean true/false—true indicates that provider operates asynchronously. |
OLEDBSimpleProvider_ GetEstimatedRows | Returns an estimated row count for data set |
OLEDBSimpleProvider_ StopTransfer | Stops in-progress data transfer |
The technician enters the application through Internet Explorer 4.0 by requesting the URL containing tech.htm. Tech.htm contains a scrollable frame that displays reclst.htm, which contains a data source object, a DHTML table to display the technician's request list, and some script code for event handling. The following DHTML code specifies the DSO and the request table that is bound to it:
<object id="oRequestList"
classid="clsid:01172939-2C2D-11D1-9C26-0080C74E5396">
</object>
<table datasrc="#oRequestList">
<tbody>
<tr>
<td><span datafld="Priority"></span></td>
<td><span datafld="Status"></span></td>
<td><span datafld="Request Date"></span></td>
<td><span datafld="Description"></span></td>
</tr>
</tbody>
</table>
When reclst.htm is loaded, the DSO is instantiated in order to render the request list table. The following Visual Basic code from the data provider within the DSO shows how the technician's data is retrieved from the HelpDesk API and used to populate the data provider's inner array:
Private Sub Class_Initialize()
Dim oTech As HDClient.CTech
' Initialize the array.
ReDim m_ReqArray(0,8)
' Get user log in name .
GetLoggedInUser(m_sAlias)
' Initialize the admin object.
If m_oAdmin.Init(m_sAlias, icTechLoggedIn) Then
' Populate a HelpDesk technician object.
Set oTech = oAdmin.GetTechByAlias(sAlias)
' Populate array with technician's requests.
ReDim m_ReqArray(oTech.Requests.Count, 8)
For lReq = 1 To oTech.Requests.Count
Set oReq = oTech.Requests(lReq – 1)
m_reqArray(lreq,ArrayCols.PKId) = oReq.PKId
M_reqArray(lReq, ArrayCols.Priority) _
=oAdmin.GetPriorities("id=" & CStr(oReq.PriorityId)).Code
m_reqArray(lreq, ArrayCols.Status) = oAdmin.GetReqStatus("id=" _
& CStr(oReq.StatusId - 1)).Code
m_reqArray(lreq, ArrayCols.RequestDate) = oReq.ReqDate
m_reqArray(lreq, ArrayCols.Description) = oReq.Desc
m_reqArray(lreq, ArrayCols.Decription_State) _
= DescState.Collapsed
m_reqArray(lreq, icColDescriptionDat) = oReq.Desc
Next
End If
End Sub
Figure 3. The populated request-list interface
Figure 3 shows the populated request list within its scrolling frame. I chose to display the complete data set in the request list table because I expect the technician's request list to be relatively short. If a large data set is expected, a paging mechanism can be used. A data-bound table can provide paging by using the DataPageSize attribute to restrict the number of records displayed. Table navigation can then be provided through event handlers on interface elements that call the table's nextPage and previousPage methods.
The technician double clicks a request in the table to expand its detail data and to display the response action interface. The mechanism for expanding the table exploits the data-binding interface with a trick that first requires some more background on data binding.
Data binding adds a recordset property to the DSO. The scriptable recordset object provides data manipulation methods (Insert, Update, and Delete) that act on data in the underlying DSO and on the bound DHTML elements. The following lines of script show how the recordset property could be used to change the value of MyField to NewValue in the current record of the data source object (dataSrc). This change will automatically update any HTML elements bound to MyField.
Datasrc.Recordset.Fields("MyField") = "NewValue"
Datasrc.Recordset.Update
The request list DSO exploits this synchronization (as well as the ability to render data as HTML) to provide an interface that toggles between an expanded and collapsed state. When the technician double clicks a request, the following script event handler, which appears to replace the description field with an empty string, is invoked.
Sub ExpandRequest()
DSC1.recordset.absoluteposition = window.event.srcElement.recordNumber
DSC1.recordset.Fields(icDescription) = ""
DSC1.recordset.update
End Sub
The recordset update first updates the data provider through SetVariant(), it then retrieves the data for the same row and column with GetVariant(), and then updates the bound element. SetVariant() intercepts updates to the description column and inserts either the HTML that displays the expanded request details or the request description, depending on the previous toggle state of the column. Figure 4 shows the expanded request details and action interface in the table cell that had previously contained the request description. A subsequent double click restores the cell to its collapsed state.
------
Sub OLEDBSimpleProvider_setVariant(ByVal lRow As Long, _
ByVal lColumn As Long, _
ByVal format As OSPFORMAT, _
ByVal vNewVal As Variant)
…
…
' Intercept and trigger a detail toggle
If lColumn = ArrayCols.Description Then
Call myEvent.aboutToChangeCell(lRow, lColumn)
ToggleDetail (lRow)
Call myEvent.cellChanged(lRow, lColumn)
Exit Sub
End If
' General case update the array
Call myEvent.aboutToChangeCell(lRow, lColumn)
myArray(lRow, lColumn) = vNewVal
Call myEvent.cellChanged(lRow, lColumn)
End Sub
ToggleDetail then switches the data in the description column to which the HTML element is bound.
Private Sub ToggleDetail(ByVal lRow As Long)
…
…
If myArray(lRow, ArrayCols.Description_State) = DescState.Collapsed Then
' put HTML to display the detail interface in the description column
sHTML = sHTML & "<iframe ID=frmDetMenu width=100% height=40 "
sHTML = sHTML & "marginheight=0 marginwidth=0 "
sHTML = sHTML & "scrollable=No src=""dethdr.htm""></iframe>"
sHTML = sHTML & "<iframe ID=frmDetList width=100% height=200 "
sHTML = sHTML & "marginheight=0 marginwidth=0 "
sHTML = sHTML & "scrollable=True src=""detlst.htm""></iframe>"
myArray(lRow, ArrayCols.Description) = sHTML
myArray(lRow, ArrayCols.Description_State) = DescState.Expanded
Else
' put only the request description into the column
myArray(lRow, ArrayCols.Description) = _
myArray(lRow, ArrayCols.Description_Dat)
myArray(lRow, ArrayCols.Description_State) = DescState.Collapsed
End If
End Sub
Figure 4. Expanded request
This article just begins to cover a client architecture using DHTML data binding with a custom data source object. I'll expand the application for inclusion with the final version of the Helpdesk sample on MSDN. The accompanying sample application can be used immediately in a stand-alone mode (with static data) as well as with the complete Helpdesk system when it is available.