|
Introduction
For the past 10 years or so, instead of simply using a search engine as my browser's start
page, I've been using my own self-designed page. The page may not look like much, but
what it lacks in looks, it more then makes up for in functionality. It has links to the
sites and admin pages that I tend to access frequently, search boxes to
send queries to the sites I often search, stock quotes for a few different stock symbols,
the latest headlines from some RSS feeds, a link to the company phone list, and a few
other odds and ends. It may not be the right approach for everyone, but I find it
very convenient and a great time saver.
The problem is that, over the years, I've added quite a few things to the page and
it's starting to get cluttered. When I recently went to add a link to the page, I
realized it was time to do some housekeeping and streamline the page some. As I started
removing some of the infrequently used information, I realized that I actually had
eight different search boxes on the page (some search engines, Wikipedia, IMDB,
MSDN Library, etc.). Wanting to reclaim some space, and yet not
wanting to give up any of the functionality, I started thinking why not just have one box and a
drop down list from which you could select which search to use.
Then the idea dawned on me that I had just discussed a relatively similar functionality
in my previous article: An Introduction to OpenSearch.
While I could certainly have accomplished my goal without OpenSearch,
I couldn't find any reason why I shouldn't use it to help accomplish the task.
So I set out to build the little system you'll find below. I'm not sure how many
of you will find it useful as it is, but, if nothing else, it does contains good examples
of how to do lots of different things. These include reading and writing XML, hiding and
showing sections of a page, dynamically populating form elements, and I even left in a
couple Trace.Write lines from when I was debugging.
The User Interface
Before I jump into the code, let's first take a look at the end result so you'll be able to
envision what I was trying to accomplish.
In the simplest form, you type the search terms you want to find into the text box, you select the
search provider you want to use from the DropDownList and you click the "Search" button.
I then decided that it would be nice if you could easily add new selections to the DropDownList.
That's where OpenSearch comes into play. Simply find the link to the OpenSearch description
document of the search provider you want to use, type the link to it into the box, and click
the "Add Provider" button.
The Code
As often happens, I'm finding that writing a decription of the code and then including the
code listing seems almost repetative, so here's the full commented code from the ASP.NET page.
After you look it over, continue reading below and I'll discuss a few of the finer points.
opensearch.aspx
<%@ Page Language="VB" Trace="False" %>
<%@ Import Namespace="System.Xml" %>
<script runat="server">
' Filename of local XML data store. Need NTFS write
' permissions to this file to add new serach providers.
Const LocalXmlFileName As String = "opensearch.xml"
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs)
If Not Page.IsPostBack Then
' If it's the first page load, populate the DropDownList and
' hide the add provider portion of the form.
PopulateProviderDDL()
pnlAddProvider.Visible = False
End If
' Provide a sample OpenSearch description document link.
'If txtProviderDescDoc.Text = "" Then
' txtProviderDescDoc.Text = "http://www.15seconds.com/search/opensearch1.1.xml"
'End If
End Sub
Protected Sub PopulateProviderDDL()
Dim xmlSearchProviders As XmlDocument = New XmlDocument()
Dim nodeProvider As XmlNode
' Read data from local XML file
xmlSearchProviders.Load(Server.MapPath(LocalXmlFileName))
' Loop through providers adding items to the DropDownList
For Each nodeProvider In xmlSearchProviders.SelectSingleNode("Providers").ChildNodes
ddlProviders.Items.Add(New ListItem(nodeProvider.Attributes("Name").Value, _
nodeProvider.Attributes("Url").Value))
Next
End Sub
Protected Sub btnSearch_Click(ByVal sender As Object, ByVal e As System.EventArgs)
Dim strDestinationURL As String
Trace.Write("ddlProviders.SelectedItem.Text = " & ddlProviders.SelectedItem.Text)
Trace.Write("ddlProviders.SelectedItem.Value = " & ddlProviders.SelectedItem.Value)
' Retrieve provider's search Url
strDestinationURL = ddlProviders.SelectedItem.Value
' Substitute in the serach terms and other common parameters
strDestinationURL = Replace(strDestinationURL, "{searchTerms}", txtSearchTerms.Text)
strDestinationURL = Replace(strDestinationURL, "{count}", "10")
strDestinationURL = Replace(strDestinationURL, "{count?}", "")
strDestinationURL = Replace(strDestinationURL, "{startIndex}", "1")
strDestinationURL = Replace(strDestinationURL, "{startIndex?}", "")
strDestinationURL = Replace(strDestinationURL, "{startPage}", "1")
strDestinationURL = Replace(strDestinationURL, "{startPage?}", "")
strDestinationURL = Replace(strDestinationURL, "{language}", "*")
strDestinationURL = Replace(strDestinationURL, "{language?}", "")
strDestinationURL = Replace(strDestinationURL, "{inputEncoding}", "UTF-8")
strDestinationURL = Replace(strDestinationURL, "{inputEncoding?}", "")
strDestinationURL = Replace(strDestinationURL, "{outputEncoding}", "UTF-8")
strDestinationURL = Replace(strDestinationURL, "{outputEncoding?}", "")
' Send browser to the result page at the provider
Response.Redirect(strDestinationURL)
End Sub
Protected Sub btnAddProvider_Click(ByVal sender As Object, ByVal e As System.EventArgs)
Dim xmlProviderDescDoc As XmlDocument = New XmlDocument()
Dim eleProvider As XmlElement
Dim nodeChild As XmlNode
Dim strProviderName As String
Dim strProviderUrl As String
If LCase(Left(txtProviderDescDoc.Text, 7)) = "http://" Then
' Retrieve OpenSearch description document
xmlProviderDescDoc.Load(txtProviderDescDoc.Text)
eleProvider = xmlProviderDescDoc.Item("OpenSearchDescription")
' Find Search Provider's "ShortName"
strProviderName = eleProvider.Item("ShortName").FirstChild.Value
'strProviderUrl = eleProvider.Item("Url").Attributes("template").Value
' Find Search Provider's "Url"
For Each nodeChild In eleProvider.ChildNodes
If nodeChild.Name = "Url" Then
If nodeChild.Attributes("type").Value = "text/html" Then
strProviderUrl = nodeChild.Attributes("template").Value
End If
End If
Next
Trace.Write("strProviderName = " & strProviderName)
Trace.Write("strProviderUrl = " & strProviderUrl)
' Save values to our local XML file.
SaveProviderInfo(strProviderName, strProviderUrl)
' Reload page so the new search option shows in the DropDownList
Response.Redirect(Request.Url.LocalPath)
End If
End Sub
Protected Sub SaveProviderInfo(ByVal strName As String, ByVal strUrl As String)
Dim xmlSearchProviders As XmlDocument = New XmlDocument()
Dim nodeProviders As XmlNode
Dim nodeNewProvider As XmlNode
Dim attName As XmlAttribute
Dim attUrl As XmlAttribute
' Load existing file
xmlSearchProviders.Load(Server.MapPath(LocalXmlFileName))
nodeProviders = xmlSearchProviders.SelectSingleNode("Providers")
' Add new node for new search provider
nodeNewProvider = xmlSearchProviders.CreateNode(XmlNodeType.Element, "Provider", "")
' Add Name and Url attributes and values
attName = xmlSearchProviders.CreateNode(XmlNodeType.Attribute, "Name", "")
attName.Value = strName
nodeNewProvider.Attributes.Append(attName)
attUrl = xmlSearchProviders.CreateNode(XmlNodeType.Attribute, "Url", "")
attUrl.Value = strUrl
nodeNewProvider.Attributes.Append(attUrl)
' Add new data to our XML document
nodeProviders.AppendChild(nodeNewProvider)
' Write data back to the file
xmlSearchProviders.Save(Server.MapPath(LocalXmlFileName))
End Sub
Protected Sub lbShowAdd_Click(ByVal sender As Object, ByVal e As System.EventArgs)
' Toggle add form visibility and link text.
If pnlAddProvider.Visible Then
lbShowAdd.Text = "Add Search"
Else
lbShowAdd.Text = "Hide Add Form"
End If
pnlAddProvider.Visible = Not pnlAddProvider.Visible
End Sub
</script>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>OpenSearch Client Page</title>
</head>
<body>
<form id="myForm" DefaultButton="btnSearch" runat="server">
<div id="searchform">
Search:
<asp:TextBox ID="txtSearchTerms" runat="server" />
<asp:DropDownList ID="ddlProviders" runat="server" />
<asp:Button ID="btnSearch" runat="server"
Text="Search"
OnClick="btnSearch_Click"
/>
<asp:LinkButton ID="lbShowAdd" runat="server"
OnClick="lbShowAdd_Click"
>Add Search</asp:LinkButton>
</div>
<asp:Panel ID="pnlAddProvider" runat="server">
Link to OpenSearch description document:
<asp:TextBox ID="txtProviderDescDoc" runat="server"
Columns="50"
/>
<asp:Button ID="btnAddProvider" runat="server"
Text="Add Provider"
OnClick="btnAddProvider_Click"
/>
</asp:Panel>
</form>
</body>
</html>
The data file that stores the names and search URLs of the providers shown in the DropDownList looks like this:
opensearch.xml
<Providers>
<Provider Name="15 Seconds" Url="http://www.15seconds.com/search/search.asp?query={searchTerms}&start={startIndex?}" />
<Provider Name="About.com" Url="http://search.about.com/fullsearch.htm?terms={searchTerms}" />
<Provider Name="Wikipedia" Url="http://en.wikipedia.org/w/index.php?title=Special:Search&search={searchTerms}" />
</Providers>
On first run, the script calls the "PopulateProviderDDL" routine to read the list of search providers from the
local "opensearch.xml" file. It then populates the DropDownList with the names and URLs of the
available search providers.
When a user enters a search phrase, selects a search provider from the DropDownList, and clicks the "Search"
button, the script takes the URL template and replaces the placeholders with the user-supplied search terms. It then
redirects the user to the results page at the selected provider's site. In terms of the basic funtionality,
that's all there is to it.
The remaining code is what allows users to relatively easily add new providers to the list.
I've split it into two sections (the "Add Provider" button handler and the
"SaveProviderInfo" subroutine) to make things easier to follow.
The "btnAddProvider_Click" routine takes the URL entered by the user,
opens the OpenSearch description document found there, and parses out the
"ShortName" and "Url" entires. It then passes these
values to "SaveProviderInfo" which adds them to the existing
local "opensearch.xml" file. Finally, control returns to
"btnAddProvider_Click" which instructs the browser to reload the page in order to
repopulate the DropDownList so the newly-added search provider will be available.
Conclusion
That's really all there is to it. For those of you not familiar with XML, the code
may seem confusing at first, but once you play with it a little and llok at a few
sample XML files, I'm sure you'll get the hang of it.
|