Related Topics: ColdFusion on Ulitzer

CFDJ: Article

ColdFusion Developer's Journal Special "Frameworks" Focus Issue: onTap

Using the onTap framework to reinvent the Pet Market application

Thanks to all of the code I could eliminate from these components, I reduced the CF/HTML/CSS code in the entire application from 85KB to 65KB. Although 20KB may not sound like a lot of code, it's roughly 25% of the entire application even after having added some of my own code (including a product-item DAO that wasn't included in the original application). That's a lot of keystrokes and time saved over a larger project.

The original Pet Market application included five CFCs: a _base.cfc that I eliminated (ontap.cfc provides an improved implementation of its only functional method), a shoppingcart.cfc for maintaining the current user's selected items and quantities and three components that are extended DAOs (category.cfc, product.cfc, and user.cfc). The original application stored product data in two tables (product and item), which prompted me to add the item.cfc for modeling data in the second table. The item.cfc also provided me with a good opportunity to highlight one of the advantages of the framework's dynamic accessors (getValue(propertyname)). In this case I could simplify the relationship between the product object and the product-item object by setting a property of "productname" in the item CFC's soft-constructor that ensures that the property is included in the structure returned from the getProperties() method and implemented the private get_productname method that instantiates the related product object and returns the value of its "name" property. So the item.cfc has a "productname" property that's drawn directly from the relevant related product object with no need for the accessing template to concern itself with the inner workings of these components or shape of the database schema.

Although the dynamic accessor feature let me eliminate many lines of code from the original CFCs, this altered structure forced me to check all of the display code to ensure that it accessed these objects using the appropriate methods (instead of arbitrarily exposed variables). For many of the display templates I opted not to replace the exposed variable references with dynamic accessor functions, chosing instead to append the desired variables to the attributes scope using the getProperties() method of the ontap.cfc and the ColdFusion native StructAppend() function. This strategy for decoupling variable use from both components and from the form and url scopes should be familiar to anyone who's used Fusebox in the past (and indeed I attempted to replace all form and url-scope variables in the application).

Show Me the Memory!
The original Pet Market application used many CFCs for different purposes, but it gained very little (if any) benefit from using CFCs. This is because the application created these objects for each request and the components didn't encapsulate their data. So no advantage could be gained from caching the data for categories or products even though this data is rarely (or never) changed; in fact, the application didn't even include any tools for updating product data. Since the onTap framework DAO objects are designed to make good use of caching techniques, I also implemented a factory object by extending the framework's factory.cfc. I instantiated the new petfactory.cfc and stored it in the variable application.petmarket.petfactory in the previously created template /ontap/_components/_appstart/100_petmarket.cfm that is executed when the application starts.

With this object in place I replaced all other instances of request.tapi.getObject() (previously CreateObject()) with calls to the methods of the application.petmarket.petfactory object (with the exception of user and shopping-cart objects instantiated at the beginning of a session). The petfactory object is now the only object that instantiates DAO components. As a result of this change, fewer queries are executed by the application, being limited only to search queries and queries executed when first instantiating a new DAO object. This caching makes the application significantly more scalable in handling large numbers of users and pet purchases. Since I created a factory object for the Pet Market application, I also took this opportunity to move the category-list and product-search queries to this factory object out of the category.cfc and product.cfc objects where they didn't belong in the first place.

A La Cart
I personally found Pet Market's implementation of a shopping-cart unacceptable for a number of reasons, both related to the model (data access and session management) and to the view (the checkout form is a horrid mess). I used several strategies to resolve what I saw as problems with the shopping cart.

