Mozilla DevCenter    
 Published on Mozilla DevCenter (http://www.oreillynet.com/mozilla/)
 See this if you're having trouble printing code examples


Creating Applications with Mozilla

Remote Application Development with Mozilla, Part 2
A Case Study of the Mozilla Amazon Browser (MAB)

by Brian King, coauthor of Creating Applications with Mozilla and Myk Melez
05/02/2003

In our first article we talked about the benefits of remote application development using Mozilla technologies such as XUL and web services support. In this article we present a case study of one such application, the Mozilla Amazon Browser (MAB), a tool for searching Amazon's catalogs.

The Mozilla Amazon Browser is an interesting case study for a number of reasons. First, it's a working prototype application, not an example constructed solely for the purpose of demonstrating the technology. Second, it exercises many areas of Mozilla's support for remote development and thus shows off the capabilities, potential, and limitations of remote XUL applications. Finally, it demonstrates Mozilla's support for web services, an important emerging technology for web application development.

In this article, we will give an overview of MAB (version 0.98, the latest version available at the time of this writing), discuss the steps that author Fabio Serra took to get it running remotely, and outline its structure and organization. We will also explain how the MAB uses Mozilla's built-in web services support to submit a search to the Amazon web service, retrieve the search results, and display them to the user.

Overview

The MAB is a tool for searching the Amazon catalogs and browsing their products. It lets users search for products (by keyword, ASIN/ISBN, UPC code, author/artist, and so on) and retrieve information about each product found, including its title, author/artist, and release date (the latter two for books/music), along with its suggested retail price, Amazon's price, and the price of a used copy sold through Amazon (if available). It also displays comments from other Amazon users about the product.

Related Reading

Creating Applications with Mozilla
By David Boswell, Brian King, Ian Oeschger, Pete Collins, Eric Murphy

Users can also save and reload searches -- although saved searches do not persist across sessions because the application does not have access to the user's hard drive, and a mechanism for saving searches to a remote server has not been implemented -- and aggregate the results of multiple consecutive searches into a single list.

The UI comprises:


Main Amazon Browser window

The MAB can be run either in its own window or as a page in a browser window. It can also be installed and run locally, although the local version doesn't currently take advantage of the additional capabilities of local applications, such as persisting information across sessions by saving it to the user's hard drive.

Getting the MAB to Run Remotely

Getting the MAB to run remotely was relatively straightforward. First, since Mozilla does not recognize remote DTDs (used in Mozilla applications for localization), localized strings had to be added to the MAB's XUL file by embedding ENTITY elements into an inline DOCTYPE declaration in the file:

<!DOCTYPE window [
 <!ENTITY windowTitle.label "M A B">
 <!ENTITY file.label "File">
 ...
]
>

While not nearly as effective as encapsulating the strings in a separate DTD file, this approach does make it possible to localize the application by creating a copy of the XUL file for each localization and using HTTP content negotiation or explicit links to provide access to localized versions.

Second, because remote XUL cannot refer to supporting CSS and JS files using chrome URLs (URLs to local XUL application files installed into the browser's chrome/ directory), the MAB's XUL file uses relative URLs to refer to those files:

<?xml-stylesheet href="../skin/mab.css" type="text/css"?>
   ...
<script src="js/mab.js" type="application/x-javascript"></script>

The only server configuration necessary was to set the Apache web server on the site hosting the application to serve XUL files with the appropriate MIME type:

AddType application/vnd.mozilla.xul+xml .xul .XUL 

The application is distributed via a web page that offers a standard HTTP link to load the application as a page in a browser or a JavaScript URL with the special chrome flag (for indicating that the window is for a Mozilla-based application) to load the application in its own window:

javascript:window.open("content/mab.xul", "MAB", "chrome,centerscreen,resizable");

Both approaches for loading the application offer the same functionality but very different user experiences. The page-in-browser approach resembles a web application, while the separate-window approach is more like a client-server application. Mozilla supports both methods, and this flexibility allows application developers to choose the style that best suits their users. With the client-server-like approach, Mozilla even allows applications with the appropriate privileges to place an alias on the user's desktop for launching the application (although the MAB doesn't take advantage of this), for example, by specifying the following command line:

mozilla -chrome http://www.infodraft.com/~faser/mab/content/mab.xul

Application Structure

MAB application files are organized into a directory tree that is much like packages in the Mozilla chrome directory. A top-level mab/ directory contains content/, locale/, and skin/ subdirectories, with the XUL and JavaScript files in the content/ and content/js/ subdirectories and the CSS stylesheets in skin/:

mab/
  content/
    js/
  locale/
  skin/

Although the MAB has a locale/ directory, it does not use it to provide localized versions of the application, since Mozilla does not recognize remote DTD files.

The content of those files is also almost exactly like a local XUL application. The main XUL file, content/mab.xul, starts with the XML declaration, stylesheet processing instructions, and DOCTYPE declaration (for localization), which are common to most XUL files:

<?xml version="1.0" encoding="UTF-8"?>
   ...
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://navigator/skin/" type="text/css"?>
<?xml-stylesheet href="../skin/mab.css" type="text/css"?>
<!DOCTYPE window [
   ...
]
>

The rest of the document comprises XUL tags that define the structure and functionality of the user interface. Here is the basic structure (reformatted, annotated, and with non-essential tags removed for clarity):

<window...> (container for all XUL; defines the application window or browser page)
  <script src="js/mab.js" type="application/x-javascript"></script> 
     (code that does the work)    
  <commandset>
     ...   (actions that can be triggered by user input) 
  </commandset>
  <keyset>
     ...   (keyboard shortcuts for various actions) 
   </keyset>
  <toolbox> (container for the menus and search toolbar)
     <menubar...>...</menubar> (menus)
     <toolbar>...</toolbar> (search form)
  </toolbox>    
  <hbox...> (main body of application; displays data about search results)  
     <vbox...>
        <tree...>...</tree> (search results as tree of products)
        <hbox>... 
           <grid>...</grid> (details--name, author, and so on--of selected product)
           <image/> ... (photo of selected product)
        </hbox>...
        <hbox...>...</hbox> (prices of selected product)
     </vbox>...
     <iframe.../> (HTML-formatted comments about the selected product)
  </hbox>...
  <statusbar...>...</statusbar> (search status)
</window>

The main JavaScript file, content/js/mab.js, contains the code that controls the MAB's behavior. It includes functions to:

The MAB primarily uses the default browser stylesheets to style its appearance, so its own stylesheet is minimal and limited to tweaking the appearance of elements with minor changes to borders, margins, padding, colors, and fonts. In addition to these main files, there are a few other XUL, JavaScript, and CSS files to handle additional windows, such as the help and preferences windows.

Note that all of the XUL, JavaScript, and CSS used in the application is almost exactly the same as would be used to build a local XUL-based application, and in fact the MAB has been packaged for local installation without any changes to its file structure and code. Even the advanced features of the Mozilla framework that the MAB doesn't use, such as XBL, RDF, and XUL templates, work the same or similarly in remote applications as they do in local ones, although there are a few bugs specific to remote applications. Unless an application needs to save data to the local hard drive or otherwise get access to restricted system resources, getting it to work remotely requires little modification.

Communicating with Amazon

The MAB uses Amazon's web services API to search the company's catalogs and retrieve results. Amazon's API can be accessed via either XML over HTTP or SOAP, and Mozilla supports both methods. The MAB's author chose to use XML over HTTP because it was simpler to implement, and he could not find any advantages to using SOAP (more info on SOAP vs. XML over HTTP at Amazon). XML over HTTP passes parameters to the remote procedure via URL parameters in a standard HTTP request, so the MAB first creates a standard HTTP URL containing the search configuration and string:

var qString = targetUrl[connectionType]+'?t='+ASSID+'&dev-t='+DEVT+'&
'+getSearchBy()+'='+escape(query)+'&mode='+getProductLine()+'&
type='+searchType+'&page='+page+'&f='+f;

In additon to the query variable, which contains the search string entered by the user, the following values make up the URL:

targetUrl[connectionType]
Base URL of Amazon server or proxy.
ASSID
Associate ID: identifies the provider of the application; required for access to Amazon web services.
DEVT
Developer token issued by Amazon to identify the application author; also required.
getSearchBy()
The type of search the user is doing (Title, Keyword, and so on).
getProductLine()
The catalog in which the user is searching (Books, DVDs, and so on).
searchType
Whether or not to include comments in the results; possible values are lite (without comments) or heavy (with comments).
page
Which page of results to retrieve (Amazon returns, at most, ten results at a time).
f
Output format (always "XML" in the MAB).

The MAB then uses Mozilla's XMLHttpRequest object to connect to the Amazon servers and initiate a search. First it creates a new XMLHttpRequest object req. If the user is connecting directly to the Amazon service, the application also requests permission to connect to the Amazon servers, which would otherwise be off-limits due to security restrictions (see A Word About Proxies below for more details). Then the MAB initiates a standard HTTP GET request using the req object and the previously constructed URL. Finally, it adds a load handler to req that will run when the MAB receives a response from the server and will process the results.

var req = new XMLHttpRequest();
  ...
if(connectionType == "direct") {
   netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
}
req.open("GET",qString,true);
req.send(null);
  ...
req.onload = function() {
  ...
}

Displaying the Results

The Amazon web service responds to a request from the MAB by searching its catalogs and returning a set of matching products as a series of XML records containing fields for product attributes. For example:

<Details url="http://www.amazon.com/exec/obidos/redirect?tag=webservices-...">
   <Asin>B00006IZOC</Asin>
   <ProductName>Dial-A-Song: 20 Years of</ProductName>
   <Catalog>Music</Catalog>
   <Artists>
      <Artist>They Might Be Giants</Artist>
   </Artists>
   <ReleaseDate>17 September, 2002</ReleaseDate>
   <Manufacturer>Rhino Records</Manufacturer>
   <ImageUrlSmall>http://images.amazon.com/images/P/B00006IZOC.01.THUMBZZZ.jpg
     </ImageUrlSmall>    
   <ImageUrlMedium>http://images.amazon.com/images/P/B00006IZOC.01.MZZZZZZZ.jpg
     </ImageUrlMedium>    
   <ImageUrlLarge>http://images.amazon.com/images/P/B00006IZOC.01.LZZZZZZZ.jpg
     </ImageUrlLarge>    
   <ListPrice>$31.98</ListPrice>
   <OurPrice>$28.99</OurPrice>
   <UsedPrice>$26.83</UsedPrice>
</Details>

Once the Amazon service has finished returning results and closes the connection, the load handler on the XMLHttpRequest object takes over and uses Mozilla's DOMParser object to convert the XML string into a DOM document:

var responseXML = new DOMParser().parseFromString(req.responseText, 'text/xml');

It then passes the document to an AmazonResult object, a custom object defined by the MAB to store search results and display them to the user:

//Create new
ObjmyAmaz = new AmazonResult();
  ...
myAmaz.parseResponse(responseXML);

The AmazonResult object stores the document in a member property and then calls its createTree method to display the data in the XUL tree:

AmazonResult.prototype.parseResponse = function(responseXML) {
   ...
   this.xmlDoc = responseXML;
   ...
   this.createTree();
   ...
}

(Note that this procedure is somewhat different when two searches are being merged together.)

createTree iterates over the search results. For each one, it retrieves the data using the getElementsByTagName and item DOM methods and creates tree rows using the createElement DOM method to create treeitem and treerow elements for each record and treecell elements for each field value. Then it uses the appendChild DOM method to append each row to the tree, causing the row to be displayed to the user:

AmazonResult.prototype.createTree = function() {    
   var nr = this.getNrRecord();
   for(var i=0;i<nr;i++) {
      var rowID = this.xmlDoc.getElementsByTagName("Details")[i].
                  getElementsByTagName('Asin').item(0).firstChild.nodeValue;
      var item = document.createElement('treeitem');
      var row = document.createElement('treerow');
      row.setAttribute('id',rowID);
      ...
      var cell_name = document.createElement('treecell');
      cell_name.setAttribute('label',this.getDetail(rowID,'ProductName',false));
      ... [creation of other cells omitted]
      
      row.appendChild(cell_name);
      ... [appending of other cells omitted] 
      item.appendChild(row);
      guiListTree.appendChild(item);
   }
...
}

Note that while the author considered using an in-memory RDF data source and a XUL template to construct the tree, he ultimately chose to use DOM methods because they were more familiar to him given his experience with traditional web development and avoided issues with accessing Mozilla's RDF API from remote applications. The downside to using DOM methods is the loss of features that come bundled with RDF and XUL templates in Mozilla, such as sorting.

Conclusion

The Mozilla Amazon Browser provides a rich interface for searching Amazon with features not found in existing HTML-based interfaces, and it highlights the potential for remote Mozilla-based applications to improve the capabilities and user experience of Internet applications. Mozilla provides XUL, an application-centric layout language with both basic and complex form controls, web services support for communicating and exchanging data with remote applications, cross-platform support, and many other features. It is an excellent framework for building the next generation of remote Web applications.

Thanks to Fabio Serra, the author of the MAB, for his help with the technical details of its implementation.

Brian King is an independent consultant who works with web and open source technologies.

Myk Melez has been working with Mozilla since 1999, when he started using the browser as a DHTML application platform.


A Word About Proxies

By default, the MAB uses a proxy that runs on the same server as the one from which it is loaded, but it can be reconfigured to connect to the Amazon service directly:


Connection Settings

The advantages of using a proxy for a remote application are:

Proxy servers also have drawbacks. In particular, they are slower than direct connections and may have scalability problems. Still, for some applications, particularly those that use a web service that is not freely accessible, are distributed by organizations that have their own level of authentication, or for whom cryptographic signing is prohibitive, a proxy server is the only workable solution.

Note that Mozilla developers are working on a new security model that lets applications bypass cross-domain restrictions when such access is explicitly permitted by the target server. The model relies on web services publishing a simple, machine-readable document that declares which services are available to which client domains. This model should make it much easier for client applications to access web services in the future.


O'Reilly & Associates recently released (September 2002) Creating Applications with Mozilla.


Return to the Mozilla DevCenter.

Copyright © 2009 O'Reilly Media, Inc.