Tutorial: Amazon.com Search API

The Anatomy of a Basic Product Search Engine.

Note: due to changes in the authentication process for Amazon Web Services API, the REST request method described in this tutorial needs to be updated. I will be making changes to the content in the near future.

As discussed in my AJAX article, Amazon offers its web API via a service model, that is to say: delivery of its information product is accomplished through a service designed to function as a back-end to an Associates’ application. The purpose of this tutorial is to examine and construct a simple search application that uses Amazon’s web service as it’s backbone. Before beginning the tutorial, take a brief look at the design of our search application in Figure 1. You will see that the search page has been broken up into ten different sections. The tutorial will focus on the construction of each of these sections.

The Anatomy of a Basic Product Search

Figure 1

The Anatomy of a Basic Product Search

Introduction

The AJAX Framework

Figure 2

The AJAX Framework

Like most web services, Amazon’s Web Service API is based on AJAX[1], short for Asynchronous JavaScript and XML,which combines the technologies of HTML, JavaScript and XML to allow a client application to asynchronously communicate with a web server without interrupting the client’s code execution (e.g., data transference and updating between a browser and a service provider like Amazon will appear to take place seamlessly to the user). To help understand the process, let’s examine the different parts.

The HTML component primarily controls the final presentation of information and consists of (X)HTML and CSS languages (although other languages can be used).

The XML component consists of XML, XSL and XSLT to handle the transference, manipulation and transformation (or display) of information.

Asynchronous transfer of information & interaction between a web server and a browser occurs via the Document Object Model and its construct, XMLHttpRequest.

JavaScript is the language through which the other AJAX technologies can interact.

Although in theory, AJAX functions to provide a seamless interaction between a server and browser, in practice, there is a price that an AJAX developer must pay for that functionality – security.

Cross-Domain Security

Cross-Domain Security

Figure 3

Cross-Domain Security

Since Amazon began offering it’s data as a web service, there has been one major obstacle to developers trying to implement it’s API – Cross-Domain Security.

Because web browsers simultaneously interact with many different internet addresses which allow access to different types of information, rules must exist to protect the data integrity and privacy of sensitive information (i.e. financial information, application code, etc.).

For most content, only interaction with content from the same domain is allowed. For example, a typical page on www.microsoft.com can freely script content on any other page on www.microsoft.com, but cannot script to pages that are located on a different Web domain.

The DHTML Object Model uses the document.domain property to enforce this restriction: only pages with identical domain properties are allowed free interaction. The protocol of the URL must also match. For instance, an HTTP page cannot access HTTPS content. The implications of these restrictions are discussed in more detail in Jason Levitt’s article[2] on XML.com excerpted below.

“But the kind of AJAX examples that you don’t see very often (are there any?) are ones that access third-party web services, such as those from Amazon, Yahoo, Google, and eBay. That’s because all the newest web browsers impose a significant security restriction on the use of XMLHttpRequest. That restriction is that you aren’t allowed to make XMLHttpRequests to any server except the server where your web page came from. So, if your AJAX application is in the page http://www.yourserver.com/junk.html, then any XMLHttpRequest that comes from that page can only make a request to a web service using the domain www.yourserver.com. Too bad — your application is on www.yourserver.com, but their web service is on webservices.amazon.com (for Amazon). The XMLHttpRequest will either fail or pop up warnings, depending on the browser you’re using.”

Jason Levitt
Fixing AJAX: XMLHttpRequest Considered Harmful

Since the Amazon data resides in a different domain from the browser application, we need a method whereby data can be passed across the two domains without violating the security restrictions. Luckily, the Amazon API has answered this puzzle with a combination of two elements of AJAX: JavaScript Object Notation (or JSON) and XSLT.

JavaScript Object Notation (JSON)

JavaScript Object Notation (JSON)

Figure 4

JavaScript Object Notation (JSON)

JSON, short for JavaScript Object Notation, is a lightweight computer data interchange format that is text-based and readable by humans. JSON uses JavaScript to create ‘name : value’ pairs known as associative arrays which it stores in a JavaScript object.

A simple JSON Object:

var fruitstand = { 'fruitA':'apple', 'fruitB':'orange' };

Where ‘fruitstand’ is an object made up of apples and oranges. If you wanted to find out the value of ‘fruitB’ in javascript, you would use:

window.alert(fruitstand.fruitB);

An alert box should display “orange”.

For more information on JSON, view Patrick Hunlock’s article[3].

Because JSON code is text-based, it is compatible with the browser’s JavaScript source which, unlike the HTTP or XML protocols handled by XMLHttpRequest, is not subject to the tighter cross domain security policies and can thus cross domain barriers as a data-string wrapped in JavaScript. The client application uses the HTML > script < tag to make a request to the web service which returns the data formatted in JSON notation.

Since our application needs JSON formatted data but Amazon’s web service natively outputs XML, the web service API uses a XSLT stylesheet to transform the native XML output into text-based JSON output.

Transforming XML with XSLT

Transforming XML with XSLT

Figure 5

Transforming XML with XSLT

XSL is an XML-based language for transforming XML tags into either HTML or an alternate set of XML tags. With XSL, a novice developer can produce rich content without complex parsing or programming knowledge.

Since this isn’t a tutorial on XSL, I will not go into too much detail about how the XSL language is used to parse XML. There are many tutorials that do this, and I strongly recommend reading the following, if you are new to XSL.

XSL Introduction by Jan Egil Refsnes
An introduction to XSL – The style sheet language of XML. What XSL is and what it can do.

XSL Transformation by Jan Egil Refsnes
How XSL can be used to transform XML documents into HTML documents, by inserting a reference to an XSL stylesheet into the XML document.

XSL on the Client by Jan Egil Refsnes
How the XML parser can be used to transform an XML document to an HTML document on the client.

XSL Sorting by Jan Egil Refsnes
How to let the XML parser sort your XML document before it is transformed to HTML.

XSL Filtering by Jan Egil Refsnes
How to let the XML parser filter your XML document before it is transformed to HTML.

Using XSLT with ECS by Amazon Associates Developer’s Guide
A brief overview of how XSLT is used in transforming requests to the Amazon web service.

An important part of the XSLT stylesheet is the namespace attribute. You must first create a namespace attribute with a url that matches the namespace URL that is returned to you in the response to your web services request. The namespace URL will typically be in the second line of the response and will look something like the namespace URL in the example below.

<xsl:stylesheet version="1.0"
xmlns:xsl=http://www.w3.org/1999/XSL/Transform
xmlns:aws="http://webservices.amazon.com/AWSECommerceService/2004-03-19">

In the above example, the third line that starts with xmlns defines a namespace prefix and URL for the style sheet. The namespace prefix is aws, and the namespace URL is http://webservices.amazon.com/AWSECommerceService/2004-03-19. In your style sheet, you may name the namespace prefix anything.

Once you have created your namespace prefix, use it to match elements in the Amazon web service ECS response. For instance, if you are trying to match an element called ItemLookupResponse, and your prefix is aws, the matching string would be aws:ItemLookupResponse.

Below is a sample XSLT stylesheet for the Amazon ECS web service.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:aws="http://webservices.amazon.com/AWSECommerceService/2004-03-19" exclude-result-prefixes="aws">

    <xsl:output method="text"/>
    <xsl:template match="/">
      <xsl:apply-templates select="aws:Items/aws:Item"/>
    </xsl:template>
    <xsl:template match="aws:Items/aws:Item">
      <tr>
        <td style="border-bottom:#C0C0C0 dotted 1px;padding:10px">
          <table cellpadding="0" cellspacing="0" style="width: 90%;padding:5px">
            <tr>
              <xsl:if test="aws:SmallImage/aws:URL">
                <td valign="top" width="50">
                  <img>
                    <xsl:attribute name="src">
                      <xsl:value-of select="aws:SmallImage/aws:URL" />
                    </xsl:attribute>
                    <xsl:attribute name="border">0</xsl:attribute>
                  </img>
                </td>
              </xsl:if>
              <td valign="top">
                <xsl:value-of select="aws:ItemAttributes/aws:Title" />
                <br />
                <span style="font-size:10px">
                  <xsl:if test="aws:ItemAttributes/aws:Author">
                    by <xsl:value-of select="aws:ItemAttributes/aws:Author" />
                  </xsl:if>
                  <xsl:if test="aws:ItemAttributes/aws:Artist">
                    by <xsl:value-of select="aws:ItemAttributes/aws:Artist" />
                  </xsl:if>
                  <xsl:if test="aws:ItemAttributes/aws:Director">
                    by <xsl:value-of select="aws:ItemAttributes/aws:Director" />
                  </xsl:if>
                  <xsl:if test="aws:ItemAttributes/aws:Composer">
                    by <xsl:value-of select="aws:ItemAttributes/aws:Composer" />
                  </xsl:if>
                  <xsl:if test="aws:ItemAttributes/aws:Manufacturer">
                    from <xsl:value-of select="aws:ItemAttributes/aws:Manufacturer" />
                  </xsl:if>
                </span>
                <br />
                <br />
                <span style="font-size:11px;">
                  List Price: <xsl:value-of
                    select="aws:ItemAttributes/aws:ListPrice/aws:FormattedPrice" />
                </span>
              </td>
            </tr>
          </table>
        </td>
      </tr>
    </xsl:template>
</xsl:stylesheet>

Combining JSON and XSLT

Combining JSON and XSLT

Figure 6

Combining JSON and XSLT

By using Amazon’s XSLT service to transform its own XML output into JSON objects, it is very easy to dynamically include this data in a web page. As illustrated in the above diagram, you do this by using the Document Object Model to create and append a new SCRIPT element to the document, which will be evaluated as it loads onto the page.

The script element is appended and/or destroyed at page load using the JSON Scriptbuilder function in the following code:

// JSON ScriptBuilder Function and Prototype, from Jason Levitt of Yahoo!
// code found here: http://devx.com/webdev/Article/30860/1954
    function JSONscriptRequest(fullUrl) {
      this.fullUrl = fullUrl;
      this.noCacheIE = '&noCacheIE=' + (new Date()).getTime();
      this.headLoc = document.getElementsByTagName("head").item(0);
      this.scriptId = 'azScriptId' + JSONscriptRequest.scriptCounter++;
    }
    JSONscriptRequest.scriptCounter = 1;
    JSONscriptRequest.prototype.buildScriptTag = function () {
      this.scriptObj = document.createElement("script");
      this.scriptObj.setAttribute("type", "text/javascript");
      this.scriptObj.setAttribute("src", this.fullUrl + this.noCacheIE);
      restquery = this.fullUrl + this.noCacheIE;
      this.scriptObj.setAttribute("id", this.scriptId);
    }
    JSONscriptRequest.prototype.removeScriptTag = function () {
      this.headLoc.removeChild(this.scriptObj);
    }
    JSONscriptRequest.prototype.addScriptTag = function () {
      this.headLoc.appendChild(this.scriptObj);
    }

Take note of the ‘fullUrl’ argument for the JSONScriptRequest function as this is the most important line of code in the application: the request string to the web service. It includes the url address of the XSLT stylesheet we will use to format and transform Amazon’s XML output into JSON.

Anatomy of a REST Request

Anatomy of a REST Request

Figure 7

Anatomy of a REST Request

Parameters of a REST Request

Figure 8

Parameters of a REST Request

Amazon employs a Representational State Transfer or RESTful architecture in the deployment of its web services. RESTful services usually have addressable resources and employ a well-defined and uniform structure in the syntax used for access to their resources. In this tutorial, REST requests will be used to access the Amazon Associates Web Service. These requests are URLs, as shown in the following example.

http://ecs.amazonaws.com/onca/xml?Service=AWSECommerceService&Operation=ItemSearch
&AWSAccessKeyId=[Access Key ID]&AssociateTag=[ID]&SearchIndex=Apparel&
Keywords=Shirt

If you substituted real IDs in this request and put the entire example in a browser, you would be sending Amazon Associates Web Service a request.

Although the preceding example is in the form you’d enter the request into a browser, it is difficult to read. For that reason, this guide would present the same request as follows.

http://ecs.amazonaws.com/onca/xml?
Service=AWSECommerceService&
Operation=ItemSearch&
AWSAccessKeyId=[Access Key ID]&
AssociateTag=[ID]&
SearchIndex=Apparel&
Keywords=Shirt

General Request Format

Part of every Amazon Associates Web Service request is the same, the other part of the request changes according to the parameters used in the request, as shown in Figure 7.

Request Terms that Remain the Same

The first two lines in the preceding example contain the endpoint, http://ecs.amazonaws.com/onca/xml, and the service name, AWSECommerceService.

The third and fourth lines identify the request submitter. The AWSAcessKey Id is required; it helps identify the request submitter. You receive an AWS Access Key ID when you sign up with Amazon Associates Web Service.

Also included in every request is a signature. This is an SHA-1 encrypted value created using the developer’s Secret Access Key, which given to every developer once they sign up to become an Associate. Because the AWS Access Key ID is sent in the clear, it cannot be used as the sole identity for authenticating the request. Instead, the AWS Access Key ID is used by the Amazon Associates Web Service to look up the Secret Access Key associated with it. If that value matches the one encrypted in the request’s signature parameter, the request is processed.

The other identifier, AssociateTag, is optional. It is an ID for an Associate. If you are an Associate, you must include your Associate tag in each request to receive a referral fee for a customer’s purchase.

Request Terms that Change

The remaining terms in the request vary significantly according to the operation chosen. The terms, however, follow a pattern, as shown in Figure 8.

The Operation parameter is required. Its value is one of the Amazon Associates Web Service operations. These operations are described in the Amazon Associates Web Service API Reference Guide.

The last lines, operation parameters, are representative of parameters that the operation requires, and optional parameters that the operation can use. Requests can contain zero or more operation (up to ten) parameters. These parameters are described in the discussion of each operation in the Amazon Associates Web Service API Reference Guide.

REST Syntax

One of the advantages to using REST is that its syntax is simple, which makes REST requests easy to read. This section summarizes all of the REST syntax rules that you must keep in mind when creating a REST request.

Spaces in Requests

Because a REST request is a URL, there can be no spaces between the parts of a request. A browser will stop reading when it runs across the first space. For example, if the last parameter read, Keywords=Blue Shirts, the request would end on “Blue.” “Shirts” would never be read. If you have key words, such as names, that do have spaces in them, you must URL-encode the space using %20. For the preceding example to work, you would include a URL-encoded space, as follows.

Keywords=Blue%20Shirts

The same problem occurs if you put spaces between the parameters in a request, as shown in the following example.

SearchIndex=Apparel& Keywords=Shirt

In this example, the request would end with “Apparel&.” Often, this kind of mistake returns an error because parameters required by the operation are not read. So, make sure to remove all spaces within a request.

Separator Characters

The question mark (?) and ampersand (&) separate the terms in a REST request. The first term in the request must always be the endpoint, which, in the preceding example, is, http://ecs.amazonaws.com/onca/xml. A question mark always follows the endpoint. The question mark tells the Amazon Associates Web Service web servers to start parsing the request for parameters.

Ampersands separate all of the other parameter name-value pairs in the request. The order of the parameter name-value pairs is inconsequential, as long as they all occur after the question mark.

Setting Parameter Values

Request parameter values are set using the format.

ParameterName=value

The following example is a parameter/value pair.

Operation=ItemSearch

Parameter values must be URL-encoded. There are some characters, such as an asterisk or space, that cannot go into a URL. There are equivalents of these characters that you use in requests instead. For example, the URL encoded equivalent of a space is %20. So, instead of writing Name=John Smith you would write Name=John%20Smith.

Response Groups

A special parameter that is optional for all Amazon Associates Web Service operations is ResponseGroup. Response groups control the kind of information returned by the request. For example, the Large response group returns a great deal of information about the items included in a response, whereas the Medium and Small response groups return less.

REST Syntax Ctd.

Paging and Sorting Through Responses

The only drawback of having so many items at your fingertips is the possibility of receiving too many in a response. Amazon Associates Web Service handles this problem in several ways:

  • Results are returned on page, generally, up to ten results per page
  • The Sort parameter orders results

Paging Through Results

It is possible to create a request that returns many thousands of items in a response. This is problematic for several reasons. Returning all of the item attributes for those items would dramatically impact the performance of Amazon Associates Web Service in a negative way. Also, posting a thousand responses on a web page is impractical.

For that reason, Amazon Associates Web Service developed the strategy of returning results a little at a time. The good news is that you can return any page of results. For example, the first request can return the last page of results. To do that, you have to specify the desired page of results using one of the parameters that enable you to return result pages.

Use the appropriate paging parameter in the request. Operations have their own paging parameters. For example, the following ItemSearch request uses ItemPage to ask for the fourth page of results.

http://ecs.amazonaws.com/onca/xml?
Service=AWSECommerceService&
AWSAccessKeyId=[AWS Access Key ID]&
Operation=ItemSearch&
Keywords=Potter&
SearchIndex=Books&
ItemPage=4

The following snippet of the response shows that the fourth page of results has been returned.

<ItemSearchRequest>
  <ItemPage>4</ItemPage>
  <Keywords>Potter</Keywords>
  <SearchIndex>Books</SearchIndex>
</ItemSearchRequest>
</Request>
<TotalResults>9729</TotalResults>
<TotalPages>973</TotalPages>

Sorting Results

To Sort Results

  1. Consult the Sort Values Appendix to determine available sort values.Available sort values vary by locale and search index.
  2. Add the Sort parameter to a request that uses one of the preceding operations.

For example, the following request returns books with “Harry Potter” in their title or description in alphabetical order.

http://ecs.amazonaws.com/onca/xml?
Service=AWSECommerceService&
AWSAccessKeyId=1MEXAMPLEZBG2&
Operation=ItemSearch&
Keywords=Harry%20Potter&
SearchIndex=Books&
Sort=titlerank&
ItemPage=29&
Version=2006-09-13

Amazon Associates Web Service provides many different sorting criteria, for example, price (high to low, or low to high), salesrank (best to worst selling, or worst to best selling), publication date, review rank, and release date. Valid sort parameters vary by search index, for example, the DigitalMusic search index can be sorted by UploadedDate. That value for Sort would not make sense in the Automotive search index, for example. Sort parameters also differ by locale.

For more information about sort values by locale and search index, see the appendix, ItemSearch Sort Values By Locale.

Default Sort Values

There are many sort values. The majority are not applied unless the Sort parameter is included in the request. There are two sort values, however, that are used by default.

  • For ItemSearch requests that do not use the BrowseNode parameter, results are sorted by Relevance
  • For ItemSearch requests that do use the BrowseNode parameter, results are sorted by BestSeller ranking

Transforming Amazon Associates Web Service Responses into HTML Using XSLT

The role of XSLT in response transformation into JSON was covered before, however, perhaps you want to use a different set of tags in Amazon Associates Web Service responses than those that are returned by default. For example, because you want to display responses on a web page, you want to turn the responses into HTML. You have two choices: you can receive the default Amazon Associates Web Service response and then transform it into HTML (or another set of XML tags) or you can tell Amazon Associates Web Service to do the transformation for you so that the result is ready for you to use.

To make Amazon Associates Web Service do the work for you, you just need to reference an XSL stylesheet in your Amazon Associates Web Service request.

Amazon Associates Web Service provides an XSLT (Extensible Stylesheet Language Transformation) service to ensure that even novice developers can produce rich content without complex parsing or programming. XSL is an XML-based language for transforming XML tags into HTML or any other set of XML tags. To use the Amazon Associates Web Service XSLT service, the request must be in REST, and the XSL style sheet must be referenced using the Style input parameter.

To transform the response

  • Include the Style parameter in your request.
  • The referenced stylesheet must be publicly accessible.

For example, the following ItemSearch request specifies the XSLT stylesheet http://ecs.amazonaws.com/xsl/aws4/item-search.xsl.

http://ecs.amazonaws.com/onca/xml?
Service=AWSECommerceService&
AWSAccessKeyId=[AWS Access Key ID]&
Operation=ItemLookup&
IdType=ASIN&
ItemId=B00008OE6I&
ResponseGroup=Large&
Style=http://ecs.amazonaws.com/xsl/aws4/item-search.xsl

Argument Reflection

An extremely useful feature of the Amazon Associates Web Service is argument reflection: any value passed into the service is reflected back, and can be incorporated into any XSL output. This is very valuable to the service’s extensibility. In this case, by inserting a reference to a callback function in our request, it allows us to “tell” our client application what to do with the response it receives from the web service. This aspect of the request will be examined in more detail later in the tutorial.

For more information on the construction of REST queries for the Amazon ECS, refer the the Amazon Associates Web Service API Reference Guide.


So far, we have covered the roles of AJAX, JSON, XSLT and REST queries in the Amazon ECS.

Next, we’ll start building the application that will parse, transmit and process our REST query.

JavaScript Functions

Our application uses several JavaScript functions to parse (build), transmit and process the request that queries the Amazon ECS for information.

These functions can be included as inline JavaScript statements or as a separate JavaScript attachment. The latter option is a lot neater in my opinion, so the search page will include an attachment to the JavaScript code controlling the web services application.

Search Function

// Search function - creates, builds, and adds remote JSON script
// to the page, which in turn calls amzJSONCallback
var amazonSearch = function(x, s, c, p)
{
   // Determine Sort Variable
   if (s && c)
   {
      if ((s == 'salesrank') && (bssort[c]))
      {
         s = bssort[c];
      }
      else if ((s == 'psrank') && (featuredsort[c]))
      {
         s = featuredsort[c];
      }
      else if ((s == 'price') && (pricedown[c]))
      {
         s = pricedown[c];
      }
      else if ((s == '-price') && (priceup[c]))
      {
         s = priceup[c];
      }
      else
      {
         s = '';
      }
   }

   // Assign default page number
   p = (typeof p == 'undefined') ? '1' : p;

   // Build Request String
   var request = 'http://xml-us.amznxslt.com/onca/xml?Service=AWSECommerceService';
   request += '&SubscriptionId=0AQDYVHJNF45Z9AXKG82';
   request += '&AssociateTag=tropidesig-20';
   request += '&Operation=ItemSearch';
   request += '&Keywords=' + x;
   request += '&SearchIndex=' + c;
   request += '&ItemPage=' + p;
   request += '&Sort=' + s;
   request += '&MerchantId=All';
   request += '&ResponseGroup=OfferFull,Medium,Reviews';
   request += '&Condition=All';
   request += '&Style=http://arstropica.com/dev/amazon/example10/ajsonCategorySearch.xsl';
   request += '&ContentType=text/javascript';
   request += '&CallBack=amzJSONCallback';

   getEl('searchResults').innerHTML = "Loading...";

   // Build Script Object
   aObj = new JSONscriptRequest(request);

   // Build Script Tag
   aObj.buildScriptTag();

   // Append Script Tag to Document
   aObj.addScriptTag();
}

The above code snippet is a JavaScript function that takes four arguments (x, s, c, p): Keyword, Sort, Category and Page, respectively. The function will build our search form output into a REST request and, using a DOM function, append it to head of our search page as the source of a <script> object. The scriptbuilder DOM function is below.

JSON ScriptBuilder Function

// JSON ScriptBuilder Function and Prototype, from Jason Levitt of Yahoo !
// code found here : http : // devx.com / webdev / Article / 30860 / 1954
function JSONscriptRequest(fullUrl)
{
   this.fullUrl = fullUrl;
   this.noCacheIE = '&noCacheIE=' + (new Date()).getTime();
   this.headLoc = document.getElementsByTagName("head").item(0);
   this.scriptId = 'azScriptId' + JSONscriptRequest.scriptCounter ++ ;
}
JSONscriptRequest.scriptCounter = 1;
JSONscriptRequest.prototype.buildScriptTag = function ()
{
   this.scriptObj = document.createElement("script");
   this.scriptObj.setAttribute("type", "text/javascript");
   this.scriptObj.setAttribute("src", this.fullUrl + this.noCacheIE);
   this.scriptObj.setAttribute("id", this.scriptId);
}
JSONscriptRequest.prototype.removeScriptTag = function ()
{
   this.headLoc.removeChild(this.scriptObj);
}
JSONscriptRequest.prototype.addScriptTag = function ()
{
   this.headLoc.appendChild(this.scriptObj);
}

As previously discussed in the JSON section, the <script> object is the part of the DOM responsible for sending the request across the domain boundary to the Amazon ECS Web Service server and accepting the server response as its source. Additionally, several ancilliary functions that will be useful in manipulating the DOM are listed below.

DOM Functions

// Basic DOM shortcut functions
function getEl(x)
{
   return document.getElementById(x)
}
function ctn(x)
{
   return document.createTextNode(x)
}
function cel(x)
{
   return document.createElement(x)
}

Two functions are also included to search string variables. One applies to string arrays, the other to regular string variables.

String Search Functions

String Search functions

// Array Search Function

function in_array(needle, haystack, strict)
{
   // http : // kevin.vanzonneveld.net
   // +   original by : Kevin van Zonneveld (http : // kevin.vanzonneveld.net)
   // *     example 1 : in_array('van', ['Kevin', 'van', 'Zonneveld']);
   // *     returns 1 : true

   var found = false, key, strict = ! ! strict;

   for (key in haystack)
   {
      if ((strict && haystack[key] === needle) || ( ! strict && haystack[key] == needle))
      {
         found = true;
         break;
      }
   }

   return found;
}

// String Search Function

function stristr( haystack, needle, bool )
{
   // http : // kevin.vanzonneveld.net
   // +   original by : Kevin van Zonneveld (http : // kevin.vanzonneveld.net)
   // *     example 1 : stristr('Kevin van Zonneveld', 'Van');
   // *     returns 1 : 'van Zonneveld'
   // *     example 2 : stristr('Kevin van Zonneveld', 'VAN', true);
   // *     returns 2 : 'Kevin '

   var pos = 0;

   pos = haystack.toLowerCase().indexOf( needle.toLowerCase() );
   if( pos == - 1 )
   {
      return false;
   }
   else
   {
      if( bool )
      {
         return haystack.substr( 0, pos );
      }
      else
      {
         return haystack.slice( pos );
      }
   }
}

Note the final parameter of the request variable in the search function is a callback value. This parameter takes advantage of the argument reflection capability of the Amazon Web Service so that it can be reflected back as part of the JSON/XSL response and used to build a function call to the amzJSONCallback function responsible for interpreting the JSON formatted output and converting it to HTML.

1. Keyword Category Search Form

For this tutorial, we shall initiate a single-category keyword search (though, specifying a category is optional for Amazon). Searching by category will allow a demonstration of Amazon’s search features such as sorting.

The search form will include a text input, drop down select box, a hidden input and send button. The text input contains the keyword to be searched, the select input, the item category in which to search and the hidden input contains the default (or updated) sort value for the results.

<form action="" onSubmit="return false;" style="margin:0px;padding:0px">
                        <input type="hidden" name="sort" value="salesrank"/>
                        <input name="field-keywords" type="text" id="asch" value="" onKeyPress="if(event.keyCode==13){amazonSearch(this.value, getEl('sort').value, getEl('SearchIndex').value)}"/>
                        <select name='SearchIndex'>
                          <option value='Classical'>Classical</option>
                          <option value='DVD'>DVD</option>
                          <option value='DigitalMusic'>Digital Music</option>
                          <option value='Electronics'>Electronics</option>
                          <option value='GourmetFood'>Gourmet Food</option>
                          <option value='Grocery'>Grocery</option>
                          <option value='HealthPersonalCare'>Health Personal Care</option>
                          <option value='HomeGarden'>Home Garden</option>
                          <option value='Industrial'>Industrial</option>
                          <option value='Jewelry'>Jewelry</option>
                          <option value='KindleStore'>Kindle Store</option>
                          <option value='Kitchen'>Kitchen</option>
                          <option value='MP3Downloads'>MP3Downloads</option>
                          <option value='Magazines'>Magazines</option>
                          <option value='Marketplace'>Marketplace</option>
                          <option value='Merchants'>Merchants</option>
                          <option value='Miscellaneous'>Miscellaneous</option>
                          <option value='Music'>Music</option>
                          <option value='MusicTracks'>Music Tracks</option>
                          <option value='MusicalInstruments'>Musical Instruments</option>
                          <option value='OfficeProducts'>Office Products</option>
                          <option value='OutdoorLiving'>Outdoor Living</option>
                          <option value='PCHardware'>PC Hardware</option>
                          <option value='PetSupplies'>Pet Supplies</option>
                          <option value='Photo'>Photo</option>
                          <option value='SilverMerchants'>Silver Merchants</option>
                          <option value='Software'>Software</option>
                          <option value='SportingGoods'>Sporting Goods</option>
                          <option value='Tools'>Tools</option>
                          <option value='Toys'>Toys</option>
                          <option value='UnboxVideo'>Unbox Video</option>
                          <option value='VHS'>VHS</option>
                          <option value='Video'>Video</option>
                          <option value='VideoGames'>Video Games</option>
                          <option value='Watches'>Watches</option>
                          <option value='Wireless'>Wireless</option>
                          <option value='WirelessAccessories'>Wireless Accessories</option>
                        </select>
                        <input type="button" onClick="amazonSearch(getEl('asch').value, getEl('sort').value, getEl('SearchIndex').value);" value="search"/>

                      </form>

The above code snippet should produce a form that looks like this.

To illustrate what the output would be without the JSON/XSL transformation, the above search form will return the raw XML response to a keyword search.

Go ahead and try a search in the above form to view what the XML output looks like.

This is the original output from which we will extract and manipulate the information we need using our XSLT stylesheet, JSON and the DOM functions.

Content Types and XSLT

Content Types

Now that we have our raw XML output from the Amazon ECS web service, the next step is to transform the XML into a format or content type we can use in our client application. Amazon currently support two basic formats: xml and text. Though, this is not an accurate assessment of the supported content types as there are multiple content types for each format. What this means is that a browser will interpret different types of text-based data differently depending on the content type. The text format, for instance, contains the plain text/plain, text/html and text/javascript content types.

To better illustrate, let’s look at our search form again – this time with a content-type option between HTML and JavaScript.

As you hopefully observed, the browser treats the same data differently depending on the content type.

Since we will be manipulating the results in JavaScript, we will be using the text/javascript content type in our request parameter.

request += '&ContentType=text/javascript';

The XSLT Stylesheet

The above form triggers the JavaScript Search function posted in the previous section. The search function builds the form output into a REST request that is then submitted to the Amazon ECS Web Service server. As mentioned in the REST Syntax section, in order to turn the response into HTML, we need the Amazon Web Service to transform the XML output into JSON format using our XSL stylesheet. Since I will not be going into how to create a stylesheet, we will use the stylesheet I created for this tutorial which you can view or download here.

XSL Stylesheet

The JSON Object

Structure of a JSON Object

Figure 9
Structure of a JSON Object

JSON objects are made up of comma-separated “pairs” of values called associative arrays. Each value may itself be another array, or any one of several data types. A JSON object may contain several arrays nested within a main array as shown in the Figure 9 above.

Now that we have used the “Style” and “text/javascript” parameters to tell the Amazon Web Service to transform the XML output into JSON, we now need to process the information contained within the JSON object into a coherent HTML format. This is where the callback function comes into play. As previously mentioned, argument reflection enables any request parameter to be passed back with the response, regardless of whether it has an immediate significance to the Amazon Web Service. We can use the argument reflection functionality to make the XSL stylesheet output the JSON object response as a function call by including a “CallBack” parameter.

request += '&CallBack=amzJSONCallback';

In the XSL stylesheet, the function call to the callback parameter becomes the top element containing all the other JSON value “pairs”. See Figure 9. The JSON object is wrapped within the amzJSONCallback function argument.

The CallBack Function

Due to the amount of information we will be pulling out of the JSON object, the callback function is quite complex.

Page with Completed CallBack Function

The function builds the search results page using the DOM functions posted above to populate the relevant HTML elements in which we want to display our search results.

The function may be broken down into ten sections as shown on the diagram at the beginning of the tutorial. The JavaScript code has been loosely annotated so you will be able to identify which code applies to each section but be warned that a lot of the code in the function is integrated so changes to the code may require additional tweaking elsewhere.

The remainder of the tutorial will deal with the various sections of the callback function as a quick reference to which code governs which section of the search result. Of course, there is a related css file governing the appearance of the result interface.

The CallBack Function

// Callback function referenced by the JSON script, This teases the
// JSON object out into DOM Nodes, and appends them to 'searchResults' DIV)
var amzJSONCallback = function(tmpData)
{
   // Set Result Count Variable
   var count = 0;

   // Set Container Div Elements for Results
   var gDiv = getEl('searchResults');
   gDiv.innerHTML = "";

   // Clear any previous results
   var sa_listTop = cel('div');
   sa_listTop.setAttribute("id", "sa_listTop");
   var scoller = cel('div');
   scoller.setAttribute("id", "scoller");
   var iDiv = cel('div');
   iDiv.setAttribute("id", "products");

   // Amazon Logo Display
   var viewOptions = cel('div');
   viewOptions.setAttribute("id", "viewOptions");
   var amzlogo = cel('img');
   amzlogo.setAttribute('src', './images/amazon_sm2.png');
   amzlogo.setAttribute('alt', 'amazon.com tutorial');
   amzlogo.cssText = "border:0px;height:20px;width:104px";
   viewOptions.appendChild(amzlogo);

   // Result Sorting
   var sortBy = cel('div');
   sortBy.setAttribute("id", "sortBy");
   var sortByem = cel('em');
   sortByem.appendChild(ctn("Sort By"));
   sortBy.appendChild(sortByem);
   var sortByul = cel('ul');
   var sortByli1 = cel('li');
   var sortByli2 = cel('li');
   var sortByli3 = cel('li');
   var popularity = cel('a');
   if (in_array(tmpData.ItemSet.sort, bssort))
   {
      popularity.style.cssText = "font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 14px; color: #999999; font-weight:bold;";
   }
   else
   {
      popularity.setAttribute("href", "javascript:amazonSearch('" + tmpData.ItemSet.keywords + "', 'salesrank', '" + tmpData.ItemSet.category + "');");
      popularity.style.cssText = "cursor:hand; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 14px; color: #669966; font-weight:bold; text-decoration:none;";
   }
   popularity.appendChild(ctn("BestSelling"));
   var featured = cel('a');
   if (in_array(tmpData.ItemSet.sort, featuredsort))
   {
      featured.style.cssText = "font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 14px; color: #999999; font-weight:bold;";
   }
   else
   {
      featured.setAttribute("href", "javascript:amazonSearch('" + tmpData.ItemSet.keywords + "', 'psrank', '" + tmpData.ItemSet.category + "');");
      featured.style.cssText = "cursor:hand; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 14px; color: #669966; font-weight:bold; text-decoration:none;";
   }
   featured.appendChild(ctn("Featured"));
   var pricesort = cel('a');
   if (in_array(tmpData.ItemSet.sort, priceup))
   {
      pricesort.setAttribute("href", "javascript:amazonSearch('" + tmpData.ItemSet.keywords + "', 'price', '" + tmpData.ItemSet.category + "');");
      pricesort.style.cssText = "cursor:hand; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 14px; color: #669966; font-weight:bold; text-decoration:none;";
      pricesort.appendChild(ctn("Price" + "\u25bc"));
   }
   else
   {
      pricesort.setAttribute("href", "javascript:amazonSearch('" + tmpData.ItemSet.keywords + "', '-price', '" + tmpData.ItemSet.category + "');");
      pricesort.style.cssText = "cursor:hand; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 14px; color: #669966; font-weight:bold; text-decoration:none;";
      pricesort.appendChild(ctn("Price" + "\u25b2"));
   }
   sortByli1.appendChild(popularity);
   sortByli2.appendChild(featured);
   sortByli3.appendChild(pricesort);
   sortByul.appendChild(sortByli1);
   sortByul.appendChild(sortByli2);
   sortByul.appendChild(sortByli3);
   sortBy.appendChild(sortByul);

   // Loop for each Result Item
   for(i = 0; i < tmpData.ItemSet.Item.length - 1;
   i ++ )
   {
      count ++ ;
      // Update Result Count

      // Set Item Variable
      var tmpItem = tmpData.ItemSet.Item[i];

      // Define Item Container Variables
      var container = cel('div');
      var content = cel('div');
      var maintable = cel('table');
      var tbody = cel('tbody');
      var tr1 = cel('tr');
      var tr2 = cel('tr');
      var td1 = cel('td');
      var td2 = cel('td');
      var td3 = cel('td');
      var subtable = cel('table');
      var subtbody = cel('tbody');
      var subtr = cel('tr');
      var subtd1 = cel('td');
      var subtd2 = cel('td');

      // Define Item Container Attributes
      container.setAttribute("id", "productsContainer");
      content.setAttribute("id", "productsContent");
      maintable.setAttribute("width", "390px");
      subtable.setAttribute("width", "100%");
      td1.setAttribute("width", "100px");
      td1.setAttribute("rowSpan", "2");
      td2.setAttribute("width", "368px");
      subtd1.setAttribute("width", "50%");
      subtd2.setAttribute("width", "50%");

      // Thumbnail Image
      var ig = cel('img');
      var a_ig = cel('a');
      if (tmpItem.thumburl)
      {
         ig.setAttribute('src', tmpItem.thumburl);
         ig.setAttribute('alt', tmpItem.title);
         ig.style.cssText = "border:0px;height:" + tmpItem.thumbdims[0] + "px;width:" + tmpItem.thumbdims[1] + "px";
      }
      else
      {
         ig.setAttribute('src', "./images/cogs.jpg");
         ig.setAttribute('alt', "No Product Image");
         ig.style.cssText = "border:0px;height:52px;width:75px";
      }

      a_ig.style.cssText = "font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 16px; color: #0068B3; font-weight: bold; text-decoration:none;";
      a_ig.setAttribute('href', tmpItem.url);
      a_ig.setAttribute('target', '_blank');

      // Item Title
      var li1 = cel('p');
      li1.style.cssText = "margin-top:0px; margin-bottom:2px;";
      var a1 = cel('a');
      a1.style.cssText = "font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 16px; color: #0068B3; font-weight: bold; text-decoration:none;";
      a1.setAttribute('href', tmpItem.url);
      a1.setAttribute('target', '_blank');
      if(tmpItem.title.length > 30)
      {
         tmpItem.title = tmpItem.title.substr(0, 29) + "..."
      }
      a1.appendChild(ctn(tmpItem.title));
      li1.appendChild(a1);

      // Item Vendor
      var li2 = cel('p');
      li2.style.cssText = "font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 11px; color: #000000; font-weight:normal; text-decoration:none;";
      if(tmpItem.vendor)
      {
         li2.appendChild(ctn("Vendor: " + tmpItem.vendor));
      }

      // Item Price
      var li3 = cel('p');
      li3.style.cssText = "font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 11px; color: #000000; font-weight:normal; text-decoration:none;";
      var a3 = cel('a');
      a3.style.cssText = "cursor:hand; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 11px; color: #990000; font-weight: bold; text-decoration:none;";
      if(tmpItem.lowestnewprice)
      {
         a3.setAttribute("href", "javascript:amazonSearch('" + tmpItem.asin + "', 'price&Condition=New', '" + tmpData.ItemSet.category + "');");
         a3.appendChild(ctn(tmpItem.lowestnewprice + " (" + tmpItem.totalnew + ")"));
         li3.appendChild(ctn("From: "));
         li3.appendChild(a3);
      }
      else if(tmpItem.price)
      {
         li3.appendChild(ctn("Price: " + tmpItem.price));
      }
      else if(tmpItem.lowestusedprice)
      {
         li3.appendChild(ctn("From: " + tmpItem.lowestusedprice + " (" + tmpItem.totalused + ") used"));
      }

      // Item Description
      var li4 = cel('p');
      li4.style.cssText = "font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 10px; color: #333333; font-weight:normal; margin-top:0px; margin-bottom:2px;";
      if(tmpItem.desc)
      {
         // RegEx used to strip out extraneous HTML and Entities in Description text
         tmpItem.desc = tmpItem.desc.replace(/<.*?>/gi, '');
         tmpItem.desc = tmpItem.desc.replace(/&.*?;/gi, ' ');
         if(tmpItem.desc.length > 121)
         {
            tmpItem.desc = tmpItem.desc.substr(0, 120) + "..."
         }
         li4.appendChild(ctn(tmpItem.desc));
      }

      // Ratings, Sellers & Reviews
      // Define Containers
      var subInfo = cel('div');
      subInfo.setAttribute("id", "subInfo");
      var subInfoUl = cel('ul');
      var subInfoLi1 = cel('li');
      var subInfoLi2 = cel('li');
      var subInfoLi3 = cel('li');
      var subInfoA1 = cel('a');
      var subInfoA2 = cel('a');
      var subInfoA3 = cel('a');
      var subInfoIg = cel('img');
      var selliconIg = cel('img');

      // Ratings
      subInfoIg.setAttribute('src', rateimage(tmpItem.avgrating));
      subInfoIg.setAttribute('alt', tmpItem.avgrating + ' Star Average');
      subInfoIg.setAttribute('align', 'center');
      subInfoIg.style.cssText = "border:0px;height:11px;width:60px;clear:none;margin-right: 5px;padding-right: 5px;";
      subInfoA1.style.cssText = "font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 8px; color: #0068B3; font-weight: bold;";
      subInfoA1.setAttribute('href', tmpItem.url);
      subInfoA1.setAttribute('target', '_blank');
      subInfoA1.appendChild(subInfoIg);

      // Reviews
      subInfoA2.style.cssText = "font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 8px; color: #0068B3; font-weight: bold;";
      subInfoA2.setAttribute('href', tmpItem.url);
      subInfoA2.setAttribute('target', '_blank');
      subInfoA2.appendChild(ctn(tmpItem.totalreviews + ' Reviews'));
      if (Number(tmpItem.totalreviews) > 0)
      {
         subInfoLi1.appendChild(subInfoA1);
         subInfoLi2.appendChild(subInfoA2);
      }
      else
      {
         subInfoLi1.style.cssText = "font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 8px; color: #666666; font-weight: bold; ";
         subInfoLi1.appendChild(ctn('No Votes'));
         subInfoLi2.appendChild(subInfoA2);
      }

      // Sellers
      selliconIg.setAttribute('src', 'images/sa_seller_ico.gif');
      selliconIg.setAttribute('alt', tmpItem.totalsellers + ' Sellers');
      selliconIg.style.cssText = "border:0px;height:10px;width:11px;clear:none;margin-right: 5px;";
      subInfoA3.style.cssText = "font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 8px; color: #666666; font-weight: bold;";
      subInfoA3.setAttribute('href', tmpItem.url);
      subInfoA3.setAttribute('target', '_blank');
      subInfoA3.appendChild(selliconIg);
      subInfoA3.appendChild(ctn(tmpItem.totalsellers + ' Sellers'));
      if (Number(tmpItem.totalsellers) > 0)
      {
         subInfoLi3.appendChild(subInfoA3);
      }
      else
      {
         subInfoLi3.style.cssText = "font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 8px; color: #666666; font-weight: bold; ";
         subInfoLi3.appendChild(ctn('No Sellers'));
      }

      // Build Ratings, Reviews & Sellers Display
      subInfoUl.appendChild(subInfoLi1);
      subInfoUl.appendChild(subInfoLi2);
      subInfoUl.appendChild(subInfoLi3);
      subInfo.appendChild(subInfoUl);

      // Build Item Content
      a_ig.appendChild(ig);
      td1.appendChild(a_ig); // Thumbnail
      td2.appendChild(li1); // Title
      td2.appendChild(li4); // Description
      subtd1.appendChild(li2); // Vendor
      subtd2.appendChild(li3); // Price
      subtr.appendChild(subtd1);
      subtr.appendChild(subtd2);
      subtbody.appendChild(subtr);
      subtable.appendChild(subtbody);
      td3.appendChild(subtable);
      tr1.appendChild(td1);
      tr1.appendChild(td2);
      tr2.appendChild(td3);
      tbody.appendChild(tr1);
      tbody.appendChild(tr2);
      maintable.appendChild(tbody);
      content.appendChild(maintable);
      container.appendChild(content);
      container.appendChild(subInfo); // Ratings, Reviews & Sellers
      iDiv.appendChild(container);
      scoller.appendChild(iDiv);
   }
   if(count != 0)
   {
      // Build Result List
      sa_listTop.appendChild(viewOptions);
      sa_listTop.appendChild(sortBy);
      sa_listTop.appendChild(scoller);

      // Build Pagination
      var pagination = paginate (tmpData.ItemSet.pagenum, tmpData.ItemSet.TotalResults, tmpData.ItemSet.category, tmpData.ItemSet.sort, tmpData.ItemSet.keywords);
      sa_listTop.appendChild(pagination);
      gDiv.appendChild(sa_listTop);
      gDiv.appendChild(cel('p'));
   }
   else // If there are no results
   {
      gDiv.appendChild(ctn("sorry, no results found for '" + getEl('asch').value + "'"));
      gDiv.appendChild(cel('p'));
   }
}

Download it here.

2. The Results Display Structure

The Results Display Structure

Figure 10

The Result Display Structure

All search results will be contained in the searchResults

element under the search form on the web page. Using the DOM functions, the data from the response will appended to HTML elements inside the list container
, which itself will be appended as a child node of the searchResults <div> element. The structure of the HTML element hierarchy is displayed in Figure 10 above. An excerpt of the corresponding code in the callback function is listed below.

The Search Result Container

var amzJSONCallback = function(tmpData)
{
   // Set Result Count Variable
   var count = 0;

   // Set Container Div Elements for Results
   var gDiv = getEl('searchResults');
   gDiv.innerHTML = "";

   // Clear any previous results
   var sa_listTop = cel('div');
   sa_listTop.setAttribute("id", "sa_listTop");
   var scoller = cel('div');
   scoller.setAttribute("id", "scoller");
   var iDiv = cel('div');
   iDiv.setAttribute("id", "products");

   // Amazon Logo Display
   var viewOptions = cel('div');
   viewOptions.setAttribute("id", "viewOptions");
   var amzlogo = cel('img');
   amzlogo.setAttribute('src', './images/amazon_sm2.png');
   amzlogo.setAttribute('alt', 'amazon.com tutorial');
   amzlogo.cssText = "border:0px;height:20px;width:104px";
   viewOptions.appendChild(amzlogo);
   .
   .
   .
  // Loop for each Result Item
   for(i = 0; i < tmpData.ItemSet.Item.length - 1;
   i ++ )
   {
      // Update Result Count
      count ++ ;
      .
      .  // Individual Item Information goes here
      .
    }
   if(count != 0)
   {
      // Build Result List
      sa_listTop.appendChild(viewOptions);
      sa_listTop.appendChild(sortBy);
      sa_listTop.appendChild(scoller);
      .
      .
      .
      sa_listTop.appendChild(pagination);
      gDiv.appendChild(sa_listTop);
      gDiv.appendChild(cel('p'));
   }
   else // If there are no results
   {
      gDiv.appendChild(ctn("sorry, no results found for '" + getEl('asch').value + "'"));
      gDiv.appendChild(cel('p'));
   }
}

The above code is intended to show the code governing the creation of the results container structure in the DOM – it is not the completed code for either the container or the search results.

The “For” loop contains the code for appending the data of each individual product item to the DOM. Each item data will reside within separate container <div> elements: ‘productsContainer’ and ‘productsContent’.

Next, we’ll examine the code for creating these individual item containers.

3. The Item Display Structure

In keeping with JSON structure, a “For” loop will iterate for the number of JSON “Item” elements in the JSON object calculated from the length property of the JSON “Item” array. If 10 Items are returned in the result, the loop cycle will process the JSON array element indices numbering 0 to 9. As an error checking measure, a “count” variable will increment each time the loop cycles. For ease of reference, a reference variable is set to correspond with the item element reference name within the JSON object; as with the item array element, this variable name will increment each time the loop cycles.

// Loop for each Result Item
   for(i = 0; i < tmpData.ItemSet.Item.length - 1;
   i ++ )
   {
      count ++ ;
      // Update Result Count

      // Set Item Variable
      var tmpItem = tmpData.ItemSet.Item[i];

      // Define Item Container Variables
      var container = cel('div');
      var content = cel('div');
      var maintable = cel('table');
      var tbody = cel('tbody');
      var tr1 = cel('tr');
      var tr2 = cel('tr');
      var td1 = cel('td');
      var td2 = cel('td');
      var td3 = cel('td');
      var subtable = cel('table');
      var subtbody = cel('tbody');
      var subtr = cel('tr');
      var subtd1 = cel('td');
      var subtd2 = cel('td');

      // Define Item Container Attributes
      container.setAttribute("id", "productsContainer");
      content.setAttribute("id", "productsContent");
      maintable.setAttribute("width", "390px");
      subtable.setAttribute("width", "100%");
      td1.setAttribute("width", "100px");
      td1.setAttribute("rowSpan", "2");
      td2.setAttribute("width", "368px");
      subtd1.setAttribute("width", "50%");
      subtd2.setAttribute("width", "50%");
         .
         .
         .
      }

The item data will reside within a table which will be appended to container <div> elements (more for styling reasons). Each HTML tag must be created as a DOM object and then appended to the appropriate parent object. This is how the resulting HTML structure is constructed.

Next, we’ll examine how to append a product title to the DOM for each result item.

4. The Product Title

As previously mentioned, JSON is based on sets of value pairs, or arrays, which are nested within each other. This nested structure is similar to the way XML is organized and, not surprisingly, both the original XML output and the JSON object share the same organization principles. So, in order to identify where the product title resides within the JSON object, one looks at its XML equivalent and, accounting for any modifications made by the XSL stylesheet, the two structures should correspond. See Figure 11 for an approximate comparison between the structures of the two content types.

XML vs. JSON Structure

Figure 11

XML vs. JSON Structure

The product item data will reside within a table. Due to the hierarchical nature of HTML structure, the DOM object for the table must be built from the inside out: i.e., the innermost tag must first be appended to its parent, which is appended to its parent, and so on. This is done in JavaScript with the appendChild method. Tag attributes are specified using the setAttribute method and style.cssText property.

Product Title Code

      // Item Title
      var li1 = cel('p');
      li1.style.cssText = "margin-top:0px; margin-bottom:2px;";
      var a1 = cel('a');
      a1.style.cssText = "font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 16px; color: #0068B3; font-weight: bold; text-decoration:none;";
      a1.setAttribute('href', tmpItem.url);
      a1.setAttribute('target', '_blank');
      if(tmpItem.title.length > 30)
      {
         tmpItem.title = tmpItem.title.substr(0, 29) + "..."
      }
      a1.appendChild(ctn(tmpItem.title));
      li1.appendChild(a1);

For aesthetic reasons, the product title has been shortened to 30 characters using the javascript substr() function. The variable for the anchor to the amazon url of the product item title is defined and the title is appended as a subnode. Finally, the anchor is appended to a paragraph tag which will reside in one of the cells of the table.

5. The Product Thumbnail

Product Thumbnail Code

      // Thumbnail Image
      var ig = cel('img');
      var a_ig = cel('a');
      if (tmpItem.thumburl)
      {
         ig.setAttribute('src', tmpItem.thumburl);
         ig.setAttribute('alt', tmpItem.title);
         ig.style.cssText = "border:0px;height:" + tmpItem.thumbdims[0] + "px;width:" + tmpItem.thumbdims[1] + "px";
      }
      else
      {
         ig.setAttribute('src', "./images/cogs.jpg");
         ig.setAttribute('alt', "No Product Image");
         ig.style.cssText = "border:0px;height:52px;width:75px";
      }

      a_ig.style.cssText = "font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 16px; color: #0068B3; font-weight: bold; text-decoration:none;";
      a_ig.setAttribute('href', tmpItem.url);
      a_ig.setAttribute('target', '_blank');

Like the product title, the thumbnail’s source url is appended to an IMG element variable. The width and height of the thumbnail are also defined as attributes of the IMG element. Since we also want the image to linked, the IMG element variable, as the innermost tag, is appended to an anchor variable for the product url. In the event that no thumbnail exists for a given item, there is a conditional statement assigning a default placeholder image in the thumbnail element.

Please note:

The anchor element variable containing the image element will be appended to a table cell in another part of the code. The reason the code was written this way is because the order of precedence in which elements are appended affects the layout of the result table. Were we to append the wrong element too early, it would end up situated in different position than desired, so for clarity, I have grouped all the code relating to appending element variables to the table element at the end of the loop code.

6. The Product Vendor

Product Vendor Code

// Item Vendor
      var li2 = cel('p');
      li2.style.cssText = "font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 11px; color: #000000; font-weight:normal; text-decoration:none;";
      if(tmpItem.vendor)
      {
         li2.appendChild(ctn("Vendor: " + tmpItem.vendor));
      }

In the above code for the product vendor, the JSON string is appended to another

element variable. Note that you cannot reuse the same element variable for different data within the loop. Each element, even those of the same type, must be defined in separate variables.

7a. Product Pricing

Product Pricing Code

// Item Price
      var li3 = cel('p');
      li3.style.cssText = "font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 11px; color: #000000; font-weight:normal; text-decoration:none;";
      var a3 = cel('a');
      a3.style.cssText = "cursor:hand; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 11px; color: #990000; font-weight: bold; text-decoration:none;";
      if(tmpItem.lowestnewprice)
      {
         a3.setAttribute("href", "javascript:amazonSearch('" + tmpItem.asin + "', 'price&Condition=New', '" + tmpData.ItemSet.category + "');");
         a3.appendChild(ctn(tmpItem.lowestnewprice + " (" + tmpItem.totalnew + ")"));
         li3.appendChild(ctn("From: "));
         li3.appendChild(a3);
      }
      else if(tmpItem.price)
      {
         li3.appendChild(ctn("Price: " + tmpItem.price));
      }
      else if(tmpItem.lowestusedprice)
      {
         li3.appendChild(ctn("From: " + tmpItem.lowestusedprice + " (" + tmpItem.totalused + ") used"));
      }

Amazon has several values for product pricing, including: Price, Lowest New Price and Lowest Used Price. In the above code for pricing, the conditional statement accounts for which pricing option value is in the response. The order of precedence is artbitrary, so The Lowest Used Price could be prioritized as the first condition instead.

7b. Product Item Description

Product Item Description Code

// Item Description
      var li4 = cel('p');
      li4.style.cssText = "font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 10px; color: #333333; font-weight:normal; margin-top:0px; margin-bottom:2px;";
      if(tmpItem.desc)
      {
         // RegEx used to strip out extraneous HTML and Entities in Description text
         tmpItem.desc = tmpItem.desc.replace(/<.*?>/gi, '');
         tmpItem.desc = tmpItem.desc.replace(/&.*?;/gi, ' ');
         if(tmpItem.desc.length > 121)
         {
            tmpItem.desc = tmpItem.desc.substr(0, 120) + "..."
         }
         li4.appendChild(ctn(tmpItem.desc));
      }

Since the product item description in sometimes written in HTML code, the above code for the product item description includes a javascript replace function to strip out any HTML entities that could compromise the document code. Regular expressions are used to match those characters associated with HTML.

8. Ratings, Reviews & Sellers

Ratings, Reviews & Sellers Code

// Ratings, Sellers & Reviews
      // Define Containers
      var subInfo = cel('div');
      subInfo.setAttribute("id", "subInfo");
      var subInfoUl = cel('ul');
      var subInfoLi1 = cel('li');
      var subInfoLi2 = cel('li');
      var subInfoLi3 = cel('li');
      var subInfoA1 = cel('a');
      var subInfoA2 = cel('a');
      var subInfoA3 = cel('a');
      var subInfoIg = cel('img');
      var selliconIg = cel('img');

      // Ratings
      subInfoIg.setAttribute('src', rateimage(tmpItem.avgrating));
      subInfoIg.setAttribute('alt', tmpItem.avgrating + ' Star Average');
      subInfoIg.setAttribute('align', 'center');
      subInfoIg.style.cssText = "border:0px;height:11px;width:60px;clear:none;margin-right: 5px;padding-right: 5px;";
      subInfoA1.style.cssText = "font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 8px; color: #0068B3; font-weight: bold;";
      subInfoA1.setAttribute('href', tmpItem.url);
      subInfoA1.setAttribute('target', '_blank');
      subInfoA1.appendChild(subInfoIg);

      // Reviews
      subInfoA2.style.cssText = "font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 8px; color: #0068B3; font-weight: bold;";
      subInfoA2.setAttribute('href', tmpItem.url);
      subInfoA2.setAttribute('target', '_blank');
      subInfoA2.appendChild(ctn(tmpItem.totalreviews + ' Reviews'));
      if (Number(tmpItem.totalreviews) > 0)
      {
         subInfoLi1.appendChild(subInfoA1);
         subInfoLi2.appendChild(subInfoA2);
      }
      else
      {
         subInfoLi1.style.cssText = "font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 8px; color: #666666; font-weight: bold; ";
         subInfoLi1.appendChild(ctn('No Votes'));
         subInfoLi2.appendChild(subInfoA2);
      }

      // Sellers
      selliconIg.setAttribute('src', 'images/sa_seller_ico.gif');
      selliconIg.setAttribute('alt', tmpItem.totalsellers + ' Sellers');
      selliconIg.style.cssText = "border:0px;height:10px;width:11px;clear:none;margin-right: 5px;";
      subInfoA3.style.cssText = "font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 8px; color: #666666; font-weight: bold;";
      subInfoA3.setAttribute('href', tmpItem.url);
      subInfoA3.setAttribute('target', '_blank');
      subInfoA3.appendChild(selliconIg);
      subInfoA3.appendChild(ctn(tmpItem.totalsellers + ' Sellers'));
      if (Number(tmpItem.totalsellers) > 0)
      {
         subInfoLi3.appendChild(subInfoA3);
      }
      else
      {
         subInfoLi3.style.cssText = "font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 8px; color: #666666; font-weight: bold; ";
         subInfoLi3.appendChild(ctn('No Sellers'));
      }

      // Build Ratings, Reviews & Sellers Display
      subInfoUl.appendChild(subInfoLi1);
      subInfoUl.appendChild(subInfoLi2);
      subInfoUl.appendChild(subInfoLi3);
      subInfo.appendChild(subInfoUl);

RateImage Function Code

// Get Rating Image Source

function rateimage(score)
{
   var score2img = Array();
   var imgsrc = "";
   score2img['0'] = 'images/rating_0_0_newr.gif';
   score2img['0.5'] = 'images/rating_0_5_newr.gif';
   score2img['1'] = 'images/rating_1_newr.gif';
   score2img['1.5'] = 'images/rating_1_5_newr.gif';
   score2img['2'] = 'images/rating_2_newr.gif';
   score2img['2.5'] = 'images/rating_2_5_newr.gif';
   score2img['3'] = 'images/rating_3_newr.gif';
   score2img['3.5'] = 'images/rating_3_5_newr.gif';
   score2img['4'] = 'images/rating_4_newr.gif';
   score2img['4.5'] = 'images/rating_4_5_newr.gif';
   score2img['5'] = 'images/rating_5_newr.gif';

   score = ((Math.round(score * 2)) / 2);
   var imgsrc = score2img[score];

   return imgsrc;
}

The above code snippets address the display of the average rating, number of reviews and sellers. The graphic ratings display is a selection from a series of star images. The RateImage function rounds the average rating to the nearest 0.5 and assigns a corresponding image source url for the IMG element. All the anchors for this section point to the main item page on Amazon, however, a little additional research may uncover whether there is a separate url for a ratings, review and seller list.

8. Pagination

Pagination Code

// Build Pagination
      var pagination = paginate (tmpData.ItemSet.pagenum, tmpData.ItemSet.TotalResults, tmpData.ItemSet.category, tmpData.ItemSet.sort, tmpData.ItemSet.keywords);
      sa_listTop.appendChild(pagination);

Pagination Function Code

// Pagination function

function paginate (cur, tres, cat, srt, key)
{
   cur = Number(cur);
   tres = Number(tres);
   var buffer = "";
   var tot = Math.ceil(tres / 10);
   var low = Math.max(1, (cur - 2));
   if (cur <= 3)
   {
      var high = Math.min((cur + (5 - cur)), tot);
   }
   else if (cur > 3)
   {
      var high = Math.min(tot, (cur + 2));
   }
   var pagination = cel('div');
   var paginationul = cel('ul');
   var pageli1 = cel('li');
   var pageli2 = cel('li');
   var pageli3 = [];
   var pageli4 = cel('li');
   var pageli5 = cel('li');
   var pageanchor1 = cel('a');
   var pageanchor2 = cel('a');
   var pageanchor3 = [];
   var pageanchor4 = cel('a');
   var pageanchor5 = cel('a');
   var clearing = cel('div');
   var pageright = cel('div');

   pagination.setAttribute("id", "pagination");

   if (cur == 1)
   {
      pageanchor1.setAttribute('href', 'javascript:void(0);');
      pageanchor1.setAttribute('className', 'off');
      pageanchor1.appendChild(ctn('First'));
      pageli1.appendChild(pageanchor1);
      paginationul.appendChild(pageli1);

      pageanchor2.setAttribute('href', 'javascript:void(0);');
      pageanchor2.setAttribute('className', 'off');
      pageanchor2.appendChild(ctn('Prev'));
      pageli2.appendChild(pageanchor2);
      paginationul.appendChild(pageli2);
   }
   else if (cur > 1)
   {
      pageanchor1.setAttribute("href", "javascript:amazonSearch('" + key + "', '" + srt + "', '" + cat + "', '" + "1');");
      pageanchor1.appendChild(ctn('First'));
      pageli1.appendChild(pageanchor1);
      paginationul.appendChild(pageli1);

      pageanchor2.setAttribute("href", "javascript:amazonSearch('" + key + "', '" + srt + "', '" + cat + "', '" + (cur-1) + "');");
      pageanchor2.appendChild(ctn('Prev'));
      pageli2.appendChild(pageanchor2);
      paginationul.appendChild(pageli2);
   }

   for (i = low; i <= high; i ++ )
   {
      pageli3[i] = cel('li');
      pageanchor3[i] = cel('a');
      if (i == cur)
      {
         pageli3[i].setAttribute('className', 'selected');
         pageanchor3[i].appendChild(ctn(i));
         pageanchor3[i].setAttribute('href', 'javascript:void(0);');
         pageanchor3[i].setAttribute('className', 'off');
         pageli3[i].appendChild(pageanchor3[i]);
         paginationul.appendChild(pageli3[i]);
      }
      else
      {
         pageanchor3[i].appendChild(ctn(i));
         pageanchor3[i].setAttribute("href", "javascript:amazonSearch('" + key + "', '" + srt + "', '" + cat + "', '" + i + "');");
         pageli3[i].appendChild(pageanchor3[i]);
         paginationul.appendChild(pageli3[i]);
      }
   }

   if ((tot > 1) && (cur < tot))
   {
      pageanchor4.setAttribute("href", "javascript:amazonSearch('" + key + "', '" + srt + "', '" + cat + "', '" + (cur + 1) + "');");
      pageanchor4.appendChild(ctn('Next'));
      pageli4.appendChild(pageanchor4);
      paginationul.appendChild(pageli4);
   }
   else
   {
      pageanchor5.setAttribute('href', 'javascript:void(0);');
      pageanchor5.setAttribute('className', 'off');
      pageanchor5.appendChild(ctn('Next'));
      pageli5.appendChild(pageanchor5);
      paginationul.appendChild(pageli5);
   }

   pagination.appendChild(paginationul);

   clearing.setAttribute('className', 'clearing');
   clearing.appendChild(ctn(' '));
   pageright.setAttribute('className', 'paginationRight');

   pagination.appendChild(clearing);
   pagination.appendChild(pageright);

   return pagination;
}

Due to its length and complexity, I isolated the pagination section of the callback function in a separate function: paginate. The paginate function takes five arguments: cur, tres, cat, srt, key. Respectively, current page, total results, category (SearchIndex), Sort and Keyword(s). All of these arguments are values in the JSON object response. We will need the first two to calculate forward and backward page movement and the last three are required for the search function.

All the paginate function really does is resubmit the original request to amazon with one parameter change in the page number. The current page and total results are used to calculate a five page navigation range relative to the current page; links with function calls to the search function are then generated and appended to a <UL> element in a <DIV> element. The resulting <DIV> element is then returned to the callback function and appended to the result list container.

10. Result Sorting

Result Sorting Code

// Result Sorting
   var sortBy = cel('div');
   sortBy.setAttribute("id", "sortBy");
   var sortByem = cel('em');
   sortByem.appendChild(ctn("Sort By"));
   sortBy.appendChild(sortByem);
   var sortByul = cel('ul');
   var sortByli1 = cel('li');
   var sortByli2 = cel('li');
   var sortByli3 = cel('li');
   var popularity = cel('a');
   if (in_array(tmpData.ItemSet.sort, bssort))
   {
      popularity.style.cssText = "font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 14px; color: #999999; font-weight:bold;";
   }
   else
   {
      popularity.setAttribute("href", "javascript:amazonSearch('" + tmpData.ItemSet.keywords + "', 'salesrank', '" + tmpData.ItemSet.category + "');");
      popularity.style.cssText = "cursor:hand; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 14px; color: #669966; font-weight:bold; text-decoration:none;";
   }
   popularity.appendChild(ctn("BestSelling"));
   var featured = cel('a');
   if (in_array(tmpData.ItemSet.sort, featuredsort))
   {
      featured.style.cssText = "font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 14px; color: #999999; font-weight:bold;";
   }
   else
   {
      featured.setAttribute("href", "javascript:amazonSearch('" + tmpData.ItemSet.keywords + "', 'psrank', '" + tmpData.ItemSet.category + "');");
      featured.style.cssText = "cursor:hand; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 14px; color: #669966; font-weight:bold; text-decoration:none;";
   }
   featured.appendChild(ctn("Featured"));
   var pricesort = cel('a');
   if (in_array(tmpData.ItemSet.sort, priceup))
   {
      pricesort.setAttribute("href", "javascript:amazonSearch('" + tmpData.ItemSet.keywords + "', 'price', '" + tmpData.ItemSet.category + "');");
      pricesort.style.cssText = "cursor:hand; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 14px; color: #669966; font-weight:bold; text-decoration:none;";
      pricesort.appendChild(ctn("Price" + "\u25bc"));
   }
   else
   {
      pricesort.setAttribute("href", "javascript:amazonSearch('" + tmpData.ItemSet.keywords + "', '-price', '" + tmpData.ItemSet.category + "');");
      pricesort.style.cssText = "cursor:hand; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 14px; color: #669966; font-weight:bold; text-decoration:none;";
      pricesort.appendChild(ctn("Price" + "\u25b2"));
   }
   sortByli1.appendChild(popularity);
   sortByli2.appendChild(featured);
   sortByli3.appendChild(pricesort);
   sortByul.appendChild(sortByli1);
   sortByul.appendChild(sortByli2);
   sortByul.appendChild(sortByli3);
   sortBy.appendChild(sortByul);

Category Specific Sort Value Arrays

// Sort Values
// Featured

var featuredsort = Array();
featuredsort["Baby"] = "psrank";
featuredsort["Beauty"] = "pmrank";
featuredsort["Classical"] = "psrank";
featuredsort["Electronics"] = "pmrank";
featuredsort["HealthPersonalCare"] = "pmrank";
featuredsort["Jewelry"] = "pmrank";
featuredsort["Kitchen"] = "pmrank";
featuredsort["Miscellaneous"] = "pmrank";
featuredsort["Music"] = "psrank";
featuredsort["MusicalInstruments"] = "pmrank";
featuredsort["OfficeProducts"] = "pmrank";
featuredsort["OutdoorLiving"] = "psrank";
featuredsort["PCHardware"] = "psrank";
featuredsort["PetSupplies"] = "+pmrank";
featuredsort["Photo"] = "pmrank";
featuredsort["Software"] = "pmrank";
featuredsort["Tools"] = "pmrank";
featuredsort["Toys"] = "pmrank";
featuredsort["VideoGames"] = "pmrank";
featuredsort["WirelessAccessories"] = "psrank";

// Price

var priceup = Array();
priceup['Apparel'] = 'inverseprice';
priceup['Automotive'] = '-price';
priceup['Baby'] = '-price';
priceup['Beauty'] = '-price';
priceup['Books'] = 'inverse-pricerank';
priceup['Classical'] = '-price';
priceup['DVD'] = '-price';
priceup['Electronics'] = '-price';
priceup['GourmetFood'] = 'inverseprice';
priceup['Grocery'] = 'inverseprice';
priceup['HealthPersonalCare'] = 'inverseprice';
priceup['HomeGarden'] = '-price';
priceup['Jewelry'] = 'inverseprice';
priceup['Kitchen'] = '-price';
priceup['Magazines'] = '-price';
priceup['Merchants'] = 'inverseprice';
priceup['Miscellaneous'] = '-price';
priceup['Music'] = '-price';
priceup['MusicalInstruments'] = '-price';
priceup['OfficeProducts'] = '-price';
priceup['OutdoorLiving'] = '-price';
priceup['PCHardware'] = '-price';
priceup['PetSupplies'] = '-price';
priceup['Photo'] = '-price';
priceup['Software'] = '-price';
priceup['SportingGoods'] = 'inverseprice';
priceup['Tools'] = '-price';
priceup['Toys'] = '-price';
priceup['VHS'] = '-price';
priceup['Video'] = '-price';
priceup['VideoGames'] = '-price';
priceup['Wireless'] = 'inverse-pricerank';

var pricedown = Array();
pricedown['Apparel'] = 'pricerank';
pricedown['Automotive'] = 'price';
pricedown['Baby'] = 'price';
pricedown['Beauty'] = 'price';
pricedown['Books'] = 'pricerank';
pricedown['Classical'] = 'price';
pricedown['DVD'] = 'price';
pricedown['Electronics'] = 'price';
pricedown['GourmetFood'] = 'pricerank';
pricedown['Grocery'] = 'pricerank';
pricedown['HealthPersonalCare'] = 'pricerank';
pricedown['HomeGarden'] = 'price';
pricedown['Jewelry'] = 'pricerank';
pricedown['Kitchen'] = 'price';
pricedown['Magazines'] = 'price';
pricedown['Merchants'] = 'pricerank';
pricedown['Miscellaneous'] = 'price';
pricedown['Music'] = 'price';
pricedown['MusicalInstruments'] = 'price';
pricedown['OfficeProducts'] = 'price';
pricedown['OutdoorLiving'] = 'price';
pricedown['PCHardware'] = 'price';
pricedown['PetSupplies'] = 'price';
pricedown['Photo'] = 'price';
pricedown['Software'] = 'price';
pricedown['SportingGoods'] = 'pricerank';
pricedown['Tools'] = 'price';
pricedown['Toys'] = 'price';
pricedown['VHS'] = 'price';
pricedown['Video'] = 'price';
pricedown['VideoGames'] = 'price';
pricedown['Wireless'] = 'pricerank';

// BestSelling

var bssort = Array();
bssort['Apparel'] = 'salesrank';
bssort['Automotive'] = 'salesrank';
bssort['Baby'] = 'salesrank';
bssort['Beauty'] = 'salesrank';
bssort['Books'] = 'salesrank';
bssort['Classical'] = 'salesrank';
bssort['DigitalMusic'] = 'songtitlerank';
bssort['DVD'] = 'salesrank';
bssort['Electronics'] = 'salesrank';
bssort['GourmetFood'] = 'salesrank';
bssort['Grocery'] = 'salesrank';
bssort['HealthPersonalCare'] = 'salesrank';
bssort['HomeGarden'] = 'salesrank';
bssort['Jewelry'] = 'salesrank';
bssort['Kitchen'] = 'salesrank';
bssort['Magazines'] = 'subslot-salesrank';
bssort['Merchants'] = 'salesrank';
bssort['Miscellaneous'] = 'salesrank';
bssort['Music'] = 'salesrank';
bssort['MusicalInstruments'] = 'salesrank';
bssort['OfficeProducts'] = 'salesrank';
bssort['OutdoorLiving'] = 'salesrank';
bssort['PCHardware'] = 'salesrank';
bssort['PetSupplies'] = 'salesrank';
bssort['Photo'] = 'salesrank';
bssort['Software'] = 'salesrank';
bssort['SportingGoods'] = 'salesrank';
bssort['Tools'] = 'salesrank';
bssort['Toys'] = 'salesrank';
bssort['VHS'] = 'salesrank';
bssort['Video'] = 'salesrank';
bssort['VideoGames'] = 'salesrank';
bssort['Wireless'] = 'salesrank';
bssort['WirelessAccessories'] = 'salesrank';

Perhaps the most difficult part of this tutorial is the result sorting functionality. Amazon does not use uniform parameters for sorting items in different categories. Each category contains its own, sometimes unique, group of sort values. After all, it wouldn’t make any sense to sort a Gourmet Food by Artist (unless you live in France). Therefore, the sorting options are configured by the current product category and sort index using associative arrays based on sort type and search index.

There are three options to sort results – Featured, BestSelling and Price. These are the most common sort indexes that Amazon offers. The “Sort By” <div> element contains three links for each sorting option. Like the paginate function, the links are javascript function calls to the search function, each with a different sort index variable; i.e., priceup for ascending price.

The associative arrays are grouped by sort index, with name : value pairs corresponding to Category : Parameter. For instance, the value (or parameter) of priceup['VideoGames'] will be different from priceup['Wireless']. The sort parameter that will be sent to Amazon is determined in the search function by matching the product search category to a parameter in the array containing the desired sort index per the below code snippet.

Sort Parameter Selection

// Determine Sort Variable
   if (s && c)
   {
      if ((s == 'salesrank') && (bssort[c]))
      {
         s = bssort[c];
      }
      else if ((s == 'psrank') && (featuredsort[c]))
      {
         s = featuredsort[c];
      }
      else if ((s == 'price') && (pricedown[c]))
      {
         s = pricedown[c];
      }
      else if ((s == '-price') && (priceup[c]))
      {
         s = priceup[c];
      }
      else
      {
         s = '';
      }
   }

Object Assembly

Appending Item Objects Code

And finally, the elements are appended to their parents until everything is contained under the searchResults <div> element. Note the conditional statement for instances where there are zero results.

// Build Item Content
      a_ig.appendChild(ig);
      td1.appendChild(a_ig); // Thumbnail
      td2.appendChild(li1); // Title
      td2.appendChild(li4); // Description
      subtd1.appendChild(li2); // Vendor
      subtd2.appendChild(li3); // Price
      subtr.appendChild(subtd1);
      subtr.appendChild(subtd2);
      subtbody.appendChild(subtr);
      subtable.appendChild(subtbody);
      td3.appendChild(subtable);
      tr1.appendChild(td1);
      tr1.appendChild(td2);
      tr2.appendChild(td3);
      tbody.appendChild(tr1);
      tbody.appendChild(tr2);
      maintable.appendChild(tbody);
      content.appendChild(maintable);
      container.appendChild(content);
      container.appendChild(subInfo); // Ratings, Reviews & Sellers
      iDiv.appendChild(container);
      scoller.appendChild(iDiv);

Appending Result Collection Objects Code

if(count != 0)
   {
      // Build Result List
      sa_listTop.appendChild(viewOptions);
      sa_listTop.appendChild(sortBy);
      sa_listTop.appendChild(scoller);

      // Build Pagination
      var pagination = paginate (tmpData.ItemSet.pagenum, tmpData.ItemSet.TotalResults, tmpData.ItemSet.category, tmpData.ItemSet.sort, tmpData.ItemSet.keywords);
      sa_listTop.appendChild(pagination);
      gDiv.appendChild(sa_listTop);
      gDiv.appendChild(cel('p'));
   }
   else // If there are no results
   {
      gDiv.appendChild(ctn("sorry, no results found for '" + getEl('asch').value + "'"));
      gDiv.appendChild(cel('p'));
   }

Conclusion

Source Code and Files

The source code for all material discussed in the tutorial, including my css sylesheet.

Download

Acknowledgements

When initially researching AJAX and its role in Web Services a few years ago, I was miffed by the apparent lack of introductory tutorials on the subject. Most of what I learned came off discussion boards and good old fashioned brainstorming. However, I was unfortunate in that I didn’t come across Kokogiak’s excellent tutorial site which outlined the entire process in the simplest of terms. This tutorial is in no small part a result of the insight I gained from his site. Credit also goes to Dare Obasanjo aka Carnage4Life for his excellent blog article on JSON and XML.

Thank you

If you’re still here and haven’t descended into a quagmire of confusion, thank you for reading this tutorial. I hope you have found it helpful. I am open to any comments, criticism, corrections, etc., so please feel free to contact me about this tutorial at info@arstropica.com.

References

The following sources were used or referenced in this tutorial. Some of the opinions and the data included in the tutorial were collected from these sources.


  1. Fixing AJAX: XMLHttpRequest Considered Harmful, Jason Levitt
  2. Mastering JSON ( JavaScript Object Notation ), Patrick Hunlock
  3. Introduction to XSL, Jan Egil Refsnes
  4. XSL – Transformation, Jan Egil Refsnes
  5. XSL – On the Client, Jan Egil Refsnes
  6. XSL Sort, Jan Egil Refsnes
  7. XSL Filter Query, Jan Egil Refsnes
  8. Using XSLT with ECS, Amazon.com
  9. Amazon.com Associates: The web’s most popular and successful Affiliate Program, Amazon.com
  10. Amazon Associates Web Service Developer Guide (API Version 2008-08-19), Amazon.com
  11. ItemSearch Sort Values By Locale, Amazon.com
  12. KOKOGIAK – Consuming Amazon’s Web API Directly with Javascript (via JSON and XSLT), Alan Taylor
  13. JSON vs. XML: Browser Security Model, Dare Obasanjo

Leave Your Response

* Name, Email, Comment are Required