Reliable Software Logo
Home > Adding Searching to your Website

Displaying the Results


Overview


Since the query page can be called in so many contexts, its code contains a lot of branches. For instance, it will display the results of a search only if the SearchString is non-empty. But then it has to do different things if it's a NewQuery or if it's supposed to UseSavedQuery. In the first case a new record set, RS, is created, with the result set. Otherwise an existing record set is retrieved and used. In either case, the variable ActiveQuery is set, and the listing of the results can proceed. Here's the overall logic:

if (SearchString != "")
{
  if (NewQuery)
  {
     ... Create a new Record Set
    ActiveQuery = true;
  }
  else if (UseSavedQuery)
  {
     ... retrieve a saved Record Set
    ActiveQuery = true;
  } // NewQuery

  if (ActiveQuery)
  {
    if (!RS.EOF)
    {
      ... list a section of the Record Set
    }
    else // RS.EOF
    {
      if (NextRecordNumber == 1)
        Response.Write ("No documents matched the query<br>");
      else
        Response.Write ("No more documents in the query<br>");
    } // ! RS.EOF
  } // ACTIVE QUERY
  ... Next and Previous button logic
} // SearchString 

Record Set

To create a new record set, you have to query a database--in this case, the Content Index. But first some initialization is called for.

First of all, some state has to be kept around between various instantiations of our ASP page. We keep this state in the global Session object, by setting its named properties. So, for instance, when the user clicks on the Next button, we have to save our current query and record set, so that it can be used in the next instatiation of the page (see the processing of Next/Previous farther down).

