BizTalk Utilities CV ,   Jobs ,   Code library  
 
 

Washington, September 15-18, 1999 – London, November 21-24, 1999

XML Client-side Data Binding

Alex Homer

This session looks at how IE4 or IE5 can be used with XML to create dynamic and network-friendly data-handling applications using client-side data binding and server-side XML document handling. The two main topic areas are:

Ø        What is XML Data Binding?

Ø        Creating and Managing XML Documents with ASP

Alex Homer
Alex is a software consultant and developer living and working in the UK. He is the CEO of Stonebroom Software, a company specializing in office integration and Internet related development that produces a range of vertical application software. Alex has worked with Wrox Press on several projects, including Professional ASP 2.0, IE5 XML Programmer's Reference, Professional ASP Techniques for Webmasters and Professional MTS MSMQ Programming with VB and ASP.

What is XML Data Binding?

Before we charge off and look at XML Data Binding, we should review what data binding actually is. Then we'll see how we can use XML to provide a data source for data binding. We'll look at:

Ø        What is Data-Binding?

Ø        Data-Management Techniques on the Web

Ø        Data-Binding in Internet Explorer 4 and 5

Ø        Displaying Data using Data-Binding

Ø        Accessing XML Data in a DSO with Script Code

What is Data-Binding?

Client-side data binding has long been the traditional way to build data-handling applications, even though many people think that the technique only arrived with Internet Explorer 4. In fact, programs like Microsoft Access have always provided data-binding in the form of data-bound controls that can be placed on a form to allow the display and updating of data from a table or query.

In Visual Basic and other programming languages like Borland Delphi, data-bound controls are available for use in your own applications. You define a connection and query that returns the data required for a form as a recordset, and drop suitable data-bound controls onto that form. They automatically reflect the matching field (or column) values of the current record, and usually allow these values to be updated, depending on the property settings for the controls, the query used to create the recordset, and the data source in use.

Data-Management Techniques on the Web

So data binding is nothing new, but what was exciting when IE4 was released was the ability to do data binding client-side in a Web browser. The traditional way of manipulating server-based data via a client-side browser is with custom CGI applications, Perl or IDC scripts, or (more recently) ASP. You send the user a page full of data extracted from the data source, displayed in a table or in HTML controls, and let them view it. If they want, they can edit the contents of the HTML controls, or request a page with editable controls in it for a specific record. After editing the details, the form is posted back to the server where the Request.Form contents are used by the CGI application or ASP to update the data source.

This is fine for simple data updates, where the user can select a record that they want to update. However, it doesn’t provide the same kind of working environment or user experience as, for example, a Microsoft Access application. Web-based applications are far less responsive (unless you have a very fast network), and often far less intuitive because the user has to navigate through many different pages selecting the records to edit, providing the new values, and then submitting them to the server.

In fact, the main reason that Access and similar applications are so much more responsive is that they don’t have to keep going back to the server each time the user carries out an action, such as moving to another record or editing a value. Instead, they generally cache the results of the query that creates the recordset locally on the client. Hence, there is no communication delay reading the next record, and updates to the data are held locally until the source data is updated en-block when appropriate. This may be when the user closes the form, moves to another page, or issues an update request manually using the menu options.

Data-Binding in Internet Explorer 4 and 5

Internet Explorer 4 and 5 provide the same kind of features within a Web page as Access and other similar applications do in their own environment. Client-side data binding overcomes to the delays and concurrency problems that arise with pure server-side data management on the Web. The delays are overcome by using the same technique as Access or a VB data-bound application—caching the recordset on the client.

This obvious solution needs a few Web-specific wrinkles to be ironed out, of course.  Amongst them:

Ø        How do we get the data from the server to the client?

Ø        How do we store it client-side, and how (i.e. with what) do we bind it to controls in a Web page?

Ø        How do we get the updates back to the server over HTTP?

Ø        How do we use this information back on the server to update the original data source?

The Microsoft Remote Data Service

The answer, when we are using RDS, is a set of three custom ActiveX (COM) components:

Ø        The data factoryis a server-based component that extracts the data from the data source, and also creates a client-side proxy object through which it communicates over HTTP with the client. The recordset is encoded as a special MIME-type, and passed across the network in this form. The proxy decodes it and makes it available to the other objects on the client as a normal recordset.

Ø        The client-side data source object(DSO) collects the data from the data factory proxy object, and handles the client-side caching. As a by-product, it makes the data available to script code running on the client, whether or not data-bound controls are used.

Ø        The data-binding agent handles the connection between the DSO and the data-bound controls on the page, displaying the current values and passing updates back to the DSO so that it can update the cached recordset.

This combination of three objects was originally called the Advanced Data Control (ADC) but in later releases has become closely integrated with ADO, and is now know as Remote Data Services (RDS). This is a typical definition for an instance of the RDS DSO: 

<OBJECT CLASSID="clsid:BD96C556-65A3-11D0-983A-00C04FC29E33"

        ID="dsoBookList" HEIGHT=0 WIDTH=0>

  <PARAM NAME="Server" VALUE="http://www.yourserver.com">

  <PARAM NAME="Connect" VALUE="DSN=books;UID=anon;PWD=">

  <PARAM NAME="SQL" VALUE="SELECT * FROM BookList">

</OBJECT>

We implement the final link in the data-binding chain by providing a set of data-bound controls that the data-binding agent can work with. Thankfully, this is not some new custom set of controls that has been added to the browser, but the normal HTML controls that we use in our ordinary forms every day. In IE4 and IE5, all HTML control elements such as the INPUTTYPE="TEXT", INPUTTYPE="RADIO" and TEXTAREA have attributes that are used to perform the connection between the data-binding agent and the control. Some other HTML elements, such as SPAN, DIV, IMG, PARAM and others also provide the same attributes and use the value to define the setting (i.e. the text content or the source of an image) that will be applied to that element:

Ø        DATASRC indicates which data source object the control is linked to (there can be more than one DSO on page). In the special case of the TABLE element, all the records stored in the client-side data cache are displayed by the repetition of  'template' HTML code that defines how a row in the table corresponds to the data in the columns of each record.

Ø        DATAFLD is used in all elements (except generally the TABLE element) to define which column or field in the recordset this control should be bound to. The special case where this attribute is used with a TABLE element is for providing nested tables to display hierarchical and master/detail recordset data.

Ø        DATAFORMATAS is used with elements that provide a textual representation of the field or column value, and instructs the browser as to whether to parse the data (treat it as HTML) or just display it 'as is' (i.e. as text). The possible values for this attribute are "TEXT" and "HTML", with the default if omitted being "TEXT".

The Internet Explorer XML Data Source Objects

In Internet Explorer 4, as XML started to come to the frontiers of application design and programmer's development thinking, it became necessary to be able to display and handle XML data on the client. There's no point in generating XML on the server and sending it across the network if the client doesn’t know what to do with it. Microsoft released an XML Data Source Object in both Java and C++ versions for IE4. Only the Java version is still available, however, and is packaged with the updated Microsoft Java runtime installation that you can download from the Java section of the Microsoft Web site. It's also available separately from the same location.

The XML DSO tries to do for XML what the RDS DSO did for any ODBC-compliant data source. However, it has one very large failing it can only read data and display it. It can’t automatically do server-side updates to the data as RDS does, because there is no server-located component to perform the action. With XML data binding, it's all done on the client.

This is what an instance of the Java DSO might look like:

<APPLET CODE="com.ms.xml.dso.XMLDSO.class"

        ID="dsoBookList" WIDTH=0 HEIGHT=0 MAYSCRIPT=true>

  <PARAM NAME="URL" VALUE="booklist.xml">

</APPLET>

The single parameter is the URL of the XML file to load into the DSO. This is a simple example of an XML data file that we might use:

<?xml version="1.0"?>

<BOOKLIST>

 <ITEM>

  <CODE>16-041</CODE>

  <CATEGORY>HTML</CATEGORY>

  <RELEASE_DATE>1998-03-07</RELEASE_DATE>

  <TITLE>Instant HTML</TITLE>

  <SALES>127853</SALES>

 </ITEM>

 <ITEM>

  <CODE>16-048</CODE>

  <CATEGORY>Scripting</CATEGORY>

  <RELEASE_DATE>1998-04-21</RELEASE_DATE>

  <TITLE>Instant JavaScript</TITLE>

  <SALES>375298</SALES>

 </ITEM>

  ... etc ...

</BOOKLIST>

The Internet Explorer 5 <XML> Data Source Object Element

If we can ditch the requirements for backward compatibility with IE4, we can use IE5 itself (or to be more precise the C++ XML DSO provided with IE5) as our data source object. This is far easier and more efficient than using the Java applet or any other technique. All we have to do is create a data island within our HTML page. This is done with the new <XML> element that is supported in IE5.

Note that <XML> is in fact an HTML element, and not an XML element. It is used in an HTML page to denote a section of the page that the browser should treat as being XML rather than HTML. In other words, it acts as the interface that exposes the XML data. The data can then be used in exactly the same way as it would with other DSOs, either bound to controls in the page or manipulated using script code.

