Hack 97 Program AWS with Mozilla

figs/expert.giffigs/hack97.gif

Mozilla provides all the tools you need to build applications that integrate with Amazon's Web Services.

Mozilla is more than an alternative web browser?it's also a platform for building applications. It has a built-in XML-based format for defining application interfaces called XUL (XML-based User-interface Language). When you combine the tag-based XUL with JavaScript and Mozilla's built-in components, you have a cross-platform development environment perfect for building web applications.

This hack provides an interface for searching Amazon. The XUL defines a simple search form, a space for search results (called a tree), and an HTML iframe for viewing the product detail pages of search results.

97.1 The Code

The first part of the application is the XUL file itself. Beyond defining the interface, it holds the code that contacts Amazon to perform the search. When you click the Search button, the doSearch( ) function is triggered. This fetches the search results from Amazon and puts them in the search results tree. The other function, displayItem( ), runs when an individual item in the search results is clicked. It sets the HTML iframe location URL to the product detail page for that item (formatted with your associate tag, of course).

Save this code in a file called hack.xul.

<?xml version="1.0" encoding="UTF-8"?>

<!-- ***** BEGIN LICENSE BLOCK *****
- Version: MPL 1.1/GPL 2.0/LGPL 2.1
-
- The contents of this file are subject to the Mozilla Public License
- Version 1.1 (the "License"); you may not use this file except in
- compliance with the License. You may obtain a copy of the License at
- http://www.mozilla.org/MPL/
-
- Software distributed under the License is distributed on an "AS IS"
- basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
- License for the specific language governing rights and limitations under
- the License.
-
- The Original Code is Mozilla XUL Amazon Hack.
-
- The Initial Developer of the Original Code is America Online, Inc.
- Portions created by the Initial Developer are Copyright (C) 2003
- the Initial Developer. All Rights Reserved.
-
- Contributor(s): Myk Melez <myk@mozilla.org>
-                 Paul Bausch <pb@onfocus.com>
-
- Alternatively, the contents of this file may be used under the terms of
- either the GNU General Public License Version 2 or later (the "GPL"), or
- the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- in which case the provisions of the GPL or the LGPL are applicable
- instead of those above. If you wish to allow use of your version of this
- file only under the terms of either the GPL or the LGPL, and not to 
- allow others to use your version of this file under the terms of the 
- MPL, indicate your decision by deleting the provisions above and replace
- them with the notice and other provisions required by the LGPL or the
- GPL. If you do not delete the provisions above, a recipient may use your
- version of this file under the terms of any one of the MPL, the GPL or 
- the LGPL.
-
- ***** END LICENSE BLOCK ***** -->

<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>

