A Paged Result Set Example

So far we've looked at the discrete pieces of an ADO query, including how to create a Connection object, how to use the Connection to execute a query, and how to iterate through the records in a Recordset. As yet, though, we haven't looked at an integrated sample.

The sample that follows is intended to illustrate how to use the Connection and Recordset objects to execute a query and produce a result set that spans across multiple pages. For an example that illustrates the same effect using the Query and Utility objects, see the samples that accompany the IIS.

The HTML Form for the Pages Exaple

Here's the HTML page for our example, ado_sample.htm:

Here's the HTML source of the Paged ADO search page, containing a <FORM> on which the query will based:

<HTML>
<HEAD><TITLE>Paged ADO Search Sample</TITLE></HEAD>
<BODY>
<H2>Paged ADO Search Sample</H2>
<P>This sample illustrates how to execute a query against 
Index Server using ADO and return the results as separate pages.</P>

<FORM method="POST" action="ado_sample.asp">
 <TABLE border="0" width="100%">
  <TR>
   <TD width="15%">Field:</TD>
   <TD width="85%"><P>
    <SELECT name="Where" size="1">
     <OPTION selected value="Content">Characterization</OPTION>
     <OPTION value="Create">Created</OPTION>
     <OPTION value="DocAuthor">Document Author</OPTION>
     <OPTION value="Directory">Directory</OPTION>
     <OPTION value="FileName">Filename</OPTION>
     <OPTION value="Path">Filepath</OPTION>
     <OPTION value="Size">Size</OPTION>
     <OPTION value="vPath">Virtual Path</OPTION>
    </SELECT></P>
   </TD>
  </TR>
  <TR>
   <TD width="15%">Operator</TD>
   <TD width="85%">
    <SELECT name="Operator" size="1">
     <OPTION value="=">=</OPTION>
     <OPTION value="&gt;">&gt;</OPTION>
     <OPTION value="&lt;">&lt;</OPTION>
     <OPTION value="!=">!=</OPTION>
     <OPTION value="&lt;=">&lt;=</OPTION>
     <option value="&gt;=">&gt;=</OPTION>
    </SELECT>
   </TD>
  </TR>
  <TR>
   <TD width="15%">Value:</TD>
   <TD width="85%"><input type="text" name="Criteria" size="41"></TD>
  </TR>
 </TABLE>

 <TABLE border="0" width="100%">
  <TR>
   <TD width="21%">Scope:</TD>
   <TD width="79%"><input type="text" name="Scope" size="37"></TD>
  </TR>
  <TR>
   <TD width="21%">Search Type:</TD>
   <TD width="79%"><input type="radio" value="Shallow" checked name="Depth">Shallow</TD>
  </TR>
  <TR>
   <TD width="21%"></TD>
   <TD width="79%"><input type="radio" value="Deep" name="Depth">Deep</TD>
  </TR>
 </TABLE>

 <TABLE border="0">
  <TR>
   <TD align="center"><input type="submit" value="Execute Query" name="Action"></TD>
   <TD align="center"><input type="reset" value="Reset" name="Action"></TD>
  </TR>
 </TABLE>
</FORM>
</BODY>
</HTML>

The ASP File for the Paged Example

The page to which the <FORM> in the HTML file we just looked at is posted is somewhat complex. We'll go through it step by step. The first part of the target page defines a few ADO constants required by our code that will call the Recordset object's Open method later in the page:

<%
Const adOpenKeyset = 1
Const adLockReadOnly = 1
Dim strQuery   'to hold our query string
...

Next, we determine whether the user clicked the "New Query" button. You won't find this button anywhere in the HTML page we looked at earlier. That's because we define it later within this page. That's right. After the HTML page above posts its data to this page, the user will get back a page that contains a "New Query" button. A click of this button will cause this page to post to itself. And when they do finally get here from clicking the "New Query" button, we simply redirect them to the original HTML page:

...
If Request("Action") = "New Query" Then
   Response.Redirect("ado_sample.htm")
   Response.End
End If
...

Building the SQL Query

Otherwise, we can proceed to build a query that reflects what the user entered into the HTML file. This input can come from one of two places: the .HTM file, or this file itself. This file takes the form fields in the .HTM file and reproduces them as a group of hidden fields. This ensures that regardless of whether we're on the first or last page of our result set, we'll always be able to reconstitute the query with which the search originated. We've included a function called BuildQuery() (defined at the end of our ASP), which parses user input into an SQL query. Here's the code of that function:

<SCRIPT LANGUAGE=VBScript RUNAT=Server>
Function BuildQuery()
  SQL = "SELECT Filename, Size, Vpath, Path, Write, Characterization FROM "
  If Request("Scope") = "" Then
    SQL = SQL & "SCOPE() "
  Else
    SQL = SQL & "SCOPE('"
    If Request("Depth") = "Shallow" Then
      SQL = SQL & "SHALLOW TRAVERSAL OF " & """" & Request("Scope")
      SQL = SQL & """" & "'" & ")"
    Else
      SQL = SQL & "DEEP TRAVERSAL OF " & """" & Request("Scope")
      SQL = SQL & """" & "'" & ")"
    End If
  End if
  If Request("WHERE") = "Content" Then
    SQL = SQL & " WHERE CONTAINS(" & "'" & Request("Criteria") & "'" & ") > 0"
    BuildQuery = SQL
  ElseIf Request("WHERE") = "Size" Then
    SQL = SQL & " WHERE " & Request("Where") & Request("Operator")
    SQL = SQL & Request("Criteria")
    BuildQuery = SQL
  Else
    SQL = SQL & " WHERE " & Request("Where") & Request("Operator")
    SQL = SQL & " '" & Request("Criteria") & "'"
    BuildQuery = SQL
  End If
End Function
</SCRIPT>   

Back in the main body of the page, our code can use this function, and check that it was successful:

...
'Build up the query string
strQuery = BuildQuery()
If strQuery = "" Then
  Response.Redirect("ado_sample.htm")
  Response.End
End If
...

Opening the Connection to Index Server

If we successfully built a query, it's time to create a Connection object, and to execute the query against that Connection using the Recordset object's Open method:

...
'Create a connection object to execute the query
Set objConn = Server.CreateObject("ADODB.Connection")
objConn.ConnectionString = "provider=msidxs"
objConn.Open
Set objRS = Server.CreateObject("ADODB.RecordSet")
objRS.Open Query, objConn, adOpenKeyset,adLockReadOnly
If objRS.EOF Then
  Response.Write("No records found!")
  Set objRS = Nothing
  objConn.Close
  Set objConn = Nothing
  Response.End
End If
...

If the Recordset we get back is not empty, the next step is to initialize the Recordset object's PageSize property. This essentially dictates that only five records from this Recordset can be examined at a time. When you reach the fifth record, the Recordset's EOF property will evaluate to True.

The Paging Mechanism

And here's where things get tricky, because this is where the paging mechanism comes into play. In the ASP, we determine whether there exists, in the page that posted to this one, a Request field named Scroll. If it does not exist, we can safely conclude that the user posted to this page from our HTML file, and that we should begin with Page 1 of our query. If such a field does exist, however, it means that this page posted to itself—and that we're in the middle of our search results. You'll see where it is defined later in our page.

So we use the value of Scroll to keep track of our position in the query results set. Later in the page we'll define buttons named Scroll, which we'll initialize to contain the word "Page" followed by a number that indicates which page we're on. Identifying that page is just a matter of using the Mid function to parse the value of Scroll:

...
'Set the page number - each page holds five records
objRS.PageSize = 5
Scroll = Request("Scroll")
If Scroll <> "" Then
  Page = mid(Scroll, 5)
  If Page < 1 Then Page = 1
Else
  Page = 1
End If
objRS.AbsolutePage = Page
%>
...

Having determined what page we're on, the last line of the code above sets the Recordset object's AbsolutePage property to contain that page number value. When we set this property, we're effectively instructing the Recordset as to where to begin counting records in the Recordset. It does this by multiplying the value of AbsolutePage by the value of PageSize that we set previously.

The HTML Part of the Page

Now, it's time to fire a page at the user. This is the HTML that creates the visible part, which consists primarily of a table displaying the search results:

...
<HTML>
<HEAD><TITLE>Paged ADO Example</TITLE></HEAD>
<BODY>
<H3>Your query returned the following results:</H3>
<TABLE border="0" width="100%" height="66">

