7.5. Web Search with RSS

With the ever-expanding technology of the Web, conventional search engines are opening the doors to more unconventional means to get you to the content you desire. The first to jump onto the scene was Yahoo! with their Y!Q service (http://yq.search.yahoo.com/publisher/index.html). This new service enables developers to embed search functionality into any web page. Y!Q provides search results related to the content of the page, giving readers more information without leaving the page.

The Y!Q service is a great idea, but it hasn't surfaced without criticism. The main argument? It requires the use of Yahoo!'s JavaScript, and you have to add a <form/> element meeting the Yahoo! requirements to perform a search. For many web site authors, it takes too much effort to use the service. And after all the work, the search results are presented in the Yahoo! style, breaking the look and feel of the web site.

Thankfully, Yahoo! isn't the only search engine breaking into this type of service. MSN Search (http://search.msn.com) provides a similar service, but it also enables the web developer to control the look, feel, and implementation. This ability comes from MSN Search providing RSS versions of its search results, making it possible to subscribe to a particular search or add the results to your page using Ajax methods.

In mid-2006, Google also jumped into competition for "search from your site" functionality, releasing Google BlogSearch (http://blogsearch.google.com), which provides results returned in either RSS or Atom formats.

7.5.1. The Server-Side Component

To run a search and get the results back in RSS format, a request can be made in the following format:

http://search.msn.com/results.aspx?q=[SEARCHTERM]&format=rss

With this knowledge, it's possible to write server-side code to retrieve the remote feed. Once again, it's necessary to create a server-side proxy to access this information, since it exists on a different server. The URL to request information from the server application looks like this:

websearch.php?search=[SEARCHTERM]

There's only one variable in the query string: search. Therefore, the application should look for this query item:

<?php
header("Content-Type: text/xml");
header("Cache-Control: no-cache");

if ( isset($_GET["search"]) ) {

    $searchTerm = urlencode( stripslashes($_GET["search"]) );

    $url = "http://search.msn.com/results.aspx?q=$searchTerm&format=rss";

    $xml = file_get_contents($url);

    echo $xml;
} else {
    header("HTTP/1.1 400 Bad Request");
}
?>

As you know, the first two lines set the required headers so that the browser will handle the data correctly. The next line of code uses the isset() function to determine whether the search key is present in the query string.

The search term must go through a variety of functions in order to send a proper request to the remote host. First, it is passed to the stripslashes() function. If magic quotes are enabled in the PHP configuration (which is the default), any quote that reaches the PHP engine is automatically escaped with a slash: "search query". The stripslashes() function removes these escape sequence, leaving only "search query". After the slashes' removal, it then goes to the urlencode() function, which properly encodes characters to be used in a query string. Spaces, quotes, ampersands, and other characters are all encoded.

NOTE

If the search term is not encoded like this, the MSN server will return a code 400: Bad Request.

When the search term is ready for transmission, it is included into the URL and stored in the $url variable. Finally, the file_get_contents() function opens the remote file, reads the contents, and returns it as a string to the $xml variable, which is printed to the page using the echo command.

7.5.2. The Client-Side Component

The client-side code departs from the classes created earlier in this chapter. Instead of creating a class and using instances of that class, this widget consists of a static object called msnWebSearch:

var msnWebSearch = {};

This object is created using object literal notation and exposes several methods to get the search results and to draw and position the HTML that contains the data. The first method is drawResultBox(), which draws the HTML for search results in the following format:

<div class="ajaxWebSearchBox">
    <div class="ajaxWebSearchHeading">MSN Search Results
        <a class="ajaxWebSearchCloseLink" href="#">X</a>
    </div>

    <div class="ajaxWebSearchResults">
        <a class="ajaxWebSearchLink" target="_blank" />
        <a class="ajaxWebSearchLink" target="_blank" />
    </div>
</div>

The result box is divided into two parts: a heading and a results pane (see Figure 7-2). The heading tells the user that this new box contains results from an MSN search. It also contains an X, which will close the box. The results pane contains block-style links, which opens a new window when clicked.

Figure 7.2. Figure 7-2

7.5.2.1. Positioning the Search Results

The position() method, as you may have guessed, positions the search box. It accepts two arguments: an event object and the HTMLElement object referencing the result box:

msnWebSearch.position = function (e, divSearchBox) {
    var x = e.clientX + document.documentElement.scrollLeft;
    var y = e.clientY + document.documentElement.scrollTop;

    divSearchBox.style.left = x + "px";
    divSearchBox.style.top = y + "px";
};

The first two lines get the left and top positions to place the search results box. Two pieces of information are required to perform this operation. First is the x and y coordinates of the mouse. This information is stored in the clientX and clientY properties.

These coordinates, however, are insufficient to properly position the results box because the clientX and clientY properties return the mouse position in relation to the client area in the browser window, not the actual coordinates in the page. To account for this, use the scrollLeft and scrollTop properties of the document element. With the final coordinates calculated, you can finally position the box where the user clicked the mouse.

NOTE

Using documentElement to retrieve the scrollLeft and scrollTop properties only works in the browser's standards mode. In "quirks mode," document.body .scrollLeft and document.body.scrollTop must be used.

7.5.2.2. Drawing the Results User Interface

The code to generate this HTML is rather lengthy because the elements are generated using DOM methods. The drawResultBox() method accepts one argument, an event object:

msnWebSearch.drawResultBox = function (e) {
    var divSearchBox= document.createElement("div");
    var divHeading = document.createElement("div");
    var divResultsPane = document.createElement("div");
    var aCloseLink = document.createElement("a");

    //more code to come
};

These first lines create the HTML elements via the createElement() method. After the elements have been created, their properties can be assigned. The first two elements to finalize are a CloseLink and divHeading:

msnWebSearch.drawResultBox = function (e) {
    var divSearchBox= document.createElement("div");
    var divHeading = document.createElement("div");
    var divResultsPane = document.createElement("div");
    var aCloseLink = document.createElement("a");

    aCloseLink.href = "#";
    aCloseLink.className = "ajaxWebSearchCloseLink";
    aCloseLink.onclick = this.close;
    aCloseLink.appendChild(document.createTextNode("X"));

    divHeading.className = "ajaxWebSearchHeading";
    divHeading.appendChild(document.createTextNode("MSN Search Results"));
    divHeading.appendChild(aCloseLink);

    //more code to come
};

A method, close(), becomes the handler for the close link's onclick event. The next group of lines populate the heading <div/> with text and the closing link.

When this result box is drawn into the page, a response from the server application has not yet been received. To show the user that something is happening, a loading message is displayed (see Figure 7-3).

Figure 7.3. Figure 7-3

To create this loading message, create another element and append it to the divResultsPane element:

msnWebSearch.drawResultBox = function (e) {
    var divSearchBox= document.createElement("div");
    var divHeading = document.createElement("div");
    var divResultsPane = document.createElement("div");
    var aCloseLink = document.createElement("a");

    aCloseLink.href = "#";
    aCloseLink.className = "ajaxWebSearchCloseLink";
    aCloseLink.onclick = this.close;
    aCloseLink.appendChild(document.createTextNode("X"));

    divHeading.className = "ajaxWebSearchHeading";
    divHeading.appendChild(document.createTextNode("MSN Search Results"));
    divHeading.appendChild(aCloseLink);

    var divLoading = document.createElement("div");
    divLoading.appendChild(document.createTextNode("Loading Search Feed"));

    divResultsPane.className = "ajaxWebSearchResults";
    divResultsPane.appendChild(divLoading);

    //more code to come
};

This code creates the loading message and appends it to divResultsPane, while also assigning a CSS class name to divResultsPane.

With these elements completed, all that remains is to add them to the divSearchBox element:

msnWebSearch.drawResultBox = function (e) {
    var divSearchBox= document.createElement("div");
    var divHeading = document.createElement("div");
    var divResultsPane = document.createElement("div");
    var aCloseLink = document.createElement("a");

    aCloseLink.href = "#";
    aCloseLink.className = "ajaxWebSearchCloseLink";
    aCloseLink.onclick = this.close;
    aCloseLink.appendChild(document.createTextNode("X"));

    divHeading.className = "ajaxWebSearchHeading";
    divHeading.appendChild(document.createTextNode("MSN Search Results"));
    divHeading.appendChild(aCloseLink);

    var divLoading = document.createElement("div");
    divLoading.appendChild(document.createTextNode("Loading Search Feed"));

    divResultsPane.className = "ajaxWebSearchResults";
    divResultsPane.appendChild(divLoading);

    divSearchBox.className = "ajaxWebSearchBox";
    divSearchBox.appendChild(divHeading);
    divSearchBox.appendChild(divResultsPane);

    document.body.appendChild(divSearchBox);

    //more code to come
};

This code appends the divHeading and divResultsPane elements to the search box and appends the search box to the page.

The final step in drawResultBox() is to position the newly drawn box and return divSearchBox to its caller:

msnWebSearch.drawResultBox = function (e) {
    var divSearchBox= document.createElement("div");
    var divHeading = document.createElement("div");
    var divResultsPane = document.createElement("div");
    var aCloseLink = document.createElement("a");

    aCloseLink.href = "#";
    aCloseLink.className = "ajaxWebSearchCloseLink";
    aCloseLink.onclick = this.close;
    aCloseLink.appendChild(document.createTextNode("X"));

    divHeading.className = "ajaxWebSearchHeading";

divHeading.appendChild(document.createTextNode("MSN Search Results"));
    divHeading.appendChild(aCloseLink);

    var divLoading = document.createElement("div");
    divLoading.appendChild(document.createTextNode("Loading Search Feed"));

    divResultsPane.className = "ajaxWebSearchResults";
    divResultsPane.appendChild(divLoading);

    divSearchBox.className = "ajaxWebSearchBox";
    divSearchBox.appendChild(divHeading);
    divSearchBox.appendChild(divResultsPane);

    document.body.appendChild(divSearchBox);

    this.position(e, divSearchBox);

    return divSearchBox;
};

The way the msnWebSearch object is set up, divSearchBox must be returned to its caller for other operations.

7.5.2.3. Displaying the Results

The populateResults() method populates the result pane with the search results. It accepts two arguments: the element to contain the results and an XParser object.

msnWebSearch.populateResults = function (divResultsPane,oParser) {
    var oFragment = document.createDocumentFragment();

    divResultsPane.removeChild(divResultsPane.firstChild);

    //more code to come
}

This method generates <a/> elements programmatically with DOM methods; these elements are appended to a document fragment created in the first line. The next line removes the loading <div/> element appended in drawResultBox(). The next step is to create the links:

msnWebSearch.populateResults = function (divResultsPane,oParser) {
    var oFragment = document.createDocumentFragment();

    divResultsPane.removeChild(divResultsPane.firstChild);

    for (var i = 0; i < oParser.items.length; i++) {
        var oItem = oParser.items[i];

        var aResultLink = document.createElement("a");
        aResultLink.href = oItem.link.value;
        aResultLink.className = "ajaxWebSearchLink";
        aResultLink.target = "_new";

aResultLink.appendChild(document.createTextNode(oItem.title.value));

        oFragment.appendChild(aResultLink);
    }

    divResultsPane.appendChild(oFragment);
}

This code cycles through the items of the feed, generates links from the data, and appends the <a/> element to the document fragment. When the loop exits, the document fragment is appended to divResultsPane to display the search results.

7.5.2.4. Closing the Results Box

To close the search results box, the msnWebSearch object provides the close() method:

msnWebSearch.close = function () {
    var divSearchBox = this.parentNode.parentNode;
    document.body.removeChild(divSearchBox);

    return false;
};

The search box isn't really closed; in fact, it is removed from the document. To do this, retrieve the divSearchBox element. The first line does this by retrieving the parent node of this element's parent. Because close() handles the onclick event, this references the link. The next line removes the divSearchBox element from the document. The last line, return false, forces the browser not to follow the default behavior of a link (going to the location noted in the href attribute).

7.5.2.5. Building the Search Interface

The last method of the msnWebSearch object is search(), which provides the interface to perform a search. You can call search() with the onclick event of an element. It accepts two methods, an event object and the search term:

msnWebSearch.search = function (e,sSearchTerm) {
    var divSearchBox = this.drawResultBox(e);
    var url = "websearch.php?search=" + encodeURIComponent(sSearchTerm);

    function parserCallback(oParser) {
        msnWebSearch.populateResults(divSearchBox.childNodes[1],oParser);
    }

    xparser.getFeed(url, parserCallback, this);
};

The first line calls the drawResultBox() method and passes the event, e, to it. The next line encodes the URL for proper transmission. The enclosed parserCallback() function is the callback function for XParser, and it will call the populateResult() method when the search feed is finished loading to populate the search box with results. The last line uses the xparser.getFeed() method to retrieve the search feed.

Of course, one of the reasons for building this widget is to make it fit the look of your own site.

7.5.3. Customizing the Web Search Widget

Thanks to CSS, you can easily customize the widget for your existing site and any redesign you may have later down the road.

The first CSS class is ajaxWebSearchBox, the class for the search box. Because the box is positioned to where the mouse was clicked, it must have a position of "absolute":

.ajaxWebSearchBox
{
    position: absolute;
    background-color: #0d1e4a;
    width: 500px;
    padding: 1px;
}

The absolute position is the only requirement. All other properties are optional according to your needs. In this example, the box has a dark-blue background, a width of 500 pixels, and 1 pixel of padding on all four sides. This padding will give the box a 1-pixel border around the box's contents.

The next class is ajaxWebSearchHeading, which contains the box's heading text and the closing link.

.ajaxWebSearchHeading
{
    position: relative;
    background-color: #1162cc;
    font: bold 14px tahoma;
    height: 21px;
    color: white;
    padding: 3px 0px 0px 2px;
}

Once again, the only required property is position. The remaining properties help to give the element a heading-like look. The background color is a lighter blue with white, bold text 14 pixels high and in the Tahoma font. The element's height is 21 pixels, and it is padded on the top and left edges.

The closing link is absolutely positioned in order to place it in the top-right corner:

a.ajaxWebSearchCloseLink
{
    position: absolute;
    right: 5px;
    top: 3px;
    text-decoration: none;
    color: white;
}

a.ajaxWebSearchCloseLink:hover
{
    color: red;
}

The element is positioned 5 pixels from the right and 3 pixels from the top, placing the element in the top-right corner. This link does not have any text decoration and is colored white. When the user hovers over the link, the text color turns red.

NOTE

Note that no visited or active pseudo-classes are used. This is because the window always ignores the href attribute of this link (it has return false in its event handler). Therefore, the link is never truly active or visited.

Next, the ajaxWebSearchResults class styles the results pane:

.ajaxWebSearchResults
{
    background-color: #d3e5fa;
    padding: 5px;
}

There are no required CSS properties for this element. The existing properties are merely to define the results pane and make it relatively easy to read. The background color is a light blue, and 5 pixels of padding surround the edges. You can also style the loading message:

.ajaxWebSearchResults div
{
    text-align: center;
    font: bold 14px tahoma;
    color: #0a246a;
}

This element does not have a class name, but you can still style it by using the parent child notation shown in the preceding example. This example places the text in the center of the <div/> element and gives it a bold, blue font 14 pixels high.

The last elements you need to style are the result links. These have a class name of ajaxWebSearchLink:

a.ajaxWebSearchLink
{
    font: 12px tahoma;
    padding: 2px;
    display: block;
    color: #0a246a;
}

a.ajaxWebSearchLink:hover
{
    color: white;
    background-color: #316ac5;
}

a.ajaxWebSearchLink:visited
{
    color: purple;
}

The only required property is display, which is set to block. This gives every link its own line. The padding, two pixels worth, gives a bit of separation between the links, making them easier to read. The font-face is Tahoma and is 12 pixels high. Their color is a dark blue, giving a nice contrast to the light blue background of ajaxWebSearchResults. When the user hovers over these links, the background color is set to blue, whereas the text color changes to white.

The visited pseudo-class is set, in the last rule in the previous code. This is to provide users with user interface cues they are already used to. By having the visited pseudo-class set to display a color of purple, users know they've already visited that link, which can save them time by not visiting a page they may not want to.

7.5.4. Using the Web Search Widget

Using this widget is simple. First, upload the websearch.php file to your web server. Next, you need an HTML document to reference all the components. The msnWebSearch object relies on the XParser class, which in turn depends on the zXml library. You must reference these files:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xml:lang="en" lang="en"  xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Ajax WebSearch</title>
    <link rel="stylesheet" type="text/css" href="css/websearch.css" />
    <script type="text/javascript" src="js/zxml.js"></script>
    <script type="text/javascript" src="js/xparser.js"></script>
    <script type="text/javascript" src="js/websearch.js"></script>
</head>

<body>


</body>
</html>

To perform a search, set the msnWebSearch.search() method as the element's onclick handler:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xml:lang="en" lang="en"  xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Ajax WebSearch</title>
    <link rel="stylesheet" type="text/css" href="css/websearch.css" />
    <script type="text/javascript" src="js/zxml.js"></script>
    <script type="text/javascript" src="js/xparser.js"></script>
    <script type="text/javascript" src="js/websearch.js"></script>

</head>

<body>

    <a href="#" onclick='msnWebSearch.search(event,""Professional Ajax"");
        return false;'>Search for "Professional Ajax"</a>

    <br /><br /><br /><br />

    <a href="#" onclick='msnWebSearch.search(event,"Professional Ajax");
        return false;'>Search for Professional Ajax</a>


</body>
</html>

The first new link performs a search for the exact phrase Professional Ajax, whereas the second searches for all the words. Also note the return false in the onclick event. Once again, this forces the browser to ignore the href attribute and is required. Clicking these links will draw the search box at the mouse's cursor, and you'll have the search results just pixels away.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset