Creating Shopping Carts


Not all shopping-cart experiences are alike, but most are reasonably similar. After you have built one cart application, others will come naturally and quickly. This section presents one way of implementing a shopping cart, which you can adapt for your own applications. First I'll discuss several approaches for remembering shopping-cart contents. Then you'll assemble a simple cart, using just a few ColdFusion templates.

This section discusses a number of concepts introduced in Chapter 20, "Working with Sessions," including the definition of a Web-based session, as well as ColdFusions special CLIENT and SESSION scopes. I suggest you read or at least skim Chapter 20 before you continue here.


Storing Cart Information

One of the first things to consider when building a shopping cart is how to store the shopping-cart information. Most users expect to be able to add items to a cart, go somewhere else (perhaps back to your storefront page or to another site for comparison shopping), and then return later to check out. This means you need to maintain the contents of each user's cart somewhere on your site.

No matter how you decide to store the information, you usually have at least two pieces of information to maintain:

  • Items Added to the Cart. In most situations, each item will have its own unique identifier (in the sample database, this is the MerchID column in the Merchandise table), so remembering which items the user has added to the cart is usually just a matter of remembering one or more ID numbers.

  • Desired Quantity. Generally, when the user first adds an item to the cart, you should assume they want to purchase just one of that item. The user can usually increase the quantity for each item by adding the same item to the cart multiple times or by going to a View Cart page and entering the desired quantity in a text field.

As far as these examples are concerned, these two pieces of information, considered together, comprise the user's shopping cart. In many situations, this is all you need to track. Sometimes, though, you also need to track some kind of option for each item, such as a color or discounted price. Typically you can deal with these extra modifiers in the same way that you'll deal with the quantity in this chapter.

In any case, you can store this information in a number of ways. The most common approaches are summarized here.

CLIENT-Scoped Lists

Perhaps the simplest approach is to simply store the item IDs and quantities as variables in the CLIENT scope. As you learned in Chapter 20, the CLIENT scope can only store simple values, rather than arrays, structures, and so on. So the simplest option is probably to maintain two variables in the CLIENT scope, a ColdFusion-style list of MerchIDs and a list of associated quantities.

Aside from its simplicity, one nice thing about this approach is that the user's cart will persist between visits, without your having to write any additional code. Also, client variables can be set up so that they work within all servers in a cluster.

See Chapter 20 for details about how long CLIENT variables are maintained and how they can be stored in the servers registry, in a database, or as a cookie on the user's machine.


This chapter includes example code for a Custom Tag called <cf_ShoppingCart>, which uses CLIENT-scoped lists to provide shopping-cart experiences for Orange Whip's visitors.

While its true that the CLIENT scope can only hold simple values, you can use the <cfwddx> tag as a way to store a complex value like an array or structure as a client variable. I'm not going to cover this here, but you can read more about <cfwddx> in Appendix B, "ColdFusion Tag Reference." There are also two chapters devoted to WDDX in our companion book, Advanced ColdFusion MX Application Development.


SESSION-Scoped Structures and Arrays

Another approach would be to maintain each user's shopping-cart data in the SESSION scope. Unlike the CLIENT scope, the SESSION scope can contain structured data, such as structures and arrays. This means your code can be more elegant and flexible, especially if you have to track more information about each item than just the desired quantity. However, SESSION variables are RAM resident and not cluster aware as ColdFusion ships, so you might want to stay away from this approach if you plan to run a number of ColdFusion servers together in a cluster. See Chapter 20 for more pros, cons, and techniques regarding session variables.

This chapter provides sample code for a ColdFusion Component (CFC) called ShoppingCart, which uses a SESSION-scoped array of structures to provide a shopping-cart experience.

Cart Data in a Database

Another approach is to create additional tables in your database to hold cart information. If you require your users to register before they add items to their carts, you could use their ContactID (or whatever unique identifiers you are using for users) to associate cart contents with users. Therefore, you might have a table called CartContents, with columns such as ContactID, MerchID, Quant, DateAdded, and whatever additional columns you might need, such as Color or Size. If you don't require users to register before using the cart, you could use the automatic CLIENT.CFID variable as a reasonably unique identifier for tracking cart contents.

This approach gives you more control than the others, in particular the capability to maintain easily queried historical information about which items users have added to carts most often and so on (as opposed to items actually purchased). It would also work in a clustered environment. You would probably need to come up with some type of mechanism for flushing very old cart records from the database, however. because they wouldn't automatically expire in the way that SESSION and CLIENT variables do.

You could handle this periodic table flushing via a scheduled template, as explained in Chapter 36, "Event Scheduling."


Building a Shopping Cart

Now that a storefront has been constructed with Add To Cart links for each product, it's time to build the actual shopping cart. This section creates two versions of a ColdFusion template called StoreCart.cfm.