Creating an XML Data Island

Getting XML data into IE5 as part of an HTML document is easy with the <XML> element. Here, we've set the ID attribute to "dsoBookList", and this magically becomes our DSO, which we can reference and use with data binding, or (as you'll see later) in client-side script:

<XML ID="dsoBookList">

  <?xml version="1.0"?>

  <BOOKLIST>

    <ITEM>

      <CODE>16-041</CODE>

      <CATEGORY>HTML</CATEGORY>

      <RELEASE_DATE>1998-03-07</RELEASE_DATE>

      <TITLE>Instant HTML</TITLE>

      <SALES>127853</SALES>

    </ITEM>

    <ITEM>

       ...

    </ITEM>

    ... etc ...

  </BOOKLIST>

</XML>

Linking to XML Data Files

Rather than embedding the data directly into the HTML page, it generally makes a lot more sense to link to it. This makes it far easier to update the XML data without having to edit the HTML page, which could contain a lot of peripheral content such as presentation information and script code. To link to an XML file, we use the SRC attribute of the <XML> element:

<XML ID="dsoBookList" SRC="booklist.xml"></XML>

Remember that the <XML> element is an HTML element, and not an XML element, so it must have a 'proper' closing tag. The XML shorthand syntax will not work, so you cannot use:

<XML ID="dsoBookList" SRC="wontwork.xml" />

Displaying Data using Data-Binding

Once our XML data has been retrieved and cached locally by a DSO, we can use it to populate HTML controls in the Web page. This is the point where the actual binding goes on, where we connect fields in the recordset with appropriate controls on the page. One the connection is complete and the data-binding agent has accomplished it's task, they act just like bound controls in any other environment—such as Microsoft Access.

The HTML Elements Used in Data Binding

HTML Element

Bound property

Update data?

Tabular binding?

Display as HTML?

A

href

No

No

No

APPLET

param

Yes

No

No

BUTTON

innerText andinnerHTML

No

No

Yes

DIV

innerText andinnerHTML

No

No

Yes

FRAME

src

No

No

No

IFRAME

src

No

No

No

IMG

src

No

No

No

INPUT TYPE=CHECKBOX

checked

Yes

No

No

INPUT TYPE=HIDDEN

value

Yes

No

No

INPUT TYPE=LABEL

value

Yes

No

No

INPUT TYPE=PASSWORD

value

Yes

No

No

INPUT TYPE=RADIO

checked

Yes

No

No

INPUT TYPE=TEXT

value

Yes

No

No

LABEL

innerText andinnerHTML

No

No

Yes

MARQUEE

innerText andinnerHTML

No

No

Yes

OBJECT

param

Yes

No

No

SELECT

text of selected option

Yes

No

No

SPAN

innerText andinnerHTML

No

No

Yes

TABLE

none

No

Yes

No

TEXTAREA

value

Yes

No

No

So, as an example, we could bind a SPAN element to the value in a field named tTitle, which is in a recordset exposed by a DSO that has the ID of dsoBookList, using:

<SPAN DATASRC="#dsoBookList" DATAFLD="tTitle"></SPAN>

The value of the tTitle field in the current record in the recordset would then be displayed in the page within the SPAN element as plain text (the default). If the tTitle field contains HTML formatting within the value, we can cause the browser to render it as such using:

<SPAN DATASRC="#dsoBookList" DATAFLD="tTitle" DATAFORMATAS="HTML"></SPAN>

However, bear in mind that in our case, the data is coming from an XML document. HTML markup generally can't be included in XML in its native form, because some elements like <HR> don’t conform to XML syntax requirements—they have no closing tag. And if the XML document contains a schema or data type definition (DTD) that specifies the structure, any HTML elements will probably void the definition. In this case, the HTML data must either be included in a CDATA section within the XML document:

<tTitle><![CDATA[<B><I>Instant HTML</I></B>]]></tTitle>

or else the angled brackets would have to be escaped individually (i.e. the data must be 'HTML encoded'):

<tTitle>&lt;B&gt;&lt;I&gt;Instant HTML&lt;/I&gt;&lt;/B&gt;</tTitle>

Dynamic Data Binding

We can also use client-side script to set up or change the bindings once the page has loaded, by changing the dataSrc, dataFld and dataFormatAs properties of the appropriate elements. To remove the bindings, we just set the properties to an empty string. To change the binding of elements within a bound table, we must remove the binding of the table first (in the <TABLE> tag), change the bindings of the elements in the table, and then reset the binding of the table. YOu'#ll see an example of dynamic binding later on.

Types of Data Binding

The Data Binding Agent object can provide two types of data binding:

Ø        tabular data binding (sometimes referred to as table repetition agent binding)

Ø        single record data binding (often called current record data binding).

Tabular Data Binding

Tabular data binding depends on the ability of the <TABLE> element to repeat the contents of the <TBODY> section once for each record. The data source object is identified within the opening <TABLE> tag, and the column or field name for each bound control is identified within each table cell. Note that the <TD> element itself does not take part in the data binding process. Instead, a bound element is placed within each cell. This could be a <SPAN> or a <DIV> element, or one of the other HTML controls. For example:

...

<XML ID="dsoBookList" SRC="booklist.xml"></XML>

...

<TABLE DATASRC="#dsoBookList">

 <THEAD>

  <TR>  

   <TH>Code</TH>

   <TH>Category</TH>

   <TH>Release Date</TH>

   <TH>Title</TH>

   <TH>Sales</TH>

  </TR>

 </THEAD>

 <TBODY>

  <TR>   

   <TD><SPAN DATAFLD="CODE"></SPAN></TD>

   <TD><SPAN DATAFLD="CATEGORY"></SPAN></TD>

   <TD><SPAN DATAFLD="RELEASE_DATE"></SPAN></TD>

   <TD><B><SPAN DATAFLD="TITLE"></B></SPAN></TD>

   <TD><SPAN DATAFLD="SALES"></SPAN></TD>

  </TR>  

 </TBODY>

</TABLE>

...

The use of a <TBODY> element is not mandatory when you want all the contents of the table to be repeated for each record, and in fact it is often omitted. IE will repeat all bound elements one for each record automatically even if there is no <TBODY> element.

Table Paging with Tabular Data Binding

ADO recordsets support paging, which is useful if there are a lot of records to display. When tabular data binding is used, a <TABLE> is bound to the DSO that provides the source recordset and both the DSO properties and the properties of the underlying recordset are exposed through this element. The properties of the recordset include the dataPageSize property. This is mapped in HTML to the DATAPAGESIZE attribute of the table: DATAPAGESIZE sets the maximum number of records that will be displayed within the body of a table.

By setting this attribute in the opening HTML <TABLE> tag, we can create a table that displays only a specified number of records:

<TABLE DATASRC="#dsoBookList" DATAPAGESIZE=10>

Then, we can move through the recordset by using its nextPage and previousPage methods, as exposed by the DSO. In IE5, two new methods have been added, firstPage and lastPage, which allow us to go directly to the first or last page of records. The bound table also exposes the recordNumber property of the underlying data set for each element within the table.

Multiple Recordset (Nested Table) Data Binding

It's possible to use a DSO to embed data into a Web page where that data doesn’t fit neatly into a single table. This is the case with data that has repeating groups of records for each master record—generally referred to as master/detail recordsets. For example, given a list of books, each one might have more than one author. We can't express this relationship neatly within a single table, so we usually lift out the repeating group into a separate table, and link (or JOIN) it back to the master table:

We can express this data as a hierarchical recordset and display it using nested tables. However, it's not all plain sailing when using XML data of this form, as the XML DSOs generally fail to expose it as a master/detail recordset successfully unless it follows a specific format. As an example, the following data file, named nonsymbooks.xml, has repeating groups (within AUTHORLIST elements). Because there could be zero or more authors in each list, the recordset is not 'symmetrical':

<?xml version="1.0" ?>

<BOOKLIST>

 <ITEM>

  <CODE>16-048</CODE>

  <TITLE>Instant JavaScript</TITLE>

  <SALES>375298</SALES>

  <AUTHORLIST>

   <AUTHOR>

    <FIRST_NAME>Martin</FIRST_NAME>

    <LAST_NAME>Williams</LAST_NAME>

   </AUTHOR>

   <AUTHOR>

    <FIRST_NAME>Cheryl</FIRST_NAME>

    <LAST_NAME>Caprialdi</LAST_NAME>

   </AUTHOR>

  </AUTHORLIST>

 </ITEM>

 <ITEM>

  <CODE>23-177</CODE>

  <TITLE>JavaScript Today</TITLE>

  <SALES>118524</SALES>

  <AUTHORLIST>

   <AUTHOR>

    <FIRST_NAME>Angela</FIRST_NAME>

    <LAST_NAME>Millania</LAST_NAME>

   </AUTHOR>

  </AUTHORLIST>

 </ITEM>

 ...

</BOOKLIST>