<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.[RETURN]
only.xul">

  <script type="application/x-javascript">
  <![CDATA[
    const ASSOC_ID = "insert associate tag ";
    const DEV_TOKEN = "insert developer token ";

  // The base URL for all searches done by this application.
  // http://.../rdf.xsl is an XSLT stylesheet that converts Amazon results
  // into RDF for consumption by this application's XUL tree template.
  // "ct=application/xml" specifies that Amazon should return the results
  // with that content type, which is necessary for Mozilla to recognize
  // them as RDF.
  const AWS_URL_BASE = "http://xml.amazon.com/onca/xml3?t=" + ASSOC_ID +  
                       "&dev-t=" + DEV_TOKEN + 
                       "&type=lite&ct=application/xml&locale=us" +
                       "&f=http://www.melez.com/mozilla/amazon/rdf.xsl ";

  function doSearch(  ) {
    // Construct a REST (XML over HTTP) URL for executing the search
    // and retrieving the results.
    var cat = document.getElementById('search-catalog').value;
    var fld = document.getElementById('search-field').value;
    var str = document.getElementById('search-string').value;
    var url = AWS_URL_BASE + "&mode=" + cat + "&" + fld + "Search=" + str;

 // Request access to Mozilla's component architecture so we can do things
 // that would normally be restricted.

netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");

 // Use Mozilla's REST API to execute the search and retrieve the results.
 // Afterwards we'll use the RDF parser to parse them into a data source.
 // We should be able to use Mozilla's RDF loader to do both retrieval
 // and parsing in a single call, but the loader requires the server
 // to return data as text/xml or application/xml, and a bug in Amazon's
 // web services API causes data to be returned as type "$ctype"
 // when you override the default "text/html" using the "ct" parameter,
 // so we work around the problem by doing the retrieval ourselves.
      var request = new XMLHttpRequest(  );
      request.open("GET", url, false);
      request.send(null);
      var results = request.responseText;

 // Create an RDF/XML data source into which the results will be parsed.
      var ds = Components 
             .classes["@mozilla.org/rdf/datasource;1?name=xml-datasource"] 
             .createInstance(Components.interfaces.nsIRDFDataSource); 

 // Create a URI object representing the base URI of the RDF data source.
 // This URI gets used by the parser when parsing relative URIs
 // in the search results.
      var ioService = Components
                        .classes["@mozilla.org/network/io-service;1"]
                          .getService(Components.interfaces.nsIIOService);
      var uri = ioService.newURI(url, null, null);

 // Create an RDF/XML parser.
      var parser = Components
                  .classes["@mozilla.org/rdf/xml-parser;1"]
                  .createInstance(Components.interfaces.nsIRDFXMLParser);

 // Parse the results, adding them to the data source.
      parser.parseString(ds, uri, results);

// Add the data source to the tree's database (collection of data sources)
// and rebuild the tree, which populates it with the items in the data 
// source.
      var tree = document.getElementById('results-tree');
      tree.database.AddDataSource(ds);
      tree.builder.rebuild(  );
   }

    function displayItem(  ) {
   // Get the URL (on the Amazon web site) of the currently selected item.
      var tree = document.getElementById('results-tree');
      var items = tree.getElementsByTagName('treeitem');
      var selectedItem = tree.currentIndex+1;
      var selectedItemURL = items[selectedItem].id;
  
      // Load the URL in the iframe widget.
      var browser = document.getElementById('item-browser');
      browser.setAttribute('src', selectedItemURL);
    }
  ]]>
  </script>

  <toolbar align="center">
    <label control="search-catalog" value="Find"/>
    <menulist id="search-catalog" persist="value">
      <menupopup>
        <menuitem label="Apparel" value="apparel" />
        <menuitem label="Baby" value="baby" />
        <menuitem label="Books" value="books" />
        <menuitem label="Classical Music" value="classical" />
        <menuitem label="DVD" value="dvd" />
        <menuitem label="Electronics" value="electronics" />
        <menuitem label="Outdoor Living" value="garden" />
        <menuitem label="Kitchen &amp; Housewares" value="kitchen" />
        <menuitem label="Magazines" value="magazines" />
        <menuitem label="Popular Music" value="music" />
        <menuitem label="Computers" value="pc-hardware" />
        <menuitem label="Camera &amp; Photo" value="photo" />
        <menuitem label="Software" value="software" />
        <menuitem label="Toys &amp; Games" value="toys" />
        <menuitem label="Tools &amp; Hardware" value="universal" />
        <menuitem label="Video" value="vhs" />
        <menuitem label="Computer &amp; Video Games" value="videogames" />
      </menupopup>
    </menulist>
    <label control="search-field" value="whose"/>
    <menulist id="search-field" persist="value">
      <menupopup>
        <menuitem label="Keyword" value="Keyword" />
        <menuitem label="Author" value="Author" />
        <menuitem label="Artist" value="Artist" />
        <menuitem label="Actor" value="Actor" />
        <menuitem label="Director" value="Director" />
        <menuitem label="Manufacturer" value="Manufacturer" />
        <menuitem label="UPC Code" value="Upc" />
      </menupopup>
    </menulist>
    <label control="search-string" value="is like"/>
    <textbox id="search-string" type="text" size="30" persist="value"
      onkeypress="if (event.keyCode == KeyEvent.DOM_VK_ENTER || [RETURN]
event.keyCode == KeyEvent.DOM_VK_RETURN) doSearch(  );" />
    <button id="search-button" label="Search" oncommand="doSearch(  );" />
  </toolbar>

  <tree id="results-tree" flex="1" enableColumnDrag="true" [RETURN]
onselect="displayItem(  );"
        datasources="rdf:null" ref="urn:amazon:ProductInfo" >[RETURN]
    <treecols>
      <treecol id="ProductName_column" label="Product" primary="true" 
flex="6" persist="width hidden ordinal" />
      <splitter class="tree-splitter"/> 
      <treecol id="Authors_column" label="Authors" flex="3" 
        persist="width hidden ordinal" />
      <splitter class="tree-splitter"/> 
      <treecol id="ReleaseDate_column" label="Release Date" flex="1" 
persist="width hidden ordinal" />
      <splitter class="tree-splitter"/> 
      <treecol id="Manufacturer_column" label="Manufacturer" flex="1" 
persist="width hidden ordinal" />
      <splitter class="tree-splitter"/> 
      <treecol id="ListPrice_column" label="List Price" flex="1" 
persist="width hidden ordinal" />
      <splitter class="tree-splitter"/> 
      <treecol id="OurPrice_column" label="Our Price" flex="1" 
persist="width hidden ordinal" />
      <splitter class="tree-splitter"/> 
      <treecol id="UsedPrice_column" label="Used Price" flex="1" 
persist="width hidden ordinal" />
      <splitter class="tree-splitter"/> 
      <treecol id="Catalog_column" label="Catalog" flex="1" 
        persist="width hidden ordinal" />
      <splitter class="tree-splitter"/> 
      <treecol id="Asin_column" label="Asin" flex="1" 
persist="width hidden ordinal" />
    </treecols>
    <template>
      <rule>
        <conditions>
          <content uri="?uri" />
          <triple subject="?uri" predicate="http://xml.amazon.com/schemas2/