Since we are now issuing a new query, we have to delete the previous values of Session ("Query") and Session ("Recordset") (it's enough to null the variables to make them eligible for garbage collection). We also set NextRecordNumber to one.

The query is a COM object. We create it by calling the global Server object, asking it to CreateObject. The name of the object is "ixsso.Query". While we are at it, we will also need a utility object "ixsso.util". Now we have to initialize the query. We specify the content index Catalog--in our case this is a local catalog called "relisoft". To say that it's local, we precede it with the double slash and dot: "query://./relisoft". (Your catalog will probably have a different name.) We set the search string, sort (by rank, descending), and the columns we want to retrieve. With so initialized query object, we perform the query to obtain a record set, RS. We set the page size arbitrarily to 10 (ten records per page), and we're ready.

Session ("Query") = null;
Session ("Recordset") = null;
NextRecordNumber = 1;

Q = Server.CreateObject ("ixsso.Query");
util = Server.CreateObject ("ixsso.util");
Q.Catalog = "query://./relisoft";
Q.Query = SearchString;
Q.SortBy = "rank[d]";
Q.Columns = "DocTitle, vpath, path, filename, characterization";
RS = Q.CreateRecordSet("nonsequential");
RS.PageSize = 10;

If instead we are continuing the display of a saved query, we retrieve it, together with the saved record set, from the Session object. We adjust the page number (the variable NextPageNumber has been set when processing the Next or Previous button.

if (Session("Query") != null && Session("RecordSet") != null)
{
  Q = Session("Query");
  RS = Session("RecordSet");
  if (RS.RecordCount != -1 && NextPageNumber != -1)
  {
    RS.AbsolutePage = NextPageNumber;
    NextRecordNumber = RS.AbsolutePosition;
  }
  ActiveQuery = true;
}
else
{
  Response.Write ("ERROR - No saved query");
}

The Listing

The code that displays the results is a little messy, as it contains interleaved JScript and HTML. Note that HTML can be either explicit, between the chunks of JScript, or it could be output using a JScript command, Response.Write. The HTML code is highlighted in the listings.

The record set contains all the values that we have requested. For instance, the file name of the result can be accessed lik this: RS("filename"). In case the result contains special characters, it should be encoded for display in an HTML document. This is done by calling Server.HTMLEncode (RS("filename"))

The record set object behaves like an iterator. To get to the next record you have to increment it, RS.MoveNext (). When the records are exhausted, the value RS.EOF becomes true.

<%
LastRecordOnPage = NextRecordNumber + RS.PageSize - 1;
CurrentPage = RS.AbsolutePage;
if (RS.RecordCount != -1 && RS.RecordCount < LastRecordOnPage)
    LastRecordOnPage = RS.RecordCount;

Response.Write ("<h2>Documents " + NextRecordNumber + " to " + LastRecordOnPage);
if (RS.RecordCount != -1)
    Response.Write (" of " + RS.RecordCount);

Response.Write (" matching the query <i>");
Response.Write (SearchString + "</i>.</h2>&nbsp;<br>");
// BEGIN first row of query results table -->
while (!RS.EOF && NextRecordNumber <= LastRecordOnPage)
{
	// This is the detail portion for Title, Abstract, URL, Size, and Modification Date.
	// If there is a title, display it, otherwise display the virtual path.
%>
<%=NextRecordNumber%>.
<%if (String (RS("DocTitle")) == "null" || RS("DocTitle") == "") {%>
    <b><a href="<%=RS("vpath")%>"><%=Server.HTMLEncode( RS("filename") )%></a></b>
<%} else {%>
    <b><a href="<%=RS("vpath")%>"><%= Server.HTMLEncode(RS("DocTitle"))%></a></b>
<%}%>

<%if (String (RS("characterization")) != "null" && RS("characterization") != "") {%>
<p>
    <b><i>Abstract:  </i></b><%= Server.HTMLEncode(RS("characterization"))%>
<%}%>
<br> 
<%
    RS.MoveNext ();
    ++NextRecordNumber;
} // and while
%>

Next and Previous Buttons

The Previous and Next buttons are displayed conditionally. For the previous button to be displayed, we have to check whether the CurrentPage is not the first page, and that we have any records to display at all. For the next button, we check whether the current record set is not at the last one, !RS.EOF.

The buttons are displayed in separate forms. When either one is clicked, the form is submitted. The action for those two forms is to call the same active page again, this time with a different set of parameters. Note the "hidden" inputs that set the values of InputString and pg (page number). The text displayed on the buttons, their "value" property, is generated programmatically. The previous button displays "Previous N documents", with N set to RS.PageSize. The next button dispalys "Next N documents" or "Next page of documents". The variable SaveQuery is set to true for later use.

<table>
  <tr>
<!--
    This is the "previous" button.
    This retrieves the previous page of documents for the query.
-->
<% 
  var SaveQuery = false;
  if (CurrentPage > 1 && RS.RecordCount != -1) 
  { %>
    <td align="left">
      <form action="<%= QueryFormPath %>" method="POST" id="Form1">
        <input type="hidden" name="InputString" value="<%=SearchString%>" id="Hidden1">
        <input type="hidden" name="pg" value="<%=CurrentPage-1%>" id="Hidden2">
        <input type="submit" value="Previous <%=RS.PageSize%> documents"
               id="Submit1" name="Submit1">
      </form>
    </td>
<%  SaveQuery = true;
  }
  //    This is the "next" button.
  //    This button retrieves the next page of documents for the query.
  //    If the RS.RecordCount is available, the number of
  //    documents on the next page will be displayed.
   
  if (!RS.EOF)
  {%>
    <td align="right">
      <form action="<%= QueryFormPath %>" method="POST" id="Form2">
        <input type="hidden" name="InputString" value="<%=SearchString%>" id="Hidden3">
        <input type="hidden" name="pg" value="<%=CurrentPage+1%>" id="Hidden4">
 
<%   NextString = "Next ";
     if (RS.RecordCount != -1)
     {
        NextSet = (RS.RecordCount - NextRecordNumber) + 1;
        if (NextSet > RS.PageSize)
            NextSet = RS.PageSize
        NextString = NextString + NextSet + " documents";
     }
     else
        NextString = NextString + " page of documents";
               
%>
        <input type="submit" value="<%=NextString%>" id="Submit2" name="Submit2">
      </form>
    </td>
    <%SaveQuery = true;%>
 <%}%>
  </tr>
</table>

Finally, if the SaveQuery variable is set, we save the query and the record set objects inside the Session object, for use in the next instance of our page--the one invoked from the Next or Previous forms.

If SaveQuery is not set, we close the record set and destroy the query, the record set, and the utility objects. We also invalidate the page number, by setting pgNum to -1.

<%
  // If either of the previous or back buttons were displayed, save the query
  // and the recordset in session variables.
  if (SaveQuery)
  {
    Session("Query") = Q;
    Session("RecordSet") = RS;
  }
  else
  {
    RS.close ();
    RS = null;
    Q = null;
    Util = null;
    pgNum = -1;
  }
} // end if SearchString 
%>

The hardest part of implementing the query form is the testing. Unfortunately, if you make any mistake in the ASP page, your server will display a meaningless error message. That's why during the debugging phase, it's best to put a lot of Response.Write statements and comment out the parts that may be causing problems. As of now (June 2006), despite testing, our own search page often behaves erratically. It definitely requires more work.