If the StoreCart.cfm template is visited without any URL parameters, it displays the items in the cart and gives the user the opportunity to either change the quantity of each item or check out, as shown in Figure 28.2. If a MerchID parameter is passed in the URL, that item is added to the user's cart before the cart is actually displayed. You will notice that the Add To Cart links generated by the <cf_MerchDisplay> Custom Tag (refer to Listing 28.1) do exactly that.

Figure 28.2. From the Shopping Cart page, users can update quantities or proceed to the Checkout phase.


The Simplest Approach

The version of the StoreCart.cfm template in Listing 28.4 is probably one of the simplest shopping-cart templates possible. As suggested in the "Storing Cart Information" section earlier in this chapter, each user's cart data is stored using two comma-separated lists in the CLIENT scope. The CLIENT.cartMerchList variable holds a comma-separated list of merchandise IDs, and CLIENT.cartQuantList holds a comma-separated list of corresponding quantities.

TIP

The next version of the template improves on this one by moving the task of remembering the user's cart into a CFML Custom Tag. It is recommended that you model your code after the next version of this template, rather than this one. Just use this listing as a study guide to familiarize yourself familiar with the basic concepts.


NOTE

To make the links to the shopping-cart page work correctly, save Listing 28.4 as StoreCart.cfm, not StoreCart1.cfm.


NOTE

This listing uses client variables, which means you need to enable the CLIENT scope in Application.cfc. The Application.cfc file for this chapter (Listing 28.8) does this. It also creates some additional code used by the CFC version of this shopping cart, discussed in the "A ColdFusion Component Version of the Shopping Cart" section, later in this chapter.


Listing 28.4. StoreCart1.cfmA Simple Shopping Cart
 <!---  Filename: StoreCart.cfm  Created by: Nate Weiss (NMW)  Purpose: Provides a simple shopping cart interface ---> <!--- Show header images, etc., for Online Store ---> <cfinclude template="StoreHeader.cfm"> <!--- URL parameter for MerchID ---> <cfparam name="URL.addMerchID" type="string" default=""> <!--- These two variables track MerchIDs / Quantities ---> <!--- for items in user's cart (start with empty cart) ---> <cfparam name="CLIENT.cartMerchList" type="string" default=""> <cfparam name="CLIENT.cartQuantList" type="string" default=""> <!--- If MerchID was passed in URL ---> <cfif isNumeric(URL.addMerchID)>   <!--- Get position, if any, of MerchID in cart list --->   <cfset currentListPos=listFind(cartMerchList, URL.addMerchID)>   <!--- If this item *is not* already in cart, add it --->   <cfif currentListPos eq 0>     <cfset CLIENT.cartMerchList=listAppend(CLIENT.cartMerchList,            URL.addMerchID)>     <cfset CLIENT.cartQuantList=listAppend(CLIENT.cartQuantList, 1)>   <!--- If item *is* already in cart, change its qty --->   <cfelse>    <cfset currentQuant=listGetAt(CLIENT.cartQuantList, currentListPos)>    <cfset updatedQuant=currentQuant + 1>    <cfset CLIENT.cartQuantList=listSetAt(CLIENT.cartQuantList, currentListPos,           updatedQuant)>   </cfif> <!--- If no MerchID passed in URL ---> <cfelse>   <!--- For each item currently in user's cart --->   <cfloop from="1" to="#listLen(CLIENT.cartMerchList)#" index="i">     <cfset thisMerchID=listGetAt(CLIENT.cartMerchList, i)>     <!--- If FORM field exists for this item's Quant --->     <cfif isDefined("FORM.quant_#thisMerchID#")>       <!--- The FORM field value is the new quantity --->       <cfset newQuant=FORM["quant_#thisMerchID#"]>       <!--- If new quant is 0, remove item from cart --->       <cfif newQuant eq 0>        <cfset CLIENT.cartMerchList=listDeleteAt(CLIENT.cartMerchList, i)>        <cfset CLIENT.cartQuantList=listDeleteAt(CLIENT.cartQuantList, i)>        <!--- Otherwise, Update cart with new quantity --->       <cfelse>         <cfset CLIENT.cartQuantList=listSetAt(CLIENT.cartQuantList, i,                newQuant)>       </cfif>     </cfif>   </cfloop>   <!--- If user submitted form via "Checkout" button --->   <cfif isDefined("FORM.isCheckingOut")>     <cflocation URL="StoreCheckout.cfm">   </cfif> </cfif> <!--- Stop here if user's cart is empty ---> <cfif CLIENT.cartMerchList eq "">  There is nothing in your cart.  <cfabort> </cfif> <!--- Create form that submits to this template ---> <cfform action="#CGI.script_name#"> <table> <tr>   <th colspan="2" bgcolor="Silver">Your Shopping Cart</th> </tr> <!--- For each piece of merchandise ---> <cfloop from="1" to="#listLen(CLIENT.cartMerchList)#" index="i">   <cfset thisMerchID=listGetAt(CLIENT.cartMerchList, i)>   <cfset thisQuant=listGetAt(CLIENT.cartQuantList, i)>   <tr>   <td>   <!--- Show this piece of merchandise --->   <cf_MerchDisplay merch showAddLink="No">   </td>   <td>   <!--- Display Quantity in Text entry field --->   <cfoutput>   Quantity:   <cfinput type="Text" name="quant_#thisMerchID#" size="3" value="#thisQuant#">   </cfoutput>   </td>   </tr> </cfloop> </table>  <!--- Submit button to update quantities --->  <cfinput type="submit" name="submit" value="Update Quantities">  <!--- Submit button to Check out --->  <cfinput type="Submit" value="Checkout" name="IsCheckingOut"> </cfform> 