You can see that there are two authors for the first <ITEM>, but only one for the second <ITEM>. There may even be <ITEM> elements in the data that have no authors (written by a ghost author perhaps?). In this form, the file won’t display master/detail records correctly—in other words, the repeated author names will not appear—unless we add some extra bits and pieces to force it to work.

XML File Structure for Master/Detail Record Binding

The reason is that the repeated data (the list of authors) is not directly within each <ITEM> element. There is an <AUTHORLIST> element containing them, very much in the style of a tree structure as is popular for XML files. Instead, we require a data file that contains the repeated elements directly within the parent 'record'. Look at the next extract to see how the <AUTHOR> element is located and repeated directly within the <ITEM> element:

 ...

 <ITEM>

  <CODE>16-048</CODE>

  <TITLE>Instant JavaScript</TITLE>

  <SALES>375298</SALES>

  <AUTHOR>

   <FIRST_NAME>Martin</FIRST_NAME>

   <LAST_NAME>Williams</LAST_NAME>

  </AUTHOR>

  <AUTHOR>

   <FIRST_NAME>Cheryl</FIRST_NAME>

   <LAST_NAME>Caprialdi</LAST_NAME>

  </AUTHOR>

 </ITEM>

 ...

Master/Detail HTML Tables

To use this kind of master/detail data with our XML DSO for data binding, we have to use nested tables:

<!-outer table bound to master recordset, no DATAFLD attribute -->

<TABLE DATASRC="#dsoBookList" WIDTH=100%>

 <THEAD>

 <TR>

  <TH ALIGN="LEFT">Code</TH>

  <TH ALIGN="LEFT">Title</TH>

  <TH ALIGN="LEFT">Sales</TH>

  <TH ALIGN="LEFT">Author(s)</TH>

 </TR>

 </THEAD>

 <TBODY>

 <TR VALIGN="TOP">  

  <TD><SPAN DATAFLD="CODE"></SPAN></TD>

  <TD><SPAN DATAFLD="TITLE"></SPAN></TD>

  <TD><SPAN DATAFLD="SALES"></SPAN></TD>

  <TD>

   <!-inner table bound to detail recordset via DATAFLD attribute-->

   <TABLE DATASRC="#dsoBookList" DATAFLD="AUTHOR">

    <TR>

     <TD>

      <SPAN DATAFLD="FIRST_NAME"></SPAN><SPAN DATAFLD="LAST_NAME"></SPAN>

     </TD>

    </TR>

   </TABLE>

  </TD>

 </TR>  

 </TBODY>

</TABLE>

This code contains a definition of a table that is bound to the DSO named dsoBookList. Within one of the table cells is a nested <TABLE> element that is also bound to dsoBookList. However, this table also has the AUTHOR field specified in the binding, through the DATAFLD attribute. Within this nested table are the bindings to the FIRST_NAME and LAST_NAME elements. If the data contained another level of 'nesting', i.e. another layer of repeated elements such as multiple first names, we would handle this in exactly the same way by adding another bound table within the existing one.

This technique also provides us with a solution for the earlier XML document, where the author details are placed within an <AUTHORLIST> element. We can include a third level of table nesting, so that we have a table bound to the main (master) recordset, which holds a nested table bound to the AUTHORLIST field, which in turn holds another nested table bound to the AUTHOR field.

Fixed Layout Tables

One other point worth remembering remember if you are aiming your pages at IE5 is that it now supports fixed layout tables, which can considerably reduce the perceived load time of a page. By adding the table-layout:fixed property to the CSS style definition of the table, and specifying the absolute width of each column, the browser knows up front how wide to make the table and the columns without having to read all the content and decide for itself. The result is that not only is processing overhead reduced, but also the table can be rendered as it gets to the browser—rather than having to wait for all the content to arrive. For example, this HTML code defines a table with three columns of fixed width:

<TABLE style="table-layout:fixed" width=600>

  <TR>

    <TD width=25%>cell content here</TD>

    <TD width=40%>cell content here</TD>

    <TD width=35%>cell content here</TD>

  </TR>

</TABLE>

Single Record Data Binding

Tabular display is fine for displaying data, but to edit it we really need to be able to display the values from one record at a time within HTML controls. As soon as a recordset is created by a DSO, the first record becomes the current record, exactly as it does when the recordset is created directly with ADO (if the recordset is empty, the EOF and BOF properties are both True at this point). We can display the values from the current record in any of the bindable HTML elements listed earlier by setting their DATASRC and DATAFLD properties. For example:

...

<XML ID="dsoBookList" SRC="booklist.xml"></XML>

...

Code: <INPUT ID="Code" TYPE="TEXT" DATASRC="#dsoBookList"

             DATAFLD="CODE" SIZE=5><P>

Title: <INPUT ID="Title" TYPE="TEXT" DATASRC="#dsoBookList"

             DATAFLD="TITLE" SIZE=30><P>

Category: <SELECT ID="Category" DATASRC="#dsoBookList"

                  DATAFLD="CATEGORY" SIZE=1>

            <OPTION VALUE="HTML">HTML

            <OPTION VALUE="Scripting">Scripting

            <OPTION VALUE="ASP">ASP

           </SELECT><P>

Release date: <SPAN ID="Release" DATASRC="#dsoBookList"

                    DATAFLD="RELEASE_DATE"></SPAN><P>

Sales to date: <SPAN ID="Sales" DATASRC="#dsoBookList"

                     DATAFLD="SALES"></SPAN><P>

...

Navigating the Recordset

If we are displaying only a single record at a time, we need to provide a way for users to move to another record. This is accomplished using the standard methods of the DSO that are exposed via ADO: move, moveFirst, moveLast, moveNext and movePrevious. So we can add some fancy controls to allow users to navigate through the records—here using the IE-specific <BUTTON> element:

...

<TABLE>

<TR><TD>

<BUTTON ONCLICK="dsoBookList.recordset.MoveFirst()">&nbsp;|&lt;&nbsp;</BUTTON >

<BUTTON onclick="if (! dsoBookList.recordset.BOF) dsoBookList.recordset.MovePrevious()">

  &nbsp;&lt;&nbsp;

</BUTTON>

<BUTTON onclick="if (! dsoBookList.recordset.EOF) dsoBookList.recordset.MoveNext()">

  &nbsp;&gt;&nbsp;

</BUTTON >

<BUTTON onclick="dsoBookList.recordset.MoveLast()">&nbsp;&gt;|&nbsp;</BUTTON >

</TD></TR>

</TABLE>

...

Note that we check the BOF property of the recordset before attempting to move backward, and the EOF property before attempting to move forward. This prevents errors being caused by moving beyond the limits of the recordset. The result looks something like this:

Data Binding Events

Both a DSO embedded within the page, and the browser itself, raise events that can be trapped and used in script on the client. We'll look at these events very briefly next. They can be divided into two groups: those raised by the browser or the controls on the page (when the user navigates to another page or edits the data in the HTML controls), and those raised by a DSO as the user edits the data it exposes. When a page containing a DSO is unloaded, or when the user edits the data in HTML controls that are bound to a DSO, various events are raised. Some can be canceled by returning the value false from the event handler routine:

Event

Cancelable?

Description

onbeforeupdate

Yes

Occurs before the data in the control is passed to the DSO.

onafterupdate

No

Occurs after the data in the control has been passed to the DSO.

onerrorupdate

Yes

Occurs if an error prevents the data being passed to the DSO.

onbeforeunload

No

Occurs before the current page is unloaded.

The window object raises theonbeforeunload event, while the rest are raised by HTML controls on the page. With the exception of the onbeforeunload event, all events bubble up through the document hierarchy. So, we can display a message when the user changes the value in a control with:

<INPUT ID="txtTitle" DATASRC="#dsoBookList" DATAFLD="tTitle">

...

<SCRIPT LANGUAGE="JScript">

function txtTitle.onbeforeupdate() {

  return confirm("Are you sure you want to change this value ?");

}

</SCRIPT>

The Data Source Object itself raises events as various actions take place. The first four are concerned with indicating the current state of the DSO as it loads the data. The others are fired when the 'current record' changes as the user moves through the recordset, or when records are inserted, deleted or changed:

Event

Cancelable?

Description

ondataavailable

No

Occurs periodically while data is arriving from the data source.

ondatasetcomplete

No

Occurs when all the data has arrived from the data source.

ondatasetchanged

No

Occurs when the data set changes, such as when a filter is applied.

onreadystatechange

No

Occurs when the readyState property of the DSO changes.

onrowenter

No

Occurs for a record when it becomes the current one during navigating the recordset.

onrowexit

Yes

Occurs for a record before another becomes the current record during navigation.

onrowsdelete

No

New in IE5. Occurs when rows are about to be deleted from the current recordset.

onrowsinserted

No

New in IE5. Occurs after rows are inserted into the current recordset.

oncellchange

No

New in IE5. Occurs when the data in a bound control or table cell changes and the focus moves from that cell.

