(This article presumes a basic knowledge of Amazon's APIs.)

Cold Fusion MX 6.1 & 7 provide strong XML and SOAP functionality to access data rich web services. Web services act like virtual databases and provide access to a wide variety of data.

One site I recently built uses web services to publish sporting event ticket information. Now, one of the biggest internet retailers, Amazon.com, makes it easier for their online associates to use web services to access their wealth of products.

Amazon.com recently released their API that allows web developers to build their own customizable storefronts without requiring a lot of extensive programming. Unfortunately, for the Cold Fusion community, the web services are geared for Microsoft's ASP.net and PERL programming languages.

This tutorial will address consuming Amazon's web services and designing your very own store.

(Before we get started, please be sure to set up a web services account at Amazon, if you don't already have one. It's free and sign up only takes a few moments. )

So, what's a guy gotta do to get the API's working? One method is to use a REST-based approach to access the data. The CFHTTP tag works perfectly in this method. (There are SOAP-based services available, but for the purposes of this tutorial, we will use REST.)



<cfhttp url="http://webservices.amazon.com/onca/xml
                 ?Service=AWSECommerceService
                 &SubscriptionId=xxxx
                 &Operation=ItemSearch
                 &SearchIndex=Books
                 &;Keywords=coldfusion
                 &ResponseGroup=Medium,Images"
                 result="xmlObj"
>
</cfhttp>



As you can see, the CFHHTP calls the amazon xml feed and passes the associate subscription id and user-specified search criteria. An object is returned with xml-formatted results.


<cfset tmp = XMLParse(XMLObj.file content)>



To view the raw data, it is necessary to parse the data with the XMLParse function.


<cfdump var="#tmp#">


Now that we've done a cfdump of the xml results packet, you can see just how much data there is for every product!

Since we're doing an ItemSearch, the root of the data packet is called 'ItemSearchResponse'. The first row gives specific data relating to the results. The xmlattribute tells which version of CommerceService is being used. The HTTPRequest identifies what programming language is being used to access the web service (ASP.net, Coldfusion, etc). The next section, RequestID, is a session counter. The Arguments attributes displays the user-specified search criteria and item page number. This information, while useful, is not of any immediate use and can be disregarded.

Now, to the heart of the matter.

The next data row, Items, contains all the search results data. Amazon has a built-in limit of 10 items per data page. However, there are thirteen results items.

The first item listings are Request, TotalResults and TotalPages. TotalResults and TotalPages are useful for extracting the total number of search results or setting up record paging.

After that, things get a little trickier. Each product has several levels of data relating to its ASIN, product images, product pricing, URLs to the corresponding Amazon.com and more. Extracting the data is fairly straightforward but cumbersome. For example, to find the products ASIN number, you would use the following:

<cfset ASIN = tmp.ItemSearchResponse.Items[1].Item[1].ASIN.xmltext>

To view the product price, you would use the following:

<cfset Price = tmp.ItemSearchResponse.Items[1].Item[1].ItemAttributes.ListPrice.FormattedPrice.xmlText>

In the previous articles' example, the search is for Books with a Keyword of Coldfusion. The amount of data for one book is staggering. There are over forty different xmlattributes and xmltext fields to sift through. Here's a sample output for the ColdFusion MX 7 WACK:

ASIN
DetailPageURL
SalesRank
Small Image (has xmlattributes for image URL, height and width)
Medium Image (has xmlattributes for image URL, height and width)
Large Image (has xmlattributes for image URL, height and width)
Category
Category Small Image (has xmlattributes for image URL, height and width)
Category Medium Image (has xmlattributes for image URL, height and width)
Category Large Image (has xmlattributes for image URL, height and width)
Author (will appear multiple times if there is more than one author)
EAN
Edition
ISBN
Amount
CurrencyCode
FormattedPrice
NumberofItems
NumberofPages
PackageDimensions (has xml attributes for height, weight and width)
PublicationDate
Publisher
Title
OfferSummary
LowestNewPrice
LowestNewFormattedPrice
LowestUsedPrice
LowestUsedFormattedPrice
CurrencyCode
TotalNew
TotalUsed
TotalCollectible
TotalRefurbished
EditorialReview (xmlattributes for Source and Content)

Whew! And this is just for ONE book!

As you might expect, the ItemAttributes vary from SearchIndex to SearchIndex. DVD's results are different from books which are different from music, etcetera.

Now that we've examined the data structure for a product, we need to determine how many items there are in the dataset and insert them into a variable.

The first order of business to determine how many results are in the xml packet. The following formula will create a variable, numItems, containing how many products there are in the Items node.

<cfset numItems = ArrayLen(tmp.itemsearchresponse.items.xmlchildren)-3>

You may be asking "Why is he deducting three?" Well, as noted before, the Item child node contains three additional items: Request, TotalPages and TotalResults. These count against the number of results and must be excluded.

You may also ask "Why not just set it to 10?" In this case, there's no telling how many items are in the results. This method prevents smaller resultsets from generating undefined item errors if there is a loop that is preset to ten.

As you begin designing your own Amazon store, you must keep in mind that not all products will have the exact same structure. Product A will have Editorial Review content while Product B won't. Also, Product C will have an EAN while Product D won't. It's because of these incongruities in the xml packets that you must use error-trapping logic to prevent your web page from generating errors.

The first error check is fairly simple. At the beginning of the Items node is a Request subnode. This node, in turn, contains an 'IsValid' child node containing a True/False response relaying whether or not the search encountered any errors during processing.

<cfset chkRequest = tmp.ItemSearchResponse.Items[1].Request.IsValid.xmltext>

One note about the xml packet structure. The format for ItemSearch is ItemSearchResponse.Items.xmlchildren. There can be up to 10 ItemSearchResponse.Items.Item childnodes, but there will only be one ItemSearchResponse.Items node. Thus, when looping through the Items node, it will always be referred to as ItemSearchResponse.Items[1].xmlchild.xmltext.

We can now begin error trapping and assigning values to variables.

<cfset ItemList = ArrayNew(1)>
<cfif chkRequest>
    <cfloop from=
"1" to="#numItems#" index="i">
        <cfset ItemList[
i] = StructNew()>
        <cfif StructKeyExists(tmp.ItemSearchResponse.Items[
1].Item[i],"ASIN")>
            <cfset ItemList[
i].ASIN = tmp.ItemSearchResponse.Items[1].Item[i].ASIN.xmltext>
        </cfif>
    </cfloop>
</cfif>


As you can see in the above example, we first create a single dimension array named ItemList. Then, if chkResult is true, the next level of error-trapping begins. A <cfloop> is started which cycles through the number of products in the xml packet. With each iteration, the ItemList array is converted to a structured array.

A <cfif> contains a StructKeyExists function to determine if the product has an ASIN. If the item has an ASIN, then ItemList[i].ASIN is created and the ASIN.xmltext is added to it.

All Amazon listed products have the same mandatory fields, like Item.ASIN and ItemAttributes.Title. Other fields, such as LowestNewPrice and EAN, are not. In some cases, one or more of these fields will not be defined in xml packet. The StructKeyExists function should, as a best practice, be run for each variable that is to be outputted. It will create additional programming needs but will minimize any potential data errors.

Now, we're going to build simple form and action pages that will incorporate the lessons learned previously.

The form itself is built using Cold Fusion MX 7. It consists of a select field, input field and a submit button. It is extremely basic and only covers a handful of the available departments.

Amazon.cfm

<cfoutput>
    <cfform name=
"getItem" action="displayresults.cfm" method="post">
        <cfselect label=
"Department:" name="SearchIndex" required="true">
            <option value=
"">...select a department...</option>
            <option value=
"DVD">DVD</option>
            <option value=
"Software">Software</option>
            <option value=
"Books">Books</option>
            <option value=
"PCHardware">PCHardware</option>
            <option value=
"Music">Music</option>
            <option value=
"VideoGames">VideoGames</option>
        </cfselect>
        <br/>
        <cfinput type=
"text" name="keyword" label="Enter Keyword:" required="false" />
        <cfinput type=
"submit" name="Submit">
    </cfform>
</cfoutput>

Now, comes the meaty part. The DisplayResults.cfm will generate a simple table listing all the products found. Clicking on the image or link will take you to the appropriate Amazon product page. It also passes your subscriptionid, so any purchases made will reflect on your Amazon Associates account! (It is possible to build a shopping cart using the Amazon API's but that's a tutorial for another day.)

Displayresults.cfm

<!--- creates record paging parameter if it doesn't exist --->
<cfif not isdefined("url.f")>
    <cfset url.f =
1>
</cfif>


<!--- checks to see if url.s exists, if yes then page was reloaded via the record paging and creates SearchIndex variable and assigns url.s value to it.
otherwise, the code checks to see if form.SearchIndex exists and then creates SearchIndex variable with form.SearchIndex value. --->

<cfif isDefined("url.s")>
    <cfset SearchIndex = URLDecode(url.s)>
    <cfelseif isDefined(
"form.SearchIndex")>
    <cfset SearchIndex = form.SearchIndex>
</cfif>


<!--- checks to see if url.k is defined. if yes, then page has been reloaded via record paging and creates Keyword variable and assigns url.k to it.
otherwise, code checks to see if form.keyword exists. if yes, then user was directed here by form page. Keyword var is created and assigned value of form.keyword.
--->

<cfif isDefined("url.k")>
    <cfset Keyword = url.k>
    <cfelseif isDefined(
"form.keyword")>
    <cfset Keyword = form.keyword>
</cfif>


<!--- creates amz var, which contains all the variables needed to create a viable search entry.
note that, for our purposes here, only the SearchIndex, Keywords and ItemPage variables have dynamic values.
--->

<cfset amz = "">
<cfset amz = amz &
"&Operation=ItemSearch">
<cfset amz = amz &
"&SearchIndex=" & SearchIndex>
<cfset amz = amz &
"&Keywords=" & keyword>
<cfset amz = amz &
"&ResponseGroup=Medium,Images">
<cfset amz = amz &
"&ItemPage=#url.f#">

<!--- service call to Amazon web services. Please replace the 'xxxx' with your Subscription ID.
The result, if the call is successful, is stored in a variable called xmlObj. --->

<cfhttp url="http://webservices.amazon.com/onca/xml?Service=AWSECommerceService&SubscriptionId=xxxxx&#amz#"
           result=
"xmlObj">
</cfhttp>


<!--- use XMLParse function to create tmp variable --->
<cfset tmp = XMLParse(XMLObj.filecontent)>

<!--- use ArrayLen to find out how many products are in XML packet and assign value to numAmzItem variable --->
<cfset numAmzItem = ArrayLen(tmp.itemsearchresponse.items.xmlchildren)-3>

<!--- was search request valid/execute properly. assigns True or False value--->
<cfset chkRequest = tmp.ItemSearchResponse.Items[1].Request.IsValid.xmltext>

<!--- if chkRequest is True, begin sifting through xml packet --->
<cfif chkRequest>

   
<!--- create new array to carry product data --->
    <cfset lstItem =
ArrayNew(1)>

    <!--- begin looping through xml packet --->
    <cfloop from=
"1" to="#numAmzItem#" index="i">

        <!--- convert current lstItem iteration into structure --->
        <cfset lstItem[i] =
StructNew()>

        <!--- checks for ASIN
                if one is found, then lstItem[i] structure is filled out with following info
        --->

        <cfif structkeyexists(tmp.ItemSearchResponse.Items[
1].Item[i],"ASIN")>
            <cfset lstItem[i].ASIN = tmp.ItemSearchResponse.Items[
1].Item[i].ASIN.xmltext>
            
           
<!--- check to see if Author exists --->
            <cfif structkeyexists(tmp.ItemSearchResponse.Items[
1].Item[i].ItemAttributes,"Author")>
                <cfset lstItem[
i].Author = tmp.ItemSearchResponse.Items[1].Item[i].ItemAttributes.Author.xmltext>
            </cfif>

            <!--- insert amazon.com URL for item --->
            <cfset lstItem[
i].URL = tmp.ItemSearchResponse.Items[1].Item[i].DetailPageURL.xmltext>
            <!--- Name of product --->
            <cfset lstItem[
i].Title = tmp.ItemSearchResponse.Items[1].Item[i].ItemAttributes.Title.xmltext>
            <!--- small image url and sizes--->
            <cfif structkeyexists(tmp.ItemSearchResponse.Items[
1].Item[i],"SmallImage")>
                <cfset lstItem[
i].smImage = tmp.ItemSearchResponse.Items[1].Item[i].SmallImage.URL.xmlText>
                <cfset lstItem[
i].smImageH = tmp.ItemSearchResponse.Items[1].Item[i].SmallImage.Height.xmlText>
                <cfset lstItem[
i].smImageW = tmp.ItemSearchResponse.Items[1].Item[i].SmallImage.Width.xmlText>
            </cfif>

            <!--- checks for list price (if available) or lowest used price
                    sets price to $0.00 if no pricing info found
            --->

            <cfif structkeyexists(tmp.ItemSearchResponse.Items[
1].Item[i].ItemAttributes,"ListPrice")>
                <cfset lstItem[
i].Price = tmp.ItemSearchResponse.Items[1].Item[i].ItemAttributes.ListPrice.FormattedPrice.xmlText>
                <cfset lstItem[
i].Amount = tmp.ItemSearchResponse.Items[1].Item[i].ItemAttributes.ListPrice.Amount.xmlText>
            <cfelseif structkeyexists(tmp.ItemSearchResponse.Items[
1].Item[i].OfferSummary,"LowestUsedPrice")>
                <cfset lstItem[
i].Price = tmp.ItemSearchResponse.Items[1].Item[i].OfferSummary.LowestUsedPrice.FormattedPrice.xmlText>
                <cfset lstItem[
i].Amount = tmp.ItemSearchResponse.Items[1].Item[i].OfferSummary.LowestUsedPrice.Amount.xmlText>
            <cfelse>
                <cfset lstItem[
i].Price = DollarFormat(0)>
                <cfset lstItem[
i].Amount = 0>
            </cfif>
            <cfif structkeyexists(tmp.ItemSearchResponse.Items[
1].Item[i],"ProductGroup")>
                <cfset lstItem[
i].ProductGroup = tmp.ItemSearchResponse.Items[1].Item[i].ProductGroup.xmlText>
            </cfif>
 
           <!--- check for Editorial Review content --->
            <cfif structkeyexists(tmp.ItemSearchResponse.Items[
1].Item[i],"EditorialReviews")>
                <cfset lstItem[
i].Review = Left(tmp.ItemSearchResponse.Items[1].Item[i].EditorialReviews.EditorialReview.Content.xmlText,100)>
            </cfif>
        </cfif>
    </cfloop>
</cfif>


<style type="text/css">
    body {font-family:tahoma;background-color:##fff}
    th {font-size:14px;font-weight:bold;text-align:center;color:##ff0000}
    td {font-size:12px;color:##000;padding:10px 5px 10px 0;}
</style>
<html>
<head></head>
<body>

<!--- begin outputting results--->
<cfoutput

About This Tutorial
Author: Chris Gomez
Skill Level: Intermediate 
 
 
 
Platforms Tested: CFMX,CFMX7
Total Views: 104,511
Submission Date: December 03, 2005
Last Update Date: June 05, 2009
All Tutorials By This Autor: 1
Discuss This Tutorial
Advertisement

Sponsored By...
Mobile App Development (IOS, Android, Cordova, Phonegap, Objective-C, Java) - Austin, Texas Mobile Apps - Touch512, LLC.