Friday, December 16, 2005

Amazon tutorial

I've posted the completed (and slightly revised) article as a tutorial on www.easycfm.com. It can be found at http://tutorial420.easycfm.com/.

Tuesday, November 29, 2005

CFMX and Amazon web services, pt 4

In this part, 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.

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.

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 results, 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">

<!--- 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>
<!--- is chkRequest True?--->
<cfif chkRequest>
<!--- Yes! --->

<!--- begin loop to create record paging --->
<cfloop from="1" to="#tmp.ItemSearchResponse.Items.TotalPages.xmltext#" index="f">
<a href="displayresults.cfm?f=#f#&s=#URLEncodedFormat(SearchIndex)#&k=#URLEncodedFormat(keyword)#">#f# </a>
</cfloop>
<!--- end loop --->

<!--- create table --->
<table border="0" cellpadding="0" cellspacing="0" width="650" align="center">

<!---
begin display result loop--->
<cfloop from="1" to="#numAmzItem#" index="i">

<!---
check to see if there is at least 1 copy of product available --->
<cfif lstItem[i].Amount gt 0>

<!---
set row background colors --->
<tr bgcolor="###iif(i MOD 2,DE('dfdfdf'),DE('f4f4f4'))#">
<!--- display image in first cell and link it to Amazon product page --->
<td><cfif structkeyexists(lstItem[i],"smImage")>
<a href="#lstItem[i].URL#"><img src="#lstItem[i].smImage#" width="#lstItem[i].smImageW#" height="#lstItem[i].smImageH#" border="0" /></a>
<cfelse>
<!--- if there is no image, then a default image is shown. You can use your own imagery instead --->
<img src="noimage.png">
</cfif></td>

<!--- second cell contains item name and ASIN and links product name to Amazon product page --->
<td><a href="#lstItem[i].URL#">#lstItem[i].Title#</a> [#lstItem[i].ASIN#]<br />
<!--- display editorial content, if any --->
<cfif structkeyexists(lstItem[i],"Review")>#lstItem[i].Review# <a href="#lstItem[i].URL#">(more)</a></cfif>
</td>
<!--- display product price --->
<td>#lstItem[i].Price#</td>
</tr>
</cfif>
</cfloop>
<tr>
<td>

<!--- create new row containing button to return back to form page--->
<cfform name="return" action="amazon.cfm" method="post">
<cfinput type="submit" name="Submit">
</cfform>
</td>
</tr>
</table>
<cfelse>
<!--- if no results found or there is an error, display no results message --->
No results found. Please try again
</cfif>
</cfoutput>

</body>
</html>

The above files will give you a quick view of how to extract data from the Amazon web services. This is not the end of the road, however. There are numerous ways of building sites, ranging from individual product links scattered across a site to product pages to search pages.

In the future, I will be posting additional articles on how to use CFMX to build a bigger, better Amazon-based store. Enjoy your new toy.

Be cool,

Chris

Sunday, November 27, 2005

CFMX and Amazon web services, pt 3

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 in the previous article, 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>

</cfloop>

  </cfif>


As you can see in the above example, the first line creates an 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.

Next up, putting the pieces together and creating a simple input/output form.

Manana,

Chris

CFMX and Amazon web services, pt 2

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.

In the next part, I'll go over how to mine the data for key information.

Manana,

Chris

Thursday, November 24, 2005

CFMX and Amazon web services, pt 1

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

Cold Fusion MX 7 provides 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 series of articles will address various ways of 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.



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


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#">


As you can see, there is a lot of data returned with this query. The next article will deal with extracting the data. Future articles will deal with the different searchindexes and displaying the data.

Manana,

Chris

Tuesday, November 22, 2005

Welcome

Welcome to CFJamz!

CFJamz is all about Cold Fusion MX 7. The latest release by Macromedia adds tons of new functionality and improvements over MX 6.1.

Flash forms, XForms, greater support for XML and SOAP as well as the new event gateways make CFMX 7 a much stronger language.

This blog is all about cool coding and functionality tips. Expect to see entries on everything from working frameworks like Fusebox or Mach-II to flash forms to good ol' cf coding.