The <cfform> section at the bottom of this template is what displays the contents of the user's cart, based on the contents of the CLIENT.CartMerchList and CLIENT.CartQuantList variables. Suppose for the moment that the current value of CartMerchList is 5,8 (meaning the user has added items number 5 and 8 to the cart) and that CartQuantList is 1,2 (meaning the user wants to buy one of item number 5 and two of item number 8). If so, the <cfloop> near the bottom of this template will execute twice. The first time through the loop, thisMerchID will be 5 and thisQuant will be 1. Item number 5 is displayed with the <cf_MerchDisplay> tag, and then a text field called quant_5 is displayed, prefilled with a value of 1. This text field enables the user to adjust the quantities for each item.

At the very bottom of the template, two submit buttons are provided, labeled Update Quantities and Checkout. Both submit the form, but the Checkout button sends the user on to the Checkout phase after the cart quantities have been updated.

Updating Cart Quantities

Three <cfparam> tags are at the top of Listing 28.4. The first makes it clear that the template can take an optional addMerchID parameter. The next two ensure that the CLIENT.cartMerchList and CLIENT.cartQuantList variables are guaranteed to exist. If not, they are initialized to empty strings, which represent an empty shopping cart.

If a numeric addMerchID is passed to the page, the first <cfif> block executes. The job of this block of code is to add the item indicated by URL.addMerchID to the user's cart. First, the listFind() function sets the currentListPos variable. This variable is 0 if the addMerchID value is not in CLIENT.cartMerchList (in other words, if the item is not in the user's cart). Therefore, this function places the addMerchID value in the user's cart by appending it to the current cartMerchList value, and by appending a quantity of 1 to the current merchQuantList value.

If, on the other hand, the item is already in the user's cart, currentListPos is the position of the item in the comma-separated lists that represent the cart. Therefore, the current quantity for the passed addMerchID value can be obtained with the listGetAt() function and stored in currentQuant. The current quantity is incremented by 1, and the updated quantity is placed in the appropriate spot in CLIENT.cartQuantList, via the listSetAt() function.

The large <cfelse> block executes when the user submits the form, using the Update Quantities or Checkout button (see Figure 28.2). The <cfloop> loops through the list of items in the user's cart. Again, supposing that CLIENT.cartMerchList is currently 5,8, then thisMerchID is set to 5 the first time through the loop. If a form variable named FORM.quant_5 exists, that form value represents the user's updated quantity for the item. If the user has specified an updated quantity of 0, it is assumed that the user wants to remove the item from the cart, so the appropriate values in cartMerchList and cartQuantList are removed using the listDeleteAt() function. If the user has specified some other quantity, the quantity in cartQuantList is updated, using the listSetAt() function.

Finally, if the user submitted the form using the Checkout button, the browser is directed to the CartCheckout.cfm template via the <cflocation> tag.

At this point, the shopping cart is quite usable. The user can go to the Store.cfm template (refer to Figure 28.1) and add items to the shopping cart. Once at the shopping cart (see Figure 28.2), the user can update quantities or remove items by setting the quantity to 0.

Encapsulating the Shopping Cart in a Custom Tag

Although the version of StoreCart.cfm in Listing 28.4 works just fine, the code itself is a bit messy. It contains quite a few list functions, which don't necessarily have to do with the conceptual problem at hand (the user's cart). Worse, other templates that need to refer to the user's cart (such as the Checkout template) must use nearly all the same list functions over again, resulting in quite a bit of code for you to maintain.

Using the Custom Tag skills you learned in Chapter 23, you can create a Custom Tag that represents the abstract notion of the user's shopping cart.

Building <cf_ShoppingCart>

The code in Listing 28.5 creates a Custom Tag called <cf_ShoppingCart>, which encapsulates all the list-manipulation details necessary to maintain the user's shopping cart. After you save Listing 28.5 as ShoppingCart.cfm in the special CustomTags folder (or just in the same folder where you'll be using it), it will be capable of accomplishing any of the tasks shown in Table 28.1.

Table 28.1. Syntax Supported by the <cf_ShoppingCart> Custom Tag Example

DESIRED ACTION

SAMPLE CODE

Add an item to the user's cart

<cf_ShoppingCart action="Add" merch>

Update the quantity of an item

<cf_ShoppingCart action="Update" merch quantity="10">

Remove an item from the cart

<cf_ShoppingCart action="Remove" merch>

Remove all items from cart

<cf_ShoppingCart action="Empty">

Retrieve all items in cart

<cf_ShoppingCart action="List" returnVariable="GetCart">


action="List" returns the cart's contents as a ColdFusion query object; the query object will contain two columns, MerchID and Quantity.)

Because the various action tasks provided by this Custom Tag all relate to a single concept (a shopping cart), you can think of the Custom Tag as an object-based representation of the cart. That makes it an ideal candidate for turning into a ColdFusion Component (CFC). See Chapter 23 for further discussion of Custom Tags and CFCs as objects.


Listing 28.5. ShoppingCart.cfmConstructing the <cf_ShoppingCart> Custom Tag
 <!---  Filename: ShoppingCart.cfm  Created by: Nate Weiss (NMW)  Purpose: Creates the <CF_ShoppingCart> Custom Tag ---> <!--- Tag Parameters ---> <cfparam name="ATTRIBUTES.action" type="string"> <!--- These two variables track MerchIDs / Quantities ---> <!--- for items in user's cart (start with empty cart) ---> <cfparam name="CLIENT.cartMerchList" type="string" default=""> <cfparam name="CLIENT.cartQuantList" type="string" default=""> <!--- This tag is being called with what ACTION? ---> <cfswitch expression="#ATTRIBUTES.action#">   <!--- *** ACTION="Add" or ACTION="Update" *** --->   <cfcase value="Add,Update">     <!--- Tag attributes specific to this ACTION --->     <cfparam name="ATTRIBUTES.merchID" type="numeric">     <cfparam name="ATTRIBUTES.quantity" type="numeric" default="1">     <!--- Get position, if any, of MerchID in cart list --->     <cfset currentListPos = listFind(CLIENT.cartMerchList, ATTRIBUTES.merchID)>     <!--- If this item *is not* already in cart, add it --->     <cfif currentListPos eq 0>       <cfset CLIENT.cartMerchList =       listAppend(CLIENT.cartMerchList, ATTRIBUTES.merchID)>       <cfset CLIENT.cartQuantList =       listAppend(CLIENT.cartQuantList, ATTRIBUTES.quantity)>      <!--- If item *is* already in cart, change its qty --->     <cfelse>       <!--- If Action="Add", add new Qty to existing --->       <cfif ATTRIBUTES.action eq "Add">         <cfset ATTRIBUTES.quantity =          ATTRIBUTES.quantity + listGetAt(CLIENT.cartQuantList, currentListPos)>       </cfif>       <!--- If new quantity is zero, remove item from cart --->       <cfif ATTRIBUTES.quantity eq 0>         <cfset CLIENT.cartMerchList =          listDeleteAt(CLIENT.cartMerchList, currentListPos)>         <cfset CLIENT.cartQuantList =          listDeleteAt(CLIENT.cartQuantList, currentListPos)>       <!--- If new quantity not zero, update cart quantity --->       <cfelse>         <cfset CLIENT.cartQuantList =          listSetAt(CLIENT.cartQuantList, currentListPos, ATTRIBUTES.quantity)>       </cfif>     </cfif>   </cfcase>   <!--- *** ACTION="Remove" *** --->   <cfcase value="Remove">     <!--- Tag attributes specific to this ACTION --->     <cfparam name="ATTRIBUTES.merchID" type="numeric">     <!--- Treat "Remove" action same as "Update" with Quant=0 --->     <cf_ShoppingCart       aCTION="Update"       merch       quantity="0">   </cfcase>   <!--- *** ACTION="Empty" *** --->   <cfcase value="Empty">     <cfset CLIENT.cartMerchList = "">     <cfset CLIENT.cartQuantList = "">   </cfcase>   <!--- *** ACTION="List" *** --->   <cfcase value="List">     <!--- Tag attributes specific to this ACTION --->     <cfparam name="ATTRIBUTES.returnVariable" type="variableName">     <!--- Create a query, to return to calling template --->     <cfset q = queryNew("MerchID,Quantity")>     <!--- For each item in CLIENT lists, add row to query --->     <cfloop from="1" to="#listLen(CLIENT.cartMerchList)#" index="i">       <cfset queryAddRow(q)>       <cfset querySetCell(q, "MerchID", listGetAt(CLIENT.cartMerchList, i))>       <cfset querySetCell(q, "Quantity", listGetAt(CLIENT.cartQuantList, i))>     </cfloop>     <!--- Return query to calling template --->     <cfset "Caller.#ATTRIBUTES.returnVariable#" = q>   </cfcase>   <!--- If an unknown ACTION was provided, display error --->   <cfdefaultcase>     <cfthrow     message="Unknown ACTION passed to &lt;CF_ShoppingCart&gt;"     detail="Recognized ACTION values are <B>List</B>, <B>Add</B>,     <B>Update</B>, <B>Remove</B>, and <B>Empty</B>.">   </cfdefaultcase> </cfswitch> 

NOTE

Some of the <cfset> tags in this template are broken somewhat unusually across two lines (the left side of the expression on one line and the right side on the next line) to make them easier to read in this book. In your actual code templates, you would probably have the whole <cfset> statement on one line, but this listing does show that ColdFusion can deal uncomplainingly with expressions spanning multiple lines.


This Custom Tag supports its various tasks by requiring an action attribute (required by the <cfparam> tag at the top of Listing 28.5), and then handling each of the supported actions in separate <cfcase> tags within a large <cfswitch> block. If the action is Add or Update, the first <cfcase> tag executes. If the action is Remove, the second one executes, and so on.

If action="Add" or action="Update", the tag accepts two additional parametersmerchID (required) and quantity (optional, defaulting to 1). The <cfcase> block for these actions is similar to the top portion of Listing 28.4, using listFind() to determine whether the item is already in the user's cart and then adding it to the cart with listAppend() or updating the quantity using listSetAt(). Also, if action="Update" and Quantity="0", the item is removed from the user's cart.

If the tag is called action="Remove", the tag just calls itself again, using action="Update" and quantity="0" to remove the item from the user's cart. So Remove is just a synonym for an Update that sets the quantity to 0.

If action="Empty", the CLIENT.cartMerchList and CLIENT.cartQuantList are emptied by setting them both to empty strings. This has the effect of removing all items from the user's cart.

Finally, if action="List", the tag creates a new ColdFusion query object using the queryNew() function, which the calling template will be capable of using as if it were generated by an ordinary <cfquery> tag. The new query has two columns, merchID and quantity. For each item in the cartMerchList and cartQuantList lists, a row is added to the query using queryAddRow(); then the MerchID and Quantity columns of the just-inserted row are set using the querySetCell() function. The end result is a simple two-column query that contains a row for each item in the user's cart. The query object is returned to the calling template with the name specified in the tag's returnVariable attribute.

Its worth learning more about returning non-database queries from Custom Tags. Check out queryNew(), queryAddRow(), and querySetCell() in Appendix C, "ColdFusion Function Reference."


One of the goals for this Custom Tag is to ensure that no other template will need to refer to the CLIENT.cartMerchList and CLIENT.cartQuantList variables. The Custom Tag will therefore be a clean abstraction of the concept of a users cart, including the storage method (currently the two lists in the CLIENT scope). If you later decide to use SESSION variables or a database table to hold each user's cart data, you only have to change the code in the Custom Tag template. See Chapter 23 for more discussion about attaining the holy grail of abstraction via Custom Tags.


Putting <cf_ShoppingCart> to Work

The version of StoreCart.cfm in Listing 28.6 is a revision of the one in Listing 28.4. As far as the user is concerned, it behaves the same way. However, it removes all references to the internal storage mechanisms (the CLIENT variables, list functions, and so on). As a result, the code reads well, and it will be clearer to future coders and easier for you to reuse and maintain.

NOTE

To make the links to the shopping-cart page work correctly, you should save Listing 28.6 as StoreCart.cfm, not StoreCart2.cfm.


Listing 28.6. StoreCart2.cfmUsing <cf_ShoppingCart> to Rebuild StoreCart.cfm Template
 <!---  Filename: StoreCart.cfm  Created by: Nate Weiss (NMW)  Purpose: Provides a simple shopping cart interface ---> <!--- Show header images, etc., for Online Store ---> <cfinclude template="StoreHeader.cfm"> <!--- If MerchID was passed in URL ---> <cfif isDefined("URL.addMerchID")>   <!--- Add item to user's cart data, via custom tag --->   <cf_ShoppingCart   action="Add"   merch> <!--- If user is submitting cart form ---> <cfelseif isDefined("FORM.merchID")>   <!--- For each MerchID on Form, Update Quantity --->   <cfloop list="#FORM.merchID#" INDEX="thisMerchID">     <!--- Update Quantity, via Custom Tag --->     <cf_ShoppingCart     action="Update"     merch     quantity="#FORM['quant_#thisMerchID#']#">   </cfloop>   <!--- If user submitted form via "Checkout" button, --->   <!--- send on to Checkout page after updating cart. --->   <cfif isDefined("FORM.isCheckingOut")>     <cflocation url="StoreCheckout.cfm">   </cfif> </cfif> <!--- Get current cart contents, as a query object ---> <cf_ShoppingCart action="List" returnVariable="getCart"> <!--- Stop here if user's cart is empty ---> <cfif getCart.recordCount eq 0>   There is nothing in your cart.   <cfabort> </cfif> <!--- Create form that submits to this template ---> <cfform action="#CGI.script_name#"> <table> <tr>   <th colspan="2" bgcolor="Silver">Your Shopping Cart</th> </tr> <!--- For each piece of merchandise ---> <cfloop query="getCart">   <tr>     <td>     <!--- Show this piece of merchandise --->     <cf_MerchDisplay     merch     showAddLink="No">     </td>     <td>     <!--- Display Quantity in Text entry field --->     <cfoutput>     Quantity:     <cfinput type="hidden" name="merchID" value="#getCart.MerchID#">     <cfinput type="text" size="3" name="quant_#getCart.MerchID#"      value="#getCart.Quantity#">     </cfoutput>     </td>   </tr> </cfloop> </table> <!--- Submit button to update quantities ---> <cfinput type="submit" name="submit" value="Update Quantities"> <!--- Submit button to Check out ---> <cfinput type="submit" value="Checkout" name="IsCheckingOut"> </cfform> 

If the template receives an addMerchID parameter in the URL, the <cf_ShoppingCart> tag is called with action="Add" to add the item to the user's cart. If the user submits the shopping cart form with the Update Quantities or Checkout button, the template loops through the merchandise elements on the form, calling <cf_ShoppingCart> with action="Update" for each one.

Then, to display the items in the user's cart, the <cf_ShoppingCart> tag is called again, this time with action="List". Because getCart is specified for the returnVariable attribute, the display portion of the code just needs to use a <cfloop> over the getCart query, calling <cf_MerchDisplay> for each row to get the merchandise displayed to the user.

A ColdFusion Component Version of the Shopping Cart

If you think about it, the <cf_ShoppingCart> tag seems to embody some of the object-based ideas that are in keeping with the ColdFusion Components framework introduced in Chapter 23. In that chapter, you learned that CFCs often represent some kind of high-level idea that you can think of as a widget or object. Each type of object can provide its own set of functions or methods. You also learned that each instance of a CFC is often populated with its own data.

A shopping cart shares all these properties, which we have already seen brought to life by <cf_ShoppingCart>. Think of the cart as a type of object. The object can execute different actions, like Add, Update, and List. We can think of these actions as the object's methods. Most important, each instance of the shopping-cart object holds its own data. That is, each individual user's shopping cart is structurally the same (all spawned from the same object type, with the same methods and so on), but holds a different set of items.

It seems, then, that a shopping cart would be an ideal candidate for turning into a ColdFusion component. As you will see in this section, it is quite easy to turn the code for the <cf_ShoppingCart> Custom Tag into a ColdFusion Component called ShoppingCart. That will give Orange Whip Studios the option of exposing the shopping cart as a Web Service in the future. It will also open up the possibility of creating a slick Flash version of the shopping experience for visitors, because of Flash's ability to interact easily with ColdFusion Components via the Flash Remoting service.

See Chapter 26, "Integrating with Macromedia Flash," for details about calling CFCs from Flash movies. See Chapter 23 for more information about ColdFusion Components in general.


Building the ShoppingCart Component

The first step in converting the shopping-cart code from the Custom Tag implementation to a CFC implementation is largely a matter of changing the large <cfswitch> block from the Custom Tag into a <cfcomponent> tag, and changing each of the <cfcase> tags to <cffunction> tags. Obviously, that's not all you must do, but you'll see that the CFC code within each <cffunction> is strongly related to the corresponding <cfcase> from the Custom Tag (see Listing 28.5).

Listing 28.7 provides code for a simple component-based shopping cart. The most obvious change is the introduction of CFC framework tags like <cfcomponent> and <cffunction> to establish the CFC's methods. Table 28.2 shows the methods exposed by the CFC.

Table 28.2. MethodsExposed by the ShoppingCart CFC

METHOD

WHAT IT DOES

add(merchID, quantity)

Adds an item to the shopping cart. The quantity argument is optional (defaults to 1).

update(merchID, quantity)

Updates the quantity of a particular item in the shopping cart.

remove(merchID)

Removes an item from the shopping cart.

empty()

Removes all items from the shopping cart.

list()

Returns a query that contains all items currently in the shopping cart.


Remember that once you create this CFC, you can view automatically generated documentation for it by visiting the URL for the .cfc file with your browser, or by choosing Get Description from the right-click menu for the component in the Components tab of the Application panel in Dreamweaver. The automatic documentation page for the ShoppingCart component is shown in Figure 28.3.

Figure 28.3. ColdFusion generates documentation for CFCs.


Listing 28.7. ShoppingCart.cfcCreating the ShoppingCart Component
 <!---  Filename: ShoppingCart.cfc  Created by: Nate Weiss (NMW)  Purpose: Creates a CFC called ShoppingCart ---> <cfcomponent output="false">   <!--- Initialize the cart's contents --->   <!--- Because this is outside of any <CFFUNCTION> tag, --->   <!--- it only occurs when the CFC is first created --->   <cfset VARIABLES.cart = structNew()>   <!--- *** ADD Method *** --->   <cffunction name="Add" access="public" returnType="void" output="false"               hint="Adds an item to the shopping cart">     <!--- Two Arguments: MerchID and Quantity --->     <cfargument name="merchID" type="numeric" required="Yes">     <cfargument name="quantity" type="numeric" required="no" default="1">     <!--- Is this item in the cart already? --->     <cfif structKeyExists(VARIABLES.cart, arguments.merchID)>       <cfset VARIABLES.cart[arguments.merchID] =              VARIABLES.cart[arguments.merchID] + arguments.quantity>     <cfelse>       <cfset VARIABLES.cart[arguments.merchID] = arguments.quantity>     </cfif>   </cffunction>   <!--- *** UPDATE Method *** --->   <cffunction name="Update" access="public" returnType="void" output="false"               hint="Updates an item's quantity in the shopping cart">     <!--- Two Arguments: MerchID and Quantity --->     <cfargument name="merchID" type="numeric" required="Yes">     <cfargument name="quantity" type="numeric" required="Yes">     <!--- If the new quantity is greater than zero --->     <cfif arguments.quantity gt 0>       <cfset VARIABLES.cart[arguments.merchID] = arguments.quantity>       <!--- If new quantity is zero, remove the item from cart --->     <cfelse>       <cfset remove(arguments.merchID)>     </cfif>   </cffunction>   <!--- *** REMOVE Method *** --->   <cffunction name="Remove" access="public" returnType="void" output="false"               hint="Removes an item from the shopping cart">     <!--- One Argument: MerchID --->     <cfargument name="merchID" type="numeric" required="Yes">     <cfset structDelete(VARIABLES.cart, arguments.merchID)>   </cffunction>   <!--- *** EMPTY Method *** --->   <cffunction name="Empty" access="public" returnType="void" output="false"               hint="Removes all items from the shopping cart">     <!--- Empty the cart by clearing the This.CartArray array --->     <cfset structClear(VARIABLES.cart)>   </cffunction>   <!--- *** LIST Method *** --->   <cffunction name="List" access="public" returnType="query" output="false"               hint="Returns a query object containing all items in shopping               cart. The query object has two columns: MerchID and Quantity.">     <!--- Create a query, to return to calling process --->     <cfset var q = queryNew("MerchID,Quantity")>     <cfset var key = "">     <!--- For each item in cart, add row to query --->     <cfloop collection="#VARIABLES.cart#" item="key">       <cfset queryAddRow(q)>       <cfset querySetCell(q, "MerchID", key)>       <cfset querySetCell(q, "Quantity", VARIABLES.cart[key])>     </cfloop>     <!--- Return completed query --->     <cfreturn q>   </cffunction> </cfcomponent> 

Aside from the structural makeup of the code, you've made another important change here. While the Custom Tag version used comma-separated lists stored in the CLIENT scope to remember the items in each user's cart, this new component version uses a struct called cart to hold the cart's contents. Each time the user selects merchandise to purchase, a key will be added to the structure to hold the quantity of that particular item. So if the user has selected three items for purchase, the structure will have three keys. Each key represents the MerchID value, with the value of that key being the Quantity.

The cart structure is stored in the CFC's VARIABLES scope. This allows all of the methods of the CFC to manipulate the values.

The Add() method at the top of Listing 28.7 does its work with just a few lines of code. It first checks to see if the merchID value already exists in the struct. If it does, the passed in quantity is added to the existing quantity. If the value doesn't exist, a new key is created with the quantity. The Update() method is even simpler. Because we are resetting the quantity and not adding, we can simply set the value in the struct. It doesn't matter if it exited already. If the quantity provided is 0, the CFC's Remove() method is called to remove the item from the cart. That Remove() function is also really simple. It just uses the structDelete() function to remove the key from the structure. This method could be modified to check to see if the key actually existed, but it really isn't necessary.

The Empty() method is the simplest of all; it simply discards all items from the VARIABLES.cart variable with the structClear() function. And the code for the List() function is very similar to the corresponding code in Listing 28.5.

Using the ShoppingCart Component

Now that the ShoppingCart component has been built, it's easy to put it to work. The first thing to do to is to make sure each user gets their own ShoppingCart instance. Take a look at Listing 28.8, a simple Application.cfc file. The <cfset> lines are nothing new; they have been used in most Application.cfc files since Chapter 19, "Introducing the Web Application Framework."

The new thing here is the addition of the onSessionStart() method and the <cfobject> tag.

Listing 28.8. Application.cfcCreating a New CFC Instance for Each User
 <!---  Filename:   Application.cfc  Created by:  Raymond Camden (ray@camdenfamily.com)  Please Note Executes for every page request! ---> <cfcomponent output="false">   <!--- Name the application. --->   <cfset this.name = "c28">   <!--- Turn on session management. --->   <cfset this.sessionManagement = true>   <cfset this.clientManagement = true>   <cffunction name="onApplicationStart" returnType="void" output="false">     <cfset APPLICATION.dataSource="ows">     <cfset APPLICATION.companyName="Orange Whip Studios">   </cffunction>   <cffunction name="onSessionStart" returnType="void" output="false">     <cfobject name="SESSION.myShoppingCart" component="ShoppingCart">   </cffunction> </cfcomponent> 

When a user first visits the application, the code inside the onSessionStart() block executes, which means that a new instance of the ShoppingCart CFC is created and stored in the SESSION scope. The CFC instance remains in ColdFusion's memory for the remainder of the user's visit, or until the server is restarted or the user's session expires. That's all you need to do to give each user a shopping cart.

NOTE

Note that the component code itself (in Listing 28.7) did not refer to the SESSION scope at all. Instead, it referred only to the VARIABLES scope provided by the CFC framework. It is only now, when the CFC is instantiated, that it becomes attached to the notion of a session.


Now all that remains is to go back to the StoreCart2.cfm template from Listing 28.6 and replace the calls to the <cf_ShoppingCart> tag with calls to the CFC's methods. Listing 28.9 is the resulting template.

NOTE

To keep the various links between pages intact, remember to save this template as StoreCart.cfm, not StoreCart3.cfm.


Listing 28.9. StoreCart3.cfmPutting the ShoppingCart CFC to Work
 <!---  Filename: StoreCart.cfm  Created by: Nate Weiss (NMW)  Purpose: Provides a simple shopping cart interface ---> <!--- Show header images, etc., for Online Store ---> <cfinclude template="StoreHeader.cfm"> <!--- If MerchID was passed in URL ---> <cfif isDefined("URL.AddMerchID")>   <!--- Add item to user's cart data --->   <cfinvoke component="#SESSION.myShoppingCart#" method="Add"   merch> <!--- If user is submitting cart form ---> <cfelseif isDefined("FORM.merchID")>   <!--- For each MerchID on Form, Update Quantity --->   <cfloop list="#FORM.merchID#" index="thisMerchID">     <!--- Add item to user's cart data --->     <cfinvoke component="#SESSION.myShoppingCart#" method="Update"     merch quantity="#FORM['quant_#thisMerchID#']#">   </cfloop>   <!--- If user submitted form via "Checkout" button, --->   <!--- send on to Checkout page after updating cart. --->   <cfif isDefined("FORM.isCheckingOut")>     <cflocation url="StoreCheckout.cfm">   </cfif> </cfif> <!--- Get current cart contents, as a query object ---> <cfset getCart = SESSION.myShoppingCart.List()> <!--- Stop here if user's cart is empty ---> <cfif getCart.recordCount eq 0>  There is nothing in your cart.  <cfabort> </cfif> <!--- Create form that submits to this template ---> <cfform action="#CGI.SCRIPT_NAME#"> <table> <tr>   <th colspan="2" bgcolor="Silver">Your Shopping Cart</th> </tr> <!--- For each piece of merchandise ---> <cfloop query="getCart">   <tr>     <td>     <!--- Show this piece of merchandise --->     <cf_MerchDisplay     merch     showAddLink="No">     </td>     <td>     <!--- Display Quantity in Text entry field --->     <cfoutput>     Quantity:     <cfinput type="hidden" name="merchID" value="#getCart.MerchID#">     <cfinput type="text" size="3" name="quant_#getCart.MerchID#"            value="#getCart.Quantity#">     </cfoutput>     </td>   </tr> </cfloop> </table>  <!--- Submit button to update quantities --->  <cfinput type="submit" name="submit" value="Update Quantities">  <!--- Submit button to Check out --->  <cfinput type="submit" value="Checkout" name="IsCheckingOut"> </cfform> 

As you learned in Chapter 23, there are two basic ways to invoke a CFC's methods from a ColdFusion template: with the <cfinvoke> tag, or by using script-style method syntax in a <cfset> or other expression. This listing shows both.

For instance, in Listing 28.6, the following code retrieved the contents of the user's cart:

 <!--- Get current cart contents, via Custom Tag ---> <cf_ShoppingCart  action="List"  returnVariable="GetCart"> 

In Listing 28.9, the equivalent line is:

 <!--- Get current cart contents, as a query object ---> <cfset getCart = SESSION.myShoppingCart.List()> 

The following <cfinvoke> syntax would do the same thing:

 <!--- Add item to user's cart data ---> <cfinvoke component="#SESSION.myShoppingCart#" method="List" returnVariable="getCart"> 

Similarly, this line calls the CFC's Add method:

 <!--- Add item to user's cart data ---> <cfinvoke component="#SESSION.myShoppingCart#" method="Add" merch> 

You could change it to the following, which would do the same thing:

 <cfset SESSION.myShoppingCart.add(URL.addMerchID)> 

As you can see, the <cfinvoke> syntax is a bit more self-explanatory because the arguments are explicitly named. However, the script-style syntax is usually much more concise and perhaps easier to follow logically. Use whatever style you prefer.



Macromedia Coldfusion MX 7 Web Application Construction Kit
Macromedia Coldfusion MX 7 Web Application Construction Kit
ISBN: 321223675
EAN: N/A
Year: 2006
Pages: 282

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net