Only the onrowexit event can be canceled by returning false from the event handler routine. All events bubble up through the document hierarchy. It's usual to take advantage of the ondatasetcomplete event for any script that you want to run once the data has arrived. Here, we're using an INPUT element named txtStatus to display appropriate messages:

<INPUT ID="txtStatus" VALUE="Initializing, please wait ...">

...

<SCRIPT LANGUAGE="JavaScript">

function dsoBookList.ondatasetcomplete() {

  txtStatus.value = "Data arrived OK";

}

</SCRIPT>

Accessing XML Data in a DSO with Script Code

We've already seen that data embedded in a Web page through a Data Source Object is exposed as a recordset, and that this recordset can be used in data binding. However, the DSOs go further than this. Once data binding has been completed, the data in our source recordset or XML document is exposed as a standard ADO client-side (or disconnected) recordset, irrespective of the kind of DSO and data file we use. We can get a reference to this recordset from the DSO's recordset property:

myRecordset = dsoMyDSO.recordset;       // JavaScript or JScript

Set myRecordset = dsoMyDSO.recordset    ' same but in VBScript

Once we have a reference to the recordset, we can access the fields to get their values:

theValue = myRecordset('field_name');   // JavaScript or JScript

theValue = myRecordset("field_name")    ' same but in VBScript

And, of course, we can access all the other properties or methods of the recordset as well. However, as we've seen, this really only makes sense if the recordset is symmetrical, in other words if it looks like a set of normal database tables with the same number of fields in each record. The DSO will attempt to expose it as such, but with irregular XML-formatted data, this isn’t always going to produce something useful. In particular, IE5 may create a recordset that bears no relation to what you expect if there are different numbers or types of child elements in each record.

In this case, we will usually access this kind of data using a different technique. This involves the Document Object Model (DOM), which exposes all the objects in the page as a tree and a series of collections. In particular, it exposes the XML content of the page (or a linked XML file) as a structure that we can access using special scripting techniques. Of course, we can access any XML data using the DOM if we wish—it doesn’t have to be in an 'irregular' format. However, that's a topic for a different conference session.

XML Data Binding in IE5 with Attributes

One issue that you may come across, if you adopt data binding in IE5 through a data island, is that it all seems to fall apart if there are any attributes within the opening element tags. Unfortunately, the data-binding agent in IE5 does not parse the XML data in the same way as the Java parser component used with IE4 or IE5, and it fails to see or display the values of the elements.

The reason is presumably connected with the way that the latest versions of ADO can stream data from a database table into XML automatically, but when they do the field values are placed in attributes of an element, rather than with each field as a separate element:

<z:row fieldname='fieldvalue' fieldname='fieldvalue' fieldname='fieldvalue' />

rather than something more familiar like:

<record>

  <fieldname>fieldvalue</fieldname>

  <fieldname>fieldvalue</fieldname>

  <fieldname>fieldvalue</fieldname>

</record>

In this case, you can continue to use the parser component instead of a data island in IE5, but as data islands are more efficient and don't need a parser to be pre-installed by the user, it's worth looking at work-arounds that are possible to solve the problem. It arises because the IE5 data-binding agent treats an attribute in an element as a separate element, so:

<myElement myAttribute="attr_value">element_value</myElement>

is treated as though it were:

<myElement><myAttribute>element_value</myAttribute></myElement>

To get round this, we have to use a data-bound table within the data-bound element to 'get at' the nested element's value rather than the outer element's value (which is an empty string). The text value of the element can be obtained by binding to the $TEXT property:

<TABLE DATASRC="#dsoData" DATAFLD="myElement">
  <TR>
    <TD>
      <INPUT TYPE="TEXT" DATASRC="#dsoData" DATAFLD="$TEXT">
    </TD>
  </TR>
</TABLE>

If we are using tabular data binding, we already have a table to hold the element values. In this case, we place a nested table inside each existing table cell:

<TABLE DATASRC="#dsoData">
  <TR>
    <TD>
      <TABLE DATASRC="#dsoData" DATAFLD="firstColumnName">
        <TR><TD><SPAN DATAFLD="$TEXT"></SPAN></TD></TR>
      </TABLE>
    </TD>
  </TR>
  <TR>
    <TD>
      <TABLE DATASRC="#dsoData" DATAFLD="nextColumnName">
        <TR><TD><SPAN DATAFLD="$TEXT"></SPAN></TD></TR>
      </TABLE>
    </TD>
  </TR>
... etc ...
</TABLE>

This also means that if we need to access the values using script, via the recordset created by the data island, we have to refer to the $TEXT property of the nested element as well. For example, we would use:

var objDI = document.all('dsoData');
strValue = objDI.recordset.fields('column_name').value.fields('$TEXT');

instead of the more usual:

var objDI = document.all('dsoData');
strValue = objDI.recordset.fields('column_name').value;

To see some examples of the data binding events at work, and the ways that we can access the values in the recordset using script, have a look at the on-line samples available from http://webdev.wrox.co.uk/books/1576/.

Creating and Managing XML Documents with ASP

The combination of XML and data binding on the client provides a very powerful and bandwidth-friendly way to provide data management applications for users. The client behaves in a similar way to compiled applications written in languages like Visual Basic, C++, Delphi, or office-style applications like Microsoft Access. In this section we move away from the client to look at the server side of the process, and explore how we can use ASP and other techniques to create and manage XML documents. We'll cover two main topic areas:

Ø        Creating XML documents from a data store

Ø        Updating the source data from an XML Document

Creating XML Documents from a Data Store

You'll have noticed that the XML DSOs we looked at in the previous section (with the exception of the in-line XML in an <XML> element) have a parameter for the XML source. In the Java DSO this is the URL parameter, set with a <PARAM> element:

<PARAM NAME="URL" VALUE="booklist.xml">

 or as the URL property at run-time. In an IE5 data island, the XML source is specified by a SRC attribute:

<XML ID="dsoBookList" SRC="booklist.xml"></XML>

These both assume that the source is an XML-format text file named booklist.xml. However, it could just as easily be booklist.asp, as long as the MIME-type of the file is "text/xml" so that the browser recognizes it as an XML file (although it seems to work fine without it—presumably the browser sees the opening <?xml version="1.0" ?> element and behaves appropriately).

So, we can get an ASP file to create our XML document as a stream rather than using a disk file. This can be done in at least three different ways:

Ø        With ASP script

Ø        With a custom component

Ø        With the ADO Persistence methods

Creating XML Documents with ASP script

The simplest, but probably not the most efficient way of creating XML is to simply iterate through a set of data and write it out enclosed with the appropriate XML tags. Best of all, you get to create the XML in whatever format you like. This example creates an XML document from a database table containing information about famous Wrox authors, and streams it back to the browser:

<%@LANGUAGE="VBScript"%>

<%

Response.Buffer = False          'allow output to be streamed to the client as it's created

Response.ContentType="text/xml"  'tell the browsaer that it's XML we're sending

%>

<?xml version="1.0"?>

<AUTHORLIST>

<%

select all the author details:

Set oConn = Server.CreateObject("ADODB.Connection")

oConn.Open "connection_string"

strSQL="SELECT * FROM Authors ORDER BY tLinkName"

Set oRs = oConn.Execute(strSQL)

strPhotoURL = ""

strInterview = ""

QUOT = Chr(34)

Do While Not oRs.EOF

  strLinkName = oRs("tLinkName")

  If oRS.Fields("bPhoto") <> 0 Then

    strPhotoURL = "http://webdev.wrox.co.uk/resources/authors/" & strLinkName & ".gif"

  End If

  If oRS.Fields("bInterview") <> 0 Then

    strInterview = "http://webdev.wrox.co.uk/resources/authors/" & strLinkName & ".htm"

  End If

  Response.Write "  <AUTHORINFO>" & vbCrlf

  Response.Write "    <AUTHOR_NAME>" & oRs("tName") & "</AUTHOR_NAME>" & vbCrlf

  Response.Write "    <SHORT_BIO>" & Server.HTMLEncode(oRs("tBio")) & "</SHORT_BIO>" & vbCrlf

  Response.Write "    <PHOTO_URL>" & strPhotoURL & "</PHOTO_URL>" & vbCrlf

  Response.Write "    <INTERVIEW_URL>" & strInterview & "</INTERVIEW_URL>" & vbCrlf

  Response.Write " </AUTHORINFO>" & vbCrlf

  oRs.MoveNext

Loop

oRs.Close

%>

</AUTHORLIST>

This creates an XML file that looks something like:

<?xml version="1.0" ?>