dev-lite.xsdDetails" object="?Details" />
          <member container="?Details" child="?item" />
        </conditions>
        <bindings>
          <binding subject="?item" predicate="http://xml.amazon.com/[RETURN]
schemas2/dev-lite.xsdProductName" object="?ProductName" />
          <binding subject="?item" predicate="http://xml.amazon.com/[RETURN]
schemas2/dev-lite.xsdAuthors" object="?Authors" />
          <binding subject="?item" predicate="http://xml.amazon.com/[RETURN]
schemas2/dev-lite.xsdReleaseDate" object="?ReleaseDate" />
          <binding subject="?item" predicate="http://xml.amazon.com/[RETURN]
schemas2/dev-lite.xsdManufacturer" object="?Manufacturer" />
          <binding subject="?item" predicate="http://xml.amazon.com/[RETURN]
schemas2/dev-lite.xsdListPrice" object="?ListPrice" />
          <binding subject="?item" predicate="http://xml.amazon.com/[RETURN]
schemas2/dev-lite.xsdOurPrice" object="?OurPrice" />
          <binding subject="?item" predicate="http://xml.amazon.com/[RETURN]
schemas2/dev-lite.xsdUsedPrice" object="?UsedPrice" />
          <binding subject="?item" predicate="http://xml.amazon.com/[RETURN]
schemas2/dev-lite.xsdCatalog" object="?Catalog" />
          <binding subject="?item" predicate="http://xml.amazon.com/[RETURN]
schemas2/dev-lite.xsdAsin" object="?Asin" />
        </bindings>
        <action>
          <treechildren>
            <treeitem uri="?item">
              <treerow>
              <treecell ref="ProductName_column" label="?ProductName" />
              <treecell ref="Authors_column" label="?Authors" />
              <treecell ref="ReleaseDate_column" label="?ReleaseDate" />
              <treecell ref="Manufacturer_column" label="?Manufacturer" />
              <treecell ref="ListPrice_column" label="?ListPrice" />
              <treecell ref="OurPrice_column" label="?OurPrice" />
              <treecell ref="UsedPrice_column" label="?UsedPrice" />
              <treecell ref="Catalog_column" label="?Catalog" />
              <treecell ref="Asin_column" label="?Asin" />
              </treerow>
            </treeitem>
          </treechildren>
        </action>

      </rule>
    </template>
  </tree>
  
  <splitter collapse="after" state="open" persist="state"> 
     <grippy/>
  </splitter>
  
  <iframe id="item-browser" src="about:blank" flex="2" persist="height" />

</window>

Because the doSearch( ) function is expecting the XML in RDF (Resource Description Framework) format, the other piece of this application is an XSL stylesheet to transform Amazon's XML into RDF. Create a file called rdf.xsl with the following code:

<?xml version="1.0" encoding="UTF-8" ?> 
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 
  xmlns="http://xml.amazon.com/schemas2/dev-lite.xsd">
  <xsl:strip-space elements="*" /> 
  <xsl:output indent="yes" media-type="application/xml" /> 
<xsl:template match="/">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 
xmlns="http://xml.amazon.com/schemas2/dev-lite.xsd">
  <xsl:apply-templates /> 
</rdf:RDF>
</xsl:template>

<xsl:template match="ProductInfo">
<ProductInfo rdf:about="urn:amazon:ProductInfo">
 <Details>
 <rdf:Seq>
  <xsl:apply-templates /> 
 </rdf:Seq>
 </Details>
</ProductInfo>
</xsl:template>

<xsl:template match="Details">
<rdf:li>
  <rdf:Description rdf:about="{@url}">
   <xsl:for-each select="./*">
    <xsl:element name="{name(  )}">
      <xsl:value-of select="." /> 
    </xsl:element>
   </xsl:for-each>
  </rdf:Description>
</rdf:li>
</xsl:template>

<xsl:template match="TotalResults" /> 
<xsl:template match="TotalPages" /> 

</xsl:stylesheet>

This stylesheet transforms the standard Amazon lite XML results into RDF, which allows the code in hack.xul to use Mozilla's built-in RDF parser to work with the results. Amazon's XSLT service makes the transformation an easy process. The reference to the stylesheet is set when the search is made at Amazon with the f= variable.

If the thought of typing all this is leaving you cold, it's all available along with the rest of this book's code at http://www.oreilly.com/catalog/amazonhks/.

97.2 Running the Hack

Once you've saved hack.xul, you can run it by double-clicking the file. It will open Mozilla and bring up the search interface, as you see in Figure 6-11.

Figure 6-11. Mozilla search application
figs/amzh_0611.gif

If you want to run this application on a remote server instead of locally, you'll need to set your web server to send the proper content type for XUL files. Add a MIME type entry for XUL files that sends application/vnd.mozilla.xul+xml as the content type to get it working.

?Myk Melez