<% RowCount = objRS.PageSize %>
<% Do While Not objRS.EOF And RowCount > 0 %>

 <TR>
  <TD width="20%" align="right"><B>Virtual Path:</B></TD>
  <TD width="80%"><%= objRS("vPath")%></TD>
 </TR>
 <TR>
  <TD width="20%" align="right"><b><strong>Physical Path:</B></TD>
  <TD width="80%"><%= objRS("Path")%></TD>
 </TR>
 <TR>
  <TD width="20%" align="right"><B><strong>Filename:</B></TD>
  <TD width="80%"><%= objRS("Filename")%></TD>
 </TR>
 <TR>
  <TD width="20%" align="right"><B><strong>Size:</B></TD>
  <TD width="80%"><%= objRS("Size") & " bytes"%></TD>
 </TR>
 <TR>
  <TD width="20%" align="right"><B><strong>Last Modified:</B></tD>
  <TD width="80%"><%= objRS("Write")%></TD>
 </TR>
 <TR>
  <TD width="20%" align="right"><B><strong>Excerpt:</B></TD>
  <TD width="80%"><%= objRS("Characterization")%></TD>
 </TR>

<% RowCount = RowCount - 1 %>
<% objRS.MoveNext %>
<% Loop %>

</TABLE>

<% Set objRS = Nothing %>
<% objConn.Close %>
<% Set objConn = Nothing %>

Before we go on, let's just reinforce what you just read. First, within a Do While loop, we initialized a RowCount variable to the Recordset's PageSize property, and with each iteration through the Recordset, we decremented this variable. The sole purpose of doing this is so that we know whether we've reached the end of our scrolling results. Think of it this way: given that RowCount is originally initialized to the Recordset's PageSize, if we run out of data before RowCount reaches 0 that means we've reached the end, not just of one of the Recordset's pages, but the end of the entire Recordset itself.

Inserting Next and Previous Page Buttons

Now, notice how we use the value of RowCount below. If the Page variable is greater than 1, we conclude we need a "previous page" button. If RowCount is equal to zero, we conclude that we haven't reached the end of our Recordset, and that we need a "next page" button. Whether there's a "next page" button, a "previous page" button or both, we give these buttons the name Scroll, and we assign a value to them. The value consists of the word "Page", followed by the number that designates the previous or next page, respectively. When the user clicks one of these buttons to post this page to itself we parse the value of Scroll (as we saw above) to determine where we are in the Recordset, and to determine what the new value of these buttons should be:

...
<FORM METHOD="POST" ACTION="ADO_SAMPLE.ASP">
<INPUT TYPE="SUBMIT" NAME="ACTION" VALUE="New Query">
<INPUT TYPE="HIDDEN" NAME="Scope" VALUE="<%=Request("Scope")%>">
<INPUT TYPE="HIDDEN" NAME="Depth" VALUE="<%=Request("Depth")%>">
<INPUT TYPE="HIDDEN" NAME="Criteria" VALUE="<%=Request("Criteria")%>">
<INPUT TYPE="HIDDEN" NAME="Operator" VALUE="<%=Request("Operator")%>">
<INPUT TYPE="HIDDEN" NAME="Where" VALUE="<%=Request("Where")%>">

<% If Page > 1 Then %>
  <INPUT TYPE="SUBMIT" NAME="Scroll" VALUE="<%="Page " & Page - 1 %>">
<% End If %>
<% If RowCount = 0 Then %>
  <INPUT TYPE="SUBMIT" NAME="Scroll" VALUE="<%="Page " & Page + 1 %>">
<% End If %>

</FORM>
</BODY>
</HTML>

The Result—Paging In Action

Here's the results of the page you see above in action. With the values for the query we entered into the HTML form page earlier in this section, we get the following results:

At the bottom of this page are two buttons—the "New Query" button we mentioned earlier, and the "next page" button:

Clicking the "Page 2" button displays the next five matching records, then adds both "next page" and "previous page" buttons:

Finally, clicking the "Page 3" button takes us to the end of the recordset, and only the "New Query" and "Page 2" buttons are shown. And clicking the "New Query" page button at any point simply takes us back to the original HTML query page:

Passing Query Strings Via HTML Controls

One final note: You may wonder why we store the elements of our query as distinct hidden HTML field controls in the page, instead of generating the appropriate SQL statement once and then passing the entire SQL statement in a hidden field from page to page. The answer lies in the nature of HTML and of the SQL itself.

When you design SQL queries to be executed against Index Server, they're likely to contain a large and varied combination of single and double quotation marks, as well as parameters. When you store such a statement in a hidden field, the browser often doesn't know what to make of the use of quotation marks in the statement. Consequently, you're likely to end up with a stray bracket or quotation mark displayed on your page.

© 1998 by Wrox Press. All rights reserved.