<AUTHORLIST>

  <AUTHORINFO>

    <AUTHOR_NAME>Dave Sussman</AUTHOR_NAME>

    <SHORT_BIO>David Sussman is a developer, trainer and author who has worked with Wrox on a

      variety of projects. He is also a Microsoft Certified Professional and a Certified

      Solution Developer.

    </SHORT_BIO>

    <PHOTO_URL>http://webdev.wrox.co.uk/resources/authors/sussmand.gif</PHOTO_URL>

    <INTERVIEW_URL>http://webdev.wrox.co.uk/resources/authors/sussmand.htm</INTERVIEW_URL>

  </AUTHORINFO>

  <AUTHORINFO>

    <AUTHOR_NAME>Chris Ullman</AUTHOR_NAME>

    <SHORT_BIO>Chris is a computer science graduate fluent in Visual Basic, Java, SQL and

      Dynamic HTML. He is also a devoted Birmingham City fan and Wrox in-house author.

    </SHORT_BIO>

    <PHOTO_URL>http://webdev.wrox.co.uk/resources/authors/ullmanc.gif</PHOTO_URL>

    <INTERVIEW_URL>http://webdev.wrox.co.uk/resources/authors/ullmanc.htm</INTERVIEW_URL>

  </AUTHORINFO>

  ...

  ...

</AUTHORLIST>

Notice how we include a full URL to the author's photo and interview page. This demonstrates one of the core reasons for using XML to distribute information. Because the file structure is simple text, and in a standard format and structure, anyone can open this ASP page and get the XML data stream sent to them. They can then use it client-side in any way they like, and the URLs will still work because they point to the relevant resources on our server. The XML data could be used a the source to power an 'author info' search engine, combined with XML data from other sources, parsed or modified client-side to extract particular information, and displayed in any way that the recipient wishes.

Here, we're displaying it using client-side data binding. Notice how the two URLs are used with HTML elements that use the DATASRC and DATAFLD attributes in place of the normal HTML attributes. The value of the current record in the recordset is used for the SRC of an <IMG> element when it is data bound, and for the HREF of an <A> element:

<XML ID="dsoBookList" SRC="authorlist.asp"></XML>

...

<TABLE DATASRC="#dsoBookList" CELLSPACING="10">

 <TR>

  <TD ROWSPAN="2"><IMG HSPACE="15" DATAFLD="PHOTO_URL"></TD>

  <TD><SPAN CLASS="auth_name" DATAFLD="AUTHOR_NAME"></SPAN></TD>

 </TR>

 <TR>

  <TD VALIGN="TOP">

   <SPAN DATAFLD="SHORT_BIO"></SPAN><P>

   Read an <B><A DATAFLD="INTERVIEW_URL">interview</A></B> with

   <SPAN DATAFLD="AUTHOR_NAME"></SPAN>

  </TD>

 </TR>

</TABLE>

The result looks like this:

Creating XML Documents with a custom component

Many suppliers provide components that can do the same job as ASP when creating XML documents, and we can of course build our own as well. The code used in the previous section can easily be converted to Visual Basic and wrapped up in an ActiveX DLL. This can provide suitable methods that return the XML document as a string, or stream it direct to the browser using Response.Write. And, like the all-ASP option described above, we can create the XML with whatever structure we like.

Some commercial components that are available for creating XML documents on various server platforms include:

Ø        DB2XML a tool for transforming relational databases into XML documents

See: http://www.informatik.fh-wiesbaden.de/~turau/DB2XML/index.html

Ø        XML-DBMS Java Packages for Transferring Data between XML Documents and Relational Databases

See: http://www.informatik.tu-darmstadt.de/DVS1/staff/bourret/xmldbms/readme.html

Ø        XMLServlet A Java Servlet that uses XML instructions to combine XML or HTML templates with one another and with live database values.

See: http://www.beyond.com/PKSN102998/prod.htm

There is also a useful list of components and other XML resources at http://www.xmlsoftware.com/, which acts as a good starting point in a search for information and software of use in your XML-based applications.

One such component is ASP2XML from Stonebroom Software. Wrox have some sample files available that contain a version of this component, including the source code, so that you can experiment and build you own version. For simple usage, code like this will create an XML file from a data store via a normal connection string—the component uses ADO internally to access the data:

<%

'create the component instance

Set objA2X = Server.CreateObject("Stonebroom.ASP2XML")   

 

' set the values required to get an XML document

strConnect = "connection_string"

strKeyField = "kBookCode"

strQueryString = "SELECT * FROM booklist WHERE tTitle LIKE '%Script%' ORDER BY tTitle"

blnOmitDataTypes = True   'makes binding easier by omitting all attributes from the XML elements

'execute the getXML method

strResult = objA2X.getXML(strConnect, strKeyField, strQueryString, , , , , , blnOmitDataTypes)

Set objA2X = Nothing       'finished with the component

'create the XML data island

Response.Write "<XML ID=" & Chr(34) & "dsoBookList" & Chr(34) & ">" & vbCrlf

Response.Write strResult & vbCrlf

Response.Write "</XML>" & vbCrlf

%>

...

The result when viewing the source of the page in the browser looks something like this:

...

<XML ID="dsoBookList">

<?xml version="1.0"?>

<BookList>

 <A2X_Record>

  <A2X_RecordNumber>00001</A2X_RecordNumber>

  <A2X_UpdateAction>NONE</A2X_UpdateAction>

  <A2X_RecordMatch>kBookCode='1274'</A2X_RecordMatch>

  <kBookCode>1274</kBookCode>

  <dReleaseDate>1998-01-12 00:00:00</dReleaseDate>

  <tTitle>Instant JavaScript</tTitle>

  <tDescription>Learn JavaScript with this book</tDescription>

 </A2X_Record>

 <A2X_Record>

   ...etc...

 </A2X_Record>

</BookList>

</XML>

...

This can easily be used in data binding by adding the appropriate HTML control definitions to the page:

...

<TABLE>

 <TR>

  <TD ALIGN="RIGHT">Book Code:</TD>

  <TD><INPUT TYPE="TEXT" DATASRC="#dsoBookList" DATAFLD="kBookCode" SIZE=10 NAME="txtBookCode"></TD>

 </TR>

 <TR>

  <TD ALIGN="RIGHT">Release Date:</TD>

  <TD><INPUT TYPE="TEXT" DATASRC="#dsoBookList" DATAFLD="dReleaseDate" SIZE=20></TD>

 </TR>

 <TR>

  <TD ALIGN="RIGHT">Title:</TD>

  <TD><INPUT TYPE="TEXT" DATASRC="#dsoBookList" DATAFLD="tTitle" SIZE=45></TD>

 </TR>

 <TR>

  <TD ALIGN="RIGHT">Description:</TD>

  <TD><TEXTAREA DATASRC="#dsoBookList" DATAFLD="tDescription" ROWS="5" COLS="40"></TEXTAREA></TD>

 </TR>

 <TR>

  <TD ALIGN="RIGHT">Action:</TD>

  <TD>

   <SELECT NAME="selUpdate" DATASRC="#dsoBookList" DATAFLD="A2X_UpdateAction" SIZE=1>

    <OPTION VALUE="NONE">NONE

    <OPTION VALUE="UPDATE">UPDATE

    <OPTION VALUE="INSERT">INSERT

    <OPTION VALUE="DELETE">DELETE

   </SELECT>

  </TD>

 </TR>

</TABLE>

...

Here's the result:

Notice that we have added an extra button marked Save, and presented the 'update action' field value in a drop-down list. The component also provides easy updates to the original data, as we'll see shortly, and these features are there to allow it to be used for this purpose.

For those that want to experiment, a sample component based on this one available for the Wrox book Beginning ASP Components (ISBN 1-861002-88-2). It is used in the document management case study described in that book, and creates XML from a database, and handles updates to the data as well. The VB source code for the component is included in the download. More details from http://www.wrox.com/ or http://webdev.wrox.co.uk/books/2882/.

Creating XML Documents with the ADO Persistence methods

In version 2.5 of ADO, as supplied with Windows 2000, new XML-based features have been added. Dave Sussman talked about these in detail, along with the other new features of ADO 2.5, in his session. We'll briefly visit the persistence features that are directly useful for working with XML. ADO 2.5 fully implements the data persistence feature first introduced in ADO 2.1. This includes the ability to persist data from a recordset into XML format using the Save method of the Recordset object with the integral ADO constant value adPersistXML as the 'format' parameter:

objRS.Save "path_and_filename", adPersistXML

In this case, the first parameter specifies the full path and name of the XML file to be created. However, ADO 2.5 introduces the concept of a stream object, which allows data from recordsets to be passed directly from one application to another without being written to and read from disk. We can use this technique to stream the XML created from the recordset contents direct to the browser by simply replacing the path and filename with the ASP Response object:

objRS.Save Response, adPersistXML

Bear in mind that when using ADO like this, we have no control over the structure of the resulting XML document. The structure that is generated by ADO 2.5 includes an XML schema that describes the data structure, followed by a single XML <z:row> element for each record. The following example highlight the actual data in an example XML document:

<xml xmlns:s='uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882'

  xmlns:dt='uuid:C2F41010-65B3-11d1-A29F-00AA00C14882'

  xmlns:rs='urn:schemas-microsoft-com:rowset'

  xmlns:z='#RowsetSchema'>

<s:Schema id='RowsetSchema'>

  <s:ElementType name='row' content='eltOnly' rs:updatable='true'>

    <s:AttributeType name='kBookCode' rs:number='1' rs:writeunknown='true'

                     rs:basecatalog='globalexample' rs:basetable='BookList'

                     rs:basecolumn='kBookCode' rs:keycolumn='true'>

      <s:datatype dt:type='string' dt:maxLength='50' rs:precision='0' rs:maybenull='false'/>

    </s:AttributeType>

    <s:AttributeType name='dReleaseDate' rs:number='2' rs:nullable='true'    

                     rs:writeunknown='true' rs:basecatalog='globalexample'

                     rs:basetable='BookList' rs:basecolumn='dReleaseDate'>

      <s:datatype dt:type='dateTime' rs:dbtype='timestamp' dt:maxLength='16' rs:scale='3'

                     rs:precision='23' rs:fixedlength='true'/>

    </s:AttributeType>

    <s:AttributeType name='tTitle' rs:number='3' rs:nullable='true' rs:writeunknown='true'

                     rs:basecatalog='globalexample'

                     rs:basetable='BookList' rs:basecolumn='tTitle'>

      <s:datatype dt:type='string' dt:maxLength='100' rs:precision='0'/>

    </s:AttributeType>

    <s:AttributeType name='tDescription' rs:number='4' rs:nullable='true'

                     rs:writeunknown='true' rs:basecatalog='globalexample'

                     rs:basetable='BookList' rs:basecolumn='tDescription'>

      <s:datatype dt:type='string' dt:maxLength='255' rs:precision='0'/>

    </s:AttributeType>

    <s:extends type='rs:rowbase'/>

  </s:ElementType>

</s:Schema>

<rs:data>

  <z:row kBookCode='1274' dReleaseDate='1998-01-12T00:00:00' tTitle='Instant JavaScript'

         tDescription='description goes here'/>

  <z:row kBookCode='138x' dReleaseDate='1998-03-13T00:00:00' tTitle='Instant Scriptlets'

         tDescription='description goes here'/>

</rs:data>

</xml>

Also notice that this isn’t actually an XML document, but the section of an HTML page that uses an <XML> element to create an inline data island. This means that we have to include client-side HTML or code to access the data—it isn’t purely XML. Usually, of course, we'll use the XML for data binding, as the <XML> element automatically becomes an XML DSO. One only problem is that it doesn’t include an ID attribute for the <XML> element, but we can get a reference to it in client-side script code by filtering the document.all collection, and then add one dynamically:

<SCRIPT LANGUAGE="VBScript">

Sub window_onload()

  'get a reference to the <XML> data island element

  Set colXML = document.all.tags("XML")

  Set objXML = colXML(0)   'the first and only XML element

  objXML.id = "dsoData"    'set the ID property

End Sub

</SCRIPT>

As an example, the following code uses ADO to create a data island in a page, and then defines a series of three nested tables that will be bound to the data in the new data island. Notice that none of the data binding attributes are set, as at the moment there is no ID in the data island <XML> element:

<%

'create an XML data island from an ADO recordset

strConnect = "connection_string"

strSQL = "SELECT * FROM BookList WHERE tTitle LIKE '%Script%'"

Set objRS = Server.CreateObject("ADODB.Recordset")

objRS.Open strSQL, strConnect, adOpenDynamic, adLockOptimistic, adCmdText

objRS.Save Response, adPersistXML  'save it to the Response

objRS.Close

%>

<!-table to bind DATASRC="#dsoData" -->

<TABLE ID="tblMain">

  <TR><TD>

    <!-table to bind DATASRC="#dsoData" DATAFLD="rs:data"-->

    <TABLE ID="tblSub">

      <TR><TD>

        <!-table to bind DATASRC="#dsoData" DATAFLD="z:row"-->

        <TABLE ID="tblData" CELLPADDING="10">

           <THEAD><TR></TR></THEAD>

           <TBODY><TR></TR></TBODY>

        </TABLE>

      </TD></TR>

    </TABLE>

  </TD></TR>

</TABLE>

...

Notice that the innermost <TABLE> element defined in the HTML has no cells in the two rows. We'll be adding these to the table dynamically with script code later on. All the script in the page is executed in response to the window_onload event. It first adds an ID to the <XML> element, then accesses the nested recordset in the <z:row> section of the XML document, and assigns it to the variable objRecs:

<SCRIPT LANGUAGE="VBScript">

Sub window_onload()

  QUOT = Chr(34) 'double-quote character

  'get a reference to the <XML> data island element

  Set colXML = document.all.tags("XML")

  Set objXML = colXML(0)   'the first XML element

  objXML.id = "dsoData"    'set the ID property

  'get a reference to the recordset in the data island

  Set objRecs = objXML.Recordset

  Set objRecs = objRecs("rs:data").value

  Set objRecs = objRecs("z:row").value

  ...

Now we can get a reference to the heading and body rows of the table, and loop through each of the records in the data section of our recordset. As long as the field name is not "$Text", we have a valid field containing data that we want to place into the page:

  ...

  'get a reference to the TR row of the innermost TABLE element

  Set objTableHead = document.all("tblData").rows(0)

  Set objTableRow = document.all("tblData").rows(1)

  'loop through the fields in the recordset

  intSpanID = 0

  For Each objField in objRecs.Fields

    strFieldName = objField.Name

    If strFieldName <> "$Text" Then  'this is a field containing data

      ...

However, recall that our table has no cells in the heading or body rows. Before we can fill in the field name or bind this field value to a cell in the table, we need to create the cells first. This is done with the insertCell method of the table row object (these methods were new in IE5). We put the field name in the new cell in the heading row, then create a new <SPAN> element with an appropriate DATAFLD attribute and insert it into the new cell in the body row:

 

      ...

      'create a new cell in the heading row of the table

      objTableHead.insertCell

      'get a reference to it, i.e. the last one in the cells collection

      Set objTableCell = objTableHead.cells(objTableHead.cells.length 1)

      objTableCell.innerHTML = "<B>" & strFieldName & "</B>"

      'create a new cell in the body row of the table

      objTableRow.insertCell

      'get a reference to it, i.e. the last one in the cells collection

      Set objTableCell = objTableRow.cells(objTableRow.cells.length 1)

      'create a SPAN element with appropriate DATAFLD attribute

      strNewSpanElem = "<SPAN DATAFLD=" & QUOT & strFieldName & QUOT & "></TD>"

      'insert the SPAN element into the cell

      objTableCell.innerHTML = strNewSpanElem

   End If

  Next

  ...

Now, having looped through all the records in the <z:row> section of the XML document and created a nested table structure with the correct DATAFLD attributes, we have to add the bindings to each of the tables to make the final connection between the data island and the elements in the page. This is simply a matter of assigning the appropriate dataFld and dataSrc attribute values to the tables:

  ...

  document.all("tblData").dataFld="z:row"

  document.all("tblData").dataSrc="#dsoData"

  document.all("tblSub").dataFld="rs:data"

  document.all("tblSub").dataSrc="#dsoData"

  document.all("tblMain").dataSrc="#dsoData"

End Sub

</SCRIPT>

The result after all this effort is a data-bound table showing the values from the ADO-persisted XML:

 

Batch Updating XML Document Files

While all of these methods can create XML files that are absolutely up-to-date compared to the data source, that might not always be necessary, and you may wish to reduce server loading by batch updating the XML file as specific intervals. Remember that ADO can write the XML to disk as a file, and the same can be done quite easily in ASP (using the FileSystemObject) or in your custom components (using whatever native file-handling code they implement).

For instance, you may prefer to create the XML file weekly, daily or on the hour—depending on how often it changes and the importance of having the most up-to-date content in it. And, if the file gets 5,000 hits per day, updating it every minute would be enough to save the code that creates it from executing for 3 of the 4 hits it gets every minute.

...

'create new file, overwriting any existing one

Set objFSO = CreateObject("Scripting.FileSystemObject")

Set objFile = objFSO.CreateTextFile("path_and_filename", True)

QUOT = Chr(34)   'double quote character

objFile.WriteLine "<?xml version=" & QUOT & "1.0" & QUOT & "?>"

objFile.WriteLine "<BOOKLIST>"

...

... iterate through the records writing the data as XML elements to the file

...

objFile.WriteLine "</BOOKLIST>"

objFile.Close 

...

Updating the source data from an XML Document

In all of the above, we still didn’t answer one obvious question: How do we handle updates to the data back on the server? The client can cache the recordset we create, and edit the contents using data-bound controls or by executing script that modifies the contents of the DSO's recordset directly. What the XML DSO's can’t do is arrange for that data to be automatically updated back on the server.  We can get round this in at least three ways:

Ø        With ASP script and the MSXML (or another) parser component

Ø        With a custom component designed to handle source data updating

Ø        With the ADO Persistence methods

There is one other issue to consider first, however. How do we get an XML document back to the server? The Remote Data Service DSO spoils us by doing this automatically when we execute its SubmitChanges method. When we get to be big boys and girls, and use XML to transfer our data back and forth, we need to find a way of handling this ourselves—or at least until Microsoft come up with a way of automating it (hint: keep your eye on DAV).

Getting an XML Document back to the Server

So one of the things that we have to accomplish before we can actually do any updating of the original data, when the updates to the original XML document are being done on the client, is to get the XML document containing the update information from the client back to the server. Probably the easiest way is to post it back from a <FORM> on the page, and then access it in our ASP page from the Request.Form collection. Here, we're going to get the document back to the server via a HIDDEN-type HTML control on the page:

...

<FORM NAME="frmUpdate" ACTION="update_source.asp" METHOD="POST">

  <INPUT TYPE="HIDDEN" NAME="hidXML">

</FORM>

...

In our page we add client-side script code that is executed in response to a user clicking some button to indicate that they have finished updating the data. The code places the XML string containing the update information into the hidden control, and submits the form to the server:

...

document.all("hidXML").value = strUpdates   'string holding XML document with the update info

document.all("frmUpdate").submit            'submit the form to the server

...

Now, the XML string can be obtained from Response.Form("hidXML").

Updating XML Documents with ASP script and the MSXML component

One of the components of Internet Explorer 5 and Windows 2000 is a C++ parser for XML. This is commonly known as the MSXML parser or MSXML component, though it has a class string (Prog ID) of "microsoft.XMLDOM". The 'DOM' bit stands for Document Object Model. This is a recommendation from the World Wide Web Consortium (W3C) that specifies details of the interfaces that an XML parser or application should provide for accessing any XML document that it hosts 

Of course, standardization is a great thing, and should always be encouraged on the 'Net where we expect our code and pages to transcend all physical boundaries (well, work on Unix/Linux-based and Windows-based systems at least). Of course, things (as usual) don’t work out that neatly because the W3C only describe the theoretical side of the specifications, and leaves it to manufacturers to put it into practice in working software. However, all this is straying from our main topic.

The MSXML component can load an XML document and present it to our code as a tree. It also provides a series of W3C-recommended methods that we can use to traverse the tree, and read the values. Basically, MSXML does the hard work of breaking our XML document down into a manageable and navigable structure.

When working with the MSXML component, we can write the XML string sent from the browser to the server's disk using the FileSystemObject in our ASP page, and then open it into the component by specifying the file path and name in a call to the load method:

'create an instance of the MS XML Parser on the server and load the XML file

Set objXML = Server.CreateObject("microsoft.XMLDOM")

objXML.load "path_and_filename"

...

However, it's easier just to feed it directly to the component as a string, this time using the loadXML method:

'create an instance of the MS XML Parser on the server and load the XML file

Set objXML = Server.CreateObject("microsoft.XMLDOM")

objXML.loadXML Request.Form("txtXML")

...

Once the document has loaded, we can check to see if it was parsed OK, in other words if it is a well-formed document. If not, we display an error message and abort the page:

...

'see if it loaded OK, i.e. is a well-formed XML file

If objXML.parseError.errorCode <> 0 Then

  'error loading document so display error details

  strError = "<B>Invalid XML file !</B><BR>" _

           & "File URL: " & objXML.parseError.url & "<BR>" _

           & "Line No.: " & objXML.parseError.line & "<BR>" _

           & "Character: " & objXML.parseError.linepos & "<BR>" _

           & "File Position: " & objXML.parseError.filepos & "<BR>" _

           & "Source Text: " & objXML.parseError.srcText & "<BR>" _

           & "Error Code: " & objXML.parseError.errorCode & "<BR>" _

           & "Description: " & objXML.parseError.reason

  Response.Write strError & "</BODY></HTML>"

  Response.End

End If

...

If things went OK, and our XML document has been successfully parsed, we can then use the methods of the DOM to get at the contents. As we extract values from the XML, we use them to build up SQL statements that will update the original data source. Assuming that we have a relatively simple XML document such as:

<?xml version="1.0"?>

<BOOKLIST_UPDATE>

  <ITEM>

    <!-original book code number to use to connect to the correct record -->

    <ORIGINAL_CODE>16-024</ORIGINAL_CODE>

    <!-new values for this record -->

    <CODE>16-041</CODE>

    <CATEGORY>HTML</CATEGORY>

    <RELEASE_DATE>1998-03-07</RELEASE_DATE>

    <TITLE>Instant HTML </TITLE>

  </ITEM>

  <ITEM>

  ... etc ...

  </ITEM>

</BOOKLIST_UPDATE >

This is the kind of code that we could use to update our data source:

...

'completed OK so can access the XML document

'connect to the data source ready to update it

Set objCon = Server.CreateObject("ADODB.Connection")

objCon.Open "connection_string"

'get a reference to the root element of the XML document BOOKLIST_UPDATE

Set objRoot = objXML.documentElement

'get a reference to the collection of ITEM child nodes

Set colItems = objRoot.childNodes

'loop through each ITEM node and collect the values to update the database

For Each objItem in colItems

   'collect the values from the ITEM's child nodes

   Set objNode = objItem.selectSingleNode("ORIGINAL_CODE")

   strOriginalCode = objNode.text

   Set objNode = objItem.selectSingleNode("CODE")

   strNewBookCode = objNode.text

   Set objNode = objItem.selectSingleNode("CATEGORY")

   strNewCategory = objNode.text

   Set objNode = objItem.selectSingleNode("RELEASE_DATE")

   strNewRelDate = objNode.text

   Set objNode = objItem.selectSingleNode("TITLE")

   strNewTitle = objNode.text

 

   'build a SQL string to update the database

   strSQL = "UPDATE booklist SET kBookCode='" & strNewBookCode & "', " _

          & "tCategory='" & strNewCategory & "', " _

          & "tRelDate='" & strNewRelDate & "', " _

          & "tTitle='" & strNewTitle & "' " _

          & "WHERE kBookCode='" & strOriginalCode & "'"

   'execute this statement to update the data source

   objCon.Execute strSQL

Next

%>

The SQL statement that this code creates from our sample XML document is:

UPDATE booklist SET kBookCode='16-041', tCategory='HTML', tRelDate='1998-03-07',

tTitle='Instant HTML' WHERE kBookCode='16-024'