First, I consolidated all of the code for the various checkout steps into the stepX.cfm templates in the /_components/petmarket/checkout/ directory so that each of these files contains all of the code for the relevant step of the checkout. The original version included only the form code in these files, while leaving code to display during subsequent steps casually misplaced in the checkout.cfm template. Then I removed the form-post code from the top of the checkout.cfm template and put it in several subdirectories of the /_components/petmarket/checkout/ directory. To target the code in these new directories I added a hidden form variable with the name "netaction" to each of the stepX.cfm form templates, indicating the relevant subdirectory. Now when each form is submitted only the relevant code for the submitted form is executed. Then I cleaned up /_components/petmarket/checkout/_process.cfm by putting the names of the steps in an array and used a loop and variables to generate the display of the list of steps and links to previous steps in the checkout process.

I must admit that the Pet Market shopping cart is an improvement over most shopping cart code I've seen because it smartly uses a query to hold the user's selected items (instead of the inappropriate multi-dimensional array used by most cart applications, which forces the programmer to use numeric indices where named keys would be more appropriate). Still, I think the shopping cart can definitely be improved.

Firstly, the display template /_shoppingcart.cfm fetches a query containing the names of all products in the database, which is then joined with the query containing the user's selected items. Although the first query is cached daily using the ColdFusion server's native query caching features, I find this technique to be unencapsulated and sloppy (the display code contains references to database schema) and I personally dislike the server's native query caching features. To alleviate these problems I implemented another of the framework's core CFCs - LinkedList.

To be honest I created the LinkedList.cfc as part of the framework core components specifically for handling memory-resident lists like shopping-carts. Although extensive, the transition from a ColdFusion query to a series of linked-list objects was reasonably simple thanks in part to the fact that several more complicated query tasks are encapsulated by the LinkedList.cfc object. When the cart is updated with quantities for one or more objects, checking to see if an item already exists in the cart is a simple function call ("itemoid",arguments.itemoid)) instead of a query of queries. When an item is removed from the cart, a similar method (item.remove()) replaces another query of queries. Displaying the contents of the cart is a simple process of looping over the pre-populated items and displaying their names, quantities, and costs (while item.hasNext()), cleanly encapsulating the data access away from the display logic. And since the objects are pre-populated from the petfactory product objects prior to being cached in the session scope, this has the added benefit of ensuring that the names of products in the user's cart won't change before checkout is complete. Although the user.cfc still has a placeOrder method, the bulk of its work is now done by the appropriate shoppingcart.cfc (user.cfc is not an appropriate place for the bulk of this code).

The coup de grâce is a bit of incidental internationalization. That is to say, i18n implemented with no expenditure of effort. In the original Pet Market application, items in the cart display in the order in which they were added to the cart. I didn't like this, preferring that items in the cart be sorted alphabetically, so I implemented the LinkedList sort method (item.sort("productname")) each time an item is added to the cart (removing an item doesn't change the sort order and as such sorting is unnecessary). It merely happens that the sort method provided by the LinkedList.cfc uses Java international collations to provide non-lexigraphical sorting. That is to say, more than a simply case-insensitive sort, but rather the names of products are sorted using the dictionary and accenting rules of the user's preferred language.

More Stories By Isaac Dealey

Isaac Dealey has worked with ColdFusion since 1997 (version 3) for clients from small businesses to large enterprises, including MCI and AT&T Wireless. He evangelizes ColdFusion as a volunteer member of Team Macromedia, is working toward becoming a technical instructor, and is available for speaking engagements.

Comments (2) View Comments

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.

Most Recent Comments
SYS-CON Brazil News Desk 02/01/06 03:38:19 PM EST

I must admit to having been excited at the prospect of the Pet Market frameworks project when Simon proposed it to us at the Fusebox & Frameworks Conference in September. I once tried to do something similar by creating a small blog application using the three popular frameworks that I was aware of at the time (Fusebox 3-4 and Mach-II) and the onTap framework.

SYS-CON India News Desk 02/01/06 02:49:28 PM EST

I must admit to having been excited at the prospect of the Pet Market frameworks project when Simon proposed it to us at the Fusebox & Frameworks Conference in September. I once tried to do something similar by creating a small blog application using the three popular frameworks that I was aware of at the time (Fusebox 3-4 and Mach-II) and the onTap framework.