To view the current price of an item, all of the items in the Shopping Cart must be accessed and all of the correct price information for each of these items must be accessed.
The component located at /atg/commerce/order/OrderHolder
is responsible for maintaining all of the customer’s orders. (ATG Consumer Commerce allows a customer to have more than one shopping cart where she can store orders. See the Working With Purchase Process Objects chapter of the ATG Commerce Programming Guide for more information on the OrderHolder
component.) The Pioneer Cycling store supports a single Shopping Cart that is referenced in the OrderHolder
component at /atg/commerce/order/OrderHolder.current
. We accessed this component through the /atg/commerce/order/ShoppingCartModifier
component. The ShoppingCartModifier
component has an order
property that is the current order from the OrderHolder
component. We used the following JSP to reference the customer’s current Order
object:
<dsp:valueof bean="ShoppingCartModifier.Order"/>
On the Shopping Cart page, the contents of the customer’s order are displayed with a ForEach
component that iterates through each ShippingGroup
in the order and then iterates through the items in each. It would be possible to simply have the ForEach
iterate through each CommerceItem
in the order directly. However, we decided that each ShippingGroup
and its items should be displayed separately. The following code snippet, taken from cart.jsp
, shows the nested iteration.
<% atg.servlet.DynamoHttpServletRequest dRequest = atg.servlet.ServletUtil.getDynamoRequest(request); %> . . . <dsp:droplet name="ForEach"> <dsp:param bean="ShoppingCartModifier.Order.ShippingGroups" name="array"/> <dsp:param name="elementName" value="ShippingGroup"/> <dsp:param name="indexName" value="shippingGroupIndex"/> <dsp:oparam name="output"> <core:switch value='<%= dRequest.getParameter("ShippingGroup.shippingGroupClassType") %>'> <core:case value="electronicShippingGroup"> <dsp:droplet name="ForEach"> <dsp:param name="array" param="ShippingGroup.CommerceItemRelationships"/> <dsp:param name="elementName" value="CiRelationship"/> <dsp:param name="indexName" value="index"/> <dsp:oparam name="outputStart"> <tr><td colspan=13> <Br>Electronic delivery to <dsp:valueof param="ShippingGroup.emailAddress"> unknown email address</dsp:valueof>: <hr size=0></td></tr> </dsp:oparam> <dsp:oparam name="output"> <dsp:setvalue param="itemName" value='<%= "item" + request.getParameter("shippingGroupIndex") + ":" +request.getParameter("index")%>'/> <%@ include file="CartLineItem.jspf" %> </dsp:oparam> </dsp:droplet> </core:case> <core:defaultCase> <dsp:droplet name="IsGiftShippingGroup"> <dsp:param name="sg" param="ShippingGroup"/> <dsp:oparam name="false"> <dsp:droplet name="ForEach"> <dsp:param name="array" param="ShippingGroup.CommerceItemRelationships"/> <dsp:param name="elementName" value="CiRelationship"/> <dsp:param name="indexName" value="index"/> <dsp:oparam name="outputStart"> <tr><td colspan=13> <br>Shipping to you:<hr size=0></td></tr> </dsp:oparam> <dsp:oparam name="output"> <dsp:setvalue param="itemName" value='<%="item" + request.getParameter("shippingGroupIndex") + ":" +request.getParameter("index")%>'/> <%@ include file="CartLineItem.jspf" %> </dsp:oparam> </dsp:droplet> </dsp:oparam> <dsp:oparam name="true"> <dsp:droplet name="ForEach"> <dsp:param name="array" param="ShippingGroup.CommerceItemRelationships"/> <dsp:param name="elementName" value="CiRelationship"/> <dsp:param name="indexName" value="index"/> <dsp:oparam name="outputStart"> <tr><td colspan=13> <br>Gifts <dsp:droplet name="GiftlistLookupDroplet"> <dsp:param name="id" param="giftlistId"/> <dsp:param name="elementName" value="giftlist"/> <dsp:oparam name="output"> for <dsp:valueof param="giftlist.owner.firstName"/> <dsp:valueof param="giftlist.owner.lastName"/> </dsp:oparam> </dsp:droplet>: <hr size=0></td></tr> </dsp:oparam> <dsp:oparam name="output"> <dsp:setvalue param="itemName" value='<%="item" + request.getParameter("shippingGroupIndex") + ":" +request.getParameter("index")%>'/> <%@ include file="CartLineItem.jspf" %> </dsp:oparam> </dsp:droplet> </dsp:oparam> </dsp:droplet> </core:defaultCase> </core:switch> </dsp:oparam> </dsp:droplet>
Getting Correct Price Information
Each CommerceItem
maintains price information in its priceInfo
property, which then in turn has an amount
property that is the currently calculated cost of the commerce item.
The priceInfo
object returned is an instance of ItemPriceInfo
. It contains information on the current price of a CommerceItem
such as list price, a list of discounts applied to the item, and the item’s onSale
status.
The following JSP code demonstrates the simplest case of displaying an item’s price, assuming that the commerce item has a parameter named commerceItem
.
<dsp:valueof converter="currency" param="commerceItem.priceInfo.amount"/>
Note that the currency TagConverter
used here automatically formats the price so it is displayed in a particular localized format.
The following JSP code displays the prices:
The price of the commerce item is: <dsp:valueof converter="currency" param="commerceItem.priceInfo.amount"/>
In the Pioneer store, we wanted to display both the sale price and the list price for an item. We used the compare
servlet bean to determine if the item has been discounted, and if so, to display both the list price as well as the discounted price.
<%-- Figure out if there have been discounts applied to this item. If there have then print out both the discounted and undiscounted price. else just print out the current total price. --%> <core:exclusiveIf> <core:ifEqual object1='<%= drequest.getParameter ("CiRelationship.commerceItem.priceInfo.amount") %>' object2='<%= drequest.getParameter ("CiRelationship.commerceItem.priceInfo.rawTotalPrice") %>'> <td></td><td></td> <td></td> <td align=right> <dsp:valueof converter="currency" param="CiRelationship.amountByAverage"/> </td> <td> </td> </core:ifEqual> <core:defaultCase> <td></td> <td align=right> <span class=strikeout><dsp:valueof converter="currency" param="CiRelationship.commerceItem.priceInfo.rawTotalPrice"/></span> </td> <td></td> <td align=right> <dsp:valueof converter="currency" param="CiRelationship.commerceItem.priceInfo.amount"/> </td> </core:defaultCase> </core:exclusiveIf>
Viewing Order Subtotal
The cart.jsp
also displays the subtotal of the order: the sum of all the commerce items minus any order-level discounts. The ShoppingCartModifier
accesses the subtotal. The priceInfo
object of the Order
object itself holds price-level information.
<dsp:valueof converter="currency" bean="ShoppingCartModifier.order.priceInfo.amount"/>
Again in the Pioneer Cycling, store, we wanted to show customers their subtotals both before discounts and after discounts.
We used the following JSP code to perform this functionality:
<%-- Obtain two subtotals. One that represents the subtotal after promotions and then the one from before promotions. This allows the end user to view the two different ones. --%> <dsp:getvalueof id="subtotal" bean="ShoppingCartModifier.order.priceInfo.rawSubtotal"> <dsp:getvalueof id="amount" bean="ShoppingCartModifier.order.priceInfo.amount"> <core:exclusiveIf> <core:ifEqual object1="<%=subtotal%>" object2="<%=amount%>"> <tr> <td colspan=9 align=right>Subtotal</td> <td></td> <td align=right> <b><dsp:valueof converter="currency" bean="ShoppingCartModifier.order.priceInfo.amount"/></b> </td> </tr> </core:ifEqual> <core:defaultCase> <tr> <td colspan=9 align=right>Subtotal with order discount</td> <td></td> <td align=right> <span class=strikeout> <dsp:valueof converter="currency" bean="ShoppingCartModifier.order.priceInfo.rawSubtotal"/> </span> </td> <td></td> <td align=right> <b><dsp:valueof converter="currency" bean="ShoppingCartModifier.order.priceInfo.amount"/></b> </td> </tr> </core:defaultCase> </core:exclusiveIf> </dsp:getvalueof> </dsp:getvalueof>
We used the Compare
servlet bean to check the actual amount of the order with the rawSubtotal
of the order. The rawSubtotal
is the subtotal before any order level discounts are applied.
Changing the Quantity Ordered of an Item
The customer can change the quantity of an item in the Shopping Cart by entering a new number into the quantity field and clicking Recalculate. cart.jsp
then returns with the quantity reset. The ShoppingCartModifier
component performs this function.
The new quantity is passed to the server via the catalogRefId
parameter and then the ShoppingCartModifier
method, handleSetOrder()
, is invoked. If the quantity parameter passed in is different than the current quantity of the commerce item, the new quantity is set.
Here is the JSP code to indicate the quantity of a particular item:
<dsp:input type="text" size="4" bean="item.catalogRefId" beanvalue="item.quantity"/>
This generates a text box that is set to the current quantity of the commerce item.
To pick up the changed quantity, it is necessary to invoke the handleSetOrder
method of the ShoppingCartModifier
component by submitting the form to the handleSetOrder
method. This is what the submit JSP tags look like.
<!-- RECALCULATE Order button: --> <!-------------------------------> <dsp:input bean="ShoppingCartModifier.setOrderByRelationshipId" type="submit" value="Recalculate"/> <!-- GoTo this URL if user pushes RECALCULATE button and there are no errors: --> <dsp:input bean="ShoppingCartModifier.setOrderByRelationshipIdSuccessURL" type="hidden" value="cart.jsp"/> <!-- stay here --> <!-- GoTo this URL if user pushes RECALCULATE button and there are errors: --> <dsp:input bean="ShoppingCartModifier.setOrderByRelationshipIdErrorURL" type="hidden" value="cart.jsp"/> <!-- stay here -->
In the Pioneer Cycling store, we wanted a user to always return to the Shopping Cart page (cart.jsp
) after he has submitted a form to display either error messages or the new cart contents. (Displaying error messages is discussed in detail later in this chapter.)
Deleting Items from the Cart
cart.jsp
enables customers to delete items from their Shopping Cart either by checking the ‘X’ button or by setting the quantity of an item to zero.
Using the ‘X’ Checkbox
To remove items from the Shopping Cart by checking the ‘X’ button, two things must happen. First, the Ids of the items to be removed must be passed to the form handler. Second, the handleSetOrder()
method in the ShoppingCartModifier
must be invoked.
The ShoppingCartModifier
component has a property named removalCatalogRefIds
, which is an array property of the catalogRefIds
that need to be removed from the cart. Thus, every item in this array is removed from the cart. The JSP for each checkbox looks like:
<dsp:input bean="ShoppingCartModifier.removalCatalogRefIds" paramvalue="item.catalogRefId " type="checkbox" checked="<%=false%>"/>
As the cart.jsp
iterates through each commerce item, it sets the parameter item to the current commerce item and then references its catalogRefId
. The catalogRefId
is added to the array only if the box is checked. For more information on using form elements in JSP, please consult the Creating Forms chapter in the ATG Page Developer’s Guide.
When the form is submitted to the handleSetOrder
method, the method iterates through the catalogRefId
objects in the removalCatalogRefIds
property and removes them from the current order.
Invoking the handleSetOrder
method is described in the section Changing the Quantity Ordered of An Item.
Setting Quantity to Zero
Removing an item by setting its quantity to zero is the same as changing an item order to any quantity. The ShoppingCartModifier
detects if the quantity equals zero. When this happens, the item is automatically removed from the cart.
About cart_edit.jsp
The cart_edit.jsp
gives the customer the ability to
Delete an item from an order
Change the quantity of an item
Change the SKU instance of an item
Deleting items from the order is the simplest feature here and depends on the handleRemoveItemFromOrder
method of the ShoppingCartModifier
. The other two features utilize the handleRemoveAndAddItemToOrder
method.
Deleting Items
The previous sections discussed one way to remove items from an order—setting the removalCatalogRefIds
property and then invoking the handleSetOrder
method on the form submit. However, if it is only necessary to remove items in this call, without also changing quantities, this can be accomplished by setting the removalCommerceIds
property and then invoking the handleRemoveItemFromOrder
method.
The following JSP adds an item to the removalCatalogRefIds
property.
<dsp:input bean="ShoppingCartModifier.removalCommerceIds" beanvalue="ShoppingCartModifier.commerceItemToEdit.Id" type="hidden"/>
This always adds the ID of the commerce itemToEdit
to the removalCommerceIds
property. If the form is submitted to the handleRemoveItemFromOrder
method using the following JSP code, the item is removed.
<!-- Delete the item from the cart --> <dsp:input bean="ShoppingCartModifier.RemoveItemFromOrder" type="submit" value="Delete"/>
Changing the Quantity or SKU Instance of an Item
This page provides one place where a user can change the quantity or the SKU instance of a commerce item. Since this functionality has to be provided by one form method, the handler needs to be general enough to handle both situations. For more information on how the handleRemoveAndAddItemToOrder
method works, see the section on the ShoppingCartModifier
component in the Configuring Merchandising Services chapter in the ATG Commerce Programming Guide.
To change either the quantity or the SKU instance of an item, the item is actually deleted and a new one is added. Therefore, to change either, the quantity and the catalogRefId
are the minimum pieces of information necessary to create a new commerce item. The removalCommerceId
always is set as described in the previous section. The quantity is set by setting a form element to the quantity property of the ShoppingCartModifier
.
<!-- Display the current quantity of the item --> <dsp:input bean="ShoppingCartModifier.quantity" beanvalue="ShoppingCartModifier.commerceItemToEdit.quantity" size="4" type="text"/>
The following JSP sets the catalogRefId
that will be added:
<br>Model Selection</b><br> <!-- Set the product id, so this item can be added to the cart if necessary --> <dsp:input bean="ShoppingCartModifier.productId" beanvalue="ShoppingCartModifier.commerceItemToEdit.auxiliaryData .productRef.repositoryId" type="hidden"/> <!-- Display all the child SKUs for this particular SKUs parent product --> <!-- When displaying info to the user, grab the displayableSKUAttributes--> <!-- property from the product and iterate through the list getting--> <!-- the SKU property for each one--> <dsp:select bean="ShoppingCartModifier.catalogRefIds"> <dsp:droplet name="/atg/dynamo/droplet/ForEach"> <dsp:param bean="ShoppingCartModifier.commerceItemToEdit.auxiliaryData .productRef.childSKUs" name="array"/> <dsp:param name="elementName" value="sku"/> <dsp:param name="indexName" value="skuIndex"/> <dsp:oparam name="output"> <dsp:droplet name="/atg/commerce/catalog/DisplaySkuProperties"> <dsp:param name="delimiter" value=" | "/> <dsp:param name="sku" param="sku"/> <dsp:param bean="ShoppingCartModifier.commerceItemToEdit.auxiliaryData .productRef" name="product"/> <dsp:param name="displayElementName" value="outputString"/> <dsp:oparam name="output"> <dsp:droplet name="/atg/dynamo/droplet/Switch"> <dsp:param name="value" param="sku.repositoryId"/> <!-- Current Sku object is the same as one in cart, make it default selected --> <dsp:getvalueof id="nameval2" bean="ShoppingCartModifier.commerceItemToEdit.auxiliaryData. catalogRef.repositoryId" idtype="java.lang.String"> <dsp:oparam name="<%=nameval2%>"> <dsp:getvalueof id="option131" param="sku.repositoryId" idtype="java.lang.String"> <dsp:option selected="<%=true%>" value="<%=option131%>"/> </dsp:getvalueof><dsp:valueof param="sku.displayName"/> </dsp:oparam> </dsp:getvalueof> <!-- The current Sku object does NOT match one is the shopping cart, don't make selected --> <dsp:oparam name="default"> <dsp:getvalueof id="option139" param="sku.repositoryId" idtype="java.lang.String"> <dsp:option value="<%=option139%>"/> </dsp:getvalueof><dsp:valueof param="sku.displayName"/> </dsp:oparam> </dsp:droplet> </dsp:oparam> <dsp:oparam name="empty"> <dsp:droplet name="/atg/dynamo/droplet/Switch"> <dsp:param name="value" param="sku.repositoryId"/> <!-- Current Sku object is the same as one in cart, make it default selected --> <dsp:getvalueof id="nameval2" bean="ShoppingCartModifier.commerceItemToEdit.auxiliaryData .catalogRef.repositoryId" idtype="java.lang.String"> <dsp:oparam name="<%=nameval2%>"> <dsp:getvalueof id="option157" param="sku.repositoryId" idtype="java.lang.String"> <dsp:option selected="<%=true%>" value="<%=option157%>"/> </dsp:getvalueof><dsp:valueof param="sku.displayName"/> </dsp:oparam> </dsp:getvalueof> <!-- Current Sku object does NOT match one is the shopping cart, don't make selected --> <dsp:oparam name="default"> <dsp:getvalueof id="option165" param="sku.repositoryId" idtype="java.lang.String"> <dsp:option value="<%=option165%>"/> </dsp:getvalueof><dsp:valueof param="sku.displayName"/> </dsp:oparam> </dsp:droplet> </dsp:oparam> <dsp:oparam name="empty"> <dsp:getvalueof id="option177" param="sku.repositoryId" idtype="java.lang.String"> <dsp:option selected="<%=true%>" value="<%=option177%>"/> </dsp:getvalueof><dsp:valueof param="sku.displayName"/> </dsp:oparam> </dsp:droplet> </dsp:oparam> </dsp:droplet> </dsp:select>
The catalogRefId
of the selected items is set to the catalogRefId
property of the ShoppingCartModifier
. Inside the select
tags, all the child SKUs of a particular product are listed out as a select box by the ForEach
component in conjunction with the productRef.childSkus
property. For each of these child SKUs, the DisplaySkuProperties
servlet bean returns the displayable SKU properties or, if it returns nothing, shows the sku.displayName
.
The submit of this form then goes to the method handleRemoveAndAddItemToOrder
method.
In Pioneer Cycling, the entire order is repriced on the Shopping Cart page because a customer may have accumulated promotions while navigating the site. Repricing the order causes all the price changes to be resaved and therefore SQL is sent to the database. Even if there are no new promotions, the order is repriced. As a performance optimization, you may decide to remove the JSP code snippet that forces the order to be repriced when the customer simply views the Shopping Cart page. If the customer changes something in the cart, or moves to the confirmation page, the order is still repriced.
The line of JSP to remove is:
<dsp:setvalue bean="ShoppingCartModifier.repriceOrder" value="ORDER_SUBTOTAL"/>
If you remove this line, when a customer looks at the Shopping Cart page, the order is not repriced, and the subsequent SQL is not be generated. Another option is to tie the repricing functionality to a submit button (such as “recalculate”) to optimize performance.
Please note that SQL is only generated for registered users, not for anonymous users.