Programming the XML DOM like this is a topic well outside our remit for this session (although so are many others that we've mentioned), and so we'll leave it there. If you want to know more be sure to attend the other sessions in the XML track that cover this in more detail.

Updating XML Documents with a custom component

The custom component we looked at earlier, which created XML for data binding, can also be used to update the original data by feeding it a suitably formatted XML file. If we change some values in the XML and set the <A2X_UpdateAction> element value to UPDATE for that record, we can get the component to flush the changes back to the data source:

Again, we can use a HIDDEN-type HTML control on the page through which we submit the XML document to the server. When the Save button on the page is clicked, we execute a JScript function named fnSaveChanges. This creates an XML document in the correct format from the values in the cached recordset in the data island, puts it into the hidden control, and submits the form to the server for processing. Notice how we check the value of each record's <A2X_UpdateAction> element, and only marshal the changed records into the result string. There's no point in wasting bandwidth sending back a list of records that haven’t changed:

...

<SCRIPT LANGUAGE="JScript">

function fnSaveChanges() {

 

  // move to last then first record to save current record values

  dsoBookList.recordset.MoveLast();

  dsoBookList.recordset.MoveFirst();

  // get a reference to the recordset in the data island

  var objRS = dsoBookList.recordset;

  var strUpdates = '<BookList>\n';   // string to hold updates XML

  // loop through records collecting any with UpdateAction

  // not set to NONE, and add to the string in strUpdates

  while (! objRS.EOF) {

    if (objRS('A2X_UpdateAction') != 'NONE') {

      strUpdates += ' <A2X_Record>\n'

         + '  <A2X_RecordNumber>' + objRS('A2X_RecordNumber') + '</A2X_RecordNumber>\n'

         + '  <A2X_UpdateAction>' + objRS('A2X_UpdateAction') + '</A2X_UpdateAction>\n'

         + '  <A2X_RecordMatch>'  + objRS('A2X_RecordMatch')  + '</A2X_RecordMatch>\n'

         + '  <kBookCode A2X_DataType="TEXT">' + objRS('kBookCode') + '</kBookCode>\n'

         + '  <dReleaseDate A2X_DataType="DATE">' + objRS('dReleaseDate') + '</dReleaseDate>\n'

         + '  <tTitle A2X_DataType="TEXT">' + objRS('tTitle') + '</tTitle>\n'

         + '  <tDescription A2X_DataType="TEXT">' + objRS('tDescription') + '</tDescription>\n'

         + ' </A2X_Record>\n';

    }

    objRS.MoveNext();

  }

  strUpdates += '</BookList>\n';

  //put updates string into hidden control on form

  document.all('hidXML').value = strUpdates;

  alert('The XML updates document that will be sent to server is:\n\n' + strUpdates)

 

  // submit form to the server where another ASP page can update data source

  document.all('frmUpdate').submit();

}

</SCRIPT>

The result when we edit the record for book code 1576 (as shown earlier) is an alert dialog like the following, after which the XML updates document is sent off the to server for processing:

On the server, our custom component can now update the source data:

'create the component instance and set the connection string value

Set objA2X = Server.CreateObject("Stonebroom.ASP2XML")        

strConnect = "connection_string"

strXML = Request.Form("txtXML")                    'XML document containing update data

lngNumUpdates = objA2X.putXML(strConnect, strXML)  'execute the putXML method

Set objA2X = Nothing                               'finished with the component

'return value is number of updated records or -1 if an error occurred

If lngNumUpdates = -1 Then

   Response.Write "Error, failed to update records"

Else

   Response.Write "Updated " & lngNumUpdates & " records."

End If

Managing Concurrent Updates

Not many commercially available components tackle the thorny problems involved in updating the source data from an XML doicument, because it is several times more complicated that getting it out of the data source in the first place. Again, the Remote Data Service DSO hides much of the complexity from us, but we have to face up to it when we use XML. The big issue is what happens in a multi-user environment, where several people could be updating the data source concurrently?

RDS manages this by keeping a copy of the original values in each of the records that it retrieves and sends to the client. Then, when the SubmitChanges method is called, it examines each one to see if the target record has been changed since the remote (client-cached) recordset was created. If so, it fails the update on these records and produces an error message. The user can then refresh the client-side recordset and examine the failed updates and decide what to do.

To do the same with XML in a custom component is relatively simple, though it does require quite some extra work. The custom component we looked at earlier does this by optionally including the original values in the XML it creates:

<?xml version="1.0"?>

<booklist>

 <A2X_Record>

  <A2X_RecordNumber>00001</A2X_RecordNumber>

  <A2X_UpdateAction>NONE</A2X_UpdateAction>

  <A2X_RecordMatch>kBookCode='1274'</A2X_RecordMatch>

  <kBookCode>1274</kBookCode>

  <dReleaseDate>1998-01-12 00:00:00</dReleaseDate>

  <A2X_Original_dReleaseDate>1998-01-12 00:00:00</A2X_Original_dReleaseDate>

  <tTitle>Instant JavaScript</tTitle>

  <A2X_Original_tTitle>Instant JavaScript</A2X_Original_tTitle>

  <tDescription>Learn JavaScript with this book</tDescription>

  <A2X_Original_tDescription>Learn JavaScript with this book</A2X_Original_tDescription>

 </A2X_Record>

 ... etc ...

</booklist>

When an XML document in this format, with the new values for the fields, is submitted back to the component it checks to see if any of the original value in the fields have changed before performing the update. If so, it fails the update with an error message. This behavior can be overridden, and the changes forced into the data source if required.

Again, for those that want to experiment, there is a sample component based on this one available for the Wrox book Beginning ASP Components (ISBN 1-861002-88-2). The VB source code for the component is included in the download. More details from http://www.wrox.com/ or http://webdev.wrox.co.uk/books/2882/.

If you are using the MSXML parser, or any other parser, on the server to update your source data, you can of course do much the same thing. All you need to do is arrange for the original XML document to contain the original field values twice, and have the client update one copy. Then you can compare the original value from the XML document with the current value in the data store, and only perform the update if they are the same.

Updating XML Documents with the ADO Persistence methods

ADO 2.5 provides us with an automated way to tackle source data updates, in much that same way as we do when using the Remote Data Service. We can get the contents of an XML document into a normal ADO recordset with the Open method of the Recordset object. We have to specify the special 'persistence' OLE-DB provider, and the new integral 'format' constant adCmdFile:

objRS.Open "path_and_filename", "Provider=MSPersist;", , , adCmdFile

The source/destination parameter (here described as path_and_filename) can be a URL if required, or a relative path. However, it can also be a stream. By editing the XML on the client, we can create a recordset that contains updates to the data and then flush these changes back to the original data source. To do so, we use the same method as we would with RDS to update the original source data—we call the UpdateBatch method:

objRS.UpdateBatch

For this to work, we must have first opened a connection to the appropriate data source. Then we assign the connection to the recordset ActiveConnection property and call the UpdateBatch method:

Set objCon = Server.CreateObject("ADODB.Connection")

Set objRS = Server.CreateObject("ADODB.Recordset")

objCon.Open "connection_string"

objRS.Open "path_and_filename"

objRS.ActiveConnection = objCon

objRS.UpdateBatch

objRS.Close

objCon.Close

So, we can use script code or data binding (or any other method) to edit the XML on the client, then use this updated XML document on the server to update our original data source directly via ADO. This example opens an XML document into a recordset, changes a value in a field, and then updates the original data source:

Set objRS = Server.CreateObject("ADODB.Recordset")

objRS.Open "path_and_filename", "Provider=MSPersist;", , , adCmdFile

objRS.Find "field_name = 'value'"

If Not objRS.EOF Then  'found a matching record so update it

  objRS.("field_name") = 'new_value'"

  objRS.Update

  Set objCon = Server.CreateObject("ADODB.Connection")

  objCon.Open "connection_string"

  objRS.ActiveConnection = objCon

  objRS.UpdateBatch   'flush changes to original recordset

  objCon.Close

End If

objRS.Close

Note that this code loads an XML document file stored on the server's disk into the recordset. For this to be a feasible option, we have to have already sent the XML document back from the client to the server, and stored it on disk as a file. This is easy enough we can use the technique we saw earlier of POSTing it from a form on the client and then collecting it from the ASP Request object as a string. We can then use the FileSystemObject in ASP to write the string to disk as a file, ready to be opened it into an ADO recordset.

Posting XML documents to a Stream

However, it's also possible to open an existing stream object that represents a recordset. This allows us to read data directly from the browser and into a recordset object on the server ready to use the UpdateBatch method. On the client, we use script code to do a direct HTTP post to the server using the new XMLHTTP component that is installed with IE5. Here's an example of the technique, using JScript:

<SCRIPT LANGUAGE="JScript">

function postXML() {   

  // create an instance of the XMLHTTP component

  var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");

  // open a connection to your server ready to POST the data

  xmlhttp.open("POST", "http://yourserver.com/update_xml.asp", false);

  // send the complete XML document by referencing the root element

  xmlhttp.send(dsoBookList.XMLDocument);

  // display any response fom the server in the page

  document.write(xmlhttp.ResponseText);

}

</SCRIPT>

On the server, we can check if the request was a POST, and if so open the data stream directly into an ADO recordset by specifying the Request object as the data source. From there, we can update the source data in the same way as before with the UpdateBatch method:

If Request.ServerVariables("REQUEST_METHOD") = "POST" Then

  Set objCon = Server.CreateObject("ADODB.Connection")

  Set objRS = Server.CreateObject("ADODB.Recordset")

  objCon.Open "connection_string"

  objRS.Open Request, , , , adCmdFile

  objRS.ActiveConnection = objCon

  objRS.UpdateBatch

  objRS.Close

  objCon.Close

End If

Session Summary

We've ranged far and wide in this session looking at topics that involve XML and data binding. When you build XML-based applications, it's often necessary to implement much of the plumbing and data handling yourself, rather than relying on some clever built-in functions of the operating system or client application to do all the work. Data binding provides just one opportunity to provide a dynamic and usable client interface, and there are plenty of other ways you can do this. For example, you can use script code to manipulate XML documents via a client-based or server-based XML parser, such as the MSXML parser or the many others that are available both as free or commercial components.

However, this session was ostensibly about data binding, so we have tried to steer a course through all the possibilities in order to concentrate on the topic in hand. As you have seen, however, we didn't manage to stay on track very well. We wandered off to look at some of the issues involved in creating the XML document in the first place, keeping the data up to date, transmitting it across the network, and managing updates to the original data back on the server.

While all these related topics might seem confusing if you haven't worked with XML much yet, you'll soon start to see how they fit into place and allow us to build high-performance XML-enabled applications. And we've really only scratched the surface. For example, programming the XML Document Object Model (DOM) is a whole can of worms in itself, as you may have guessed from our brief foray into it. And we haven’t evened mention XSL (the XML Stylesheet Language), which adds a whole range of new opportunities for transforming, querying, filtering and sorting XML data on the server or on the client.

So, my apologies go out both to those of you who are overwhelmed with all the different surfaces we have scratched in this session, and also to all those who feel that we didn’t dig into any one of the topics in enough detail. However, there are plenty of XML-specific sessions that you can attend to learn more about all the different topics.

Don’t forget that you can see and download many examples of client-side XML data binding and server side XML data management (including using the DOM and XSL stylesheets) from the Web Developer site at http://webdev.wrox.co.uk/books/1576/:

 

Recent Jobs

Integration Specialist Needed - Wor
Virtualization Server Infrastructur
A great opportunity to Digital Vide
here is a greate opportunity as a S
A great opportunity as a Network En

View all Jobs (Add yours)
View all CV (Add yours)




Chicago Web Site Design
spfxmasks
conference calling
Juicy couture sunglasses
air freshener
odor remover


    Email TopXML  

Front Page Daily Stuff TopXML Forum XML blogs XML Newsgroups BizTalk