Commerce
Version 9.4
Consumer Commerce Reference Application Guide
Oracle ATG
One Main Street
Cambridge, MA 02142
USA
ATG Consumer Commerce Reference Application Guide
Product version: 9.4
Release date: 10-31-11
Document identifier: ConsumerCommerceReferenceGuide1111050007
Copyright © 1997, 2011 Oracle and/or its affiliates. All rights reserved.
This software and related documentation are provided under a license agreement containing restrictions on use and disclosure and are
protected by intellectual property laws. Except as expressly permitted in your license agreement or allowed by law, you may not use, copy,
reproduce, translate, broadcast, modify, license, transmit, distribute, exhibit, perform, publish, or display any part, in any form, or by any
means. Reverse engineering, disassembly, or decompilation of this software, unless required by law for interoperability, is prohibited.
The information contained herein is subject to change without notice and is not warranted to be error-free. If you find any errors, please
report them to us in writing. If this software or related documentation is delivered to the U.S. Government or anyone licensing it on behalf of
the U.S. Government, the following notice is applicable:
U.S. GOVERNMENT RIGHTS
Programs, software, databases, and related documentation and technical data delivered to U.S. Government customers are "commercial
computer software" or "commercial technical data" pursuant to the applicable Federal Acquisition Regulation and agency-specific
supplemental regulations. As such, the use, duplication, disclosure, modification, and adaptation shall be subject to the restrictions and
license terms set forth in the applicable Government contract, and, to the extent applicable by the terms of the Government contract, the
additional rights set forth in FAR 52.227-19, Commercial Computer Software License (December 2007). Oracle America, Inc., 500 Oracle
Parkway, Redwood City, CA 94065.
This software or hardware is developed for general use in a variety of information management applications. It is not developed or intended
for use in any inherently dangerous applications, including applications that may create a risk of personal injury. If you use this software or
hardware in dangerous applications, then you shall be responsible to take all appropriate fail-safe, backup, redundancy, and other measures
to ensure its safe use. Oracle Corporation and its affiliates disclaim any liability for any damages caused by use of this software or hardware in
dangerous applications.
Oracle and Java are registered trademarks of Oracle and/or its affiliates. Other names may be trademarks of their respective owners.
Intel and Intel Xeon are trademarks or registered trademarks of Intel Corporation. All SPARC trademarks are used under license and are
trademarks or registered trademarks of SPARC International, Inc. AMD, Opteron, the AMD logo, and the AMD Opteron logo are trademarks or
registered trademarks of Advanced Micro Devices. UNIX is a registered trademark licensed through X/Open Company, Ltd.
This software or hardware and documentation may provide access to or information on content, products, and services from third parties.
Oracle Corporation and its affiliates are not responsible for and expressly disclaim all warranties of any kind with respect to third-party
content, products, and services. Oracle Corporation and its affiliates will not be responsible for any loss, costs, or damages incurred due to
your access to or use of third-party content, products, or services.
For information about Oracle's commitment to accessibility, visit the Oracle Accessibility Program website at http://www.oracle.com/us/
corporate/accessibility/index.html.
Oracle customers have access to electronic support through My Oracle Support. For information, visit http://www.oracle.com/support/
contact.html or visit http://www.oracle.com/accessibility/support.html if you are hearing impaired.
ATG Consumer Commerce Reference Application Guide iii
Table of Contents
1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Getting Started . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
File Organization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Setting Up E-mail . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Viewing the Site . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
About this Guide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2. Business Users’ Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Setting Up the Catalog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Designing Profiles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Creating Profile Groups . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Matching Content with Target Audiences . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Merchandising Capabilities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Processing Orders in Pioneer Cycling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Pricing in Pioneer Cycling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3. Profile Management . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
User Profiles and Pages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
Users . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
Managing Profiles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
Login and Registration Page . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
Login . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
Registration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
My Profile page . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
Address Book page . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Edit Billing Address . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Managing Alternate Shipping Addresses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Creating an Alternate Shipping Address . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Editing an Alternate Shipping Address . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Deleting an Alternate Shipping Address . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
Moving an Alternate Shipping Address to the Default Shipping Address . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
Contact Customer Service page . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
Credit Card Page . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
Viewing and Searching History Page . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
Categories and Products Recently Browsed . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
Items Recently Searched For . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
Profile Repository Extensions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
4. Extending the Product Catalog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
Background . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
Adding Properties to Product Definition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
Adding simple properties to SKU item type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
Adding New Item Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
Creating Subtypes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
5. Displaying and Accessing the Product Catalog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
Product Template Pages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
Creating the Bike Template Pages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
Using ProductLookup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
Displaying Links to Products or Categories . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
Displaying Product Prices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
Adding Items to the Shopping Cart from the Catalog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
Displaying Related Products . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
Displaying Inventory Information . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
Adding Compare Functionality . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
iv ATG Consumer Commerce Reference Application Guide
Adding Historical Navigation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
Category Template Pages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
JSP Fragment Parts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
Using the Cache Component to Improve Performance of Java Server Pages . . . . . . . . . . . . . . . . . . . . . . . . . . 92
Adding Catalog Navigation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
Historical Navigation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
Searching and Filtering the Catalog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
Product Searching . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
Simple Search . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
Advanced Search . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
Filtering . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
Comparing SKUs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
Adding Compare Functionality . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
CompareSkusFormHandler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
Selecting two SKUs to Compare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
Displaying the side-by-side values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
Displaying Errors found by CompareSkusFormHandler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
6. Pricing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
Setting Product Prices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
Standard Prices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
Promotional Prices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
Displaying Prices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
Shipping Prices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
Shipping Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
Shipping Calculators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
Creating Your Own Pricing Models . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
7. Order Processing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
Reviewing and Editing Orders . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
The Shopping Cart Page . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
Item Pricing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
Entering Information Necessary for Checkout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
Secure Checkout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
Choosing Between Basic and Advanced Checkout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
Basic Checkout Process . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
Advanced Checkout Process . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
Additional Order Capture Information . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
Entering a Valid Credit Card . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
Displaying Error Messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
Express Checkout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
Enabling Express Checkout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
Deactivating Express Checkout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
Modifying the Shopping Cart . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
8. Order History . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173
Displaying a User’s Orders . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173
Displaying a Single Order . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176
Canceling Orders . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179
9. Inventory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
Overview of the Pioneer Cycling Inventory System . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
Implementing an Inventory System . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188
Accessing Inventory Information . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188
10. Merchandising . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193
Promotions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193
Implementing Promotions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194
ATG Consumer Commerce Reference Application Guide v
Targeting Content for Promotions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
Slots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198
Gift Certificates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200
Purchase of Gift Certificate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200
E-mail Delivery of Gift Certificates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
Using Gift Certificates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
Coupons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
Creating Coupons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202
Claiming Coupons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202
Gift Registry . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203
Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203
ATG Consumer Commerce Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203
Pioneer Cycling Gift Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204
Creating Wish Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204
Creating and Displaying Gift Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204
Adding Items to Gift Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214
Gift Purchasing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216
Bundles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 226
Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 226
Bundle building example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
Displaying bundles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
A. Pioneer Cycling Scenarios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229
Customer Services . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230
NewGear . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230
NotifyByArea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230
PleaseComeBack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230
Marketing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230
Customer Survey . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230
Helmet No Discount . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 231
Helmet Promotional Offer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 231
Loyalty . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 231
New Customers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232
Profiling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232
Browsing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232
BuildProfile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232
Sales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233
Buy2Get1Free . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233
Default . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233
FreeGift . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233
FreeShipping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234
IncreasePurchaseSize . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234
ShowProducts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234
Scenario Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235
Changing URLs in E-mail Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235
B. Optimizing Performance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237
Prioritizing Web Site Concerns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237
Streamlining JSP Construction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238
Limit Excess Droplet Invocations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238
Use the Cache Servlet Bean . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239
Avoid “Expensive” Functionality . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241
Writing your own Servlet Beans . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241
Using the Broadest Scope Possible . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 242
Reloading the Catalog Repository Cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 242
vi ATG Consumer Commerce Reference Application Guide
Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245
1 Introduction 1
1 Introduction
The Consumer Commerce Reference Application has three purposes:
• To be a demonstration of ATG Consumer Commerce functionality that customers can install and experiment
with before they begin work on their own site.
• To act as a template that customers can adapt to build their own site.
• To be a guide on best practices in implementing a commerce site with ATG Consumer Commerce.
The Pioneer Cycling store is a pre-built, personalized commerce site that is targeted mainly to cyclists. It utilizes
the rich B2C functionality of ATG Consumer Commerce to provide a foundation and a guide to help you quickly
and easily create your own personalized, innovative, commerce-intensive Web sites.
Pioneer Cycling is made up of pre-configured Java components and page templates that provide the core
functionality you need to quickly implement a broad range of merchandising and order processing activities
typical of an online commerce site. We used the basic functionality available in ATG Consumer Commerce, but
extended the functionality to implement specific features.
We included the following features (both standard ATG Consumer Commerce and customized) in the Pioneer
Cycling site:
• Express check-out
• Coupons
• Gift certificates
• Product packages (products composed of multiple SKUs, or Bundles)
• Multiple shipping address and payment types per order
• Wish list and gift registry
• Dynamic product comparison charts
• Product searching
• Inventory information
• Multiple locales
• Customer self-service area
We designed the Pioneer Cycling store as an example of some of the ways that ATG Consumer Commerce can
help you create a better and more effective e-commerce Web site. You will want to evaluate your business goals
and use the ATG Commerce features that are appropriate for your strategy. The processes and functionality that
this manual discusses can be customized to fit the specific needs of your Web application.
2 1 Introduction
The rest of this chapter provides you with the basic information you need to understand and get started with
the Pioneer Cycling store.
Getting Started
The instructions for installing and starting the Pioneer Cycling store of ATG Consumer Commerce Reference
Application are specific to your application server. Consult the ATG Commerce Programming Guide for your
application server for information on installing the ATG Commerce and ATG Consumer Commerce Reference
Application.
Pioneer Cycling is part of the standard implementation of ATG Consumer Commerce. (If you chose a
custom installation, Pioneer Cycling may not have been installed.) The standard ATG Consumer Commerce
implementation also includes the ATG Control Center and a demonstration MySQL database. For instructions
on installing demonstration data and starting ATG Consumer Commerce, Pioneer Cycling, and the ATG Control
Center, see the ATG Installation and Configuration Guide.
File Organization
The PioneerCyclingJSP directory contains all files necessary to implement the Pioneer Cycling store using
ATG Consumer Commerce. It contains the following directories:
Directory Description
config contains atg/dynamo/service/j2ee/J2EEContainer.properties, which installs the
PioneerCyclingJSP application in Dynamo’s J2EE container
j2ee-apps contains directories that hold individual J2EE applications
META-INF contains MANIFEST.MF, which declares the PioneerCyclingJSP application dependent on
the Pioneer Cycling module, and specifies other configuration information for the Dynamo
module
All the JSP files for Pioneer Cycling are located at <ATG9dir>/PioneerCyclingJSP/j2ee-apps/pioneer/
web-app/en.
Setting Up E-mail
To configure the Pioneer Cycling site so that customers receive e-mail from Pioneer Cycling, you must set the e-
mail host to a valid server and enable several scenarios.
First, specify the e-mail server:
1. In the Pages and Components > Components by path menu of the ACC, go to /atg/dynamo/service/
SMTPEmail.
2. Set the emailHandlerHostName property to a valid mail server.
1 Introduction 3
Second, enable the scenarios that send e-mail when an order is fulfilled and delivered.
1. Go to the Scenarios > Scenarios menu.
2. Select and enable the following Scenarios:
DCS/Fulfillment
DCS/ReceiveOrder
Note: If your mail server checks to see if sender e-mail addresses are valid, you must change the sender address
in all e-mail templates, which are located at <ATG9dir>/PioneerCyclingJSP/j2ee-apps/pioneer/web-
app/en/emails.
Viewing the Site
Once you assemble and deploy an application that includes the ATG platform, ATG Commerce, and Pioneer
Cycling, you can use the following URL to access the Pioneer Cycling store:
http://hostname:port/PioneerCyclingJSP
The port you use depends on your application server and how it’s configured. For example, JBoss uses the
following URL by default:
http://hostname:8080/PioneerCyclingJSP
For more information on default ports, see the ATG Installation and Configuration Guide.
About this Guide
In this guide, we demonstrate the best practices for building a commerce site such as Pioneer Cycling and
explain how each feature is implemented. We have included many code samples to help you understand the
functionality and use it in your own projects by making small customizations. This guide also describes any
additions or extensions made to the core ATG Consumer Commerce functionality. The commerce areas of the
site are built in a modular fashion, with a minimum of interdependencies—so that the site can be broken apart
and re-implemented in pieces.
Page developers, programmers, and business users should use this manual to see the features available to them
and then make decisions about the most useful ways that they can customize these features for their site.
This guide includes the following chapters:
Business Users’ Overview (page 5)
Outlines the basic functionality implemented in the Pioneer Cycling site.
Profile Management (page 13)
Describes how the Pioneer Cycling Site collects, stores, presents, and retrieves customer data.
It also describes the login, logout, register, and edit profile utilities.
4 1 Introduction
Extending the Product Catalog (page 57)
Describes how the database schema, object model, and user interface of the store catalog
work.
Displaying and Accessing the Product Catalog (page 69)
Describes how the catalog is displayed and accessed.
Pricing (page 127)
Describes how prices are calculated, taking into account list price, sales, personalized
promotions, coupons, taxes, and shipping costs.
Order Processing (page 135)
Describes how to collect and process the data necessary for completing a purchase
(including items purchased and shipping and billing information). Also describes
transactions with backend systems such as inventory and credit card authorization.
Order History (page 173)
Describes how to record and display customer’s order history.
Inventory (page 187)
Describes how the inventory system in Pioneer Cycling works with the product catalog and
the ordering process.
Merchandising (page 193)Describes how the personalization and special commerce
features are designed to improve the customers’ shopping experience and increase sales.
Appendix A, Pioneer Cycling Scenarios (page 229)
Describes how business strategies for the Pioneer Cycling store were implemented as
scenarios in DSS.
Appendix B, Optimizing Performance (page 237)
Describes the structural choices you can make that will improve the performance of your site.
2 Business Users’ Overview 5
2 Business Users’ Overview
The Commerce Reference Application is a fictional Internet commerce site. This store, Pioneer Cycling, offers
a full set of commerce features demonstrating how the ACC offers easy catalog management, merchandising,
profiling, content targeting, pricing, promotions, and analysis.
In the Pioneer Cycling store, we created a catalog of bicycles and bike accessories to demonstrate ways to use
the core features found in ATG Consumer Commerce and to suggest many ways to build and analyze effective
e-commerce scenarios with ATG Relationship Management Platform. The application also incorporates ATG
Relationship Management Platform comprehensive personalization functionality, which customizes many of the
site’s features according to a customer’s tastes and maintains a complete profile for each user.
We designed the Pioneer Cycling Store to allow visitors to explore the site without registering. However, visitors
are encouraged to register, which gives us greater access to their personal information and gives them a
personalized shopping experience.
The Pioneer Cycling Store:
• Maintains multiple shopping carts for registered users and guests through the use of wish lists.
• Maintains a profile for each of its users containing account information, including a record of all orders. Users
can edit some aspects of their own profiles.
• Creates a customized page for each registered user. This page provides access to an address book, saved
credit cards, express checkout settings, recently visited items and searches, wish lists, a shopping profile,
recent purchases, and full order history.
• Delivers targeted promotions and discounts to the user throughout the store.
• Offers claimable coupons, gift certificates, and a gift registry.
• Provides basic, comprehensive, and express checkout processes. The comprehensive checkout supports
customer registration and multiple shipping addresses. The express checkout streamlines the checkout
process by using predefined shipping and billing information.
• Searches for products and categories by name, keywords, manufacturer, or part number.
• Allows users to compare similar products side by side.
• Allows users to filter product categories to only show certain types of products.
• Provides a historical navigation feature to help customers maintain their place while shopping.
• Cross-sells related products, and suggests replacements for out-of-stock SKUs.
• Displays content and prices for three locales: English, French, and Japanese
6 2 Business Users’ Overview
Setting Up the Catalog
We used the ACC to set up and manage the store catalog. Some of these tasks included:
• Managing the catalog hierarchy
• Specifying category, product, and SKU attributes
• Creating promotions
We customized and extended the standard ATG Consumer Commerce catalog to create the Pioneer Cycling
catalog. For more information, see the Using and Extending the Standard Catalog chapter in the ATG Commerce
Programming Guide.
In the ACC, you can see the catalog hierarchy by selecting Catalog Management > Product Catalog. Each
category has subcategories and products. When you look at a category, you can see its attributes, images, and
template. Products provide access to individual SKUs.
The Pioneer Cycling catalog hierarchy in the ACC
We chose product attributes to enable features in the store such as filtering, comparing, searching, and
navigation. Also, we can track these attributes when users browse or purchase products and better understand
customer behavior. We can use that information later to target promotions and featured products to users.
2 Business Users’ Overview 7
You can also browse items by selecting Catalog Management > Catalog Elements. This view allows you to see
items by type, such as Category, Product, or SKU. You can also use the query editor to narrow the list of items
to find an exact item more easily. For example, as shown in the screenshot below, you could search for all the
categories that begin with the letter A.
Searching for categories using the Catalog Management > Catalog Elements section of the ACC.
You can select Pricing > Promotions to view all the discounts available to customers in the store. Typically,
scenarios are developed to deliver these discounts to the right customers at the right time. Some examples of
promotions are “10% off everything in the store” or “Free water bottle, with purchase of Jersey.”
8 2 Business Users’ Overview
“Free water bottle with jersey” promotion
Designing Profiles
Everything we know about customers from their site activity is stored in their customer profiles. Determining
what information to record for each customer is an important part of designing the store. The profile in the
Pioneer Cycling store records a customer’s:
• name and contact info
• shipping and billing information
• credit card numbers
• checkout preferences
• wish lists or gift registries
2 Business Users’ Overview 9
• shopping preferences and items recently browsed
• personal information such as gender, birthday, and clothing sizes
We used several methods of gathering profile information from users in the Pioneer Cycling site. The registration
process asks users to answer specific questions. Also, we implemented scenarios to record profile information
that is then used to target promotions and specific products to users. For example, the “Browsing” scenario
records every item or category that a customer browses. The “BuildProfile” scenario monitors the products a
customer browses or adds to the shopping cart to determine what classes of product interest the customer. For
more information on these scenarios, see Appendix A, Pioneer Cycling Scenarios (page 229).
The Pioneer Cycling “Browsing” scenario in the ACC.
Creating Profile Groups
ATG Relationship Management Platform allows business users to segment a customer population. Often it is
desirable to divide customers into recognizable groups, rather than to treat each individually. For example,
you can design initiatives for key demographic groups. We created a profile group for Pioneer Cycling called
“BMXRiders” for people who have purchased a BMX bike. You can specify profile groups in the ACC by selecting
Targeting > Profile Groups.
10 2 Business Users’ Overview
Defining Profile Groups in the ACC
Matching Content with Target Audiences
You can group site content in the same way as you group customers; then, you can match the two using
business rules called content targeters. Business rules define the content that you show to each profile group. In
addition to varying content delivery according to a visitor’s profile, you can also change it according to the date,
the time of day, and other conditions. You can specify business rules by selecting Targeting > Content Targeters
in the ACC.
Merchandising Capabilities
The Pioneer Cycling Store demonstrates many new techniques for enhancing the merchandising capabilities of
an online store. ATG Consumer Commerce allows products to be combined in innovative ways. Using the ACC,
you can organize products in many categories, and group them across categories.
You can give any product in the store a list of specific products to cross-sell or a group of related products. For
example, a branded water bottle may be categorized in both Accessories > Drink Holders and Brand X Products
> Bottles, a member of the product group called “Summer Products,” and cross-sold with a sports drink.
The merchandising powers of business users are also expanded using scenarios. Scenarios allow you to offer
discounts, suggest products, or display incentives to customers according to their actions on the site. For
example, a scenario may track when a customer adds Product A to the shopping cart, then give the customer a
50% discount on Product B. Scenarios can offer different incentives depending upon the user’s profile, the day
of the week, or chance operations. Scenarios maintain their state between site visits and are valuable tools for
designing a complete customer experience.
The Pioneer Cycling store also offers customers enhanced merchandising through coupons, gift certificates, and
wish lists.
2 Business Users’ Overview 11
Processing Orders in Pioneer Cycling
We implemented three distinct ordering processes in the Pioneer Cycling store.
• “Basic checkout” is useful for sites that do not employ customer registration and require only simple shipping
and payment information.
• “Full checkout” is appropriate for sites that offer multiple shipping addresses, purchases from gift registries,
and customer address books.
• “Express checkout” allows repeat customers to complete orders without entering any new information each
time they check out. Orders are placed with shipping and billing information saved in their profiles.
Refer to the Order Processing (page 135) chapter for more information on checkout.
Pricing in Pioneer Cycling
In the Pioneer Cycling store, we implemented a flexible pricing engine that can be easily manipulated by the
business user. The pricing engine applies a number of pricing models to a particular cost such as item cost,
shipping cost, or tax cost. The store generates a price for an object and then applies the pricing models against
this list price. You can specify the list price and the pricing models in the ACC. For example, we specified that
Pioneer Cycling customers who order over $400 of merchandise receive free shipping. The pricing engine can
apply this model and other restrictions and promotions to calculate list, item, order, shipping, and tax costs
dynamically.
Each SKU in the store has a list price and a sale price. When an item is flagged “on sale,” its sale price becomes
active. Each SKU also has a cost price field so that you can analyze cost margins if desired.
Gross modifications to item prices can be made using “global promotions.” Global promotions are given to
every customer on the store, and provide a flexible method for modifying pricing with any rule that can be
expressed as a promotion. For example, every product in the store could be marked down by 2%, or, all items in
a category could be $5 off in September. In addition, the store catalog shows customers the difference between
the current price and the list price.
Entire orders may be priced with the same rules. Shipping costs can be similarly discounted. For example, you
can offer a shipping discount on orders above a certain dollar amount, or free shipping, depending upon the
contents of the order.
Refer to the Merchandising (page 193) chapter for more information.
12 2 Business Users’ Overview
3 Profile Management 13
3 Profile Management
This chapter describes how the Pioneer Cycling Site collects, stores, presents, and retrieves
customer data. It includes the following sections:
User Profiles and Pages (page 13)
Describes the classes of users who visit the site and the pages we used to manage their
profile information.
Login and Registration Page (page 14)
Describes how the Login and Registration pages were customized in the Pioneer Cycling site.
My Profile page (page 26)
Demonstrates how customers can modify their own personal information.
Address Book page (page 29)
Describes how customers can create, edit, and delete billing and shipping addresses.
Contact Customer Service page (page 37)
Describes how we created a customer feedback page.
Credit Card Page (page 39)
Describes how customers can enter and save credit card information.
Viewing and Searching History Page (page 44)
Describes how we created a page to record the categories and products recently viewed and
searched by the customer.
Profile Repository Extensions (page 51)
Describes the extensions made to ATG Consumer Commerce and DPS for the Pioneer Cycling
store site.
User Profiles and Pages
Users
There are three classes of users who access the Pioneer Cycling bike store:
• Anonymous users, about whom the site has no information
• Members, who have registered on the site
• Administrators, who have privileged access to information on the site
14 3 Profile Management
When anonymous users visit the site, they can browse the catalog and other pages without providing any
information, or they can register as members through a registration page. Returning registered members can
log in with their user names and passwords, or have cookies remember their login information. Once they log
in, members can access the My Profile page that allows them to maintain personal and commerce profiles and
to track their order information. Certain features or areas of the store require membership. For example, the
Advanced Checkout process requires a user to be a member and logged in.
Administrators can view and manipulate user profile data using the Profile interface in the ACC. This interface
enables them to search, create, edit, duplicate, and delete user profiles. Customers orders can be administered
using ATG Commerce Assist, which is described in the ATG Commerce Assist User Guide.
Managing Profiles
We used several pages to manage user profile information in the Pioneer Cycling site. We customized the user
profile management in the Pioneer Store so that customers can:
• Define multiple shipping addresses in an address book. Each address is identified by a nickname and can be
copied to the default shipping address.
• Store information for multiple credit cards, identified by nicknames, to select during the checkout process.
• See a summary list of products and product categories they recently viewed, with a hyperlink to each product
or category page.
• See a summary of recent searches; each search string is presented as a hyperlink that carries out the search if
invoked.
• Send e-mail to the customer service group at Pioneer Cycling.
• View information on the kind of products they prefer based on their past orders.
Login and Registration Page
You can view the JSP code for the login and registration process in the <ATG9dir>/PioneerCyclingJSP/
j2ee-apps/pioneer/web-app/en/user directory.
Login
We used my_profile.jsp to log in users. The content of the page depends on whether or not the user is
logged in, based on the transient property of the /atg/userprofiling/Profile servlet bean.
Note: The “. . .” markers in the following code sample indicate a place where code has been removed to clarify
the sample.
<dsp:importbean bean="/atg/userprofiling/Profile"/>. . .<dsp:droplet name="/atg/dynamo/droplet/Switch"> <dsp:param bean="Profile.transient" name="value"/> <dsp:oparam name="true"> <dsp:include page="LoginFragment.jsp" flush="true"></dsp:include> </dsp:oparam> <dsp:oparam name="false">
3 Profile Management 15
<dsp:include page="ProfileFragment.jsp" flush="true"></dsp:include> </dsp:oparam></dsp:droplet>
If the transient property is true, the user is not logged in, andLoginFragment.jsp is displayed, inviting him
to do so:
LoginFragment.jsp
The following is the portion of LoginFragment.jsp that we used to generate the login form.
<dsp:form action="my_profile.jsp" method="post"><!-- If there is a problem with the login, return to this page to display error msgs --><dsp:input bean="B2CProfileFormHandler.loginErrorURL" beanvalue="/OriginatingRequest.requestURI" type="hidden"/><!-- always display empty username/login fields to our visitor --><dsp:input bean="B2CProfileFormHandler.extractDefaultValuesFromProfile" type="hidden" value="false"/> <table> <tr> <td>Username</td><td><dsp:input bean="B2CProfileFormHandler.value.login"
16 3 Profile Management
size="10" type="text"/></td> </tr> <tr> <td>Password</td><td><dsp:input bean="B2CProfileFormHandler.value.password" size="10" type="password"/></td> </tr> </table> <p><dsp:input bean="B2CProfileFormHandler.login" type="submit" value="Log in"/></dsp:form>
We created a new component located at /atg/userprofiling/B2CProfileFormHandler.
(See the Working withForms and Form Handlers chapter in the ATG Programming
Guide for more information on ProfileFormHandler. This component is an
instance of the atg.projects.b2cstore.B2CProfileFormHandler class, which
extends theatg.commerce.profile.CommerceProfileFormHandler, a subclass
ofatg.userprofiling.ProfileFormHandler. (Most often, a web application would just keep the location of
the form handler for the profile in the original location /atg/userprofiling/ProfileFormHandler because
it is easy to find. You can move a component to any location but it is easiest to keep the standard components in
the standard places for easier site maintenance.)
The login process itself is not customized; the login form associates its input tags with the value property of the
handler, and the Log in button invokes thehandleLogin() method to log in the user.
Clicking on the Log in button always brings the user back to my_profile.jsp. If the login attempt is
unsuccessful, error messages are displayed in LoginFragment.jsp; otherwise,ProfileFragment.jsp is
displayed.
If the user is not registered, he is invited to do so with a Become a member link to register.jsp.
Registration
The registration process in the Pioneer Store collects the information that is stored in the user’s profile. Below is
the portion of register.jsp that produces the registration form.
Like most forms in our store, the form submits to itself as directed by the form tag’s action attribute. The form
handler method on the server side redirects to the appropriate URL (indicated by the createSuccessUrl
property) if the creation is successful. The comments interspersed in the JSP code below explain how the various
form fields are used.
<!-- Assume registration failure --> <dsp:getvalueof id="form80" bean="/OriginatingRequest.requestURI" idtype="java.lang.String"><dsp:form action="<%=form80%>" method="POST">
<!-- Don't assign a new repository id during registration - keep transient one: This is particular crucial because we store this ID in the ownerId field of the addresses (see below). --> <dsp:input bean="B2CProfileFormHandler.createNewUser" type="hidden" value="false"/>
<!-- If registration succeeds, go to my profile page and welcome new customer there --> <dsp:input bean="B2CProfileFormHandler.createSuccessURL" type="hidden" value="my_profile.jsp"/>
3 Profile Management 17
<input type="hidden" name="first_time" value="yes">
<!-- User must confirm password when registering --> <dsp:input bean="B2CProfileFormHandler.confirmPassword" type="hidden" value="true"/>
<!-- A registered customer is a member --> <dsp:input bean="B2CProfileFormHandler.value.member" type="hidden" value="true"/>
<!-- Need to set Registration Date -->
<table cellspacing=0 cellpadding=0 border=0>
<!-- Setup gutter and make space --> <tr> <td width=30%><dsp:img height="1" width="100" src="../images/d.gif"/><br></td> <td> </td> <td><dsp:img height="1" width="300" src="../images/d.gif"/></td> </tr>
<tr valign=top> <td width=30%> <dsp:droplet name="Switch"> <dsp:param bean="B2CProfileFormHandler.formError" name="value"/> <dsp:oparam name="true"> <span class=registrationerror> <span class=help>There were problems with your registration:</span><p> <UL> <dsp:droplet name="ProfileErrorMessageForEach"> <dsp:param bean="B2CProfileFormHandler.formExceptions" name="exceptions"/> <dsp:oparam name="output"> <LI> <dsp:valueof param="message"/> </dsp:oparam> </dsp:droplet> </UL> </span> </dsp:oparam> </dsp:droplet> </td> <td></td> <td> <!-- 1. I am: --> <table width=100% cellpadding=0 cellspacing=0 border=0> <tr><td class=box-top-profile> Me</td></tr> </table> <p> Name (first, middle, last)<br> <dsp:input bean="B2CProfileFormHandler.value.firstName" maxlength="40" size="15" type="text" required="<%=true%>"/> <dsp:input bean="B2CProfileFormHandler.value.middleName" maxlength="40" size="10" type="text"/> <dsp:input bean="B2CProfileFormHandler.value.lastName" maxlength="40" size="15" type="text" required="<%=true%>"/> <p> Email address <br>
18 3 Profile Management
<dsp:input bean="B2CProfileFormHandler.value.email" maxlength="30" size="30" type="text"/> <p> Daytime telephone<br> <dsp:input bean="B2CProfileFormHandler.value.daytimeTelephoneNumber" maxlength="20" size="20" type="text"/><br> <p> <br> </td> </tr>
<tr valign=top> <!-- 2. Billing/Main Address --> <td width=30%> <span class=help>Please provide us with the address where you wish to be billed.<p> You may use this same address for shipping, or enter a separate shipping address below.</span> </td> <td></td> <td> <table width=100% cellpadding=0 cellspacing=0 border=0> <tr><td class=box-top-profile> My billing address</td></tr> </table> <p> Street address <br>
<!-- Store the ProfileID in ownerId field of the address. This tells us this address "belongs to" (and can be edited) by the user. --> <dsp:input bean="B2CProfileFormHandler.value.billingAddress.ownerId" beanvalue="Profile.id" type="hidden"/>
<dsp:input bean="B2CProfileFormHandler.value.billingAddress.address1" maxlength="30" size="40" type="text" required="<%=true%>"/><br> <dsp:input bean="B2CProfileFormHandler.value.billingAddress.address2" maxlength="30" size="40" type="text"/><br> <table cellpadding=0 cellspacing=0> <tr> <td>City<br> <dsp:input bean="B2CProfileFormHandler.value.billingAddress.city" maxlength="30" size="20" type="text" required="<%=true%>"/></td> <td> State<br> <dsp:select bean="B2CProfileFormHandler.value.billingAddress.state"> <%@ include file="StatePicker.jspf" %> </dsp:select> </td> <td> Postal Code<br> <dsp:input bean="B2CProfileFormHandler.value.billingAddress.postalCode" maxlength="10" size="10" type="text" required="<%=true%>"/></td> </tr> </table> <table cellpadding=0 cellspacing=0> <tr> <td>Country<br> <dsp:select bean="B2CProfileFormHandler.value.billingAddress.country" required="<%=true%>"> <%@ include file="CountryPicker.jspf" %>
3 Profile Management 19
</dsp:select> </td> </tr> <tr> <td>Telephone<br> <dsp:input bean="B2CProfileFormHandler.value.billingAddress.phoneNumber" maxlength="15" size="15" type="text"/></td> </tr> </table> <p> <br> </td> </tr>
<tr valign=top> <!-- 3. Ship it here: --> <td width=30%> <span class=help> If you know that you will want to ship products to a different address, then you may enter an alternate shipping address here. <p>If you will always ship products to your billing address, then indicate that here. </span> </td> <td></td> <td> <table width=100% cellpadding=0 cellspacing=0 border=0> <tr><td class=box-top-profile> My main shipping address</td></tr> </table> <p>
<dsp:input bean="B2CProfileFormHandler.shipToBillingAddress" type="radio" checked="<%=true%>" value="true"/> Use billing address for shipping<br> <dsp:input bean="B2CProfileFormHandler.shipToBillingAddress" type="radio" value="false"/> Use the new address below for shipping <p> Street address <br>
<!-- Store the ProfileID in ownerId field of the address. This tells us this address "belongs to" (and can be edited) by the user. --> <dsp:input bean="B2CProfileFormHandler.value.shippingAddress.ownerId" beanvalue="Profile.id" type="hidden"/>
<dsp:input bean="B2CProfileFormHandler.value.shippingAddress.address1" maxlength="30" size="40" type="text"/><br> <dsp:input bean="B2CProfileFormHandler.value.shippingAddress.address2" maxlength="30" size="40" type="text"/><br> <table cellpadding=0 cellspacing=0> <tr> <td>City<br> <dsp:input bean="B2CProfileFormHandler.value.shippingAddress.city" maxlength="30" size="20" type="text"/></td> <td> State<br> <dsp:select bean="B2CProfileFormHandler.value.shippingAddress.state"> <%@ include file="StatePicker.jspf" %>
20 3 Profile Management
</dsp:select> </td> <td> Postal Code<br> <dsp:input bean="B2CProfileFormHandler.value.shippingAddress.postalCode" maxlength="10" size="10" type="text"/></td> </table> Country<br> <dsp:select bean="B2CProfileFormHandler.value.shippingAddress.country" required="<%=true%>"> <%@ include file="CountryPicker.jspf" %> </dsp:select> <p> <br> </td> </tr>
<tr valign=top> <!-- 4. Username/password: --> <td width=30%> <span class=help>When you return to the site, log in with this username and password to manage your account information.</span> <p> </td> <td></td> <td> <table width=100% cellpadding=0 cellspacing=0 border=0> <tr><td class=box-top-profile> My profile</td></tr> </table> <p> Choose a username <span class=help>( i.e. 'joeshmoe22' )</span><br> <dsp:input bean="B2CProfileFormHandler.value.login" maxlength="20" size="15" type="text"/> <p> Choose a password <span class=help>( i.e. 'pA$$weRD22' )</span><br> <dsp:input bean="B2CProfileFormHandler.value.password" maxlength="35" size="15" type="password"/><br> Enter password again to be sure you have it right<br> <dsp:input bean="B2CProfileFormHandler.value.confirmPassword" maxlength="35" size="15" type="password"/> <p> <br> </td> </tr>
<tr valign=top> <!-- 5. Optional Personal Data: --> <td width=30%> <span class=help>These questions are optional, but it's nice if you tell us.</span> </td> <td></td> <td> <%@ include file="../common/UserPreferencesFormFragment.jspf" %> </td>
<tr valign=top> <!-- 6. Submit: --> <td width=30%> <td></td> <td>
3 Profile Management 21
<table width=100% cellpadding=0 cellspacing=0 border=0> <tr><td class=box-top-profile> All done?</td></tr> </table> <p> <p> <br>
<!-- Submit form to handleCreate() handler --> <dsp:input bean="B2CProfileFormHandler.create" type="submit" value=" Register --> "/> <p> <br> </td>
</tr> </table></dsp:form></dsp:getvalueof>
The User Profile
The traits of the user profile are defined in the DPS, ATG Consumer Commerce, and the Pioneer Store. Some
input fields (first name, last name and several address fields) in the form have the required attribute set to
true in the input tag. This means that the ProfileFormHandler ensures that these fields are filled in before
performing the registration. If the user submits the form with any of those fields blank, the form handler
generates errors that are reported to the user via the formExceptions property. You can make fields required
by editing the JSP file. There are two additional required fields, login and password. Unlike the first name and
last name fields, which are important at an application level, the fields login and password are needed for the
user profiling system to function and so they are set as required in the repository definition file for DPS. If the
user fails to fill in login or password before submitting the form, the result from the user’s point of view is the
same as leaving the last name blank.
22 3 Profile Management
top half of register.jsp
3 Profile Management 23
bottom half of register.jsp
Profile Trait Defined In
firstName DPS1
middleName DPS
lastName DPS1
email DPS
login DPS2
password DPS2
24 3 Profile Management
Profile Trait Defined In
gender DPS
dateOfBirth DPS
daytimeTelephoneNumber ATG Commerce
size Pioneer Store
1 required in the input form tags
2 defined as required in the userProfile.xml in the DPS config layer
The input tags that collect these properties are tied to the value property of the ProfileFormHandler
component; for example, the firstName property is collected with: <dsp:input
bean="B2CProfileFormHandler.value.firstName" type="text" required="<%=true%>"/>
The user profile also defines three user addresses:
Profile Trait Defined in
homeAddress DPS
billingAddress ATG Commerce
shippingAddress ATG Commerce
The Pioneer Store does not use the homeAddress, but collects the billingAddress and shippingAddress.
These addresses are collected using the value property; for example, the address1 field is collected with the
tag: <dsp:input bean="B2CProfileFormHandler.value.billingAddress.address1" type="text"
required="<%=true%>"/>
The following fields are collected for each address (some are marked as required in the input tag):
Address Field Collected? Required?
firstName no yes*
middleName no yes*
lastName no yes*
address1 yes yes
address2 yes no
city yes yes
3 Profile Management 25
Address Field Collected? Required?
state yes no
postalCode yes yes
country yes yes
telephone sometimes no
*These three fields are copied from the user’s profile when the address is created during registration. They can
be modified later when the user edits the billing or shipping address. (The telephone is collected for the billing
address, but not for the shipping address.)
Customized Registration
The value property of the ProfileFormHandler, defined as a java.util.Map, collects registration data.
The B2CProfileFormHandler class adds a second map, editValue, which stores customized profiling fields.
This map holds data addresses that go into the property secondaryAddresses. That property is a map, which
requires all values to be stored with a key. The key into seondaryAddresses is the “nickname” which the user
enters, so the form handler takes the editValue from the form, creates an address from it, and stores it under
the nickname provided by the user.
We used the handleCreateUser() method in the ProfileFormHandler class to create a new user. It
invokes two other methods. Before doing anything, it invokes preCreateUser(); before exiting, it invokes
postCreateUser(). Both these methods can be extended to provide additional functionality.
The B2CProfileFormHandler class defines a preCreateUser() method that copies the firstName,
middleName, and lastName fields in the user profile to the billing and shipping addresses, so that the user only
has to enter them once during the registration process. The user can modify these addresses to use a different
name.
The addressProperties property of the B2CprofileFormHandler defines the address properties that are to
be copied. This value may be configured in the properties file, and defaults to:
firstName, middleName, lastName,address1, address2city, state, postalCode,country, ownerId
The B2CProfileFormHandler class also defines a postCreateUser() method that:
1. Sets the registration date to “now.” (We found this method to be very efficient. Setting the date using a
scenario is less efficient but more flexible.)
2. Sets the value of profile.billingAddress to the shipping address if the user checks the
shipToBillingAddress with a radio button in the registration form.
26 3 Profile Management
My Profile page
The My Profile page (my_profile.jsp) allows site customers to modify their own profiles (for example, update
e-mail addresses). In addition, customers can monitor their own orders and check on the status of or cancel an
order.
3 Profile Management 27
my_profile.jsp
The My Profile page enables a member to edit her personal information. It is divided into three functional areas:
• My personal profile maintains personal contact information.
• My commerce profile holds information about a customer’s shopping experiences on this site, including
shopping cart information, alternate shipping addresses, saved credit cards, lists of recently viewed items, and
the customer’s product preferences.
• My orders lists the number of orders the customer has placed and how many of them have been shipped.
The bulk of the My Profile page is static html text and links. The section under “My Personal Profile” has
dynamic content that is rendered by the file ProfileFragment.jsp a portion of which is shown below. First,
the personal profile information is displayed using dsp:valueof tags for several properties of the /atg/
userprofiling/Profile component. Second, there is a static link to edit my profile and a Logout button.
The Logout button is a very simple form that submits to the B2CProfileFormHandlerhandleLogout method.
We simply provided a URL to which the form handler redirects the user upon success.
A portion of ProfileFragment.jsp:
<table width=100% cellpadding=0 cellspacing=0 border=0><tr><td class=box-top-profile>My personal profile</td></tr></table><p><dsp:valueof bean="Profile.firstName"/><dsp:valueof bean="Profile.middleName"/><dsp:valueof bean="Profile.lastName"/><br>My birthday is <dsp:valueof bean="Profile.dateOfBirth" date="MMMM d, yyyy">??</dsp:valueof>.<br><dsp:valueof bean="Profile.email"/><br>Member since <dsp:valueof bean="Profile.registrationDate" date="MMMM d, yyyy">January 1, 1900</dsp:valueof>.<p>> <dsp:a href="edit_profile.jsp">edit my profile</dsp:a><br>> <dsp:a href="change_password.jsp">change my password</dsp:a><br><p><dsp:form action="../store_home.jsp"> <dsp:input bean="/atg/userprofiling/B2CProfileFormHandler.logoutSuccessURL" beanvalue="/OriginatingRequest.requestURI" type="HIDDEN"/> <dsp:input bean="/atg/userprofiling/B2CProfileFormHandler.logout" type="submit" value="Logout"/></dsp:form>
We extended the userProfile.xml file in the Pioneer Cycling configuration layer to add additional attributes
to the profile definition. We extended the userProfile repository definition file to add the following
properties to the user item-descriptor:
• Clothing size
• Weight preference
• Price preference
• Number of orders
• Cumulative order amount
28 3 Profile Management
• Bikes owned
• User keywords
• Items bought
• Categories viewed recently
• Products viewed recently
• Recent user searches
The SQL Repository Architecture chapter in the ATG Repository Guide explains the XML definition language syntax.
PioneerCycling/config/atg/userprofiling/userProfile.xml
<gsa-template xml-combine="append">
<header> <name>Bikestore Related Profile Changes</name> <author>DCS Team</author> <version>$Id: userProfile.xml,v 1.35 2000/07/17 18:14:03 astreb Exp$</version> </header>
<item-descriptor name="user"> <table name="b2c_user" type="auxiliary" id-column-name="id"> <property category="Pioneer Cycling" name="size" data-type="enumerated" default="unknown" column-name="clothing_size" display-name="Clothing size"> <attribute name="useCodeForValue" value="false"/> <option value="unknown" code="0"/> <option value="extra small" code="1"/> <option value="small" code="2"/> <option value="medium" code="3"/> <option value="large" code="4"/> <option value="extra large" code="5"/> </property> <property category="Pioneer Cycling" name="weightPreference" data-type="int" column-name="weight_preference" display-name="Weight preference"/> <property category="Pioneer Cycling" name="pricePreference" data-type="int" column-name="price_preference" display-name="Price preference"/> <property category="Pioneer Cycling" name="numOrders" display-name="Number of orders" data-type="int" column-name="num_orders" default="0"/> <property category="Pioneer Cycling" name="cumulativeOrderAmount" display-name="Cumulative order amount" data-type="double" column-name="cum_order_amt" default="0"/> </table> <table name="b2c_bike_owned" type="multi" id-column-name="id" multi-column-name="sequence_num"> <property category="Pioneer Cycling" name="bikesYouOwn" data-type="list" component-data-type="String" column-name="bike" display-name="Bikes owned"/> </table>
3 Profile Management 29
<table name="b2c_user_keyword" type="multi" id-column-name="id" multi-column-name="sequence_num"> <property category="Pioneer Cycling" name="userKeywords" data-type="list" component-data-type="String" column-name="keyword" display-name="User keywords"/> </table> <table name="b2c_item_bought" type="multi" id-column-name="id" multi-column-name="sequence_num"> <property category="Pioneer Cycling" name="itemsBought" data-type="list" component-item-type="sku" repository="/atg/commerce/catalog/ProductCatalog" column-name="item" display-name="Items bought"/> </table> <!-- The following transient properties are used to record categories & pages viewed, and recent searches --> <property category="Pioneer Cycling" name="categoriesViewed" display-name="Categories viewed recently" data-type="set" component-data-type="String" /> <property category="Pioneer Cycling" name="productsViewed" display-name="Products viewed recently" data-type="set" component-data-type="String" /> <property category="Pioneer Cycling" name="recentSearches" display-name="Recent user searches" data-type="set" component-data-type="String"/>
</item-descriptor>
</gsa-template>
Address Book page
The Address Book page allows members to update the addresses associated with their profiles by:
• Editing billing address
• Creating and editing alternate shipping addresses
• Copying an alternate shipping address to the default shipping address
Edit Billing Address
The Address Book page starts with a link to edit_billing_address.jsp. This page simply contains a form
that invokes the handleUpdateUser() handler. There is no customization.
30 3 Profile Management
Managing Alternate Shipping Addresses
The Address Book page lists all of the alternate shipping addresses the registered user has defined. Each address
is identified by a nickname, which is the key to the address in the secondaryAddresses map property of the
user’s profile.
Creating an Alternate Shipping Address
To create an alternate shipping address, a registered member clicks on the add new address link to go to
new_shipping_address.jsp, where a form collects the address fields and the nickname for the new address.
The form’s input tags are associated with the editValue property of the B2CProfileFormHandler; the Add
Address button invokes the handleNewAddress() handler as demonstrated in the following example.
Note: The “. . .” markers in the following code sample indicate a place where code has been removed to clarify
the sample.
<dsp:form action="address_book.jsp" method="POST">
. . . Give this address a nickname for your address book:<br> <dsp:input bean="B2CProfileFormHandler.editValue.nickname" maxlength="40" size="20" type="text"/> <p>
<!-- Store the ProfileID in ownerId field of the new address.
3 Profile Management 31
This tells us this address "belongs to" (and can be edited) by the user. --> <dsp:input bean="B2CProfileFormHandler.editValue.ownerId" beanvalue="Profile.id" type="hidden"/>
Name<br> <dsp:input bean="B2CProfileFormHandler.editValue.firstName" maxlength="100" size="15" type="text" required="<%=true%>"/> <dsp:input bean="B2CProfileFormHandler.editValue.middleName" maxlength="100" size="10" type="text"/> <dsp:input bean="B2CProfileFormHandler.editValue.lastName" maxlength="100" size="15" type="text" required="<%=true%>"/><br> Street address <br> <dsp:input bean="B2CProfileFormHandler.editValue.address1" maxlength="30" size="40" type="text" required="<%=true%>"/><br> <dsp:input bean="B2CProfileFormHandler.editValue.address2" maxlength="30" size="40" type="text"/><br> <table cellpadding=0 cellspacing=0> <tr> <td>City<br> <dsp:input bean="B2CProfileFormHandler.editValue.city" maxlength="30" size="20" type="text" required="<%=true%>"/></td> <td>State<br> <dsp:select bean="B2CProfileFormHandler.editValue.state"> <%@ include file="StatePicker.jspf" %> </dsp:select> </td> <td>Postal Code<br> <dsp:input bean="B2CProfileFormHandler.editValue.postalCode" maxlength="10" size="10" type="text" required="<%=true%>"/></td> </tr> </table> Country<br> <dsp:select bean="B2CProfileFormHandler.editValue.country" required="<%=true%>"> <%@ include file="CountryPicker.jspf" %> </dsp:select><br> <p> </td></tr><tr valign=top> <!-- 6. Submit: --> <td></td> <td></td> <td> <table width=100% cellpadding=0 cellspacing=0 border=0> <tr><td class=box-top-profile> Done?</td></tr> </table> <p> <br> <!-- Submit form to handleUpdate() handler --> <dsp:input bean="B2CProfileFormHandler.newAddress" type="submit" value=" Add Address --> "/> . . .
32 3 Profile Management
new_shipping_address.jsp
The handleNewAddress() handler creates a new address in the secondaryAddresses map, indexed with the
provided nickname, and sets its properties based on the entries in the editValue map property:
MutableRepository repository = (MutableRepository) profile.getRepository(); Map secondaryAddresses = (Map) profile.getPropertyValue("secondaryAddresses");
// What address fields are we looking for String[] addressProperties = getAddressProperties();
// Get editValue map, containing the user form data
3 Profile Management 33
HashMap newAddress = (HashMap) getEditValue(); String nickname = (String) newAddress.get("nickname");
// If we successfully collected all needed user input, create a new address try { // Create a repository item to be filled with stuff. MutableRepositoryItem address = repository.createItem("contactInfo");
// Copy values from the newAddress object Object property; for (int i = 0; i < addressProperties.length; i++) { property = newAddress.get(addressProperties[i]); if (property != null) address.setPropertyValue(addressProperties[i], property); }
// Update adress into the db and insert into secondaryAddresses map repository.addItem(address); secondaryAddresses.put(nickname, address);
Editing an Alternate Shipping Address
The list of alternate shipping addresses in the Address Book page provides a link to the Edit Shipping Address
page, where the member can modify the address or its nickname.
34 3 Profile Management
edit_shipping_address.jsp
There are two forms on this page. The first invokes handleChangeNickname() to change the nickname
of the address, the second updates the address data with changes entered by the user by invoking either
handleUpdateAddress() or handleUpdateAddressAndMakeDefault(). The first only updates the address;
the second updates the address and copies it to the default shipping address.
The operation of the upper form for changing the nickname of an address depends on two fields. One field is
hidden and pre-populated with the old nickname of the address. The form handler uses this value to determine
which address is being edited. The second form, which changes the contents of the address, also starts with the
nickname in a hidden field for the same reason.
3 Profile Management 35
Because the B2CProfileFormHandler is session scoped, you could create a property in the form handler in
both cases and set it to the value of the address being edited when the user enters the form to edit the shipping
address. There is no real benefit to one way or the other, but the way we implemented it in Pioneer Cycling
would also work for a request scoped form handler.
Note: The “. . .” markers in the following code sample indicate a place where code has been removed to clarify
the sample.
<dsp:form action="address_book.jsp" method="POST">. . . <dsp:input bean="B2CProfileFormHandler.editValue.nickname" type="hidden"/> <dsp:input bean="B2CProfileFormHandler.editValue.newNickname" maxlength="40" size="20" type="text"/><!-- Submit form to handleChangeAddressNickname() handler --> <dsp:input bean="B2CProfileFormHandler.changeAddressNickname" type="submit" value="Change Nickname ->"/>. . .</dsp:form>. . .<dsp:form action="address_book.jsp" method="POST">. . . <dsp:input bean="B2CProfileFormHandler.editValue.nickname" type="hidden"/> Name<br> <dsp:input bean="B2CProfileFormHandler.editValue.firstName" maxlength="100" size="15" type="text" required="<%=true%>"/>. . . Street address <br> <dsp:input bean="B2CProfileFormHandler.editValue.address1" maxlength="30" size="40" type="text" required="<%=true%>"/><br>. . . <!-- Update Address --> <dsp:input bean="B2CProfileFormHandler.updateAddress" type="submit" value=" Update --"/>. . . <dsp:input bean="B2CProfileFormHandler.updateAddressAndMakeDefault" type="submit" value=" Update & Set As Default --"/>. . .
The editValue property must be initialized to the current value of that shipping address before the
Edit Shipping Address page is displayed to the member. The hyperlink in the Address Book page sets the
editAddress property of the ProfileFormHandler to the ID of the address to be edited:
<dsp:a bean="B2CProfileFormHandler.editAddress" href="edit_shipping_address.jsp" paramvalue="key">edit this</dsp:a>
The property has an associated handler, handleEditAddress(), that populates the editValue property with
the address data. It looks at the EditAddress property for the ID of the address to edit:
String nickname = getEditAddress();Map secondaryAddress = (Map) getProfile().getPropertyValue("secondaryAddresses");MutableRepositoryItem theAddress = (MutableRepositoryItem) secondaryAddress.get(nickname);Map edit = getEditValue();
36 3 Profile Management
// save nickname in editValue map –- the updateAddress handler will need it edit.put("nickname", nickname);// ...as will, perhaps, the changeAddressNickname handleredit.put("newNickname", nickname);String[] addressProps = getAddressProperties();// Store each property in the map, for use by edit_secondary_address.jspObject property;for (int i = 0; i < addressProps.length; i++) { property = theAddress.getPropertyValue(addressProps[i]); if (property != null) edit.put(addressProps[i], property);}
The member makes any changes to the address and invokes one of the three submit buttons (Change
Nickname, Update, or Update & Set As Default) to commit those changes.
Deleting an Alternate Shipping Address
Like editing an address, when the customer clicks the remove this hyperlink to delete an alternate shipping
address, the setRemoveAddress() method is passed the target address, and the handleRemoveAddress()
method does the actual removal.
The JSP sample below was taken from address_book.jsp and abbreviated for the purpose of demonstrating
how to delete an address using a link. The ForEach servlet bean iterates over the secondaryAddress property,
which is a java.util.Map. When ForEach iterates over a Map or Dictionary or Hashtable (or any multi-valued
object that contains keys and values), it binds the param:key to each key in the object in the very same way
that it binds element to each value in the object. In the case of the secondaryAddresses property, a link
is generated which, when clicked, sets the value of property removeAddress to the key or nickname of the
address and then invokes the method handleRemoveAddress.
<dsp:getvalueof id="requestURL" idtype="java.lang.String" bean="/OriginatingRequest.requestURI"><dsp:droplet name="ForEach"> <dsp:param bean="Profile.secondaryAddresses" name="array"/> <dsp:oparam name="output"> > <dsp:a bean="B2CProfileFormHandler.removeAddress" href="<%=requestURL%>" paramvalue="key">remove this</dsp:a> </dsp:oparam></dsp:droplet></dsp:getvalueof>
Note that all the handlers in the B2CprofileFormHandler class have associated success and error properties.
For example, the handleRemoveAddress() method redirects the site visitor to the page identified by
removeAddressSuccessURL() if the address is successfully removed, and to the page identified by
removeAddressErrorURL() if an error occurred during the removal process. In both cases, redirection only
occurs if the property has a non-null value otherwise this user is sent to the page defined by the action attribute.
Moving an Alternate Shipping Address to the Default Shipping Address
We used a dropdown menu to allow the member to select an alternate shipping address and move it to the
default shipping address. The site visitor cannot change the default shipping address on this page by entering
3 Profile Management 37
a new one; he must edit one of the alternate addresses and move it to the default using the Update & Set As
Default button.
The following JSP sample, taken fromaddress_book.jsp demonstrates how the form is created. We listed all
the secondaryAddresses and the user’s billingAddress in a select drop-down list in order to display all of
the addresses that the user could select as the default shipping address. The form submits back to the page it
resides on, address_book.jsp and invokes the method handleSelectDefaultAddress on the form handler
B2CProfileFormHandler.
<dsp:form action="address_book.jsp" method="POST"> Change it to be: <dsp:select bean="B2CProfileFormHandler.editValue.defaultAddressNickname"> <dsp:option value="My Billing Address"/>My Billing Address <dsp:droplet name="ForEach"> <dsp:param bean="Profile.secondaryAddresses" name="array"/> <dsp:oparam name="output"> <dsp:getvalueof id="addressId" param="key" idtype="java.lang.String"> <dsp:option value="<%=addressId%>"/> </dsp:getvalueof><dsp:valueof param="key"/> </dsp:oparam> </dsp:droplet> </dsp:select> <p> <dsp:input bean="B2CProfileFormHandler.selectDefaultAddress" type="submit" value="Change default"/></dsp:form>
Contact Customer Service page
This page allows the member to send e-mail to the Pioneer Cycling customer service group.
38 3 Profile Management
contact_customer_service.jsp
The e-mail is generated by the /atg/dynamo/service/EmailFormHandler component, which allows a page
designer to collect user form input, and to create and send an e-mail message. The EmailFormHandler, an
instance of the atg.service.email.EmailFormHandler class, normally collects the following fields: sender,
recipient, subject, and body.
It provides a handler, handleSendEmail(), which calls the protected method SendMail() to perform
the e-mail creation and sending steps. SendMail() was intended to be overridden by a derived
class. In the Pioneer Store, we redefined the EmailFormHandler component to be an instance of the
atg.projects.b2cstore.B2CEmailFormHandler class. This class defines the additional properties,
customerServiceEmailAddress and profile. It overrides SendMail() to set the sender property
to the firstNameMiddleNamelastName<emailAddress> (extracted from the site visitor’s profile)
and to set the recipient to the customerServiceEmailAddress property. (By default, this property is
3 Profile Management 39
Note: e-mail is not actually sent unless the atg/dynamo/service/SMTPEmail component is configured with
an SMTP service. By default, it is not. To configure Pioneer Cycling to send e-mail, see the Setting up e-mail
section in the Introduction (page 1) chapter of this guide.
Credit Card Page
On this page, a member can define a list of named credit cards to be used during the checkout process.
As with other customized pages in the Pioneer Cycling store, we used the editValue map property to collect
the credit card information. The Add Card button invokes the handleCreateNewCreditCard() method,
which creates a credit card in the creditCards map property of the user profile using the information the
user enters. The card is identified by a nickname. If the customer does not provide a nickname, the system
40 3 Profile Management
automatically creates a nickname using an abbreviation for the credit card type appended by the credit card
number.
To look up the credit card type abbreviation, we used a ResourceBundle. The ResourceBundle is used for
internationalization and to map key/value pairs in a file.
The file B2CUserResources.properties is a java.util.ResourceBundle used by Java files specific to
Pioneer Cycling. A ResourceBundle basically consists of strings that should be used in the Java code and
user-friendly strings that map to each of those strings. For the credit card abbreviations, we use the actual
creditcard type name (MasterCard, Visa, American_Express, Discover) to look up the abbreviation. This
snippet from B2CUserResources.properties shows how the types and abbreviations of credit cards are
stored.
# CreditCard abbreviationsMasterCard=MCVisa=VISAAmerican_Express=AMEXDiscover=DISC
For security reasons, credit card numbers should never be displayed fully on the browser. To display credit cards
to the user on pages such as the My Saved Credit Cards page, we used a special credit card tag converter that
masks all digits but the last 4 with an ‘X’ or another character chosen by the site developer. (For example, a credit
card with the number 4111 1111 1111 1111 would be displayed as XXXX XXXX XXXX 1111.) The creditcard
converter is invoked in JSP by specifying a creditcard value to the converter attribute of the valueof tag as
shown here:
<dsp:valueof converter="creditcard" param="element.creditCardNumber"/>
We also used the converter to display the credit card nickname because, if it were generated automatically,
it would also contain the credit card number. In this case, the groupingsize property of the creditcard
converter is specified as zero so that the nickname is not divided into groups of four characters:
<dsp:valueof converter="creditcard" groupingsize="4" param="element.creditCardNumber"/>
These are highlights of the handleCreateNewCreditCard() method, stressing the most important sections:
//------------ Submit: CreateNewCreditCard ------------/** * Creates a new credit card using the entries entered in the editValue map * by the credit_card.jsp page * * @param pRequest the servlet's request * @param pResponse the servlet's response * @exception ServletException if there was an error while executing the code * @exception IOException if there was an error with servlet io */public boolean handleCreateNewCreditCard(DynamoHttpServletRequest pRequest, DynamoHttpServletResponse pResponse) throws ServletException, IOException{ Profile profile = getProfile(); MutableRepository repository = (MutableRepository) profile.getRepository(); Map creditCards = (Map) profile.getPropertyValue("creditCards"); ResourceBundle bundle = ResourceUtils.getBundle(RESOURCE_BUNDLE,
3 Profile Management 41
getLocale(pRequest));
// Identify credit card properties we need from user String[] cardProperties = getCardProperties();
// Get editValue map, containing the credit card properties HashMap newCard = (HashMap) getEditValue();
boolean isMissingField = false; // Verify all required fields entered before creating new card for (int i = 0; i < cardProperties.length; i++) { if (newCard.get(cardProperties[i]) == null || ((String) newCard.get(cardProperties[i])).length() == 0) { generateFormException(MSG_MISSING_CC_PROPERTY, cardProperties[i], pRequest); isMissingField = true; } } if (isMissingField) return true;
// Verify that card number and expiry date are valid if (!validateCreditCard(newCard, bundle)) { // form exceptions added by validateCreditCard method return true; }
// Check that the nickname is not already used for a credit card String cardNickname = (String) newCard.get("creditCardNickname"); if( creditCards.get(cardNickname) != null ) { String rawErrorStr = bundle.getString(MSG_DUPLICATE_CC_NICKNAME); String formattedErrorStr = (new MessageFormat(rawErrorStr)).format(new String[] {cardNickname}); addFormException(new DropletFormException(formattedErrorStr, new String(getAbsoluteName() + "editValue.creditCardNickname"), MSG_DUPLICATE_CC_NICKNAME)); return true; }
try { // Create a repository item to be filled with stuff. MutableRepositoryItem card = repository.createItem("credit-card");
// Copy values from the newCreditCard object for (int i = 0; i < cardProperties.length; i++) card.setPropertyValue(cardProperties[i], newCard.get(cardProperties[i]));
// Set billing address card.setPropertyValue("billingAddress", profile.getPropertyValue("billingAddress"));
// Insert card into the db repository.addItem(card);
// Insert the credit card into the creditCards map // Did the user provide us with a name?
42 3 Profile Management
String key = (String) newCard.get("creditCardNickname"); if (key == null || key.trim().length() == 0) { // If no name, generate unique key // Use the credit card type (convert any spaces to _ so as to find it in // the bundle StringBuffer buffer = new StringBuffer((String) card.getPropertyValue("creditCardType")); for (int i = 0; i < buffer.length(); i++) { if (buffer.charAt(i) == ' ') buffer.setCharAt(i, '_'); } // Generate the key as CARD-ABBREV + CardNumber String abbrev = bundle.getString(buffer.toString()); if (abbrev == null) abbrev = ""; key = abbrev + (String) card.getPropertyValue("creditCardNumber"); }
// Set the new credit card creditCards.put(key, card);
// empty out the map newCard.clear(); } catch (RepositoryException repositoryExc) { generateFormException(MSG_ERR_CREATING_CC, repositoryExc, pRequest); if (isLoggingError()) logError(repositoryExc);
return redirectIfPossible(getCreateCardErrorURL(), pRequest, pResponse); }
return redirectIfPossible(getCreateCardSuccessURL(), pRequest, pResponse);
}
Note that handleCreateNewCreditCard() calls the validateCreditCard() method, which does two
things. First, it checks that the credit number is composed solely of digits and spaces. Second, it checks that the
card has not expired.
validateCreditCard()
//------ Utility method: validate credit card number & expiration date ------/** * Validate the credit card number entered and the expiration date * (must be later than today). * * @param card A hashmap containing the user-entered credit card data * @param bundle A ResourceBundle providing the error message text * @return true if the credit card is valid **/protected boolean validateCreditCard(HashMap card, ResourceBundle bundle) {
try { // 1. Check that the credit card number is composed solely of digits and spaces String cardNumber = (String) card.get("creditCardNumber"); if (cardNumber == null) {
3 Profile Management 43
throw new B2CProfileException( MSG_INVALID_CC, "The card number is a required field for a credit card entry", "creditCardNumber"); }
for (int i = 0 ; i < cardNumber.length(); i++) { char c = cardNumber.charAt(i); if ((c < '0' || c > '9') && c != ' ') { throw new B2CProfileException( MSG_INVALID_CC, "The card number must consist only of digits and spaces", "creditCardNumber"); } }
// 2. Get expiration month & year int cardExpirationYear = 0; int cardExpirationMonth = 0;
java.util.Calendar now = java.util.Calendar.getInstance(); int year = now.get(Calendar.YEAR); // convert month from 0-11 to 1-12 int month = now.get(Calendar.MONTH) + 1; // Convert year and month to integer values try { cardExpirationYear = Integer.parseInt((String) card.get("expirationYear")); } catch (NumberFormatException exc) { throw new B2CProfileException(MSG_INVALID_CC, "The year of expiration must be specified", "expirationYear"); } try { cardExpirationMonth = Integer.parseInt((String) card.get("expirationMonth")); } catch (NumberFormatException exc) { throw new B2CProfileException(MSG_INVALID_CC, "The month of expiration must be specified", "expirationMonth"); }
// 3. Check that the card has not expired if (cardExpirationYear < year) { throw new B2CProfileException(MSG_INVALID_CC, "The card you entered is past its expiration date", "expirationYear"); } if (cardExpirationYear == year) { if (cardExpirationMonth < month) { throw new B2CProfileException(MSG_INVALID_CC, "The card you entered is past its expiration date", "expirationMonth"); } } return true; } catch (B2CProfileException exc) { String rawErrorStr = bundle.getString(exc.getCode()); String formattedErrorStr = exc.getMessage(); (new MessageFormat(rawErrorStr)).format(new String[] {exc.getMessage()}); addFormException(new DropletFormException(formattedErrorStr,
44 3 Profile Management
new String(getAbsoluteName() + "editValue." + exc.getDescription()), MSG_INVALID_CC)); return false; }}
You could easily override this method for more rigorous validation. You could check that certain credit card
types have a specific number of digits; for example, Visa cards have sixteen digits. In addition, all credit cards are
validated during the checkout process by the commerce pipeline. See the section on Additional Order Capture
Information (page 164) in the Order Processing (page 135) chapter for more information.
Viewing and Searching History Page
The customer can click on the things I recently looked at and for hyperlink in the My Commerce Profile section
of the My Profile page for a record of categories and products that she recently viewed and her recent searches
on the Pioneer Cycling site.
3 Profile Management 45
pages_viewed.jsp
Categories and Products Recently Browsed
In order to record the categories and products browsed by a customer in her profile during a given visit
to Pioneer Cycling, we added two transient properties to the visitor’s profile: categoriesViewed and
productsViewed. We defined these properties as transient to avoid collecting and storing extraneous
information in the database. They can be seen in the following XML excerpt from PioneerCycling/config/
atg/userprofiling/userProfile.xml.
Note: The “. . .” markers in the following code sample indicate a place where code has been removed to clarify
the sample.
<item-descriptor name="user"> . . . <!-- These transient properties used to record categories & pages viewed --> <property name="categoriesViewed" display-name="Categories viewed recently" data-type="set" component-data-type="String" />
46 3 Profile Management
<property name="productsViewed" display-name="Products viewed recently" data-type="set" component-data-type="String" /> . . .</item-descriptor>
To record the categories and products viewed by a member during a visit to the Pioneer Store site, we needed
to generate a DMS (Dynamo Messaging System) message each time a category or product is viewed, and
record that message in the member’s profile. We used the Commerce component /atg/commerce/catalog/
CategoryBrowsed, as shown below, to generate the DMS message when a category is viewed.
…<dsp:droplet name="/atg/commerce/catalog/CategoryLookup"> <dsp:param bean="/OriginatingRequest.requestLocale.locale" name="repositoryKey"/> <dsp:oparam name="output"> <%/* Send a Views Item event that we browsed this category */%> <dsp:droplet name="/atg/commerce/catalog/CategoryBrowsed"> <dsp:param name="eventobject" param="element"/> </dsp:droplet></dsp:droplet>…
CategoryBrowsed is invoked by the following pages:
catalog/category_all_bikes.jspcatalog/category_bikes.jspcatalog/category_bundle.jspcatalog/category_clothing.jspcatalog/category_generic.jspcatalog/category_parts.jsp
A DMS message is sent by the component /atg/commerce/catalog/ProductBrowsed when the site visitor
views a product. It is invoked as follows:
. . .<dsp:droplet name="/atg/commerce/catalog/ProductLookup"> <dsp:param bean="/OriginatingRequest.requestLocale.locale" name="repositoryKey"/> <dsp:param name="elementName" value="Product"/> <dsp:oparam name="output"> <%/* Add this product to the user's list of products browsed. */%> <dsp:droplet name="/atg/commerce/catalog/ProductBrowsed"> <dsp:param name="eventobject" param="Product"/> </dsp:droplet></dsp:droplet>. . .
ProductBrowsed is invoked by the following pages:
catalog/product_bike.jspcatalog/product_bundle.jspcatalog/product_generic.jspcatalog/product_gift_certificate.jsp
3 Profile Management 47
catalog/product_part.jsp
The messages generated by these components are received by the Scenario Server. We created a Pioneer
Cycling Store scenario named Browsing to accept ItemViewed messages. It acts on incoming messages by
updating the user’s profile accordingly as shown below:
The Browsing scenario shown in the ACC.
The DMS messages generated by CategoryBrowsed and ProductBrowsed are of type atg.dps.ViewItem.
The Patch Bay definition file, /atg/dynamo/messaging/dynamoMessagingSystem.xml is used to deliver
these messages to the Scenario Server. The components are defined as message sources in the Commerce
configuration layer, and the Scenario Server is defined as a message sink in the User Profiling configuration layer.
For more about scenarios, see the Creating Scenarios chapter in the ATG Personalization Guide for Business Users.
Items Recently Searched For
The Pioneer Store also keeps track of and displays recent member searches. The word or words of each search
are displayed as hyperlinks that bring the customer to the search results page, where a list of matching products
and categories is displayed. For example, if the customer searched for a helmet and a water bottle, the list of
hyperlinks would look like the screenshot below.
48 3 Profile Management
As in the case of recently viewed categories and products, we added a transient set property to the visitor’s
profile: recentSearches.
<item-descriptor name="user"> . . . <!-- This transient properties used to record recent user searches --> <property name="recentSearches" display-name="Recent user searches" data-type="set" component-data-type="String" /> . . .
This feature is supported by making modifications to the component /atg/commerce/catalog/
CatalogSearch. It is defined as an instance of atg.commerce.catalog.SearchFormHandler; in the Pioneer
Store, we extended this class with atg.projects.b2cstore.B2CSearchFormHandler, which:
• Adds a one-step search facility.
• Generates a DMS message of type atg.projects.b2cstore.search whenever a search is performed,
passing the search text as the body of the message.
One-step Search
The hyperlink brings the site visitor back to the Simple Search page (in the search subdirectory of the Pioneer
Store site); clicking on this link also causes the search to be carried out. We did this by adding a property to the
CatalogSearch component: oneStepSearch. The hyperlink sets this property to the recorded search string,
and the search is carried out by the handleOneStepSearch() handler prior to displaying the Simple Search
page.
3 Profile Management 49
SimpleSearch.jsp
Send DMS Message to Scenario Server
In the Pioneer Store, the CatalogSearch component, which performs all user searches, also sends the
search string to the Scenario Server as a DMS message. Sending the message is delegated to a component
that implements the atg.dms.patchbay.MessageSource interface. The Pioneer StoreCatalogSearch
component uses the /atg/commerce/catalog/SearchEventSender component to send its message. This
component is identified by the SearchEventSender property of the CatalogSearch.
The SearchEventSender component, an instance of atg.projects.b2cstore.SearchEventSender,
implements the MessageSource methods, as well as the fireSearchEvent() method, which issues a
message to the Scenario Server.
public boolean fireSearchEvent(String searchString){ // Send message notifying that a search has been performed
50 3 Profile Management
if (mStarted) { // if message source was successfully started try { SearchMessage search = new SearchMessage(); search.setSearchString(searchString); ObjectMessage msg = getMessageSourceContext().createObjectMessage(); msg.setObject(search); msg.setJMSType("atg.projects.b2cstore.search"); getMessageSourceContext().sendMessage(msg); } catch (JMSException jmse) { System.out.println("Error sending message recording search: " + jmse); } } return true;}
Tying all this together involves configuring the Patch Bay to deliver a message from the SearchEventSender
to the Scenario Server. A message type must be defined, SearchEventSender identified as a message source
for this kind of message, and the Scenario Server identified as a message sink. For more information on the Patch
Bay, refer to the chapter on the Dynamo Message System in the ATG Programming Guide. Here is an overview of
the Patch Bay configuration file provided by the Pioneer Store:
<ATG9dir>/atg/dynamo/messaging/dynamoMessagingSystem.xml
<dynamo-message-system> <patchbay> <message-source> <nucleus-name> /atg/commerce/catalog/SearchEventSender </nucleus-name> <output-port> <port-name> DEFAULT </port-name> <output-destination> <provider-name> local </provider-name> <destination-name> localdms:/local/DPSTopic/CatalogSearchEvents </destination-name> <destination-type> Topic </destination-type> </output-destination> </output-port> </message-source> <message-sink> <nucleus-name> /atg/scenario/ScenarioManager </nucleus-name> <input-port> <port-name> IndividualEvents </port-name> <input-destination> <provider-name> local </provider-name> <destination-name> localdms:/local/DPSTopic/CatalogSearchEvents </destination-name> <destination-type> Topic </destination-type> </input-destination> </input-port> </message-sink> </patchbay> <!-- local JMS definitions -->
3 Profile Management 51
<local-jms> <jndi-prefix>/local</jndi-prefix> <topic-name>/DPSTopic/CatalogSearchEvents</topic-name> </local-jms> <!-- message registry definitions --> <message-registry> <message-family> <message-family-name> atg </message-family-name> <message-type> <jms-type> atg.projects.b2cstore.search </jms-type> <message-class> atg.projects.b2cstore.SearchMessage </message-class> <message-context> request </message-context> <display-name> Searches the catalog </display-name> <description> Generated when user searches a catalog </description> </message-type> </message-family> </message-registry></dynamo-message-system>
Profile Repository Extensions
This section describes extensions made to ATG Consumer Commerce and DPS for the Pioneer Cycling store site.
DPS performs the collection, storage, and retrieval of data specific to individual users of the Web application. A
profile is the data stored for an individual customer based on forms she fills out or her actions on the site. Once
this data is collected, it provides Web site developers the ability to show the customer personalized products
and content based on this profile.
In the Pioneer Cycling site, we used personalization in a variety of ways to make a compelling shopping
experience. DPS provides basic personalization capability, but you can easily extend this functionality to fit the
needs of your Web application. The Pioneer Cycling site demonstrates several ways to extend this functionality.
Please refer to the Setting up a Profile Repository chapter in the ATG Personalization Programming Guide and
the SQL Content Repositories chapter in the ATG Repository Guide for more detailed information about the SQL
Repository definitions in DPS and Consumer Commerce.
DPS stores customer data in a repository, which consists of user item types and several supporting item types.
The default DPS repository contains item types defined with the set of properties that are needed by most
personalized Web applications. In ATG Consumer Commerce, some additional properties have been added to
the user item type to make possible many commerce features. Pioneer Cycling adds yet more item types and
properties to provide functionality that is specific to a bike store.
These extensions are defined in the XML file <ATG9dir>/atg/userprofiling/userProfile.xml. This file
is combined with the files of the same name from ATG Consumer Commerce, DSS, and DPS. These files are
combined per the rules of XML combination to produce one XML file that is then parsed and used by ATG to
describe the item types in the repository. (See the Nucleus: Organizing JavaBean Components chapter of the for
more information on XML file combination.) The combined file is reparsed each time the repository starts and it
is never written out to disk, so we only need to maintain the separate files, not the combined one.
52 3 Profile Management
The underlying storage of the user profile repository in Pioneer Cycling is a relational database. The SQL Profile
Repository is used to expose that data via the Repository API. In the XML file, we describe the mapping of
repository items to the relational database tables and columns.
Here are the properties we added to the user item type:
Property Description
size The customer’s clothing size. We use this property for targeting size-
appropriate clothing to the user.
weightPreference This integer value is incremented or decremented according to the weight
class of the items purchased by the customer. If the customer purchases
many lightweight items, he has a low value for weightPreference and
we would use that low value to target additional lightweight items to the
customer.
pricePreference This property works much like weightPreference. Products have, in
addition to the actual price, a price class that indicates if the product is
cheap, moderate, expensive, or luxury, as compared to similar products. If a
customer tends to buy cheap items, we can target our best bargains to that
customer. A customer that tends to purchase luxury items can be shown
more expensive items.
numOrders A count of the number of orders that this user has placed with the store. This
can be used as a measure of customer loyalty.
cumulativeOrderAmount A running total of the amount of money spent on the site. This can be used
for targeting promotions or identifying trends in the customer base. This
number represents the amount spent in the currency indicated by the
customer’s selected locale. If the user changes his locale from ja_JP to
en_US between purchases, then the total, a sum of yen and dollars, will not
be meaningful. We assume that a customer chooses a locale and sticks to it.
bikesOwned We use a scenario to keep track of the types of bikes a user owns so that we
can target compatible products to the user.
userKeywords A list of keywords the customer picks up based on his activities in the store.
This property’s value is populated by DSS. When a user purchases a product,
that product’s keywords are appended to the userKeywords. When a user
conducts a product search, the search term is added to the userKeywords.
In general, when a user expresses interest in an item, we store the keywords
for the item of interest so that we might use that information later to
personalize the user’s experience.
itemsBought A list of the IDs of items purchased by the user.
categoriesViewed This transient property keeps track of a list of the catalog categories recently
visited by the user.
productsViewed This transient property keeps track of a list of the products recently visited
by the user.
3 Profile Management 53
Property Description
recentSearches This transient property keeps track of a list of the strings that the user has
searched on recently.
The following example shows the XML file for Pioneer Cycling. Please refer to the SQL Repository Architecture
chapter in the ATG Repository Guide for the proper definition file syntax.
<gsa-template xml-combine="append">
<header> <name>Bikestore Related Profile Changes</name> <author>DCS Team</author> <version>$Change: 224215 $$DateTime: 2001/12/27 14:00:56 $$Author: bbarber $</version> </header>
<item-descriptor name="user">
<table name="b2c_user" type="auxiliary" id-column-name="id"> <attribute name="resourceBundle" value="atg.projects.b2cstore.UserProfileTemplateResources"/>
<property category-resource="categoryPioneerCycling" name="size" data-type="enumerated" default="clothingSizeUnknown" column-name="clothing_size" display-name-resource="clothingSize"> <attribute name="resourceBundle" value="atg.projects.b2cstore.UserProfileTemplateResources"/> <attribute name="useCodeForValue" value="false"/> <option resource="clothingSizeUnknown" code="0"/> <option resource="clothingSizeExtraSmall" code="1"/> <option resource="clothingSizeSmall" code="2"/> <option resource="clothingSizeMedium" code="3"/> <option resource="clothingSizeLarge" code="4"/> <option resource="clothingSizeExtraLarge" code="5"/> </property> <property category-resource="categoryPioneerCycling" name="weightPreference" data-type="int" column-name="weight_preference" display-name-resource="weightPreference"> <attribute name="resourceBundle" value="atg.projects.b2cstore.UserProfileTemplateResources"/> </property> <property category-resource="categoryPioneerCycling" name="pricePreference" data-type="int" column-name="price_preference" display-name-resource="pricePreference"> <attribute name="resourceBundle" value="atg.projects.b2cstore.UserProfileTemplateResources"/> </property> <property category-resource="categoryPioneerCycling" name="numOrders" display-name-resource="numOrders" data-type="int" column-name="num_orders" default="0"> <attribute name="resourceBundle"
54 3 Profile Management
value="atg.projects.b2cstore.UserProfileTemplateResources"/> </property> <property category-resource="categoryPioneerCycling" name="cumulativeOrderAmount" display-name-resource="cumulativeOrderAmount" data-type="double" column-name="cum_order_amt" default="0"> <attribute name="resourceBundle" value="atg.projects.b2cstore.UserProfileTemplateResources"/> </property> </table>
<table name="b2c_bike_owned" type="multi" id-column-name="id" multi-column-name="sequence_num"> <property category-resource="categoryPioneerCycling" name="bikesYouOwn" data-type="list" component-data-type="String" column-name="bike" display-name-resource="bikesYouOwn"> <attribute name="resourceBundle" value="atg.projects.b2cstore.UserProfileTemplateResources"/> </property> </table> <table name="b2c_user_keyword" type="multi" id-column-name="id" multi-column-name="sequence_num"> <property category-resource="categoryPioneerCycling" name="userKeywords" data-type="list" component-data-type="String" column-name="keyword" display-name-resource="userKeywords"> <attribute name="resourceBundle" value="atg.projects.b2cstore.UserProfileTemplateResources"/> </property> </table> <table name="b2c_item_bought" type="multi" id-column-name="id" multi-column-name="sequence_num"> <attribute name="resourceBundle" value="atg.projects.b2cstore.UserProfileTemplateResources"/> <property category-resource="categoryPioneerCycling" name="itemsBought" data-type="list" component-item-type="sku" repository="/atg/commerce/catalog/ProductCatalog" column-name="item" display-name-resource="itemsBought"> <attribute name="resourceBundle" value="atg.projects.b2cstore.UserProfileTemplateResources"/> </property> </table>
<!-- The following transient properties used to record categories & pages viewed, and recent searches --> <property category-resource="categoryPioneerCycling" name="categoriesViewed" display-name-resource="categoriesViewed" data-type="set" component-data-type="String"> <attribute name="resourceBundle" value="atg.projects.b2cstore.UserProfileTemplateResources"/> </property>
<property category-resource="categoryPioneerCycling"
3 Profile Management 55
name="productsViewed" display-name-resource="productsViewed" data-type="set" component-data-type="String"> <attribute name="resourceBundle" value="atg.projects.b2cstore.UserProfileTemplateResources"/> </property>
<property category-resource="categoryPioneerCycling" name="recentSearches" display-name-resource="recentSearches" data-type="set" component-data-type="String"> <attribute name="resourceBundle" value="atg.projects.b2cstore.UserProfileTemplateResources"/> </property>
</item-descriptor>
</gsa-template>
We used the following SQL script to create the database schema to store the user profile data. Note that the
tables declared in the XML as type="auxiliary" have one row per user. The tables declared as type="multi"
have one row per user per value, so if there are1000 users and each customer purchases 5 items, there will be
5000 rows in the b2c_item_bought table.
CREATE TABLE b2c_user ( id VARCHAR(40) NOT NULL, clothing_size INT NULL, wants_prod_reviews TINYINT NULL, weight_preference INTEGER NULL, price_preference INTEGER NULL, num_orders INTEGER NULL, cum_order_amt DOUBLE PRECISION NULL, PRIMARY KEY(id));
CREATE TABLE b2c_user_keyword ( id VARCHAR(40) NOT NULL, sequence_num INTEGER NOT NULL, keyword VARCHAR(50) NOT NULL, PRIMARY KEY(id, sequence_num));
CREATE TABLE b2c_item_bought ( id VARCHAR(40) NOT NULL, sequence_num INTEGER NOT NULL, item VARCHAR(40) NOT NULL, PRIMARY KEY(id, sequence_num));
CREATE TABLE b2c_bike_owned ( id VARCHAR(40) NOT NULL, sequence_num INTEGER NOT NULL, bike VARCHAR(40) NOT NULL, PRIMARY KEY(id, sequence_num));
56 3 Profile Management
4 Extending the Product Catalog 57
4 Extending the Product Catalog
ATG Consumer Commerce comes with a default set of product catalog item types that most stores will find
useful. These include product, category, and SKU. However, you may find that you need to extend the standard
product catalog definition to meet the needs of your site. You may want to add additional properties to the
item types included in ATG Consumer Commerce or add new item types that are specific to your needs. ATG
Consumer Commerce is very flexible and makes these types of customizations to the product catalog easy to do.
To implement the specialized features of the Pioneer Cycling site, we extended the basic ATG Consumer
Commerce product catalog definition in three main ways. This chapter includes the following sections:
Background (page 57)
Explains what you need to know in order to extend the catalog.
Adding Properties to Product Definition (page 58)
We added simple properties (or attributes) to item types already defined in ATG Consumer
Commerce. For example, we added the property style to the product item type.
Adding New Item Types (page 61)We added new item types to hold product and SKU
property values that can’t be stored in a single, simple property. Adding a new item type
is necessary if a product or SKU property value cannot be represented as a simple string or
number.
Creating Subtypes (page 63)We created new item types by subclassing the product and
SKU item types. This allowed us to create new attributes for just one subtype if desired
Background
This section assumes you have read and understood the SQL Repository Architecture chapter in the ATG
Repository Guide. ATG Commerce stores its product catalog data in a SQL database and accesses that data
via the SQL Repository Adapter. In order for the SQL Repository Adapter to be able to access your product
catalog information, all of the information regarding the definition of your item types must be defined in an
XML repository template definition file. The XML definition file contains information about the item types in
your product catalog, the properties of each item type, how the item types and their properties are stored in
the database, and other important “meta” data. Any changes you want to make to the base item types have
to be made inside the XML definition file. Because all data is stored in a database, any additions have to be
accompanied by SQL DDL scripts for creating new tables for the new information. In summary, to add any new
item types or properties you need to write XML scripts and SQL DDL scripts.
The XML definition files reside in the configuration path. The entire definition of your product catalog could
reside in a single XML file, or the definition may be spread out among multiple files. In addition, these definition
58 4 Extending the Product Catalog
files may be layered among several configuration layers similar to the way properties files are. XML files from
different configuration layers are combined according to highly structured rules. For more information, see the
XML File Combination section in the Nucleus: Organizing JavaBean Components chapter of the ATG Programming
Guide.
ATG Consumer Commerce comes with an XML definition file that contains the information for the standard
product catalog. A reference to this XML file is found in the /atg/commerce/catalog/ProductCatalog
component. The location of this XML file is /atg/commerce/catalog/productCatalog.xml.
There are a number of ways you can add an attribute to an existing item type. Our approach was to leave
all of the base XML files intact. We created a new XML definition file called /atg/commerce/catalog/
productCatalog.xml, which can be found in PioneerCycling/config/config.jar. This file is later
merged with the base productCatalog.xml file included in ATG Consumer Commerce by the XML combiner.
ATG Consumer Commerce also comes with SQL DDL scripts that create the SQL tables in the SQL database.
Because any new attributes must be stored in a database table, we had to add SQL DDL scripts for each
new attribute. Again, our approach was to leave the ATG Consumer Commerce SQL DDL scripts intact. We
created new Pioneer Cycling SQL DDL scripts that can be found in <ATG9dir>/PioneerCycling/sql/
db_components/database/b2c_product_catalog_ddl.sql.
Adding Properties to Product Definition
For the Pioneer Cycling Catalog, we extended the basic ATG Consumer Commerce product item type with the
following new properties:
Property Description
available This property indicates whether this product is available in the current locale.
highlightPriority This int property makes it possible to highlight some products in the catalog
user interface via a targeter configured to look for high highlightPriority
values. We created this value to push items that are overstocked.
priceRange This enumerated property classifies items into cheap, normal, expensive, and
very expensive price categories. It helps customers locate items in a specified
price class. We also use it to track the kinds of items customers buy by storing
the priceRange of items they purchase. We can use this information to predict
future interests and to target content and products to the customer.
(A product’s actual price is stored in a SKU as an absolute number. Thus, we
have no way to see how prices compare within a category. For example, a $200
bike could be considered cheap, whereas a $200 helmet would be considered
expensive.)
weightRange This enumerated property classifies the relative weight of a product into heavy,
normal, light, and extralight so that customers of Pioneer Cycling can look for
bikes in a specific weight range. The exact weight of a product is stored on the
SKU.
4 Extending the Product Catalog 59
Property Description
style This property is a reference to an item of type “Style” which indicates the sort of
style a product is, such as sporty, funky, retro, or feminine. These values are used
for finding similarities between products.
manufacturer This property is a reference to an item of type “Manufacturer” that indicates the
company that makes the product.
The standard ATG Consumer Commerce product catalog stores the product item type attributes in the
dcs_product table. We didn’t want to modify the standard ATG Consumer Commerce dcs_product table,
so we created a new SQL table to hold these new attributes. The new table is called b2c_product. (All the
tables we created for the Pioneer Cycling Site have names that start with b2c.) The b2c_product table contains
columns for all of the new attributes. In order for these properties to be accessible on all products the two tables
(the original dcs_product and the new b2c_product tables) are “linked” together with the product_id
attribute.
Because these tables are linked together by a join at the database level, performance is impacted. Another
option is to add new columns to the dcs_product table. We elected to use a separate table that joins to
dcs_product because we wanted users to be able to uninstall the Pioneer Cycling site, an example application,
and keep other ATG products. When designing a production quality application, developers should use all the
techniques they ordinarily would to optimize database performance.
CREATE TABLE b2c_product (product_id VARCHAR(40) NOT NULLREFERENCES dcs_product(product_id),available TINYINT NULL,manufacturer VARCHAR(40) NULL REFERENCES b2c_manufacturer(manufacturer_id),highlight_priority INTEGER NULL,price_range INTEGER NULL,weight_range INTEGER NULL,style VARCHAR(40) NULL REFERENCES b2c_style(style_id),PRIMARY KEY(product_id));
Here is the XML file for the new product item type attributes. All of the code is within <item-descriptor
name="product"> tags. Because the product item-descriptor already exists in the standard ATG Consumer
Commerce XML definition file, this new information is merged with the existing product item-descriptor via
the XML File Combination algorithm mentioned earlier. Note that all of the new attributes are between the
"<table name="b2c_product">. . .</table>" tags so that the Repository API knows this data is stored in
the new b2c_product database table. Also note that two of the attributes have enumerated values (priceRange
and weightRange). This means that when product property values are entered in the ACC, the ACC provides a
dropdown list of possible values.
<item-descriptor name="product"> <table name="b2c_product" type="auxiliary" id-column-name="product_id"> <property name="available" data-type="boolean" column-name="available"/> <property name="highlightPriority" data-type="int" column name="highlight_priority"/> <property name="priceRange" data-type="enumerated" default="NORMAL" column name="price_range"> <attribute name="useCodeForValue" value="false"/>
60 4 Extending the Product Catalog
<option value="CHEAP" code="0"/> <option value="NORMAL" code="1"/> <option value="EXPENSIVE" code="2"/> <option value="VERY EXPENSIVE" code="3"/> </property> <property name="weightRange" data-type="enumerated" default="NORMAL" column name="weight_range"> <attribute name="useCodeForValue" value="false"/> <option value="HEAVY" code="0"/> <option value="NORMAL" code="1"/> <option value="LIGHT" code="2"/> <option value="EXTRA LIGHT" code="3"/> </property> </table></item-descriptor>
Adding simple properties to SKU item type
For the Pioneer Cycling Catalog, we extended the basic ATG Consumer Commerce SKU item type with the
following new properties:
Property Description
color The color of the SKU.
exactWeight The weight, in ounces, grams, or other units, of the SKU.
Here is the SQL DDL for adding the new attributes to the SKU item type. Again, we created a new table
(b2c_sku) to hold the new attributes instead of editing the SQL DDL that comes with ATG Consumer Commerce:
CREATE TABLE b2c_sku ( sku_id VARCHAR(40) NOT NULL REFERENCES dcs_sku(sku_id), color VARCHAR(100) NULL, exact_weight DOUBLE PRECISION NULL, PRIMARY KEY(sku_id));
Here is the code sample from the XML file that defines these additions to the SKU item-type definition:
<item-descriptor name="sku" sub-type-property="type">… <table name="b2c_sku" type="auxiliary" id-column-name="sku_id"> <property name="color" data-type="string" column-name="color"/> <property name="exactWeight" data-type="double" column-name="exact_weight"/> </table></item-descriptor>
4 Extending the Product Catalog 61
Adding New Item Types
If you want to add a simple attribute, such as an integer or string, you can simply add the attribute to the
existing product or SKU item type as shown above. However, some item types are more complicated; for
example, they may be multi-valued or have several associated attributes. In these cases, you may need to create
a brand new item type to contain the values, and then add a reference to this new item type in the product or
SKU. We needed to do this for the Pioneer Cycling store.
We added a couple of new item types to the Pioneer Cycling product catalog: manufacturer and style. These
item types hold information that product objects can reference. If it was simple information, we could have
represented it as a new attribute on the product item type. Because it was more complicated, we pulled it out
into a separate new item type. In order to tie the information together, each product contains a reference to
one of these new item types. These changes required two steps: creating the new item type, and updating the
product to contain a reference to the new information. The steps for adding manufacturer and style were
very similar so we only explain one of them here.
Manufacturer
The manufacturer item type defines the manufacturer of a product and has the following attributes:
Item Description
id Unique identifier
displayName Name of manufacturer
description Description of the manufacturer
longDescription Detailed description of the manufacturer
image Manufacturer logo
keywords List of keywords for this manufacturer (multivalued attribute)
We used SQL DDL scripts for adding the b2c_manufacter database tables. Here is the SQL DDL script we used
to add the main manufacturer table to the database:
CREATE TABLE b2c_manufacturer ( manufacturer_id VARCHAR(40) NOT NULL, manufacturer_name VARCHAR(200) NULL, description VARCHAR(200) NULL, long_description VARCHAR(40) NULL REFERENCES dcs_media_txt (media_id), image VARCHAR(40) NULL REFERENCES dcs_media_bin (media_id), PRIMARY KEY(manufacturer_id));
This is the SQL DDL script we used for the table that holds the multi-valued keyword attribute. (Multi-valued
attributes must be stored in a separate table).
62 4 Extending the Product Catalog
CREATE TABLE b2c_mnfr_keywrd ( manufacturer_id VARCHAR(40) NOT NULL REFERENCES b2c_manufacturer(manufacturer_id), sequence_num INTEGER NOT NULL, keyword VARCHAR(254) NOT NULL, PRIMARY KEY(manufacturer_id, sequence_num));
Then, we added an attribute to the b2c_product table so that the product can contain a reference to the new
manufacturer object:
CREATE TABLE b2c_product (… manufacturer VARCHAR(40) NULL REFERENCES b2c_manufacturer(manufacturer_id)…);
We had to add a new item-descriptor to the XML file for the manufacturer:
<item-descriptor name="manufacturer"> <table name="b2c_manufacturer" type="primary" id-column-name="manufacturer_id"> <property name="id" column-name="manufacturer_id"/> <property name="displayName" data-type="string" column-name="manufacturer_name"/> <property name="description" data-type="string" column-name="description"/> <property name="longDescription" item-type="media" column-name="long_description"/> <property name="image" item-type="media" column-name="image"/> </table> <table name="b2c_mnfr_keywrd" type="multi" id-column-name="manufacturer_id" multi-column-name="sequence_num"> <property name="keywords" data-type="list" component-data-type="string" column-name="keyword"/> </table></item-descriptor>
Finally, we updated the product item-descriptor to include a reference to the new manufacturer property.
Here is a portion of that XML script:
<item-descriptor name="product">… <property name="manufacturer" item-type="manufacturer" column name="manufacturer"/>…</item-descriptor>
After all of these changes were made, the displayName property of a manufacturer of a particular product can
be displayed in a JSP file using the following tag:
4 Extending the Product Catalog 63
<dsp:valueof param="childProduct.manufacturer.displayName"/>
The displayName property associates an item with a text string that users will see through the ACC interface.
For example, a product item refers to a Manufacturer item. In the ACC, the manufacturer field of a product
shows a pick list of the displayNames of all manufacturer items in the repository. If the manufacturer has no
display name, then the id property is used.
Style
We added the new style item-type in exactly the same way as manufacturer.
Here is the portion of productCatalog.xml that describes the item type “style”.
<item-descriptor name="style" display-name="Style"> <table name="b2c_style" type="primary" id-column-name="style_id"> <property name="id" column-name="style_id"/> <property name="displayName" data-type="string" column-name="style_name"/> </table></item-descriptor>
It has just two properties, id and displayName. Functionally, style is similar to an enumerated type property,
which has a code and a value. However, with this implementation, a new style can be added via the ACC while
ATG Consumer Commerce is running. If style were an enumerated property of Product, a developer would
have to add new styles when ATG Consumer Commerce was not running.
Creating Subtypes
In the Pioneering Cycling site, we wanted to subclass both the product and SKU item types. Subtypes
automatically inherit all of the attributes from a parent item type, but can have new properties that apply only to
that one subtype. Creating subtypes is useful when one subtype needs a property another subtype doesn’t. If all
possible attributes were added to the base item-type, users might enter values for certain properties that were
not relevant for all items.
For example, we created three new SKU item types: clothing-sku, bike-sku and part-and-accessory-
sku. All three of these new SKU items types are subtypes of SKU. This means that the new item types inherit
all of the attributes that a SKU has, but each can have some new attributes. When you create a new SKU in the
ACC, you can specify that it be of item type bike-sku for example. The ACC then allows you to specify all of the
properties that are associated with a bike-sku.
Product subtypes
We created two new subtypes for products: frame-product and frame-fit-product.
When we created new products with frames (such as a bike), we made the product of type frame-product.
This frame-product subtype has a new property called frame-type that is only relevant for frame-
products.
If the product was a part that fits on a particular bike frame (such as a crankset), we made it of type frame-fit-
product. This subtype has a new multi-valued property called compatibleframes that contains a list of all of
the frame types this part is compatible with. All other products, such as helmets, are simply of type product.
64 4 Extending the Product Catalog
We wanted to capture this information on our products to provide customers with the following types of
functionality:
• Show all the parts with a particular bike.
• Show all the bikes compatible with a particular part (for example, a brake).
• Warn customers when they add products that are incompatible to their shopping carts.
This is how we updated the XML script for the product item-descriptor to specify the two new subtypes of
product (frame-product and frame-fit-product):
<item-descriptor name="product">… <table name="dcs_product" type="primary" id-column-name="product_id"> <property name="type"> <attribute name="useCodeForValue" value="false"/> <option value="product" code="0"/> <option value="frame-product" code="1"/> <option value="frame-fit-product" code="2"/> </property> </table>…</item-descriptor>
Because frame-product subtypes have a new attribute called frame-type, we created a table to hold this
information with this SQL DDL:
CREATE TABLE b2c_frame_product ( product_id VARCHAR(40) NOT NULL REFERENCES dcs_product(product_id), frame_type INTEGER NULL, PRIMARY KEY(product_id));
We also had to add a new item-descriptor for the new frame-product subtype. Here is the XML definition for it.
Notice that by specifying super-type="product" this item type becomes a subclass of the existing product
item type, and then inherits all properties from it. The frame-type property is enumerated. When you create a
product of type frame-product, ACC provides you with a dropdown list of possible values for the frameType
value (A, B, C, etc.). Also, the frame-type property value is stored in the b2c_frame_product table created
above.
<item-descriptor name="frame-product" super-type="product" sub-type-value="frame-product" id-space-name="product"> <table name="b2c_frame_product" type="auxiliary" id-column-name="product_id"> <property name="frameType" data-type="enumerated" column-name="frame_type"> <option value="A" code="1"/> <option value="B" code="2"/> <option value="C" code="3"/> <option value="D" code="4"/> <option value="E" code="5"/> <option value="F" code="6"/> <option value="G" code="7"/>
4 Extending the Product Catalog 65
<option value="H" code="8"/> <option value="I" code="9"/> </property </table></item-descriptor>
Because frame-fit-product subtypes have a new multi-valued attribute called compatibleframes, we
created a table to hold this information. Here is the SQL DDL to do that. Notice that the column name (frame)
does not necessarily need to match the property name (compatibleframes).
CREATE TABLE b2c_compat_frame ( product_id VARCHAR(40) NOT NULL REFERENCES dcs_product(product_id), sequence_num INTEGER NOT NULL, frame INTEGER NOT NULL, PRIMARY KEY(product_id, sequence_num));
We also added a new item-descriptor for the new frame-fit-product subtype. Here is the XML
definition for it. Again, note that this subtype inherits from the product super type. It has one attribute
(compatibleframes), a multi-valued attribute whose data is stored in the b2c_compat_frame table.
<item-descriptor name="frame-fit-product" super-type="product" sub-type-value="frame-fit-product" id-space-name="product"> <table name="b2c_compat_frame" type="multi" id-column-name="product_id" multi-column-name="sequence_num"> <property name="compatibleframes" data-type="list" component-data-type="string" column-name="frame"/> </table></item-descriptor>
SKU subtypes
For the Pioneering Cycling site, we created three new SKU item types: clothing-sku, bike-sku and part-
and-accessory-sku. All three of these new SKU items types are subtypes of SKU.
Here is the XML for adding the three new subtypes. It was added to the SKU item-descriptor:
<item-descriptor name="sku" sub-type-property="type"> <table name="dcs_sku" type="primary" id-column-name="sku_id"> <property name="type"> <option value="sku" code="0"/> <option value="clothing-sku" code="1"/> <option value="bike-sku" code="2"/> <option value="part-and-accessory-sku" code="3"/> </property> </table>… </item-descriptor>
Clothing-SKU
The clothing-sku item type has the following three new enumerated properties:
66 4 Extending the Product Catalog
Property Description
size Indicates the size of a garment (S,M, L, XL)
proof Used for storing what a garment is impervious to (for example, waterproof or windproof ).
material Indicates the product material (for example, cotton, spandex).
These new attributes are stored in a new table in the database called b2c_clothing_sku. Here is the SQL DDL
for creating this new table.
CREATE TABLE b2c_clothing_sku ( sku_id VARCHAR(40) NOT NULLREFERENCES dcs_sku(sku_id), clothing_size INTEGER NULL, proof INTEGER NULL, material INTEGER NULL, PRIMARY KEY(sku_id));
Here is the XML definition for the clothing-sku item-descriptor. Notice that by specifying super-
type="sku" this item type is a subclass of the exiting SKU item type, and inherits all properties from it. The
properties are all enumerated so the ACC provides dropdown lists when entering SKU values:
<item-descriptor name="clothing-sku" super-type="sku" sub-type-value="clothing-sku" id-space-name="sku" display-name="Clothing SKU"> <table name="b2c_clothing_sku" type="auxiliary" id-column-name="sku_id"> <property category="Pioneer Cycling - Clothing" name="size" data-type="enumerated" column-name="clothing_size" display-name="Size"> <option value="S" code="0"/> <option value="M" code="1"/> <option value="L" code="2"/> <option value="XL" code="3"/> </property> <property category="Pioneer Cycling - Clothing" name="proof" data-type="enumerated" column-name="proof" display-name="Proof"> <attribute name="useCodeForValue" value="false"/> <option value="None" code="0"/> <option value="Water" code="1"/> <option value="Wind" code="2"/> <option value="Child" code="3"/> <option value="Crash" code="4"/> <option value="Tear" code="5"/> <option value="Dent" code="6"/> <option value="Fire" code="7"/> <option value="Monkey" code="8"/> </property> <property category="Pioneer Cycling - Clothing" name="material" data-type="enumerated" column-name="material" display-name="Material"> <attribute name="useCodeForValue" value="false"/> <option value="Unknown" code="0"/> <option value="Cotton" code="1"/> <option value="Spandex" code="2"/>
4 Extending the Product Catalog 67
<option value="Kevlar" code="3"/> <option value="Nylon" code="4"/> <option value="Polyester Blend" code="5"/> <option value="Silk" code="6"/> <option value="Wool" code="7"/> <option value="Goretex" code="8"/> </property> </table> </item-descriptor>
Bike-SKU and Part-and-Accessory-SKU
The new bike-sku and the part-and-accessory-sku item types have the following attributes:
Property Description
metal The type of metal used to make the bike.
dimensions A map where the keys are the names of the dimensions measured and the values are the
measurements.
The bike-sku and part-and-accessory-sku item types have exactly the same properties; they could be
combined into a single item type. The reason for keeping them separate is that if we want to know if a particular
SKU is a bike or a part, we can test the item type and have the answer easily.
Here is the SQL DDL script for the new b2c_bike_sku table created to store the new metal attribute:
CREATE TABLE b2c_bike_sku ( sku_id VARCHAR(40) NOT NULL REFERENCES dcs_sku(sku_id), metal VARCHAR(40) NULL REFERENCES PRIMARY KEY(sku_id));
Because the dimensions attribute is multi-valued, the values are stored in a separate table (b2c_dimensions).
The dimensions attribute is accessed as a Map. The Map type provides a flexible way of storing multiple key/
value pairs. The list of possible keys is not hard coded or predetermined. When fetching a value from a Map
attribute, we pass in a key and the Map returns the associated value. So, if the dimensions Map value for a
particular bike was {tubeLength=5.0,pedalDiameter=6.0} and we passed in the key: tubeLength, it would
return the value 5.0.
Here is the SQL DDL creating the table to store the dimensions property. Note that the tag column holds the
key, and the measurement column holds the value.
CREATE TABLE b2c_dimensions ( sku_id VARCHAR(40) NOT NULL REFERENCES dcs_sku(sku_id), tag VARCHAR(100) NOT NULL, measurement DOUBLE PRECISION NULL, PRIMARY KEY(sku_id, tag));
Here is the XML file for creating the bike-sku item type:
68 4 Extending the Product Catalog
<item-descriptor name="bike-sku" super-type="sku" sub-type-value="bike-sku" id-space-name="sku"> <table name="b2c_bike_sku" type="auxiliary" id-column-name="sku_id"> <property name="metal" data-type="enumerated" column-name="metal"> <attribute name="useCodeForValue" value="false"/> <option value="Unknown" code="0"/> <option value="Aluminium" code="1"/> <option value="Chromoly" code="2"/> <option value="Steel" code="3"/> <option value="Carbon" code="4"/> <option value="Titanium" code="5"/> </property> </table> <table name="b2c_dimensions" type="multi" id-column-name="sku_id" multi-column-name="tag"> <property name="dimensions" data-type="map" component-data-type="double" column-name="measurement"/> </table></item-descriptor>
After all of these changes were made, a particular SKU’s tubeLength value (stored in the dimensions Map
property) could be displayed in the following simple (yet very dynamic) way. Remember that the Map keys are
not hard-coded or predetermined, so any key stored in the Map can be displayed in this way:
<dsp:valueof param="Sku.dimensions.tubelength"/>
In summary, we have shown you how we customized the Pioneer Cycling product catalog to meet our needs by
adding SQL DDL scripts, and XML item-descriptor files. Just about any customization your site needs is possible
with the flexible ATG architecture.
5 Displaying and Accessing the Product Catalog 69
5 Displaying and Accessing the
Product Catalog
This chapter describes how the catalog pages are displayed to customers and how customers
navigate the catalog.
Product Template Pages (page 69)
Describes how to create product template pages for displaying the products and SKUs in
the product catalog. Detailed JSP code examples are shown to help you understand how we
implemented this for the Pioneer Cycling store.
Category Template Pages (page 88)Describes how to create category template pages for
displaying the categories in the product catalog.
Adding Catalog Navigation (page 95)Describes how to create a navigational structure for
your site.
Searching and Filtering the Catalog (page 101)Describes how to add search features to
your pages and how users can filter the results.
Comparing SKUs (page 120)Describes how to add side-by-side comparison to your pages.
Product Template Pages
In order to allow users to browse through your catalog of products, you need to create pages that display
information about the various categories and products you sell. You could create a separate Java Server Page
for every single product, but clearly this would be extremely inefficient. Each time a new product was created or
removed from your catalog, modifications would have to be made.
For Pioneer Cycling, we created generic template pages for products and product categories. These template
pages are simply Java Server Pages that display general information about one category or one product.
These pages are general and data-driven; they are not hard-coded for a specific product or category. All of
the information displayed on them comes from the repository. These general, dynamic pages are convenient
because once written, they can typically be used to display any category or any product in a product catalog.
For example, a very simple site could have one category template page file and one product template page file.
Because these pages are dynamic, they are easy to maintain. As you add more products to your repository, you
don’t necessarily have to change the product catalog template pages.
70 5 Displaying and Accessing the Product Catalog
However, you may decide to display different information about one category of products than another.
For example, in the Pioneer Cycling Store, we display slightly different information for bikes than for all
other products. On the bike template page, we included a Compare to Other Products button that brings
up a form that allows users to compare the features of one bike to another. We did not want this button
to appear for other products. For this reason, we created several template pages in the Pioneer Cycling
code: product_generic.jsp, product_bike.jsp, product_part.jsp, product_bundle_jsp, and
product_gift_certificate.jsp. Most products use the product_generic.jsp template file; only bikes
use the product_bike.jsp template file. However, because much of the template page content is similar for
the two template pages, lots of the supporting code is pulled out into various JSP code fragment files, which can
be inserted in the body of a JSP file (using the dsp:include tag or JSP’s include directive). This makes your
template pages easier to maintain.
By creating page fragments and using the dsp:include tag or JSP’s include directive to embed them in
other pages, you can reuse JSP code and simplify the page development process. However, extensive use of this
technique slows down site performance slightly. See Appendix B: Optimizing Performance for more information
on reusing code. In this case, we used the dsp:include tag syntax in moderation, but did not avoid it entirely.
Template Page File Name in database
Every category and product in the repository has a template.url database value that defines the template
page to use to display this product or category.
The template page (that is, the template.url value) for all bike products is:
<ATG9dir>/PioneerCyclingJSP/j2ee-apps/pioneer/web-app/en/catalog/product_bike.jsp.
The template page for all other products is:
<ATG9dir>/PioneerCyclingJSP/j2ee-apps/pioneer/web-app/en/catalog/product_generic.jsp.
Later, if we decided to display certain products (such as bike helmets) using a totally different template page,
we could create a third template page, and modify the template.url value for all helmets in the database to
reference the new template page name.
In Pioneer Cycling, all the catalog media files (templates and images) are stored on the file system and the URLs
for accessing those files are stored in the repository. It is also possible to use the repository item types media-
internal-binary and media-internal-text to store images and text in the repository.
Creating the Bike Template Pages
The following sections describe pieces of the product_bike.jsp product catalog template page in the Pioneer
Cycling store. You should be able to use this product catalog template page as a stepping-stone for building
your own product template page. The code in the following sections can be found in:
<ATG9dir>/PioneerCyclingJSP/j2ee-apps/pioneer/web-app/en/catalog/product_bike.jsp.
This file is discussed in the sequence most useful for explanation, rather than from beginning to end.
5 Displaying and Accessing the Product Catalog 71
A rendered template page for one bike product
JSP Comments versus HTML Comments
We used JSP comments <%--My Comment--%> rather then in HTML comment tags (<!—My Comment-->) to
enclose many of the comments in our pages. JSP comments do not end up in the page source code, which
keeps the source code as small and light as possible. (HTML comments end up in page source code, which the
page source code longer, and can be seen by users.)
72 5 Displaying and Accessing the Product Catalog
Using ProductLookup
We used ProductLookup, a component that is an instance of ItemLookup, to fetch product attributes from the
repository on the template pages. This is the beginning of the code from a template page where we used the
ProductLookup component:
Note: The “. . .” markers in the following code sample indicate a place where code has been removed to clarify
the sample.
<dsp:droplet name="/atg/commerce/catalog/ProductLookup">
<%-- *ProductLookup expects an "id" param, but because we already *have a page level "id" param, we don't need to pass it in *here. --%>
<dsp:param bean="/OriginatingRequest.requestLocale.locale" name="repositoryKey"/> <dsp:param name="elementName" value="Product"/> <dsp:oparam name="output">
. . . <dsp:valueof param="Product.displayName"/>
. . . </dsp:oparam></dsp:droplet>
ProductLookup takes an id parameter as input. It then binds the Product parameter to the product with the
id that was passed in. In this code example, we didn’t explicitly pass the id parameter to the ProductLookup
component. The page-level id parameter was already defined at the top of the page as an input parameter and
thus implicitly passed to ProductLookup. The code inside the output parameter section of ProductLookup
simply refers to the Product parameter when it needs to access the product’s property values.
The following code shows how we displayed the product’s longDescription property, or, a default value if the
product does not have a longDescription property value:
<!-- Display the Product description here: --><dsp:valueof param="Product.longDescription"> This product doesn't have a longDescription yet...</dsp:valueof>
The following code example shows how we displayed the product’s image. Note that we used the getvalueof
tag of the DSP tag library expose a scripting variable to use for the product image:
<dsp:getvalueof id="smallImageUrl" param="Product.smallImage.url" idtype="java.lang.String"> <dsp:img src="<%=smallImageUrl%>"/></dsp:getvalueof>
5 Displaying and Accessing the Product Catalog 73
Displaying Links to Products or Categories
Because there is extensive linking between products and categories in the Pioneer Cycling store, we created
a code fragment to produce hyperlinks to products or categories in ItemLink.jsp. This code fragment was
created for convenience and to make our template pages easier to read.
<%/* ------------------------------------------------------- * Display a link to the Item (Product or Category). * The link will take you to the jsp page which displays * this Item. The jsp page is fetched from the * "template.url" attribute on the Item. * If an Image is passed in, both the Image, and a text * link is displayed - clicking either the Image or the * text link brings the user to the above described jsp page. * ------------------------------------------------------- */%><dsp:importbean bean="/atg/dynamo/droplet/IsNull"/><DECLAREPARAM NAME="Item" CLASS="java.lang.Object" DESCRIPTION="A Repository Item to display a link to - typically a Product or Category"><DECLAREPARAM NAME="Image" CLASS="java.lang.String" DESCRIPTION="The optional param is used to display an image along with the link." OPTIONAL><DECLAREPARAM NAME="navAction" CLASS="java.lang.String" DESCRIPTION="How to change the navigation history. Choices are push, pop and jump. Blank is treated as push." OPTIONAL><DECLAREPARAM NAME="DisplayText" CLASS="java.lang.String" DESCRIPTION="This optional string can be passed in to display different text (than what is default). The default text to display is the Item's DisplayName" OPTIONAL><% /* Display link in bold: */ %><b><%/* ------------------------------------------------------- * Display a clickable link to the Item (Product or Category). * The link will take you to the jsp page which displays * this Item. The jsp page to go to for this Item is * specified in the "template.url" attribute on the Item. * ------------------------------------------------------- */%>
<dsp:getvalueof id="templateUrl" idtype="String" param="Item.template.url"><dsp:a page="<%=templateUrl%>"> <dsp:param name="id" param="Item.repositoryId"/> <dsp:param name="navCount" bean="/atg/commerce/catalog/CatalogNavHistory.navCount"/>
<% /* These set for breadcrumb navigation: */ %> <dsp:param name="navAction" param="navAction"/>
<dsp:getvalueof id="imageInp" param="Image">
74 5 Displaying and Accessing the Product Catalog
<core:ifNotNull value="<%=imageInp%>"> <font color=000000> <dsp:getvalueof id="imageURL" param="Image.url" idtype="java.lang.String"> <core:ifNotNull value="<%=imageURL%>"> <img border="1" src="<%=imageURL%>"> </core:ifNotNull> </dsp:getvalueof> </font> <br> </core:ifNotNull> </dsp:getvalueof>
<%-- Show DisplayText if set, otherwise show item's display name --%> <dsp:valueof param="DisplayText"><dsp:valueof param="Item.displayName"/></dsp:valueof>
</dsp:a></dsp:getvalueof>
<% /* end link in bold: */ %></b>
The ItemLink.jsp code fragment takes at least one parameter: Item. The item must be a repository item
(for example, a product or a category) that has a template.url value in the database. The value of the
template.url property is the JSP template file that should be used to display this repository item. The code
creates a hyper link to whatever JSP file is stored in the item’s template.url value.
<%/* Display a link to the Product: */%><dsp:include page="../common/ItemLink.jsp" flush="true"> <dsp:param name="Item" param="Product"/> <dsp:param name="Image" param="Product.thumbnailImage"/> <dsp:param name="DisplayText" param="Product.displayName"/> <dsp:param name="navAction" value="jump"/></dsp:include>
The above call to the ItemLink.jsp code fragment is turned into the a HTML code similar to the following after
it is rendered:
<b><a href="/PioneerCyclingJSP/en/catalog/product_generic.jsp?id=prod10023&navAction=jump&navCount=5"> <font color=000000> <img src="/MEDIA/ProductCatalog/m10027_helmets_red_sm.gif" border=1> </font> <br> Shatterproof Helmet</a></b>
Displaying Product Prices
The Pioneer Cycling site uses two code fragments to display prices on the product template pages. These
fragments can be found in the following directories:
• <ATG9dir>/PioneerCyclingJSP/j2ee-apps/pioneer/
5 Displaying and Accessing the Product Catalog 75
web-app/en/Common/DisplaySKUPrice.jsp
• <ATG9dir>/PioneerCyclingJSP/j2ee-apps/pioneer/web-app/en/DisplayProductPrice.jsp.
List Price versus User’s Price
In the Pioneer Cycling store, we made a distinction between the list price for a product and the user’s price:
• list price: the full, undiscounted price of a product.
• user’s price: the price of a product for a certain user, after all discounts and promotions for which the user
is eligible have been calculated. It does not include order-specific promotions (promotions that depend on
other items on the order).
For example, on Mother’s Day the Pioneer Cycling store offers all women 20% off. The list price of a helmet is
$100; the user’s price (for women) would be $80. The store also offers 50% off the price of a helmet with the
purchase of a bike. However, the user’s price displayed on the product template page does not show this 50%
discount, even if she is purchasing a bike. This order-level discount is displayed on the Shopping Cart page. Order-
level discounts and promotions are calculated with the pricing calculators and are discussed in the Promotions
section of the Merchandising (page 193) chapter.
SKU Price
The DisplaySkuPrice.jsp code fragment displays the price of a single SKU. The code fragment below only
addresses US currency. Code for different locales and currencies is addressed later in the manual.
<DECLAREPARAM NAME="Sku" CLASS="java.lang.Object" DESCRIPTION="A Sku repository Item - REQUIRED">
<dsp:importbean bean="/atg/commerce/pricing/PriceItem"/><dsp:importbean bean="/atg/dynamo/droplet/Compare"/>
<%/* The PriceItem droplet will fetch SKU * Price Info for this user. Any general * promotions this user is eligible for WILL * be reflected in the price. Any order * dependent promotions (ie buy one x, get * y 50% off) will NOT be reflected in the * price info returned by this droplet. */%><dsp:droplet name="/atg/commerce/pricing/PriceItem"> <dsp:param name="item" param="Sku"/> <dsp:param name="elementName" value="PricedSku"/>
<dsp:oparam name="output"> <dsp:droplet name="/atg/dynamo/droplet/Compare"> <dsp:param name="obj1" param="PricedSku.priceInfo.ListPrice"/> <dsp:param name="obj2" param="PricedSku.priceInfo.amount"/>
<%/*ListPrice is greater, so display it first in strikout mode: */%> <dsp:oparam name="greaterthan"> <span class="strikeout"> <dsp:valueof converter="currency" param="PricedSku.priceInfo.ListPrice"/> </span> </dsp:oparam> </dsp:droplet>
76 5 Displaying and Accessing the Product Catalog
<%/*Display their price: */%> <dsp:valueof converter="currency" param="PricedSku.priceInfo.amount"/> </dsp:oparam></dsp:droplet> <%/*PriceItem droplet*/%>
We used the ATG Consumer Commerce PriceItem servlet bean to calculate the price for this SKU for this
user. It is passed the SKU that needs to be priced, <dsp:param name="item" value="Sku"/>, and it
passes back the SKU in a parameter called PricedSku with user-specific prices filled into it (<dsp:param
name="elementName" value="PricedSku"/>).
The Compare servlet compares the product’s list price to the customer’s price. If the ListPrice is higher, it is
displayed first, but with a “strike-out” font so the customer can see the discount she is getting. The user’s price is
always displayed.
Inside PriceItem, the list price is displayed like this:
<dsp:valueof converter="currency" param="PricedSku.priceInfo.ListPrice"/>
The user’s price is displayed like this:
<dsp:valueof converter="currency" param="PricedSku.priceInfo.amount"/>
This is the call to the DisplaySkuPrice code fragment:
<dsp:include page="../../common/DisplaySkuPrice.jsp" flush="true"> <dsp:param name="Sku" param="Sku"/></dsp:include>
5 Displaying and Accessing the Product Catalog 77
The list price and user’s price for each SKU is displayed on the product page.
Displaying Product Price
We used the DisplayProductPrice.jsp code fragment to display the price of a product versus the price
of an individual SKU. It simply fetches the first SKU on the product and calls its DisplaySkuPrice.jsp code
fragment. If your site has products with differently priced SKUs, you need to expand this functionality.
Adding Items to the Shopping Cart from the Catalog
In the Pioneer Cycling store, customers can look at products in the catalog and add them directly to their
shopping carts or wish lists. The JSP snippet used to add items to a cart can be found in <ATG9dir>/
PioneerCyclingJSP/j2ee-apps/pioneer/web-app/en/catalog/common/AddToCart.jsp
This is the code example we used to include the code fragment in our template page. The AddToCart.jsp code
fragment takes a product repository item, so it is passed the Product parameter that was initialized inside the
ProductLookupDroplet.
<dsp:getvalueof id="pval0" param="Product"> <dsp:include page="common/AddToCart.jsp" flush="true"> <dsp:param name="product" value="<%=pval0%>"/> </dsp:include>
78 5 Displaying and Accessing the Product Catalog
</dsp:getvalueof>
This is the body of the AddToCart.jsp code fragment:
<DECLAREPARAM NAME="Product" CLASS="java.lang.Object" DESCRIPTION="A Product repository Item - REQUIRED.">
<DECLAREPARAM NAME="SkuDisplayFormat" CLASS="java.lang.String" DESCRIPTION="How the list of SKUs should be displayed. Either DropDownList or RadioButtons - default is a dropdown list">
<dsp:importbean bean="/atg/commerce/order/ShoppingCartModifier"/><dsp:importbean bean="/atg/dynamo/droplet/IsNull"/>
<%/*Create a form for user to select a SKU and add it to their cart:*/%><dsp:getvalueof id="form16" bean="/OriginatingRequest.requestURI" idtype="java.lang.String"><dsp:form action="<%=form16%>" method="post">
<%/*Store this Product's ID in the Form Handler: */%> <dsp:input bean="ShoppingCartModifier.ProductId" paramvalue="Product.repositoryId" type="hidden"/>
<%/*set id param so that the Navigator won't get messed up in case of an error that makes us return to this page.*/%> <input name="id" type="hidden" value='<dsp:valueof param="Product.repositoryId"/>'>
<table cellpadding=0 cellspacing=0 border=0> <tr> <td class=box-top-store>Add to Cart</td> </tr>
<tr> <td class=box>
<%/*Display any errors that have been generated during Cart operations: */%> <dsp:include page="../../common/DisplayShoppingCartModifierErrors.jsp" flush="true"></dsp:include>
Add
<%/*Textbox with QTY the user wants to order: */%> <dsp:input bean="ShoppingCartModifier.quantity" size="4" type="text" value="1"/>
<dsp:droplet name="/atg/dynamo/droplet/Switch"> <dsp:param name="value" param="SkuDisplayFormat"/>
<%/*------------RadioButtons format ------------------*/%> <dsp:oparam name="RadioButtons">
<%/*For each of the SKUs in this Product, add a radio button:*/%> <dsp:droplet name="/atg/dynamo/droplet/ForEach"> <dsp:param name="array" param="Product.childSKUs"/> <dsp:param name="elementName" value="Sku"/>
5 Displaying and Accessing the Product Catalog 79
<dsp:oparam name="output">
<br>
<%/*If they select this SKU, store its ID in the form handler:*/%> <dsp:input bean="ShoppingCartModifier.catalogRefIds" paramvalue="Sku.repositoryID" name="SkuGroup" type="radio"/>
<%/*Display the SKU's display name for the radio button:*/%> <dsp:valueof param="Sku.displayName"/>
</dsp:oparam> </dsp:droplet> <%/*ForEach SKU droplet*/%>
<br>
</dsp:oparam>
<%/*------------DropDownList format (default) ------------------*/%> <dsp:oparam name="default">
<%/*Create a dropdown list will all Sku in the Product. Store the selected SKU's id in the Form Handler: */%> <dsp:select bean="ShoppingCartModifier.catalogRefIds">
<%/*For each of the SKUs in this Product, add the SKU to the dropdown list:*/%> <dsp:droplet name="/atg/dynamo/droplet/ForEach"> <dsp:param name="array" param="Product.childSKUs"/> <dsp:param name="elementName" value="Sku"/> <dsp:param name="indexName" value="skuIndex"/> <dsp:oparam name="output">
<%/*This is the ID to store, if this SKU is selected in dropdown:*/%> <dsp:getvalueof id="option136" param="Sku.repositoryID" idtype="java.lang.String"><dsp:option value="<%=option136%>"/></dsp:getvalueof>
<%/*Display the SKU's display name in the dropdown list:*/%> <dsp:valueof param="Sku.displayName"/>
</dsp:oparam> </dsp:droplet> <%/*ForEach SKU droplet*/%>
</dsp:select> <br>
</dsp:oparam> </dsp:droplet> <%/*Switch on SkuDisplayFormat*/%>
<%/* URL to go to if user's session expires while he is filling out this form */%> <dsp:input bean="ShoppingCartModifier.sessionExpirationURL" type="hidden" value="../../common/SessionExpired.jsp"/>
<%/* ADD TO CART BUTTON: Adds this SKU to the Order*/%> <dsp:input bean="ShoppingCartModifier.addItemToOrder" type="submit" value=" Add to Cart "/>
80 5 Displaying and Accessing the Product Catalog
<%/* Goto this URL if NO errors are found during the ADD TO CART button processing:*/%> <dsp:input bean="ShoppingCartModifier.addItemToOrderSuccessURL" type="hidden" value="../checkout/cart.jsp"/>
</td> </tr> </table></dsp:form></dsp:getvalueof>
As with other code fragments, the code takes a Product parameter:
<DECLAREPARAM NAME="Product" CLASS="java.lang.Object" DESCRIPTION="A Product repository Item - REQUIRED.">
The AddToCart.jsp code also takes an optional parameter that indicates how the list of child SKUs should
be displayed. The code can display the SKUs in a dropdown list or as a list of option buttons. The default is
DropDownList.
ShoppingCartModifier form handler
The AddToCart code uses the ShoppingCartModifier component to add items to the shopping cart. This
ATG Consumer Commerce form handler (or a subclass you create) has many methods and properties that
perform most of the tasks required for manipulating a shopping cart. We imported it at the top of the page to
access its properties and methods. Here is the code that includes the form handler on our template page:
<dsp:importbean bean="/atg/commerce/order/ShoppingCartModifier"/>
In order to add a SKU to the user’s shopping cart we needed to pass the ShoppingCartModifier
the repositoryId of the product and the repositoryId of the SKU. These values are passed to the
ShoppingCartModifier when the user pushes the Add To Cart button.
The ProductId is stored in the ShopingCartModifier form handler via a hidden text box, a common
technique for setting properties on a form handler. Here is the code to do that:
<dsp:input bean="ShoppingCartModifier.ProductId" paramvalue="Product.repositoryId" type="hidden"/>
Creating a Dropdown List of All Child SKUs
The repositoryId of the SKU the user wants to add is stored in the form handler’s catalogRefIds property.
The form handler is passed the IDs of the SKUs the user selects from a dropdown list. HTML allows the creation
of a dropdown list using the <select ... > tag. Within this tag is a list of zero or more <option> tags for each
item in the dropdown list.
In the following code, we used the ForEach component to cycle through all of the child SKUs of the product
and to create a dropdown list selection for each one. If there are five SKUs for a product, it creates five items in
the dropdown list. When the user selects one of these SKUs in the dropdown list, the repositoryID of that SKU
is stored in the catalogRefIds property of the ShoppingCartModifier. Here is the code:
<%/*Create a dropdown list will all Sku in the Product.
5 Displaying and Accessing the Product Catalog 81
Store the selected SKU's id in the Form Handler: */%> <dsp:select bean="ShoppingCartModifier.catalogRefIds">
<%/*For each of the SKUs in this Product, add the SKU to the dropdown list:*/%> <dsp:droplet name="/atg/dynamo/droplet/ForEach"> <dsp:param name="array" param="Product.childSKUs"/> <dsp:param name="elementName" value="Sku"/> <dsp:param name="indexName" value="skuIndex"/> <dsp:oparam name="output">
<%/*This is the ID to store, if this SKU is selected in dropdown:*/%> <dsp:getvalueof id="option136" param="Sku.repositoryID" idtype="java.lang.String"> <dsp:option value="<%=option136%>"/> </dsp:getvalueof>
<%/*Display the SKU's display name in the dropdown list:*/%> <dsp:valueof param="Sku.displayName"/>
</dsp:oparam> </dsp:droplet> <%/*ForEach SKU droplet*/%>
</dsp:select>
After page compilation, the above code is converted into HTML that looks something like this:
<select name="/atg/commerce/order/ShoppingCartModifier.catalogRefIds"> <option value="sku20014">1.5 Liter Bottle Red <option value="sku20015">1.5 Liter Bottle Blue</select>
For example, if the user chooses “1.5 Liter Bottle Red,” the value “sku20014” (the selected SKU’s repository ID) is
in the ShoppingCartModifier.catalogRefIds property.
Add to Cart button
When the Add To Cart button is pushed, the handleAddItemToOrder method on the
ShoppingCartModifier form handler is called. This method does all of the work necessary for adding this item
to the order. Here is the code for the Add To Cart button:
<%/* ADD TO CART BUTTON: Adds this SKU to the Order*/%><dsp:input bean="ShoppingCartModifier.addItemToOrder" type="submit" value=" Add to Cart "/>
If the addItemToOrder operation succeeds or fails, you may want to redirect the
customer to another page. The ShoppingCartModifier has two properties that can be
set on a page to indicate where the user should be redirected if the addItemToOrder
operation succeeds or fails: ShoppingCartModifier.addItemToOrderSuccessURL and
ShoppingCartModifier.addItemToOrderErrorURL
Here is an example of how we set the SuccessURL property using a hidden text box.
<%/* Goto this URL if NO errors are found during the ADD TO CART button processing:*/%><dsp:input bean="ShoppingCartModifier.addItemToOrderSuccessURL" type="hidden"
82 5 Displaying and Accessing the Product Catalog
value="../checkout/cart.jsp"/>
Displaying Form Errors
If any errors are found during method calls on ShoppingCartModifier, the form handler stores all of these
errors. They can be displayed with the following code fragment:
<dsp:include page="../../common/DisplayShoppingCartModifierErrors.jsp" flush="true"></dsp:include>
The ShoppingCartModifier is described in detail in the Order Processing (page 135) chapter.
Displaying Related Products
The product template pages in the Pioneer Cycling store display related products. The relatedProducts
property on a product returns a list of zero or more products that are categorized as being related to this
product. For a given product, we wanted the template page to display these related products as image
links to the products. We needed this functionality in several places, so we created a code fragment called
RelatedProducts.jsp to display this information. Here is the code fragment:
<dsp:importbean bean="/atg/dynamo/droplet/IsNull"/><p> <!-- Display a table of related products. --> <dsp:droplet name="/atg/dynamo/droplet/TableForEach"> <dsp:param name="array" param="Product.relatedProducts"/> <dsp:param name="elementName" value="relatedProduct"/> <dsp:param name="numColumns" value="2"/>
<dsp:oparam name="outputStart">
<!-- Display this header only if there are some related products --> <b>Related products</b><P>
<table cellspacing=4 cellpadding=0 border=0> </dsp:oparam>
<dsp:oparam name="outputRowStart"> <tr valign=top> </dsp:oparam>
<dsp:oparam name="output"> <td> <dsp:droplet name="IsNull"> <dsp:param name="value" param="relatedProduct"/> <dsp:oparam name="false"> <dsp:include page="../common/ItemLink.jsp" flush="true"> <dsp:param name="Item" param="relatedProduct"/> <dsp:param name="Image" param="relatedProduct.thumbnailImage"/> <dsp:param name="navAction" value="jump"/> <dsp:param name="DisplayText" param="relatedProduct.displayName"/> </dsp:include> <dsp:include page="../common/DisplayProductPrice.jsp" flush="true"> <dsp:param name="Product" param="relatedProduct"/>
5 Displaying and Accessing the Product Catalog 83
</dsp:include>
<%/* Create an add to cart button to add 1 of sku #1 to the cart */%> <dsp:importbean bean="/atg/commerce/order/ShoppingCartModifier"/> <dsp:getvalueof id="form67" bean="/OriginatingRequest.requestURI" idtype="java.lang.String"> <dsp:form action="<%=form67%>" method="post"> <dsp:input bean="ShoppingCartModifier.ProductId" paramvalue="relatedProduct.repositoryId" type="hidden"/> <input name="id" type="hidden" value='<dsp:valueof param="relatedProduct.repositoryId"/>'> <dsp:input bean="ShoppingCartModifier.quantity" type="hidden" value="1"/> <dsp:input bean="ShoppingCartModifier.catalogRefIds" paramvalue="relatedProduct.childSKUs[0].repositoryID" type="hidden"/> <dsp:input bean="ShoppingCartModifier.addItemToOrderSuccessURL" type="hidden" value="../checkout/cart.jsp"/> <dsp:input bean="ShoppingCartModifier.addItemToOrder" type="submit" value="Get It"/> </dsp:form></dsp:getvalueof>
</dsp:oparam> </dsp:droplet> </td> </dsp:oparam>
<dsp:oparam name="outputRowsEnd"> </tr> </dsp:oparam>
<dsp:oparam name="outputEnd"> </table> </dsp:oparam>
<dsp:oparam name="empty"> </dsp:oparam>
</dsp:droplet>
84 5 Displaying and Accessing the Product Catalog
Related Products
We wanted to display the related products in a table, so we used the TableForEach component.
It displays the data in two columns (<dsp:param name="numColumns" value="2"/>). Each
of the products is bound to the parameter relatedProduct as the droplet cycles through the
TableForEach (<dsp:param name="elementName" value="relatedProduct"/>). Because each
relatedProduct is of type product, product properties are accessible (<dsp:param name="Image"
param="relatedProduct.thumbnailImage"/>). In the “output” parameter section of the TableForEach,
we used ItemLink.jsp (described earlier) to display a link (with image) to the related product. Note that
nothing is hard-coded on this page; everything is data driven.
<dsp:include page="../common/ItemLink.jsp" flush="true"> <dsp:param name="Item" param="relatedProduct"/> <dsp:param name="Image" param="relatedProduct.thumbnailImage"/> <dsp:param name="navAction" value="jump"/> <dsp:param name="DisplayText" param="relatedProduct.displayName"/></dsp:include>
Displaying Inventory Information
For each SKU of the product on the template page, we wanted to display some inventory information: SKU
display name, price, and inventory level.
5 Displaying and Accessing the Product Catalog 85
Inventory Information
We needed this functionality on all of our template pages so we put the code into a separate code fragment
called DisplaySkuInfo.jsp.
Here is an example of how this code fragment is included from the template page. The code fragment is passed
the Product parameter whose SKUs need to be displayed:
<dsp:getvalueof id="pval0" param="Product"><dsp:include page="common/DisplaySkuInfo.jsp" flush="true"><dsp:param name="Product" value="<%=pval0%>"/></dsp:include></dsp:getvalueof>
This is the actual code for the DisplaySkuInfo.jsp code fragment:
<DECLAREPARAM NAME="Product" CLASS="java.lang.Object" DESCRIPTION="A Product repository Item - REQUIRED.">
<dsp:importbean bean="/atg/commerce/pricing/PriceItem"/>
86 5 Displaying and Accessing the Product Catalog
<dsp:importbean bean="/atg/commerce/inventory/InventoryLookup"/>
<dsp:droplet name="/atg/dynamo/droplet/ForEach"> <dsp:param name="array" param="Product.childSKUs"/> <dsp:param name="elementName" value="sku"/>
<dsp:oparam name="outputStart"> <table cellspacing=4 cellpadding=0 border=0> <tr> <td> <b>Models available</b> </td> <td> Price </td> <td> </td> <td> Inventory </td> </tr> <tr><td colspan=4><hr size=0></td></tr> </dsp:oparam>
<dsp:oparam name="outputEnd"> </table> </dsp:oparam>
<dsp:oparam name="output">
<dsp:droplet name="InventoryLookup"> <dsp:param name="itemId" param="sku.repositoryId"/> <dsp:param name="useCache" value="true"/> <dsp:oparam name="output"> <tr valign=top> <td><dsp:valueof param="sku.displayName"/></td> <td> <dsp:getvalueof id="pval0" param="sku"> <dsp:include page="../../common/DisplaySkuPrice.jsp" flush="true"> <dsp:param name="Sku" value="<%=pval0%>"/> </dsp:include></dsp:getvalueof><br> </td> <td></td> <td> <dsp:valueof param="inventoryInfo.availabilityStatusMsg">Unknown </dsp:valueof></td> </tr> </dsp:oparam> </dsp:droplet> </dsp:oparam></dsp:droplet> <%/*ForEach sku droplet*/%>
To display information about each of the child SKUs of a product, we used the ForEach component. ForEach is
passed an array that is the list of SKUs associated with the product (the childSkus property value). As the code
cycles through each of the SKUs in the product, it binds the Sku parameter to the current SKU. Here is the start
of this code section:
<dsp:droplet name="/atg/dynamo/droplet/ForEach"> <dsp:param name="array" param="Product.childSKUs"/> <dsp:param name="elementName" value="sku"/>. . .
5 Displaying and Accessing the Product Catalog 87
The Sku parameter that was bound in the ForEach component is used to access property values on the SKU.
Any information to be displayed on the page is put in the output parameter sections of ForEach (for example,
output, outputStart, or outputEnd). To display the SKU’s displayName attribute, we did the following:
<dsp:droplet name="/atg/dynamo/droplet/ForEach"> <dsp:param name="array" param="Product.childSKUs"/> <dsp:param name="elementName" value="sku"/>. . . <dsp:oparam name="output">. . . <dsp:valueof param="sku.displayName"/>. . . </dsp:oparam></dsp:droplet>
We used the DisplaySkuPrice.jsp code fragment to display the SKU’s price.
<dsp:getvalueof id=""pval10" param="sku"><dsp:include page="../../common/DisplaySkuPrice.jsp" flush="true"> <dsp:param name="Sku" value="<%=pval0%>"/></dsp:include></dsp:getvalueof>
We used the InventoryLookup component (which is explained in more detail later) to display the SKU’s
inventory level:
<dsp:droplet name="InventoryLookup"> <dsp:param name="itemId" param="sku.repositoryId"/> <dsp:param name="useCache" value="true"/> <dsp:oparam name="output"> <dsp:valueof param="sku.displayName"/> <dsp:getvalueof id="pval0" param="sku"> <dsp:include page="../../common/DisplaySkuPrice.jsp" flush="true"> <dsp:param name="Sku" value="<%=pval0%>"/> </dsp:include> </dsp:getvalueof><br> <dsp:valueof param="inventoryInfo.availabilityStatusMsg">Unknown </dsp:valueof> </dsp:oparam></dsp:droplet>
Adding Compare Functionality
We included a Compare with Other Products button on the bike template pages that allows customers to
compare two bikes side by side.
Adding Historical Navigation
The top of each product template page includes historical navigation information that makes it easy for users to
jump back to previously viewed pages.
88 5 Displaying and Accessing the Product Catalog
These features are covered in detail below in another section so we can continue to focus on the process of
creating template pages.
Category Template Pages
Just as template pages are created to display products, template pages are also created to display categories.
The Pioneer Cycling store has six different category template pages. These templates can be found in the
<ATG9dir>/PioneerCyclingJSP/j2ee-apps/pioneer/web-app/en/catalog/ directory. The templates
are:
• category_clothing.jsp
• category_parts.jsp
• category_bundle.jsp
• category_bikes.jsp
• category_all_bikes.jsp
• category_generic.jsp
5 Displaying and Accessing the Product Catalog 89
category_generic.jsp
The category_generic.jsp template page displays products of type Accessory. The following example is
the code for this JSP fragment:
<% /*This template displays all subcategories or products of any categoryin a four column format.*/ %>
<DECLAREPARAM NAME="id" CLASS="java.lang.String" DESCRIPTION="The id of the category to display"><DECLAREPARAM NAME="navAction" CLASS="java.lang.String" DESCRIPTION="How to change the navigation history. Choices are push, pop and jump. Blank is treated as push.">
<dsp:setvalue bean="/atg/userprofiling/Profile.currentLocation" value="catalog_category"/>
90 5 Displaying and Accessing the Product Catalog
<dsp:droplet name="/atg/commerce/catalog/CategoryLookup"> <dsp:param bean="/OriginatingRequest.requestLocale.locale" name="repositoryKey"/> <dsp:oparam name="output">
<%/* Send a Views Item event that we browsed this category */%> <dsp:droplet name="/atg/commerce/catalog/CategoryBrowsed"> <dsp:param name="eventobject" param="element"/> </dsp:droplet>
<dsp:include page="../common/HeadBody.jsp" flush="true"> <dsp:param name="pagetitle" param="element.displayName"/> </dsp:include>
<dsp:include page="../common/StoreBrand.jsp" flush="true"></dsp:include>
<span class=storelittle> <dsp:include page="common/breadcrumbs.jsp" flush="true"><dsp:param name="displaybreadcrumbs" value="true"/></dsp:include> </span> <p>
<!-- 2 column table --> <table cellspacing=0 cellpadding=0 border=0>
<!-- 1st column: --> <tr valign=top> <td> <dsp:img src="../images/cat7.jpg"/> </td> <td> </td> <td> <!-- Show promotions --> <dsp:include page="../common/DisplayMediaSlot.jsp" flush="true"><dsp:param name="NumToDisplay" value="1"/></dsp:include> <p> <!-- Search --> <dsp:include page="../search/SimpleSearchFragment.jsp" flush="true"><dsp:param name="FormAction" value="SimpleSearch.jsp"/></dsp:include> </td> </tr> </table>
<span class=storebig> Browse <dsp:valueof param="element.displayName"/> </span> <p> <% /* Note that we are displaying both child categories and products here. In our catalog setup, any category that uses this template will have only childProducts or childCategories but not both. Therefore we can DisplayChildren of both kinds like this and one will be empty and thus render invisible. */ %>
<dsp:include page="DisplayChildrenGeneric.jsp" flush="true"> <dsp:param name="ChildArray" param="element.childCategories"/>
5 Displaying and Accessing the Product Catalog 91
<dsp:param name="Columns" value="4"/> </dsp:include>
<dsp:include page="DisplayChildrenGeneric.jsp" flush="true"> <dsp:param name="ChildArray" param="element.childProducts"/> <dsp:param name="Columns" value="4"/> </dsp:include>
<p>
<dsp:include page="../common/CatalogFooter.jsp" flush="true"></dsp:include> <dsp:include page="../common/Copyright.jsp" flush="true"></dsp:include>
</dsp:oparam></dsp:droplet>
JSP Fragment Parts
CurrentLocation
The user profile has a property called currentLocation that may be used by complex targeting systems to
enable the targeter to know what kind of targeting information is available. It is an enumerated property type
whose choices are unknown, home, store_home, shopping_cart, catalog_category, catalog_product,
catalog_search, checkout, and profile. Because this is a category page, we set the property like this:
<dsp:setvalue bean="/atg/userprofiling/Profile.currentLocation" value="catalog_category"/>
HTML Header and Title
The HeadBody.jsp fragment is invoked on every page in the site to render the HTML header and the opening
<BODY> tag. This is a good example of separating fragments that are used on every page into reusable droplets.
This fragment takes a pagetitle parameter and uses it as the HTML <TITLE>. In passing this parameter
to HeadBody.jsp we used the single quotation mark ( ' ) syntax to evaluate '"Pioneer Cycling - "
+request.getParameter("element.displayName")' as a Java statement. When '"Pioneer Cycling
- " +request.getParameter("element.displayName")' is compiled, executed, and rendered on the
browser, the user sees “Pioneer Cycling – Accessories.”
CategoryLookup Component
We used the CategoryLookup component to get a Category object for a particular category ID. The id
parameter required by CategoryLookup is passed to this page as a URL parameter, so it doesn’t have to
be passed again here. The user’s locale is passed as the repositoryKey parameter so that the category is
generated in the proper language. (See the section on Adding Support for Multi Locales for more information).
In response to these parameters, CategoryLookup binds the element parameter to the Category repository
item that it found. The element parameter is in scope between the <dsp:oparam name="output"> and </
dsp:oparam> tags.
CategoryBrowsed Droplet
We used the CategoryBrowsed component to generate an ItemViewed message for this category. The
message can have any number of listeners. The listener for the ItemViewed event is a ATG Consumer Commerce
92 5 Displaying and Accessing the Product Catalog
scenario that logs the message to a data set that can later produce reports on the categories viewed, how often
they were viewed, and by which users. Pioneer Cycling has an additional listener that is a scenario that records
the category in the user’s categoriesViewed property.
Product display pages use the ProductBrowsed component, which is analogous to CategoryBrowsed.
Navigation History
We used breadcrumbs.jsp to collect and display navigation history information. It is also invoked by product
pages. For more information, please refer to the section on Adding Catalog Navigation (page 95).
Displaying Child Products and Categories
Repository items of type Category have the properties childProducts and childCategories. These
properties describe the hierarchical relationships between categories and between categories and products.
DisplayChildrenGeneric.jsp is invoked to display the products and categories in the hierarchy contained
by the current category. It displays an array of repository items in a table. The parameter is supplied to specify
the number of columns in the table and the array to display.
Displaying Promotions
We used DisplayMediaSlot.jsp to display promotions. A “slot” is an ATG Relationship Management Platform
term for a generic place to hold objects. Slots can be populated in many ways but the most common way is by
using a scenario. DisplayMediaSlot.jsp can display any number of objects from the slot. In this page, it is
invoked with the numToDisplay parameter set to “1”.
Search
We used SimpleSearchFragment.jsp to display the Search form on each catalog page. There are two kinds
of searching in Pioneer Cycling: simple search and advanced search. The formAction parameter indicates
which search method to use.
Using the Cache Component to Improve Performance of Java Server Pages
The Cache component allows ATG products to serve content straight from an in-memory cache rather than
recomputing the content each time. When used properly, the Cache component can help the performance of
your site.
We chose to use the Cache component on the page that displays the whole bike categories,
category_all_bikes.jsp. We recommend that you use the Cache component on a small number of popular
pages. The pages you choose should have content that is not personalized, or, if it is personalized, it should not
have many options. Most of the content on cateogry_all_bikes.jsp is the same for every person who looks
at it.
Cache was used for the portion of the page that shows the title “Browse Whole Bikes” and the listing of the
categories of bikes, with a link for each bike, a picture, and a description.
5 Displaying and Accessing the Product Catalog 93
If ATG is rewriting URLs then the Cache component must disable itself unless it is told that there are no URLs
in the content being cached. In this example, the Cache component is used on those sections of the page
that include no URLs. We also used the key parameter because there are many possible values for the output
parameter of this invocation of the Cache component. The following version is actually included in Pioneer
Cycling.
Note: The “. . .” markers in the following code sample indicate a place where code has been removed to clarify
the sample.
<span class=storebig> Browse <dsp:valueof param="element.displayName"/></span>
. . .
<dsp:droplet name="ForEach"> <dsp:param name="array" param="element.childCategories"/> <dsp:param name="elementName" value="childCategory"/> <dsp:param name="indexName" value="CategoryIndex"/>
94 5 Displaying and Accessing the Product Catalog
<dsp:oparam name="outputStart"> <table cellspacing=5 cellpadding=0 border=0> </dsp:oparam>
<dsp:oparam name="outputEnd"> </table> </dsp:oparam>
<dsp:oparam name="output">
<tr valign=top> <td> <dsp:droplet name="/atg/dynamo/droplet/IsNull"> <dsp:param name="value" param="childCategory.thumbnailImage.url"/> <dsp:oparam name="false"> <dsp:include page="../common/ItemLink.jsp" flush="true"> <dsp:param name="Item" param="childCategory"/> <dsp:param name="Image" param="childCategory.thumbnailImage"/> <dsp:param name="DisplayText" value=""/> <dsp:param name="navAction" value="push"/> </dsp:include> </dsp:oparam> </dsp:droplet>
</td> <td></td>
<td> <!-- Display a link to the page which displays the Category: --> <dsp:include page="../common/ItemLink.jsp" flush="true"> <dsp:param name="Item" param="childCategory"/> <dsp:param name="navAction" value="push"/> </dsp:include><br>
<dsp:droplet name="/atg/dynamo/droplet/Cache"> <dsp:param name="hasNoURLs" value="true"/> <dsp:param name="key" param="childCategory.repositoryId"/> <dsp:oparam name="output"> <dsp:valueof param="childCategory.longDescription"> No description.</dsp:valueof> </dsp:oparam> </dsp:droplet> </td>
</tr>
</dsp:oparam>
</dsp:droplet>
5 Displaying and Accessing the Product Catalog 95
Adding Catalog Navigation
Hierarchical Navigation
To make it easy to browse the product catalog in the Pioneer Cycling site, we organized the product catalog into
a hierarchy of categories, with several levels of subcategories and products. To impose this hierarchy, we defined
properties of category called childProducts and childCategories that are lists of categories or products as
appropriate. There is no limit to the number of places that a single item (product or category) can appear as a
child.
Navigation down the hierarchy in any category can be shown in a Java Server Page by rendering a link to each of
the childProducts and childCategories. There are many ways to use the hierarchy to display the content in
the catalog. This manual describes one way to navigate.
On the parts category page of Pioneer Cycling that shows bike parts, the left panel contains a display of all
the categories of parts and their subcategories, using an iterative approach. This implementation requires
knowing the depth of the tree, or at least knowing how deep you want to traverse. We used the JSP snippet
below, CategoryTreeTwoLevel.jsp, to generate the content of the panel. It retrieves the parts category
with the CategoryLookup component. The itemId parameter required by CategoryLookup is supplied by
the Java Server Page that invokes this fragment. Then, a ForEach component renders a link to each of the
96 5 Displaying and Accessing the Product Catalog
childCategories of parts. For each child category of parts, a nested ForEach component renders a link to
each of its child categories.
<dsp:droplet name="/atg/commerce/catalog/CategoryLookup"><dsp:param bean="/atg/dynamo/servlet/RequestLocale.locale" name="repositoryKey"/>
<dsp:oparam name="output">
<dsp:droplet name="/atg/dynamo/droplet/ForEach"> <dsp:param name="array" param="element.childCategories"/> <dsp:param name="elementName" value="child"/> <dsp:param bean="/OriginatingRequest.requestLocale.locale" name="repositoryKey"/>
<dsp:oparam name="output"> <dsp:include page="../common/ItemLink.jsp" flush="true"> <dsp:param name="Item" param="child"/> <dsp:param name="navAction" value="jump"/> <dsp:param name="Image" param="child.thumbnailImage"/> <dsp:param name="DisplayText" param="child.displayName"/> </dsp:include> <br>
<dsp:droplet name="/atg/dynamo/droplet/ForEach"> <dsp:param name="array" param="child.childCategories"/> <dsp:param name="elementName" value="grandChild"/>
<dsp:oparam name="output"> <dsp:include page="../common/ItemLink.jsp" flush="true"> <dsp:param name="Item" param="grandChild"/> <dsp:param name="navAction" value="jump"/> <dsp:param name="Image" param="grandChild.thumbnailImage"/> <dsp:param name="DisplayText" param="grandChild.displayName"/> </dsp:include>
<br> </dsp:oparam> </dsp:droplet>
</dsp:oparam> </dsp:droplet>
</dsp:oparam></dsp:droplet>
You could also use a recursive algorithm to display the whole tree. The JSP code sample below retrieves the
category on the top-most level with the CategoryLookup component and invokes itself to recurse through
the child categories. A category ID is passed in as the ID parameter in the URL. The major drawback of this
implementation is that if your tree has any circular references (for example, a category containing an ancestor
category as a child category), it will recurse forever.
<dsp:importbean bean="/atg/dynamo/droplet/ForEach"/><dsp:importbean bean="/atg/commerce/catalog/CategoryLookup"/><!-- display all products under this category and subcategories.
5 Displaying and Accessing the Product Catalog 97
-- the id parameter specifies the repository id of the top-most -- category. --><dsp:droplet name="CategoryLookup"> <dsp:param bean="/OriginatingRequest.requestLocale.locale" name="repositoryKey"/> <dsp:param name="id" param="id"/> <dsp:oparam name="output"> <!-- display a link to each product in a category --> <!-- the param element is a Category object passed in to this droplet --> <dsp:droplet name="ForEach"> <dsp:param name="array" param="element.childProducts"/> <dsp:oparam name="output"> <dsp:getvalueof id="templateUrl" param="element.template.url" idtype="java.lang.String"> <dsp:a href="<%=templateUrl%>"><dsp:valueof param="element.displayName"/></dsp:a> </dsp:getvalueof><br> </dsp:oparam> </dsp:droplet> <!-- recursively call this droplet to display products for all childCategories --> <dsp:droplet name="ForEach"> <dsp:param name="array" param="element.childCategories"/> <dsp:oparam name="output"> <dsp:getvalueof id="repId" param="element.repositoryId"> <dsp:include page="test.jsp" flush="true"> <dsp:param name="id" value="<%=repId%>"/> </dsp:include> </dsp:getvalueof> </dsp:oparam> </dsp:droplet> </dsp:oparam></dsp:droplet>
Historical Navigation
The top of the template pages displays historical navigation information by listing the hierarchy of categories
that a customer has already visited on his or her way to the current page.
For example, if a customer navigates from the top of the Pioneer Cycling catalog to the Clothing page, to the
Men’s Jersey’s page, and then to the product page for a men’s Black Arrow Jersey, the navigation history (at the
top of the page) looks like this:
98 5 Displaying and Accessing the Product Catalog
Each of the items in the hierarchical navigation is a link. For example, if the user clicks on Clothing she is
brought to the category template page for clothing.
If a customer goes directly from the home page to the product page of the Black Arrow Jersey (for example,
if it was a featured product on the home page) the historical navigation also reflects the catalog’s hierarchical
structure. We wanted the navigation path to be intuitive to the customer and therefore, the historical navigation
list reflects the category pages so that the user can have a sense of how this item fits into the catalog. To do this,
we invoke the CatalogNavHistoryCollector component with the navAction parameter set to “jump”.
There are two parts to using historical navigation: collecting the information and displaying it.
Collecting the Navigation History
We used the component /atg/commerce/catalog/CatalogNavHistoryCollector to collect the locations
visited and to add them to the array of visited locations. In Pioneer Cycling, the visited locations are the
repository items that represent the products and categories of the product catalog. This snippet, taken from
breadcrumbs.jsp, invokes the CatalogNavHistoryCollector:
<dsp:droplet name="/atg/dynamo/droplet/Switch"> <dsp:param name="value" param="no_new_crumb"/>
5 Displaying and Accessing the Product Catalog 99
<dsp:oparam name="true"> </dsp:oparam> <dsp:oparam name="default"> <dsp:droplet name="CatalogNavHistoryCollector"> <dsp:param name="navAction" param="navAction"/> <dsp:param name="item" param="element"/> </dsp:droplet> </dsp:oparam></dsp:droplet>
There are two things to note in this JSP snippet. Although both these techniques are specific to the Pioneer
Cycling implementation, they can be applied to any site. First, notice that the required parameter navCount is
not passed to CatalogNavHistoryCollector. The navCount parameter is set in the Java Server Page that
invokes this JSP snippet. Since JSP files invoked as servlets from other JSP files are in a sub-scope of their callers,
they have access to the same parameters as the caller. Second, we used the no_new_crumb parameter to decide
whether to invoke the snippet. This is just a switch on a parameter passed to the page to determine whether
to add the current location to the NavHistory or not. However, it demonstrates how we decided to address
navigation for pages that do not represent catalog items. For example, the search page, the shopping cart, and
the user profile page are not added to the NavHistory like regular catalog pages.
Displaying the Navigation History
The property /atg/commerce/catalog/CatalogNavHistory.navHistory is a LinkedList of locations.
The CatalogNavHistoryCollector populates this list as described in the preceding section. The following
snippet demonstrates how the navigation history is displayed in Pioneer Cycling. A ForEach component
iterates over the NavHistory list and a link is created for each item in the list. Comments in the JSP describe the
variations from that behavior.
<dsp:droplet name="/atg/dynamo/droplet/Switch"> <dsp:param name="value" param="displaybreadcrumbs"/> <dsp:oparam name="true">
<dsp:droplet name="/atg/dynamo/droplet/ForEach"> <dsp:param bean="CatalogNavHistory.navHistory" name="array"/> <dsp:param name="elementName" value="crumb"/> <dsp:oparam name="output">
<dsp:droplet name="/atg/dynamo/droplet/Switch"> <% /* ------------------------------------------------- * We want to put a separator between the items in the navHistory. In * this example we put a greater than sign between them. We use a * switch droplet to identify the first item in the array because we * don't want to render a separator before the first item. * ------------------------------------------------- */ %> <dsp:param name="value" param="count"/> <dsp:oparam name="1"> </dsp:oparam>
<dsp:oparam name="default"> > </dsp:oparam>
</dsp:droplet>
<dsp:getvalueof id="countStr" param="count" idtype="Integer">
100 5 Displaying and Accessing the Product Catalog
<dsp:getvalueof id="catNavCount" bean="/atg/commerce/catalog/CatalogNavHistory.navCount"> <dsp:getvalueof id="templateUrl" idtype="String" param="crumb.template.url">
<dsp:droplet name="/atg/dynamo/droplet/Switch"> <% /* ------------------------------------------------- * Use a switch droplet to compare size to count. When * they are the same, then we are on the last item in * array iterated by the ForEach. * ------------------------------------------------- */ %> <dsp:param name="value" param="size"/> <dsp:oparam name="<%=countStr.toString()%>"> <dsp:droplet name="/atg/dynamo/droplet/Switch"> <% /* ------------------------------------------------- * The last item in the list is generally the item we are * currently visiting and should therefore not be a link. * In some cases, when we do not want to add a new breadcrumb, * we want the last item to be a link. We do this on the * shopping cart page, search page, and others. This is * indicated by the "no_new_crumb" parameter. * ------------------------------------------------- */ %> <dsp:param name="value" param="no_new_crumb"/> <dsp:oparam name="true"> <dsp:a page="<%=templateUrl%>"> <dsp:valueof param="crumb.displayName"/> <%-- These set for breadcrumb navigation: --%> <dsp:param name="navAction" value="pop"/> <dsp:param name="id" param="crumb.repositoryId"/> <dsp:param name="navCount" value="<%=catNavCount%>"/> </dsp:a> </dsp:oparam> <dsp:oparam name="default"> <dsp:valueof param="crumb.displayName"/> </dsp:oparam> </dsp:droplet> </dsp:oparam> <dsp:oparam name="default"> <dsp:a page="<%=templateUrl%>"> <dsp:valueof param="crumb.displayName"/> <% /* These set for breadcrumb navigation: */ %> <dsp:param name="navAction" value="pop"/> <dsp:param name="id" param="crumb.repositoryId"/> <dsp:param name="navCount" value="<%=catNavCount%>"/> </dsp:a> </dsp:oparam> </dsp:droplet> </dsp:getvalueof> </dsp:getvalueof> </dsp:getvalueof>
</dsp:oparam> </dsp:droplet>
</dsp:oparam>
5 Displaying and Accessing the Product Catalog 101
</dsp:droplet> <%/* end ForEach */%>
Searching and Filtering the Catalog
Product Searching
Searching allows customers to find certain products that satisfy a set of criteria. In the Pioneer Cycling store, we
implemented two types of searching—simple and advanced. Simple searches allow a customer to search the
catalog for keywords. In an advanced search, a customer can also search using additional product attributes
such as manufacturer, weight range, or part number.
Simple Search
In a simple search, customers search the catalog by entering text. For example, a customer could search
for “shatter-proof helmets.” The customer’s input is searched as a complete string (for example, the
above query would be based on “shatter-proof helmets” and not “shatter-proof” or “helmets”). We used
SearchFormHandler to build up the query to apply to the SQL Repository (Product Catalog) and to return
a result set (empty if no products are found). The result set(s) is a collection of repository items that can be
displayed in any format. Simple searches return both products and categories.
102 5 Displaying and Accessing the Product Catalog
We used properties or configuration files to define the action a page performs and Boolean arguments to
specify the type of searching to perform. Property names specify which catalog properties to search. The single
configurable form handler simplifies catalog searching and should support all searching requirements. If your
store requires custom searching beyond configuration changes, you can easily extend this form handler or write
another handler.
We implemented the simple search feature as a collection of Java Server Pages, page fragments, and a
Nucleus component configuration file (properties file). The configuration file creates and configures a Nucleus
component of type atg.commerce.catalog.SearchFormHandler to perform the search. The simple search
functionality allows the user to enter a string and get products or categories that match the keywords as results.
Products have multiple properties such as keywords, description, longDescription, and displayName.
Keywords are a one-to-many property and the others are one-to-one properties. A keyword search looks
through all keyword properties for a string input. A text search looks through all text properties for a string
input, but does not search keyword properties.
SimpleSearchFragment.jsp
We wanted customers to be able to search from other store pages besides the search page,
SimpleSearch.jsp. Therefore, we created the small search form fragment that defines the search text entry
field and the Search button to embed in any page that requires searching.
5 Displaying and Accessing the Product Catalog 103
This fragment imports the CatalogSearch component in order to configure its searchInput property and
invoke its seach handler.
Finally, the form redirects to the page name passed via the FormAction parameter. This page fragment declares
a page parameter that is used as the action for the search form. In other words, the FormAction parameter is
the file name of the page to which the user is redirected when the search form’s submit method is invoked.
The following example shows SimpleSearchFragment.jsp:
<!--This page fragment is intended to be imbedded on any page thatrequires a search capability. The FormAction parameter is the nameof the page to redirect to display the results of the search.-->
<DECLAREPARAM NAME="FormAction" CLASS="java.lang.String" DESCRIPTION="The name of the that will be the action of this form">
<dsp:importbean bean="/atg/userprofiling/Profile"/><dsp:importbean bean="/atg/commerce/catalog/CatalogSearch"/><dsp:importbean bean="/atg/commerce/catalog/CatalogNavHistory"/>
<!-- Title: Simple Search Form Fragment -->
<dsp:getvalueof id="successUrl" param="FormAction" idtype="java.lang.String"><dsp:form action="<%=successUrl%>" method="POST">
Search for <dsp:input bean="CatalogSearch.searchInput" size="20" type="text"/> <input name="repositoryKey" type="hidden" value='<dsp:valueof bean="/OriginatingRequest.requestLocale.locale"/>'> <!-- use this hidden form tag to make sure the search handler is invoked if someone does not hit the submit button --> <dsp:input bean="CatalogSearch.search" type="hidden" value="Search"/> <dsp:input bean="CatalogSearch.search" type="submit" value="Search"/>
</dsp:form></dsp:getvalueof>
The hidden input tag is an HTML (rather than ATG-specific) workaround for forms with a single input field. The
hidden input allows the user to invoke the submit method by typing the return key within the text input field.
Without this hidden tag, the user must click the Search button.
SimpleSearch.jsp
SimpleSearch.jsp is a page built of several JSP code fragments that provide their own specific functionality.
The SimpleSearch page uses two search-specific page fragments: SimpleSearchFragment.jsp accepts the
search input string and invokes the search itself and SearchResultsFragment.jsp displays the search results.
SimpleSearchFragment.jsp takes a parameter called FormAction to indicate the page to go to when the
form is submitted. In SimpleSearch.jsp, this value is set to SimpleSearch.jsp, causing the search page to
reload when the search completes.
SearchResultsFragment.jsp is passed the value of the CatalogSearch component’s
searchResultsByItemType parameter in order to format the results.
The following example shows SimpleSearch.jsp:
<%/* This JSP code demonstrates how to perform a simple search on the
104 5 Displaying and Accessing the Product Catalog
* product catalog repository. The CatalogSearch form handler component takes customer input and returns search results. This form handler is configured with the following prooperties: * * doKeywordSearch=true # search for input in keyword properties * keywordPropertyNames=keywords # which properties to search * itemTypes=category,product # which repository item types to search * $scope=session # the CatalogSearch component is session scoped * * The CatalogSearch form handler's handleSubmit() method will * generate a repository query based on the input. The results of * the query are retrieved via the * CatalogSearch.searchResultsByItemType property and passed to the * SearchResultsFragment JSP droplet to be displayed. */%><dsp:importbean bean="/atg/dynamo/droplet/ForEach"/><dsp:importbean bean="/atg/commerce/catalog/CatalogSearch"/><dsp:importbean bean="/atg/commerce/catalog/Navigator"/><dsp:setvalue bean="/atg/userprofiling/Profile.currentLocation" value="catalog_search"/>
<dsp:include page="../common/HeadBody.jsp" flush="true"></dsp:include>
<dsp:include page="../common/StoreBrand.jsp" flush="true"></dsp:include>
<span class=storelittle> <% /* Display Navigation information (list of all parent * categories as links users can jump to) */ %> <dsp:include page="../catalog/common/breadcrumbs.jsp" flush="true"> <dsp:param name="displaybreadcrumbs" value="true"/> <dsp:param name="no_new_crumb" value="true"/> </dsp:include></span><p><span class=storebig>Shop by Searching</span><br><p><table cellpadding=0 cellspacing=2 border=0> <tr> <td class=box-top-store>Simple Search</td> </tr> <tr> <td class=box> <% /* Use the SimpleSearchFragment JSP droplet, which contains * a simple form, to get the search input and to invoke the * CatalogSearch component's handleSubmit() method. */ %> <dsp:include page="SimpleSearchFragment.jsp" flush="true"><dsp:param name="FormAction" value="SimpleSearch.jsp"/></dsp:include> <p> <dsp:a href="AdvancedSearch.jsp">Use the advanced search form
5 Displaying and Accessing the Product Catalog 105
instead</dsp:a> <br><font size=-1><i>Simple search result set size limited to 50.</i></font><br> </td> </tr></table><p><%/* Display the results with the SearchResultsFragment JSP droplet */%><dsp:getvalueof id="pval0" bean="CatalogSearch.searchResultsByItemType"><dsp:include page="SearchResultsFragment.jsp" flush="true"><dsp:param name="ResultArray" value="<%=pval0%>"/></dsp:include></dsp:getvalueof>
<p>
<dsp:include page="../common/CatalogFooter.jsp" flush="true"></dsp:include><dsp:include page="../common/Copyright.jsp" flush="true"></dsp:include>
</BODY></HTML>
SearchResultsFragment.jsp
We used the SearchResultsFragment page fragment to iterate and display search results from both
SimpleSearch.jsp and AdvancedSearch.jsp. ForEach is sent the parameter array, which can take
many kinds of multi-valued objects. The type in this case is a java.util.Map that is the product of the
SearchFormHandler component.
The Map has one entry for each value specified in the itemTypes value of the component’s configuration file. In
both SimpleSearch and AdvancedSearch the itemTypes are category and product.
The Map is a collection of search result arrays and their associated itemType identifiers. The value associated
with category and product in the Map is an array of category or product items, or null if no results are found
for that key.
The category and product Map elements are extracted by iterating through the Map with the standard
ForEach component in the outermost ForEach droplet tag. For each element of the Map, the Switch
component is then invoked to distinguish the category and product elements, and to affect the order in
which they’re rendered on the page.
Once the category and product search result array is obtained, it is necessary to iterate through each element
of that array to display the results. In order to detect an empty result array, we used another Switch component,
this time for the unsetOPARAM feature of Switch that identifies an empty input array and a message is rendered
indicating that no items of the given type were found. When the array is non-null, the default OPARAM tag
of the Switch is rendered. In this case, another call to the ForEach component iterates through the array and
ItemLink.jsp is invoked for each element to properly format and display the repository item.
<%/* ---------------------------------------------------------------This jsp droplet demonstrates how to display the contents of searchthat potentially returns both category and product repository items.The one paramater, ResultArray, accepts a HashMap that containselements with the keys "category" and "product". The values of thesekeys are collections of category or product repository items found inthe search.
106 5 Displaying and Accessing the Product Catalog
----------------------------------------------------------------*/%>
<DECLAREPARAM NAME="ResultArray" CLASS="java.util.Map" DESCRIPTION="Array of Search Results">
<dsp:importbean bean="/atg/dynamo/droplet/Switch"/><dsp:importbean bean="/atg/dynamo/droplet/ForEach"/>
<dsp:droplet name="ForEach"> <dsp:param name="array" param="ResultArray"/>
<%/*Each item in this array is a Collection of Categories or Products...*/%> <dsp:param name="elementName" value="ResultCollection"/>
<dsp:oparam name="output"> <dsp:droplet name="Switch">
<%/*The key tells us if this is a Collection of Products or Categories: */%> <dsp:param name="value" param="key"/>
<%/*For the list of CATEGORIES: */%> <dsp:oparam name="category"> <b>We found these categories matching your search</b> <blockquote>
<dsp:droplet name="Switch"> <dsp:param name="value" param="ResultCollection"/> <dsp:oparam name="default"> <p> <%/*For each Category in the Collection: */%> <dsp:droplet name="ForEach"> <dsp:param name="array" param="ResultCollection"/> <dsp:param name="elementName" value="Category"/> <dsp:oparam name="output">
<%-- Display a link to the Category: --%> <dsp:include page="../common/ItemLink.jsp" flush="true"> <dsp:param name="Item" param="Category"/> <dsp:param name="Image" param="Category.thumbnailImage"/> <dsp:param name="DisplayText" param="Category.displayName"/> <dsp:param name="navAction" value="jump"/> </dsp:include>
<br> </dsp:oparam> </dsp:droplet> </dsp:oparam> <%-- If NO Categories returned by the search: --%> <dsp:oparam name="unset"> No category items in the catalog could be found that match your query </dsp:oparam> </dsp:droplet><%/*ForEach Category*/%>
</blockquote> <P> </dsp:oparam>
5 Displaying and Accessing the Product Catalog 107
<%/*For the list of PRODUCTS: */%> <dsp:oparam name="product"> <p> <b>We found these products matching your search</b> <blockquote><p>
<dsp:droplet name="Switch"> <dsp:param name="value" param="ResultCollection"/>
<dsp:oparam name="default">
<%/*For each Product in the Collection: */%> <dsp:droplet name="ForEach"> <dsp:param name="array" param="ResultCollection"/> <dsp:param name="elementName" value="Product"/>
<dsp:oparam name="output"> <dsp:valueof param="element.manufacturer.displayName"/>
<%/* Display a link to the Product: */%> <dsp:include page="../common/ItemLink.jsp" flush="true"> <dsp:param name="Item" param="Product"/> <dsp:param name="Image" param="Product.thumbnailImage"/> <dsp:param name="DisplayText" param="Product.displayName"/> <dsp:param name="navAction" value="jump"/> </dsp:include>
<br> </dsp:oparam> </dsp:droplet> <%/*ForEach Product*/%>
</dsp:oparam>
<%/*If NO Products returned by the search: */%> <dsp:oparam name="unset"> No product items in the catalog could be found that match your query<p> </dsp:oparam>
</dsp:droplet> </blockquote><P> </dsp:oparam> </dsp:droplet>
</dsp:oparam>
</dsp:droplet>
Advanced Search
In addition to the keyword and text search of the simple search functionality, the advanced search functionality
allows the customer to search for products using by part number, manufacturer, and weight range, which are
defined as product properties.
108 5 Displaying and Accessing the Product Catalog
AdvProductSearch.properties Nucleus Configuration File
The advanced search functionality uses the SearchFormHandler but requires a slightly more complex
properties file than the simple search Nucleus component. Advanced searches return only products because the
additional search criteria are based on products only.
advanced search
The following example shows the properties of the AdvProductSearch component:
$class=atg.commerce.catalog.SearchFormHandler$scope=sessiondoKeywordSearch=truekeywordsPropertyNames=keywordsdoTextSearch=truetextSearchPropertyNames=description,displayNamedoAdvancedSearch=trueadvancedSearchPropertyNames=weightRange,manufacturer,childSKUsminScore=1catalogTools=CatalogToolsitemTypes^=CatalogTools.productItemTypes
As with the simple search, the SearchFormHandler is session scoped. Keyword and text searching are
configured identically for both simple and advanced searches and they both use the same catalog. For the
enumerated types used in the search (manufacturer and weightRange), the possible values are inserted into
<dsp:select> input tags on the form. These values are not coded into the form, but instead are retrieved from
the catalog via the propertyValuesByType property of the SearchFormHandler.
5 Displaying and Accessing the Product Catalog 109
The propertyValuesByType property is a Map with property names for keys and arrays of all possible
values for the given property. By default, no enumerated property information is available from the
propertyValuesByType property of the SearchFormHandler. Instead, you must explicitly set the names of
required enumerated properties in the search component’s advancedSearchPropertyNames property. In this
case, the values are weightRange, manufacturer, and childSKUs.
The search form’s categories in the advanced search, manufacturer and weightRange, are populated with
valid choices for each item. These choices (<select> components) are loaded from the repository rather than
coded into the page so that the page need not be changed when changes are made to the repository.
To display the list of all repository items of type category we used the RepositoryValues servlet bean. It
takes an input parameter itemDescriptorName that in this case is set to category because that is the type of
repository item that we want to list. The servlet bean outputs an array of repository items that can be rendered
using the ForEach component.
For the manufacturer and weightRange query values, the product item descriptor is specified and product’s
manufacturer and weightRange properties are passed via the propertyName parameter. The definitions
of these two properties in the productCatalog.xml file differ. The manufacturer property is an item-type
property, a link to another item-descriptor, while the weightRange property is an enumerated data-type. The
RepositoryValues servlet bean is able to provide appropriate values for either type of property.
Note: The “. . .” marker in the following code sample indicate a place where code has been removed to clarify the
sample.
<item-descriptor name="product"> <table name="b2c_product" type="auxiliary"id-column-name="product_id"> <property name="manufacturer" item-type="manufacturer"column-name="manufacturer"/></item-descriptor>
<item-descriptor name="manufacturer"> . . .</item-descriptor>
AdvancedSearch.jsp:
<dsp:setvalue bean="/atg/userprofiling/Profile.currentLocation" value="catalog_search"/><dsp:importbean bean="/atg/userprofiling/Profile"/><dsp:importbean bean="/atg/dynamo/droplet/ForEach"/><dsp:importbean bean="/atg/dynamo/droplet/For"/><dsp:importbean bean="/atg/dynamo/droplet/Switch"/><dsp:importbean bean="/atg/dynamo/droplet/Compare"/><dsp:importbean bean="/atg/commerce/catalog/AdvProductSearch"/><dsp:importbean bean="/atg/commerce/catalog/RepositoryValues"/>
<HTML><HEAD><TITLE>Pioneer Cycling Store</TITLE><dsp:link rel="STYLESHEET" href="../common/Style.css" type="text/css" title="Prototype Stylesheet"/></HEAD><BODY>
<dsp:include page="../common/StoreBrand.jsp" flush="true"></dsp:include>
110 5 Displaying and Accessing the Product Catalog
<span class=storelittle><!-- Display Navigation information (list of all parent --><!-- categories as links users can jump to) --><dsp:include page="../catalog/common/breadcrumbs.jsp" flush="true"><dsp:param name="displaybreadcrumbs" value="true"/><dsp:param name="no_new_crumb" value="true"/></dsp:include></span><p><span class=storebig>Shop by Searching</span><br><p>
<dsp:form action="AdvancedSearch.jsp" method="POST"> <table cellpadding=0 cellspacing=0 border=0> <tr><td colspan=3 class=box-top-store>Advanced Search</td></tr> <tr valign=top><td class=box>
<!-- For each property specified in AdvProductSearch.advancedSearchPropertyNames, retrieve all possible property values. This allows the customer to pick one to search on for advanced searching. -->
<!-- Category --> Search in <dsp:select bean="AdvProductSearch.hierarchicalCategoryId"> <dsp:option value=""/>-- Entire Store -- <dsp:droplet name="RepositoryValues"> <dsp:param name="itemDescriptorName" value="category"/> <dsp:oparam name="output"> <dsp:droplet name="ForEach"> <dsp:param name="array" param="values"/> <dsp:param name="sortProperties" value="+displayName"/> <dsp:oparam name="output"> <dsp:getvalueof id="option85" param="element.repositoryId" idtype="java.lang.String"> <dsp:option value="<%=option85%>"/> </dsp:getvalueof> <dsp:valueof param="element.displayName"/> </dsp:oparam> </dsp:droplet> </dsp:oparam> </dsp:droplet> </dsp:select> <p>
<!-- Text Search --> Text Search for <dsp:input bean="AdvProductSearch.searchInput" size="30" type="text"/> <BR><br>
<!-- Part Number --> Search for Part Number (sku id)<br> <dsp:input bean="AdvProductSearch.propertyValues.childSKUs" size="15" type="text"/> </br>
<td class=box> </td>
<td class=box> Match these attributes
5 Displaying and Accessing the Product Catalog 111
<p>
<!-- Manufacturer --> Manufacturer <dsp:select bean="AdvProductSearch.propertyValues.manufacturer"> <dsp:option value=""/>-- Any -- <dsp:droplet name="RepositoryValues"> <dsp:param name="itemDescriptorName" value="product"/> <dsp:param name="propertyName" value="manufacturer"/> <dsp:oparam name="output"> <dsp:droplet name="ForEach"> <dsp:param name="array" param="values"/> <dsp:param name="sortProperties" value="+id"/> <dsp:oparam name="output"> <dsp:getvalueof id="option140" param="element.id" idtype="java.lang.String"><dsp:option value="<%=option140%>"/></dsp:getvalueof><dsp:valueof param="element.id"/> </dsp:oparam> </dsp:droplet> </dsp:oparam> </dsp:droplet> </dsp:select> <p>
Weight Range <dsp:select bean="AdvProductSearch.propertyValues.weightRange"> <dsp:option value=""/>-- Any -- <dsp:droplet name="RepositoryValues"> <dsp:param name="itemDescriptorName" value="product"/> <dsp:param name="propertyName" value="weightRange"/> <dsp:oparam name="output"> <dsp:droplet name="ForEach"> <dsp:param name="array" param="values"/> <dsp:oparam name="output"> <dsp:getvalueof id="option174" param="element" idtype="java.lang.String"><dsp:option value="<%=option174%>"/></dsp:getvalueof><dsp:valueof param="element"/> </dsp:oparam> </dsp:droplet> </dsp:oparam> </dsp:droplet> </dsp:select> <p>
<nobr> <dsp:input bean="AdvProductSearch.currentResultPageNum" type="hidden" value="1"/> <input name="repositoryKey" type="hidden" value='<dsp:valueof bean="/OriginatingRequest.requestLocale.locale"/>'> <dsp:input bean="AdvProductSearch.search" type="hidden" value="Search"/> <dsp:input bean="AdvProductSearch.search" type="submit" value=" Search "/> <p>
<dsp:a href="SimpleSearch.jsp">Search with the simple form instead</dsp:a></b> </td></tr> </table>
112 5 Displaying and Accessing the Product Catalog
</dsp:form><p>
<dsp:droplet name="Compare"> <dsp:param bean="AdvProductSearch.resultSetSize" name="obj1"/> <dsp:param bean="AdvProductSearch.maxResultsPerPage" name="obj2"/> <dsp:oparam name="greaterthan"> Now viewing results <dsp:valueof bean="AdvProductSearch.startIndex"/> to <dsp:valueof bean="AdvProductSearch.endIndex"/> out of <dsp:valueof bean="AdvProductSearch.resultSetSize"/> </dsp:oparam></dsp:droplet>
<!-- Display Results --><dsp:getvalueof id="pval0" bean="AdvProductSearch.searchResultsByItemType" idtype="java.util.Map"><dsp:include page="SearchResultsFragment.jsp" flush="true"><dsp:param name="ResultArray" value="<%=pval0%>"/></dsp:include></dsp:getvalueof>
<dsp:droplet name="Switch"> <dsp:param bean="AdvProductSearch.resultSetSize" name="value"/> <dsp:oparam name="0"> </dsp:oparam> <dsp:oparam name="default"> <dsp:droplet name="For"> <dsp:param bean="AdvProductSearch.resultPageCount" name="howMany"/> <dsp:oparam name="output"> <dsp:droplet name="Switch"> <dsp:param bean="AdvProductSearch.currentResultPageNum" name="value"/> <dsp:getvalueof id="nameval2" param="count" idtype="java.lang.Integer"> <dsp:oparam name="<%=nameval2.toString()%>"> <dsp:valueof param="count"/> </dsp:oparam> </dsp:getvalueof> <dsp:oparam name="default"> <dsp:a href="AdvancedSearch.jsp" bean="AdvProductSearch.currentResultPageNum" paramvalue="count"> <dsp:valueof param="count"/> </dsp:a> </dsp:oparam> </dsp:droplet> </dsp:oparam> </dsp:droplet> </dsp:oparam></dsp:droplet>
<p>
<dsp:include page="../common/StoreFooter.jsp" flush="true"></dsp:include>
</BODY></HTML>
5 Displaying and Accessing the Product Catalog 113
Filtering
In the Pioneer Cycling store, we used filtering to allow the user to limit the types of parts that are displayed on
category_parts.jsp. The filtering functionality allows the user to filter parts by manufacturer, by frame types
that the parts should fit, and by the part’s general price category.
Filtering functionality is composed of three pieces:
• The Java Server Page where the filter’s criteria are specified (search/filtering.jsp).
• The PartsFilterFormHandler, which accepts the criteria and generates the
RuleBasedRepositoryItemGroup targeting object.
• The Parts Java Server Page that uses the RuleBasedRepositoryItemGroupFilter to display the filtered
parts.
The Filtering Page
The filtering page initializes the PartsFilterFormHandler with filtering criteria including a list of
manufacturers, types of frames the parts fit, and general price categories for the parts.
filtering.jsp:
<dsp:importbean bean="/atg/commerce/catalog/FilterCreator"/><dsp:importbean bean="/atg/commerce/catalog/RepositoryValues"/><dsp:importbean bean="/atg/dynamo/droplet/ForEach"/>
<dsp:include page="../common/HeadBody.jsp" flush="true"></dsp:include>
114 5 Displaying and Accessing the Product Catalog
<dsp:include page="../common/StoreBrand.jsp" flush="true"></dsp:include>
<dsp:include page="../catalog/common/breadcrumbs.jsp" flush="true"><dsp:param name="displaybreadcrumbs" value="true"/><dsp:param name="no_new_crumb" value="true"/></dsp:include></span><p>
<span class=storebig>Shop by Filtering</span><br><span class=help>Filtering allows you to see only parts in the catalog that meet the criteria that you indicate here.</span>
<p><dsp:form action="../catalog/category_parts.jsp" method="POST">
<table width=100% cellpadding=0 cellspacing=0 border=0> <tr> <td colspan=3 class=box-top-store>Filter Catalog</td> </tr>
<tr valign=top><td class=box> <dsp:input bean="FilterCreator.searchAllManufacturers" type="radio" value="true"/> Show products from all manufacturers<br> <dsp:input bean="FilterCreator.searchAllManufacturers" type="radio" value="false"/> Show products from only these manufacturers<br> <dsp:select multiple="<%=true%>" bean="FilterCreator.manufacturers" size="4"> <dsp:droplet name="RepositoryValues"> <dsp:param name="itemDescriptorName" value="product"/> <dsp:param name="propertyName" value="manufacturer"/> <dsp:oparam name="output"> <dsp:droplet name="ForEach"> <dsp:param name="array" param="values"/> <dsp:param name="sortProperties" value="+id"/> <dsp:oparam name="output"> <dsp:getvalueof id="option76" param="element.id" idtype="java.lang.String"><dsp:option value="<%=option76%>"/></dsp:getvalueof><dsp:valueof param="element.id"/> </dsp:oparam> </dsp:droplet> </dsp:oparam> </dsp:droplet> </dsp:select> <p>
<dsp:input bean="FilterCreator.anyFrame" type="radio" value="true"/> Show products that fit any frame<br> <dsp:input bean="FilterCreator.anyFrame" type="radio" value="false"/> Show only products that fit this frame<br> <dsp:select bean="FilterCreator.frameType"> <dsp:droplet name="RepositoryValues"> <dsp:param name="itemDescriptorName" value="frame-product"/> <dsp:param name="propertyName" value="frameType"/> <dsp:oparam name="output"> <dsp:droplet name="ForEach"> <dsp:param name="array" param="values"/>
5 Displaying and Accessing the Product Catalog 115
<dsp:oparam name="output"> <dsp:getvalueof id="option116" param="element" idtype="java.lang.String"><dsp:option value="<%=option116%>"/></dsp:getvalueof><dsp:valueof param="element"/> </dsp:oparam> </dsp:droplet> </dsp:oparam> </dsp:droplet> </dsp:select> <br> <br> </td>
<td class=box> </td>
<td class=box> <dsp:input bean="FilterCreator.priceSelectionType" type="radio" value="allPrices"/> Show products of all prices<br> <dsp:input bean="FilterCreator.priceSelectionType" type="radio" value="priceCategory"/> Show products that are priced<br> <dsp:select bean="FilterCreator.priceCategory" size="4"> <dsp:droplet name="RepositoryValues"> <dsp:param name="itemDescriptorName" value="product"/> <dsp:param name="propertyName" value="priceRange"/> <dsp:oparam name="output"> <dsp:droplet name="ForEach"> <dsp:param name="array" param="values"/> <dsp:oparam name="output"> <dsp:getvalueof id="option166" param="element" idtype="java.lang.String"><dsp:option value="<%=option166%>"/></dsp:getvalueof><dsp:valueof param="element"/> </dsp:oparam> </dsp:droplet> </dsp:oparam> </dsp:droplet> </dsp:select> <br> <P>
<%/* Set these hidden params to cause the action to return to the * previous page */%> <input name="navCount" type="hidden" value='<dsp:valueof bean="/atg/commerce/catalog/CatalogNavHistory.navCount"/>'> <input type="hidden" name="navAction" value="jump"> <input type="hidden" name="id" value="cat10005">
<dsp:input bean="FilterCreator.submit" type="submit" value=" Filter Catalog "/> </td> </tr></table>
<p>
</dsp:form>
</td>
116 5 Displaying and Accessing the Product Catalog
</tr></table>
<p>
<dsp:include page="../common/StoreFooter.jsp" flush="true"></dsp:include><dsp:include page="../common/Copyright.jsp" flush="true"></dsp:include>
</BODY></HTML>
filtering.jsp imports and uses two Nucleus components, FilterCreator and RepositoryValues. The
RepositoryValues configuration file sets a single property, the catalog for which products will be filtered.
RepositoryValues component:
$class=atg.repository.servlet.PossibleValuesrepository^=CatalogTools.catalog
The FilterCreator component configures the PartsFilterFormHandler class’s repository property with
the store catalog and sets the itemDescriptor to product, since parts are products.
FilterCreator component
$class=atg.projects.b2cstore.PartsFilterFormHandler$scope=sessionloggingIdentifier=Parts Filter Form Handlerrepository^=CatalogTools.catalogitemDescriptor=product
The PartsFilterFormHandler
The PartsFilterFormHandler accepts manufacturer names, frame types, and price categories and generates
an atg.targeting.RuleBasedRepositoryItemGroup, which is a targeting rule set made up of SGML
Content Targeting Rules.
5 Displaying and Accessing the Product Catalog 117
The PartsFilterFormHandler overrides the getRuleRepresentation() methods of its parent class. This
allows it to specify an SGML targeting rule.
Generating the Targeting Rules
The targeting rules are SGML rules that specify logical operations such as “and”, “or”, or “includes” to accept
or reject repository items. See the Creating Rules for Targeting Content chapter of the ATG Personalization
Programming Guide for more information on targeting rules.
If no filtering criteria have been entered by the user, getRuleRepresentation() returns a rule that accepts all
items:
<ruleset> <accepts> <rule op=any> </rule> </accepts>
118 5 Displaying and Accessing the Product Catalog
</ruleset>
When one or more conditions are specified via filtering.jsp, the PartsFilterFormHandler generates
an “and” rule within the <accepts> rule that contains one or more child rules that specify the actual filtering
criteria:
<ruleset> <accepts> <rule op=and> <rule op=isoneof> <valueof target="manufacturer.id"> <valueof constant="[BatGoose, DZ Supply]"> </rule> <rule op=eq> <valueof target="priceRange"> <valueof constant="EXPENSIVE"> </rule> </rule> </accepts></ruleset>
The top level <rule op=and> tag within the <accepts> tag is necessary because the filtering conditions must
be grouped together with an “and” rule. In the above case, the manufacturer.id should be one of the listed
constants and the price range should equal EXPENSIVE. Without the parent “and” rule, the filtering conditions
are grouped together with an “or” rule.
Creating the RuleBasedRepositoryItemGroup
The RuleBasedRepositoryItemGroup is a Java Targeting object created from the
SGML rules that were generated by the PartsFilterFormHandler. It is later used by the
RuleBasedRepositoryItemGroupFilter to filter an array of parts repository items.
We created the RuleBasedRepositoryItemGroup within FilterFormHandler. Within its
handleSubmit() method, the parent class, FilterFormHandler, retrieves the SGML rules from
PartsFilterFormHandler via the overridden getRuleRepresentation() method and generates a
RuleBasedRepositoryItemGroup object from those rules. The RuleBasedRepositoryItemGroup object
is available via the getRepositoryItemGroup() method of the FilterFormHandler or, in JSP, via the
repositoryItemGroup property.
You can duplicate the functionality of FilterFormHandler in your own code. The handleSubmit()
method of FilterFormHandler simply calls the TargeterUtils.createGroup() method to create the
RuleBasedRepositoryItemGroup.
Filtering and Displaying Parts Repository Items
The RuleBasedRepositoryItemGroupFilter component takes the RepositoryItemGroup created by
FilterCreator and an array of repository items and outputs a subset of the input array that contains only
those repository items that pass the filter.
ProductsLineItem.jsp Fragment
<dsp:importbean bean="/atg/dynamo/droplet/ForEach"/><dsp:importbean bean="/atg/commerce/catalog/FilterCreator"/><dsp:importbean bean="/atg/targeting/RuleBasedRepositoryItemGroupFilter"/>
<declareparam name="array" class="java.lang.Object[]"
5 Displaying and Accessing the Product Catalog 119
description="An array of repository items">
<%/* Filter the array of repository items in the page param, "array" */%><dsp:droplet name="RuleBasedRepositoryItemGroupFilter"> <dsp:param name="inputItems" param="array"/> <dsp:param bean="FilterCreator.repositoryItemGroup" name="repositoryItemGroup"/> <dsp:oparam name="output">
<%/* Display each filtered item */%>
<dsp:droplet name="ForEach"> <dsp:param name="array" param="filteredItems"/> <dsp:param name="elementName" value="childProduct"/> <dsp:oparam name="outputStart"> <table cellspacing=3 cellpadding=0 border=0> </dsp:oparam> <dsp:oparam name="output"> <tr valign=top> <td> <dsp:valueof param="childProduct.manufacturer.displayName"/> </td> <td> </td> <td colspan=3> <b> <dsp:include page="../common/ItemLink.jsp" flush="true"> <dsp:param name="Item" param="childProduct"/> <dsp:param name="Image" param="childProduct.thumbnailImage"/> <dsp:param name="DisplayText" param="childProduct.displayName"/> <dsp:param name="navAction" value="push"/> </dsp:include> </b> </td> </tr>
<%/* List all child SKUs of the product */%> <dsp:droplet name="ForEach"> <dsp:param name="array" param="childProduct.childSKUs"/> <dsp:param name="elementName" value="childSku"/> <dsp:oparam name="output"> <tr valign=top> <td colspan=2></td> <td> <dsp:valueof param="childSku.displayName"/> </td> <td> </td> <td align=right> <dsp:include page="../common/DisplaySkuPrice.jsp" flush="true"> <dsp:param name="Sku" param="childSku"/> <dsp:param name="Image" param="childSku.thumbnailImage"/> <dsp:param name="navAction" value="push"/> <dsp:param name="DisplayText" param="childSku.displayName"/> </dsp:include> </td> </tr> </dsp:oparam> </dsp:droplet> <tr> <td colspan=5><hr size=0></td> </tr> </dsp:oparam>
120 5 Displaying and Accessing the Product Catalog
<dsp:oparam name="outputEnd"> </table> </dsp:oparam> </dsp:droplet> </dsp:oparam></dsp:droplet>
The ProductsLineItem.jsp fragment imports the FilterCreator component to access
the parts RuleBasedRepositoryItemGroup from its repositoryItemGroup property, and
imports the RuleBasedRepositoryItemGroupFilter servlet bean in order to apply the
RuleBasedRepositoryItemGroup to each repository item. ProductsLineItem.jsp also declares a
page parameter called array that is set by the caller to the array of parts repository items to display. All the
actual filtering work is done up front in the first invocation, RuleBasedRepositoryItemGroupFilter. As
parameters, the array that’s been passed to this page fragment is passed on to the inputItems parameter in
the filter servlet bean, and the RuleBasedRepositoryItemGroup created by the PartsFilterFormHandler
(described above) is passed in as the repositoryItemGroup parameter. In the output open parameter,
the ForEach servlet bean iterates the RuleBasedRepositoryItemGroupFilter's output parameter
filteredItems that contains the list of repository items that have passed the filter.
Comparing SKUs
In the Pioneer Cycling store, we wanted users to be able to compare SKUs side by side. In this section we discuss
how we implemented this feature.
Adding Compare Functionality
We included a Compare with Other Products button on the bike template pages that allows customers to
compare two bikes side by side. When customers click this button, it adds all of the SKUs for this product to a
session-scoped SKU comparison list. Pushing the button also brings customers to the “Shop by Comparing”
page, which allows them to pick two different SKUs (typically for two different bike models) and do a side-by-
side comparison against attributes such as color, price, and weight.
5 Displaying and Accessing the Product Catalog 121
CompareSkusFormHandler
The Pioneering Cycling store uses the ATG Consumer Commerce CompareSkusFormHandler to implement this
comparison functionality. The CompareSkusFormHandler has several methods and properties that hold the
information needed to compare items. This section shows how we used these methods and properties to allow
users to compare two bikes.
First, we included the form handler component on the bike template page:
<dsp:importbean bean="/atg/commerce/catalog/CompareSkusFormHandler"/>
Then we added the button for adding SKUs to the SKU compare list. In the Pioneer Cycling store, the bike
template page includes a button that says Compare With Other Products. When the customer clicks this
button, all of the SKUs for the product are added to the SKU compare list. Here is the code for this:
122 5 Displaying and Accessing the Product Catalog
<dsp:getvalueof id="form179" bean="/OriginatingRequest.requestURI" idtype="java.lang.String"><dsp:form action="<%=form179%>" method="post">
<%/*Display any errors that have been generated during Compare *operations: */%> <dsp:include page="../common/DisplayCompareSkusFormHandlerErrors.jsp" flush="true"></dsp:include>
<input name="id" type="hidden" value='<dsp:valueof param="Product.repositoryId"/>'>
<!-- Add this ProductID to the Compare List: --> <dsp:input bean="CompareSkusFormHandler.ProductToCompare" paramvalue="id" type="hidden"/>
<%/*Goto this URL if no errors are found during Compare button *processing: */%> <dsp:input bean="CompareSkusFormHandler.AddToCompareListSuccessURL" type="hidden" value="compare_bikes.jsp"/>
<%/*COMPARE button: */%> <dsp:input bean="CompareSkusFormHandler.addToCompareList" type="submit" value="Compare With Other Products"/>
</dsp:form></dsp:getvalueof>
Adding Items to the Compare List
When the customer clicks the Compare With Other Products button on the bike product page, the page first
sets the form handler’s ProductToCompare property with the repository ID of the bike product. This is done via
a hidden text box. Here is the code that sets this property in the form handler:
<!-- Add this ProductID to the Compare List: --><dsp:input bean="CompareSkusFormHandler.ProductToCompare" paramvalue="id" type="hidden"/>
The CompareSkusFormHandler.addToCompareList method is called when the customer actually clicks the
button. It stores all of the SKUs associated with the ProductToCompare in the form handler’s SkuCompareList
property. Here is the code for the button:
<%/*COMPARE button: */%><dsp:input bean="CompareSkusFormHandler.addToCompareList" type="submit" value="Compare With Other Products"/>
The page also sets the SuccessURL property. This value tells the form handler where to redirect at the end
of the CompareSkusFormHandler.addToCompareList method. If this product is successfully added to the
compare list, the user is redirected to the compare_bikes.jsp page. Here is the code that stores this value in
the form handler:
<dsp:input bean="CompareSkusFormHandler.AddToCompareListSuccessURL" type="hidden" value="compare_bikes.jsp"/>
5 Displaying and Accessing the Product Catalog 123
Selecting two SKUs to Compare
After a user has added some SKUs to the compare list, there is a page that lets them select two specific SKUs and
do the side-by-side comparison. compare_bikes.jsp performs this functionality.
compare_bikes.jsp
The form handler’s SkuCompareList property contains the list of the SKUs that have been added thus far. This
list is used to populate two dropdown lists. The user selects one SKU from each of these dropdown lists.
Here is the code that displays a dropdown list with all of SKUs in the SkuCompareList property. The code
uses the DSP tag library’s <dsp:select> tag for creating a dropdown list. The indexes (not the repository IDs) of
the SKUs they select are stored in the form handler’s SelectedIndex1 and SelectedIndex2 properties. Here
is the code for the first SKU dropdown list:
<!-- First dropdown list: -->
124 5 Displaying and Accessing the Product Catalog
<!-- Display a drop down list of all Skus they can compare: --><!-- Store the index of their selection in the "SelectedIndex1" attribute --><!-- in the CompareSkusFormHandler. --><dsp:select bean="CompareSkusFormHandler.SelectedIndex1">
<!-- For each Sku they can select: --><dsp:droplet name="/atg/dynamo/droplet/ForEach"> <dsp:param bean="CompareSkusFormHandler.SkuCompareList" name="array"/> <dsp:param name="elementName" value="Sku"/> <dsp:oparam name="output">
<!-- If this sku is selected, save its list INDEX in SelectedIndex1 --> <dsp:getvalueof id="option92" param="index" idtype="java.lang.Integer"> <dsp:option value="<%=option92.toString()%>"/> </dsp:getvalueof>
<!-- Display the name of the Sku in the dropdown: --> <dsp:valueof param="Sku.displayName"/>
</dsp:oparam> <!-- output param for each Sku --></dsp:droplet> <!-- ForEach Sku -->
</dsp:select>
The above dropdown code for selecting the first SKU and almost identical code for selecting the second SKU are
nested inside of a <dsp:form action> statement that includes the code for the Compare button. Here is the
remaining code:
<dsp:form action="compare_bikes.jsp" method="POST">
<!—Code for first dropdown list (see above) -->
<!—Code for second dropdown list (see above) -->
<!-- COMPARE button: -->
<dsp:input bean="CompareSkusFormHandler.compareSkus" type="submit" value=" Compare "/>
</dsp:form>
The first selected SKU for comparison can be referenced by indexing the skuCompareList. Likewise, the
product with which the SKU is associated can be referenced in the same manner.
Displaying the side-by-side values
After the SelectedIndex1 and SelectedIndex2 properties are set, the rest of compare_bikes.jsp displays
the values of the two SKUs side by side.
Because the code that displays the side-by-side values has to repeatedly get the selected SKUs and associated
products to fetch their values, we created some page parameters so the display code is easier to read and more
efficient. Here is the code that sets up the local parameters on the page:
<dsp:setvalue beanvalue="CompareSkusFormHandler.SelectedIndex1" param="Index1"/>
5 Displaying and Accessing the Product Catalog 125
<dsp:setvalue beanvalue="CompareSkusFormHandler.SkuCompareList[param:Index1]" param="Sku1"/><dsp:setvalue beanvalue="CompareSkusFormHandler.ProductCompareList[param:Index1]" param="Product1"/>
<dsp:setvalue beanvalue="CompareSkusFormHandler.SelectedIndex2" param="Index2"/><dsp:setvalue beanvalue="CompareSkusFormHandler.SkuCompareList[param:Index2]" param="Sku2"/><dsp:setvalue beanvalue="CompareSkusFormHandler.ProductCompareList[param:Index2]" param="Product2"/>
We added a link to each product at the top of the side-by-side comparison chart, in case the customer wants to
return to the product page (and, for example, add the item to her shopping cart).
A link to each product is displayed in the header of the comparison chart.
We used the ItemLink.jsp code fragment described earlier. It requires a product repository item and takes the
product page parameter that was just set in the previous code example.
<!-- Display the Product name as a link to the Product page: --><dsp:include page="../common/ItemLink.jsp" flush="true"> <dsp:param name="Item" param="Product1"/> <dsp:param name="navAction" value="push"/></dsp:include><br>
Various SKU properties are displayed in the following way:
<!-- Display the SKU display name: --><dsp:valueof param="Sku1.displayName"/>
126 5 Displaying and Accessing the Product Catalog
. . .
<!- Display the SKU display name: --><dsp:valueof param="Sku2.displayName"/>
Displaying Errors found by CompareSkusFormHandler
The pages that reference the CompareSkusFormHandler (product_bike.jsp and compare_bikes.jsp)
include the following code for displaying any form errors that are found by the CompareSkusFormHandler:
<%/*Display any errors that have been generated during Compare operations: */%><dsp:include page="../common/DisplayCompareSkusFormHandlerErrors.jsp" flush="true"></dsp:include>
This code fragment simply displays any form errors caught by the CompareSkusFormHandler.
<%/*Display any errors found by the CompareSkusFormHandler: */%><dsp:droplet name="/atg/dynamo/droplet/ErrorMessageForEach"> <dsp:param bean="CompareSkusFormHandler.formExceptions" name="exceptions"/>
<dsp:oparam name="outputStart"> <font color=cc0000><STRONG><UL> </dsp:oparam>
<dsp:oparam name="outputEnd"> </UL></STRONG></font> </dsp:oparam>
<dsp:oparam name="output"> <LI><dsp:valueof param="message"/> </dsp:oparam>
</dsp:droplet>
6 Pricing 127
6 Pricing
This chapter describes the pricing engine used in the Pioneer Cycling site and includes the following sections:
Setting Product Prices (page 127)
Describes how to set list and sale prices for each item in the repository.
Displaying Prices (page 128)
Describes how to use the PriceItem droplet to display prices.
Shipping Prices (page 130)
Describes how to create shipping prices.
Creating Your Own Pricing Models (page 132)
Describes how to further customize the pricing for your store.
Setting Product Prices
The Pioneer Cycling site demonstrates how you can use ATG Relationship Management Platform’s flexible
pricing to create both standard and promotional prices.
Standard Prices
The listPrice and salePrice are properties of SKU that are assigned by the merchant using the ACC and
stored in the catalog repository. The onSale flag determines the price to use as the base price for the item. The
catalog administrator can set these properties using the ACC.
Promotional Prices
In addition to listPrice and salePrice, it is possible to offer different prices depending on circumstances
such as timing, other products being purchased, or the customer’s profile. The actual prices displayed to the
customer are calculated dynamically using a combination of standard prices and promotions. You can use
the ACC to build the pricing models that determine promotional prices. See the section on Promotions (page
193) in the Merchandising (page 193) chapter of this guide or refer to the Using and Extending Pricing Services
chapter in the ATG Commerce Programming Guide for more information.
128 6 Pricing
Displaying Prices
To calculate or display a price in a Java Server Page, we used /atg/commerce/pricing/PriceItem.
Three different types of prices are available: listPrice, salePrice, and amount. The amount price is the
personalized price that is calculated dynamically by the pricing engine for a particular user at a particular time.
The PriceItem component calculates different prices for the same item depending on whether that item is
displayed alone (for example, in the product template page of the catalog) or in the context of the shopping
cart.
In the Pioneer Cycling store, we made a distinction between the list price for a product and the user’s price:
• list price: the full, undiscounted price of a product.
• user’s price: the price of a product for a certain user, after all discounts and promotions for which the user
is eligible have been calculated. It does not include order-specific promotions (promotions that depend on
other items on the order).
ATG Consumer Commerce enables you to set up pricing models in which a special price on one item depends on
the purchase of another item (for example, buy a bike, get a helmet at half price). When the helmet is displayed
alone, its full price (minus any single-item discounts) is displayed. Even if the customer has a bike in his shopping
cart, entitling him to the helmet at half price, the helmet still displays outside the cart at list price because there
is no way to know whether or not he will keep the bike in his cart. Therefore, promotional prices that depend on
other purchases are only displayed in the shopping cart.
Here’s another example: on Mother’s Day the Pioneer Cycling store offers all women 20% off. The list price of
a helmet is $100; the user’s price (for women) would be $80. The store also offers 50% off the price of a helmet
with the purchase of a bike. However, the user’s price displayed on the product template page does not show this
50% discount, even if the she is purchasing a bike. This order-level discount is displayed on the Shopping Cart
page. Order-level discounts and promotions are calculated with the pricing calculators and are discussed in the
Promotions (page 193) section of the Merchandising (page 193) chapter.
PriceItem takes a SKU repository item as its item parameter. It outputs an element parameter that is of type
CommerceItemImpl that we have renamed to SKU using the elementName parameter. This SKU parameter
contains a personalized PriceInfo object with an amount property that is the lowest price available to the user
at that time.
The Switch component invoked on the Format parameter allows many ways of displaying prices using a single
JSP fragment. Site developers who do not require this flexibility may want to remove this Switch for improved
performance.
The following example shows how we used the /atg/commerce/pricing/PriceItem component in the
Pioneer Cycling Store to display prices in an extremely personalized way.
<!-- Display the price of the SKU: --> <dsp:droplet name="PriceItem"> <dsp:param name="item" param="Product.childSkus[0]"/> <dsp:param name="elementName" value="Sku"/> <dsp:oparam name="output">
<!-- Check which Format caller wants: --> <dsp:droplet name="Switch"> <dsp:param name="value" param="Format"/>
<!-- Displays the List Price: --> <dsp:oparam name="ListPrice">
6 Pricing 129
<dsp:valueof converter="currency" param="Sku.priceInfo.ListPrice"/> </dsp:oparam>
<!-- Displays the SALE Price. Doesn't check if Item is on sale: --> <dsp:oparam name="SalePrice"> <dsp:valueof converter="currency" param="Sku.priceInfo.SalePrice"/> </dsp:oparam>
<!-- Displays the YOUR Price: --> <dsp:oparam name="YourOnly"> <dsp:valueof converter="currency" param="Sku.priceInfo.amount"/> </dsp:oparam>
<dsp:oparam name="All"> <br><b>All price info (for debug):</b> <br><b>OnSale: </b><dsp:valueof param="Sku.priceInfo.onSale"/> <br><b>ListPrice: </b><dsp:valueof converter="currency" param="Sku.priceInfo.ListPrice"/> <br><b>SalePrice: </b><dsp:valueof converter="currency" param="Sku.priceInfo.SalePrice"/> <br><b>Amount: </b><dsp:valueof converter="currency" param="Sku.priceInfo.amount"/> </dsp:oparam>
<dsp:oparam name="default"> <!-- Check if Product is on sale: --> <dsp:droplet name="Switch"> <dsp:param name="value" param="Sku.priceInfo.onSale"/> <dsp:oparam name="false"><dsp:valueof converter="currency" param="Sku.priceInfo.amount"> no price</dsp:valueof></dsp:oparam> <dsp:oparam name="true">On Sale for <dsp:valueof converter="currency" param="Sku.priceInfo.salePrice"> no price</dsp:valueof> </dsp:oparam> </dsp:droplet> </dsp:oparam> </dsp:droplet> </dsp:oparam> </dsp:droplet> <!-- PriceItem -->
The example above is an extremely resource-intensive way to display prices. If your site does not require
dynamic generation of personalized pricing, you can remove any invocations to the PriceItem component for
a large performance improvement.
The example below shows how you can use static pricing and still display listPrice or salePrice using the
onSale property.
<dsp:droplet name="Switch"> <dsp:param name="value" value="product.childSkus[0].onSale"/> <dsp:oparam name="false"> <dsp:valueof param="product.childSkus[0].listPrice"> </dsp:oparam> <dsp:oparam name="true"> <dsp:valueof param="product.childSkus[0].salePrice"> </dsp:oparam>
130 6 Pricing
</dsp:droplet>
Shipping Prices
ATG Consumer Commerce provides a robust framework in which multiple shipping methods and discounts can
be defined. There are two different types of shipping price calculations: calculating the base price of shipping
(for example, setting the price of any shipment via a Ground method at $5) and calculating a discount on the
existing price of shipping (for example, giving 10% off the existing shipping cost).
To calculate the base price of shipping ATG Consumer Commerce provides several different calculators:
atg.commerce.pricing.FixedPriceShippingCalculatoratg.commerce.pricing.PriceRangeShippingCalculatoratg.commerce.pricing.WeightRangeShippingCalculator
In addition to these calculators, there are several abstract calculators that can be easily extended to gain more
functionality. For more information, refer to the section on calculators in the Using and Extending Pricing Services
chapter in the ATG Commerce Programming Guide.
Shipping Methods
We chose to offer three different shipping methods to the customers of the Pioneer Cycling Store: Ground, Two
Day, and Next Day. During the checkout process, the customer elects one of these three shipping methods.
The base price of shipping for each method is calculated using a specific instance of one of the calculators
mentioned above.
Shipping Calculators
There are three steps to setting up the various shipping methods: configuring the specific calculator instances,
adding these calculator instances to our ShippingPricingEngine, and finally, displaying these shipping
methods to the customer via AvailableShippingMethods.
Configuring Shipping Calculators
In the Pioneer store, we used the atg.commerce.pricing.FixedPriceShippingCalculator for the Ground
and Two Day shipping methods and atg.commerce.pricing.PriceRangeShippingCalculator for Next
Day shipping. We created properties files to generate instances of these calculators. For example, for the Ground
shipping method, we created the Ground.properties file with the following configuration:
# Define a Ground shipping method$class=atg.commerce.pricing.FixedPriceShippingCalculator
# name of shipping methodshippingMethod=Ground# cost of shipping methodamount=5.0
6 Pricing 131
This properties file creates an instance of a shipping calculator and sets the properties of that new shipping
calculator. It gives it a method name (which is exposed to the user when they are selecting their shipping
method) and the fixed cost of shipping. In this case, when the calculator is exposed to the user as a shipping
method it has the name “Ground” associated with it and the cost is $5.
We also chose to use another instance of the FixedPriceShippingCalculator for the Pioneer store to set the
cost of all Two Day shipments to $20. This is what the properties file looks like:
# Define a Two day shipping method$class=atg.commerce.pricing.FixedPriceShippingCalculator
# name of this shipping methodshippingMethod=Two Day
# cost of shipping methodamount=20.0
Finally, we created a Next Day shipping method using an instance of the PriceRangeShippingCalculator.
The PriceRangeShippingCalculator sets the cost of shipping based upon a range of prices. Here is the
properties file that we created:
# Define a Next Day shipping method$class=atg.commerce.pricing.PriceRangeShippingCalculator
# name of shipping methodshippingMethod=Next Day
# range of costsranges=00.00:10.00:10.00,\ 10.01:30.00:15.00,\ 30.01:50.00:20.00,\ 50.01:70.00:25.00,\ 70.01:100.00:30.00,\ 100.01:MAX_VALUE:40.00
The first line of the ranges property states, “if the cost is between $0 and $10, set the price to $10.” The next line
states, “if the price is between $10.01 and $30 set the price to $15.” This allows the cost of shipping to vary based
upon the amount of the order.
We created these properties files at the Nucleus namespace at:
/atg/commerce/pricing/shipping/Ground.properties/atg/commerce/pricing/shipping/TwoDay.properties/atg/commerce/pricing/shipping/NextDay.properties
Adding Calculators to Pricing Engine
The ShippingPricingEngine is a structural component that does not actually calculate shipping prices itself.
The actual price calculations are performed by the Pricing Calculator components that are configured as the
preCalculators and postCalculators properties of the ShippingPricingEngine. This component is
located at /atg/commerce/pricing/ShippingPricingEngine.
# Calculators that ShippingPricingEngine will use to determine shippingpreCalculators=shipping/Ground,\
132 6 Pricing
shipping/NextDay,\ shipping/TwoDay
The values shipping/Ground and shipping/NextDay and shipping/TwoDay are Nucleus references to the
calculators that were defined above.
Enabling Customers to Choose Shipping Methods
During the checkout process, the customer must choose from a list of available shipping methods. Then, the
selected method is associated with the shipping group selected in the customer’s order.
To display the various shipping methods that a customer can choose in the Pioneer Cycling store, we used the
servlet bean located at /atg/commerce/pricing/AvailableShippingMethods. It renders an array of the
availableShippingMethods.
We used the AvailableShippingMethods servlet bean instead of accessing the availableMethods property
of the ShippingPricingEngine for several reasons. Most importantly, the AvailableShippingMethods
servlet bean determines which shipping methods are available for a particular shipping group. For example, an
order going to Alaska might not be able to be shipped overnight.
<droplet bean="AvailableShippingMethods"> <param name="shippingGroup" value="bean:ShoppingCartModifier.shippingGroup"> <param name="pricingModels" value="bean:UserPricingModels.shippingPricingModels"> <param name="profile" value="bean:Profile"> <oparam name="output"> <select bean="ShoppingCartModifier.shippingGroup.shippingMethod"> <droplet bean="ForEach"> <param name="array" value="param:availableShippingMethods"> <param name="elementName" value="method"> <oparam name="output"> <option value="param:method"><valueof param="method"></valueof> </oparam> </droplet> </select> </oparam></droplet>
This JSP does several things. First, it exposes the list of available shipping methods via the parameter
availableShippingMethods of the AvailableShippingMethods servlet bean. Second, it iterates
through this array using a ForEach component. Third, it sets the name of the shipping method that the
user selects in the shippingGroup. In this case, it is the single shippingGroup that is created in an Order.
If there was more than one shipping group, like on some of the Pioneer Cycling store checkout pages
(shipping_info_multiple.jsp) then the correct shippingGroup would need to be referenced by iterating
through the list of shipping groups in the order, and calling the AvailableShippingMethods servlet bean for
each of them.
Creating Your Own Pricing Models
A PricingModel is a RepositoryItem that can calculate the price of an item, given a method to calculate a
price and parameters to use. The pricing models available in ATG Consumer Commerce take care of the pricing
6 Pricing 133
needs for most store sites, but you may want to offer your own promotions. This section describes the pricing
models that exist in ATG Consumer Commerce and how to create your own pricing model.
ATG Consumer Commerce supports the following types of pricing calculators:
• Percent discount (for example, 5% off all helmets)
• Set price (for example, buy 2 jerseys, get 1 jersey for $20)
• Amount off (for example, $5 off all gloves).
Discounts can be applied to any of the following:
• Items (objects in shopping cart)
• Order (the order total)
• Tax (tax on an order)
• Shipping (cost of shipping).
A pricing model has two pieces of information, pricingCalculatorService and adjuster. The
pricingCalculatorService specifies which calculator to use to determine the discount. The adjuster
indicates how much to adjust. For example, in the case of a percent discount, if the adjuster is set to 5, the
calculator takes a 5% discount off an item, an order, tax, or shipping charges.
134 6 Pricing
7 Order Processing 135
7 Order Processing
This chapter describes the order capture functionality in the Pioneer Cycling site and includes the following
sections:
Reviewing and Editing Orders (page 135)
Describes how customers can review and modify production information in their orders.
Entering Information Necessary for Checkout (page 146)
Describes how to enter shipping and payment information.
Additional Order Capture Information (page 164)
Describes how to validate credit cards and display error messages.
Express Checkout (page 165)Describes how to set up express checkout capabilities.
Order capture can be broken down into two large parts: allowing the user to review and edit items chosen for
purchase, and capturing the information necessary for the payment and shipping of their purchased goods.
There are two different methods for the second part of the process in the Pioneer Cycling store: a basic process
and a more advanced process.
Note: The order processing pages for Pioneer Cycling (JSP version) use a mixture of tags from the DSP tag
libraries. It might seem more intuitive to write the pages using only DSP tags, but we replaced some of the DSP
tags with simpler core tags to work around the limitations of some application servers’ JSP execution engines.
Reviewing and Editing Orders
checkout/cart.jsp and checkout/full/cart_edit.jsp allow users to review and edit their product
selections and quantities.
136 7 Order Processing
snapshot of cart.jsp
7 Order Processing 137
snapshot of cart_edit.jsp
The Shopping Cart Page
The “Shopping Cart” page, cart.jsp, gives users the ability to
• View the line item pricing for a particular item.
• View an order subtotal.
• Change the quantity ordered of a particular item in their cart.
• Delete items from their shopping cart.
The following sections discuss how each of these pieces of functionality is performed.
Item Pricing
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
138 7 Order Processing
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">
7 Order Processing 139
<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.
140 7 Order Processing
<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.
7 Order Processing 141
<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">
142 7 Order Processing
<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.
7 Order Processing 143
<!-- 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
144 7 Order Processing
• 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
7 Order Processing 145
.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>
146 7 Order Processing
</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.
Entering Information Necessary for Checkout
The second part of the order capture is the checkout process. This process collects information such as shipping
information and payment information. In the Pioneer Cycling store, we implemented two different types of
checkout processes: basic and advanced.
7 Order Processing 147
The basic process allows a user to perform the following functions:
• Enter a single address that all purchased goods can be sent to
• Enter a single credit card number as a payment method
The advanced checkout process adds to this functionality by allowing:
• The ability to ship to multiple locations
• The ability to pay with multiple payment methods
We used the ShoppingCartModifier component to allow a user to perform these operations.
Secure Checkout
When customers enter credit card information in the checkout process or on the My Profile page, they
must be transferred from a non-secure server to a secure server. In order to configure this, you must
change the properties of the ProtocolChange component. You can edit these properties in the Pages and
Components> Components by path section of the ACC. The component pathname is /atg/dynamo/droplet/
ProtocolChange.
Set the values of the secure properties appropriate to your server. Note that the securePort (the port setup for
secure server) need not equal 443.
Set the enable property to true.
148 7 Order Processing
Changing the properties of atg/dynamo/droplet/ProtocolChange in the Component Editor
If you do not change these values, the ProtocolChange component stays in non-secure (http) protocol. This is
useful for development environments.
On cart.jsp, the ProtocolChange servlet bean is invoked to set the success URL for the checkout button to
the SSL equivalent of its former location.
7 Order Processing 149
On the co_confirm page, ProtocolChange is invoked again, but this time it sets the success URL of the
confirm button to the non-SSL equivalent of the former Thank You page.
Choosing Between Basic and Advanced Checkout
In the Pioneer Cycling store, we provided users with the ability to switch between basic and advanced checkout
processes. By default, the English locale uses the advanced checkout process and the French and Japanese
sections use the basic checkout. The moveToPurchaseInfoSuccessURL parameter in cart.jsp is set to
determine which checkout process is used. Here is the JSP to use basic checkout:
<!-- CHECKOUT Order button: -->
<dsp:input bean="ShoppingCartModifier.moveToPurchaseInfoByRelId" type="submit" value="Checkout"/>
<dsp:input bean="ShoppingCartModifier.moveToPurchaseInfoByRelIdSuccessURL" type="hidden" value="basic/order_info.jsp"/>
<dsp:input bean="ShoppingCartModifier.moveToPurchaseInfoByRelIdErrorURL" type="hidden" value="cart.jsp"/>
The basic process is used if the moveToPurchaseInfoSuccessURL parameter is set to basic/
order_info.jsp. Thus, if the above JSP is uncommented the basic checkout process is used. The following
section creates a button for advanced checkout.
<dsp:input bean="ShoppingCartModifier.moveToPurchaseInfoByRelId" type="submit" value=" Checkout"/>
<%--Use this snippet if you want regular http checkout:<dsp:input bean="ShoppingCartModifier.moveToPurchaseInfoByRelIdSuccessURL" type="hidden" value="full/order_info.jsp"/>
--%>
<%-- Use this snippet if you want to use SSL checkout:--%><dsp:droplet name="/atg/dynamo/droplet/ProtocolChange"> <dsp:param name="inUrl" value="full/order_info.jsp"/> <dsp:oparam name="output"> <dsp:input bean="ShoppingCartModifier.moveToPurchaseInfoByRelIdSuccessURL" paramvalue="secureUrl" type="hidden"/> </dsp:oparam></dsp:droplet>
<dsp:input bean="ShoppingCartModifier.moveToPurchaseInfoByRelIdErrorURL" type="hidden" value="cart.jsp"/>
Basic Checkout Process
The basic checkout process allows a customer to send an order to a single location using a single credit card as a
form of payment.
150 7 Order Processing
When an order is created, it has by default one shipping group and one payment group:
atg.commerce.order.HardgoodShippingGroup and atg.commerce.order.CreditCard. If these are
the only two groups that exist when a user checks out, then relationship objects are created between
the commerce items in the order and these groups. shipItemRel links all the commerce items to the single
shipping group; payItemRel links all the commerce items to the single payment group. The basic checkout
process populates these two groups.
Basic Shipping Information Input
Shipping information entered by the customer includes the address and method. The customer can input
information to the shipping group by entering the shipping address or copying the billing address to the
shipping address. The customer also can select from a choice of shipping methods.
This JSP enters shipping information into the order object:
<!-- Store the ProfileID in ownerId field of the new address. This tells us this address "belongs to" (and can be edited) by the user. --><dsp:input bean="ShoppingCartModifier.shippingGroup.shippingAddress.ownerId" beanvalue="Profile.id" type="hidden"/>
<dsp:getvalueof id="firstName" bean="ShoppingCartModifier.shippingGroup.shippingAddress.firstName">
<% if((firstName== null) || (firstName.equals(""))) { %> <dsp:input bean="ShoppingCartModifier.shippingGroup.shippingAddress.firstName" beanvalue="Profile.shippingAddress.firstName" size="15" type="text"/><%} else {%> <dsp:input bean="ShoppingCartModifier.shippingGroup.shippingAddress.firstName" size="15" type="text"/><%}%>
</dsp:getvalueof>
<dsp:getvalueof id="middleName" bean="ShoppingCartModifier.shippingGroup.shippingAddress.middleName">
<% if((middleName== null) || (middleName.equals(""))) { %> <dsp:input bean="ShoppingCartModifier.shippingGroup.shippingAddress.middleName" beanvalue="Profile.shippingAddress.middleName" size="10" type="text"/><%} else {%> <dsp:input bean="ShoppingCartModifier.shippingGroup.shippingAddress.middleName" size="10" type="text"/><%}%>
</dsp:getvalueof>
There are several things to notice about this JSP. First, note that each piece of information
is inserted somewhere in the default shipping group. The line <dsp:input type="text"
bean="ShoppingCartModifier.shippingGroup.shippingAddress"> refers to the default shipping group
7 Order Processing 151
address and then to each element of the shippingAddress. For example, shippingAddress.state refers to
the state to which the order is sent.
If the default shippingAddress is not empty, it displays the information in it. If it is empty, it tries to populate
those fields with information from the user’s Profile object. If a customer is registered and is going through the
checkout process for the first time, her shipping address information is prepopulated with her information from
her Profile. She can then choose to use this information or to change it. The customer can also go back to the
store to add other items to her cart.
This JSP sets the ownerId of the address to the id of the current user’s Profile. This indicates that the user is
the owner of this address and has the ability to edit that address if necessary.
Copy billing address to shipping address
In the basic checkout, an unregistered customer must enter a billing address first. She can then elect to copy the
billing address information to her shipping address or enter a new one. The ability to copy the billing address is
handled by the following JSP:
<!-- Indicate if the billing address should be copied to the shipping address --> <dsp:input bean="ShoppingCartModifier.CopyBillingAddrToShippingAddr" name="copyAddress" type="radio" checked="<%=true%>" value="true"/> Ship items to my billing address<br> <dsp:input bean="ShoppingCartModifier.CopyBillAddrToShippingAddr" name="copyAddress" type="radio" value="false"/> Ship items to the new address below <p>
If the customer decides to use her billing address as the shipping address, the Boolean property
ShoppingCartModifier.copyBillingAddrToShippingAddr remains true, and the billing address that she
entered is automatically copied to the shipping address. Otherwise, the Boolean property is set to false and
nothing is copied.
Choosing a shipping Method
Finally, the customer must select a shipping method such as ground or next day. The following JSP code handles
shipping method selection. Note: The “. . .” marker in the following code sample indicate a place where code has
been removed to clarify the sample.
<dsp:importbean bean="/atg/commerce/pricing/UserPricingModels"/><dsp:importbean bean="/atg/commerce/pricing/AvailableShippingMethods"/><dsp:importbean bean="/atg/userprofiling/Profile"/><dsp:importbean bean="/atg/commerce/order/ShoppingCartModifier"/><dsp:importbean bean="/atg/dynamo/droplet/ForEach"/><dsp:importbean bean="/atg/dynamo/droplet/Switch"/>
. . .
<!-- The droplet AvailableShippingMethods is able to give us a list of the shipping methods that should be available to a user. --> Shipping Method<br><dsp:droplet name="AvailableShippingMethods"><dsp:param bean="ShoppingCartModifier.shippingGroup" name="shippingGroup"/><dsp:param bean="UserPricingModels.shippingPricingModels" name="pricingModels"/><dsp:param bean="Profile" name="profile"/><dsp:oparam name="output"> <dsp:select bean="ShoppingCartModifier.shippingGroup.shippingMethod">
152 7 Order Processing
<dsp:droplet name="ForEach"> <dsp:param name="array" param="availableShippingMethods"/> <dsp:param name="elementName" value="method"/> <dsp:oparam name="output"> <dsp:getvalueof id="option199" param="method" idtype="java.lang.String"><dsp:option value="<%=option199%>"/></dsp:getvalueof><dsp:valueof param="method"/> </dsp:oparam> </dsp:droplet> </dsp:select></dsp:oparam></dsp:droplet>
The shipping method that the customer selects is inserted into the shippingMethod property of the
shippingGroup. For more information, consult the Shipping Prices (page 130) section of the Pricing chapter in
this document.
ATG Consumer Commerce also supports soft goods. For example, gift certificates are delivered via e-mail. See
the Gift Certificates (page 200) section in the Merchandising chapter of this guide for more information.
Billing information
The second part of the basic checkout process is when the customer enters the billing address and credit
information.
For more information about credit card properties, see the Working with Purchase Process Objects chapter of the
ATG Commerce Programming Guide.
Getting billing address information
The following JSP captures the billing address for the credit card:
Name<br><dsp:droplet name="IsEmpty"> <dsp:param bean="ShoppingCartModifier.paymentGroup.billingAddress.firstName" name="value"/> <dsp:oparam name="false"><dsp:input bean="ShoppingCartModifier.paymentGroup.billingAddress.firstName" size="15" type="text"/> </dsp:oparam> <dsp:oparam name="true"><dsp:input bean="ShoppingCartModifier.paymentGroup.billingAddress.firstName" beanvalue="Profile.billingAddress.firstName" size="15" type="text"/> </dsp:oparam></dsp:droplet>
<dsp:droplet name="IsEmpty"> <dsp:param bean="ShoppingCartModifier.paymentGroup.billingAddress.middleName" name="value"/> <dsp:oparam name="false"><dsp:input bean="ShoppingCartModifier.paymentGroup.billingAddress.middleName" size="10" type="text"/> </dsp:oparam> <dsp:oparam name="true"><dsp:input bean="ShoppingCartModifier.paymentGroup.billingAddress.middleName" beanvalue="Profile.billingAddress.middleName" size="10" type="text"/> </dsp:oparam>
7 Order Processing 153
</dsp:droplet>
Credit Card Information
In addition to the address associated with the paymentGroup, it is necessary to obtain credit card information
such as the credit card number and expiration date. The credit card object in the order is accessed through the
paymentGroup property of the ShoppingCartModifier.
New card type <dsp:select bean="ShoppingCartModifier.paymentGroup.creditCardType" required="<%=true%>"><dsp:option value="Visa"/>Visa<dsp:option value="MasterCard"/>Master Card<dsp:option value="AmericanExpress"/>American Express<dsp:option value="Discover"/>Discover</dsp:select><br>
New card number and expiration date<br><dsp:input bean="ShoppingCartModifier.paymentGroup.creditCardNumber" maxsize="20" size="20" type="text" required="<%=true%>"/><br>
<!-- Set the month that the card will expire on -->Month: <dsp:select bean="ShoppingCartModifier.paymentGroup.expirationMonth"><dsp:option value="1"/>January<dsp:option value="2"/>February<dsp:option value="3"/>March<dsp:option value="4"/>April<dsp:option value="5"/>May<dsp:option value="6"/>June<dsp:option value="7"/>July<dsp:option value="8"/>August<dsp:option value="9"/>September<dsp:option value="10"/>October<dsp:option value="11"/>November<dsp:option value="12"/>December</dsp:select>
<!-- Set the year that the card will expire on -->Year: <dsp:select bean="ShoppingCartModifier.paymentGroup.expirationYear"><dsp:option value="2000"/>2000<dsp:option value="2001"/>2001<dsp:option value="2002"/>2002<dsp:option value="2003"/>2003<dsp:option value="2004"/>2004<dsp:option value="2005"/>2005<dsp:option value="2006"/>2006<dsp:option value="2007"/>2007<dsp:option value="2008"/>2008<dsp:option value="2009"/>2009<dsp:option value="2010"/>2010</dsp:select>
Advanced Checkout Process
The advanced checkout process builds on the basic checkout process by giving the user the ability to ship to
multiple locations as well as to pay with multiple forms of payment. While the user can elect to ship/pay with
154 7 Order Processing
one method in the advanced checkout process, we do not discuss that here since it mirrors that of the basic
checkout process. The ability to ship and pay with multiple methods are examined.
Ability to Ship to Multiple Locations
The ability to ship items to multiple locations is defined as the ability to take an order and split the items up into
multiple shipping groups. For example, a customer with five bikes and five water bottles in his shopping cart can
choose to send the bikes to his house using over night delivery and the water bottles to his work address using
second day mail. Or, he could send one bike and two water bottles to his summer house and the remainder to
another address.
Two different pages compose provide this functionality: ship_to_multiple.jsp and
shipping_info_multiple.jsp. Both of these files can be found in <ATG9dir>/PioneerCyclingJSP/j2ee-
apps/pioneer/web-app/en/checkout/full.
ship_to_multiple.jsp creates new shipping groups in the order and assigns each commerceItem to them.
shipping_info_multiple.jsp populates any missing address information to the shipping groups/profile.
ship_to_multiple.jsp allows the user to do several things. Its primary function is to reassign
commerceItems from one shipping address to another. Its secondary function is the ability to create a new
nicknamed address in the user’s address book.
Reassigning Items to Shipping Addresses
Reassigning items from one shipping group to another first requires that the current division of commerce items
among shipping groups be displayed to the user. The JSP iterates through all the commerce items in the order.
7 Order Processing 155
Then, it determines how each commerce item is being shipped and displays its destination address and relevant
quantity. This is the JSP that displays the current information:
<!--For each CommerceItem in the Shopping Cart do the following: --><!-----------------------------------------------------------------><dsp:droplet name="ForEach"> <dsp:param bean="ShoppingCartModifier.order.commerceItems" name="array"/> <dsp:param name="elementName" value="CommerceItem"/> <dsp:oparam name="output">
<!-For each ShippingGroupRelationship for one CommerceItem do the following:-> <!---------------------------------------------------------------------------> <dsp:droplet name="ForEach"> <dsp:param name="array" param="CommerceItem.ShippingGroupRelationships"/> <dsp:param name="elementName" value="ShippingGroupRelationship"/> <dsp:oparam name="output"><dsp:droplet name="Switch"><dsp:param name="value" param="ShippingGroupRelationship.shippingGroup.shippingGroupClassType"/><dsp:oparam name="electronicShippingGroup"> <%/**----------------------------------- * Users can not move things to be shipped via * electronic shipping groups to another type * of shipping group, so don't display goods for * electronic shipping group. */ %></dsp:oparam><dsp:oparam name="hardgoodShippingGroup"> <tr valign=top>
<!-- Display the QTY value: --> <td align=center> <dsp:valueof param="ShippingGroupRelationship.Quantity"/> </td> <td></td>
<!-- Display PRODUCT SKU: --> <td> <dsp:valueof param="CommerceItem.auxiliaryData.catalogRef.displayName"/> </td> <td></td>
Notice that this JSP checks whether the shipping group is of type electronic or hard good. If it is electronic, no
information is displayed since electronic goods are delivered via e-mail unlike tangible goods.
After displaying the current delivery information, the site allows the user to make changes. For instance, if five
bikes are currently going to “My Shipping Address” the user might want to change the destination to be “My
Work Address”. Here is the JSP that does this:
<!-- Each MOVE button will be surrounded by its own "<dsp:form ... action </form>" so that only the values for this one MOVE are saved to the handler: --><dsp:form action="ship_to_multiple.jsp" method="post">
Ship
<!-- Display a textbox containing the current product quantity: -->
156 7 Order Processing
<!-- Save value they enter in QuantityToMove property on handler: --> <dsp:input bean="ShoppingCartModifier.QuantityToMove" paramvalue="ShippingGroupRelationship.Quantity" size="2" maxlength="2" type="text"/>
<!-- Store CommerceItem ID being modified: --> <dsp:input bean="ShoppingCartModifier.CommerceItemIdToEdit" paramvalue="CommerceItem.id" type="hidden"/>
<!-- Store OriginalShippingAddressName --> <dsp:input bean="ShoppingCartModifier.originalShippingAddressName" paramvalue="ShippingGroupRelationship.ShippingGroup.Description" name="3" type="hidden"/>
of these to
<!-- Create a drop-down list with all possible addresses. Save the value they selected in the NewShippingAddressName property on the Handler: --> <dsp:select bean="ShoppingCartModifier.newShippingAddressName"> <%-- <dsp:select bean="ShoppingCartModifier.newShippingAddressName" nodefault="<%=true%>"> --%>
<!-- Add each ProfileAddressName to the dropdown list: --><!-- We want to automatically select the address that the relationship --><!-- exists for. --><dsp:droplet name="ForEach"> <dsp:param bean="ShoppingCartModifier.profileAddressNames" name="array"/> <dsp:param name="elementName" value="ProfileAddressName"/> <dsp:oparam name="output">
<!-- SET THE DEFAULT IN THE DROP DOWN LIST --> <dsp:droplet name="Switch"> <dsp:param name="value" param="ProfileAddressName"/>
<!-- if this is the address this ShippingGroupRelationship is for, select this address in the drop down list: --> <dsp:getvalueof id="nameval4" param="ShippingGroupRelationship.ShippingGroup.Description" idtype="java.lang.String"> <dsp:oparam name="<%=nameval4%>"> <dsp:getvalueof id="option233" param="ProfileAddressName" idtype="java.lang.String"> <dsp:option selected="<%=true%>" value="<%=option233%>"/> </dsp:getvalueof> </dsp:oparam> </dsp:getvalueof>
<!- Otherwise, don't select this address in dropdown list: --> <dsp:oparam name="default"> <dsp:getvalueof id="option241" param="ProfileAddressName" idtype="java.lang.String"> <dsp:option selected="<%=false%>" value="<%=option241%>"/> </dsp:getvalueof> </dsp:oparam>
</dsp:droplet>
7 Order Processing 157
<dsp:valueof param="ProfileAddressName"/> </dsp:oparam></dsp:droplet> <!-- ForEach ProfileAddress -->
</dsp:select> <!-- Dropdown list of ProfileAddressNames -->
<!-- MOVE NOW button --><!-----------------><dsp:getvalueof id="itemId" param="CommerceItem.id" idtype="java.lang.String"> <dsp:input submitvalue="<%=itemId%>" bean="ShoppingCartModifier.MoveToNewShippingAddress" name="<%=itemId%>" type="submit" value="save this change"/> <!-- NOTE: Because we'll have m ultiple buttons on the same page, we need to give them each a unique NAME or correct values aren't stored in the handler: --></dsp:getvalueof>
<!-- Goto this URL if NO errors are found during the MOVE NOW button processing: --><dsp:input bean="ShoppingCartModifier.moveToNewShippingAddressSuccessURL" type="hidden" value="ship_to_multiple.jsp"/> <!--stay on this page-->
<!-- Goto this URL if errors are found during the MOVE NOW button processing: --><dsp:input bean="ShoppingCartModifier.moveToNewShippingAddressErrorURL" type="hidden" value="ship_to_multiple.jsp"/> <!-- stay on this page -->
This JSP shows all of the variables necessary for moving items among shipping groups as well as how the site
displays the address where items are currently being sent.
There are four properties in the ShoppingCartModifier that must be set in order to perform a move from one
shipping group to another: quantityToMove, commerceItemIdToEdit, originalShippingAddressName,
and newShippingAddressName. The JSP above sets all four of them. quantityToMove defaults to
the current quantity in the particular shipping group, although the user can change this number.
The originalShippingAddressName is automatically set to the current shipping group name. The
newShippingAddressName is set to the address selected by the user out of their list of addresses. Finally, the
commerceItemIdToEdit indicates the commerce item to be moved from one shipping group to another. The
description of the shipping group identifies each shipping group. It is set to the nickname of the address
associated with the shipping group.
Finally, we need to submit the form to the ShoppingCartModifier to instantiate the new shipping group.
ship_to_multiple.jsp has multiple form elements on one page. That is, each line listing a commerce item
and its associated shipping group is surrounded by its own form. If this is not done, then the properties of the
ShoppingCartModifier being set, such as quantityToMove, will be written over many times since each
listed commerce item would try to set this property.
The JSP submits the form to the handleMoveToNewShippingAddress() method of the
ShoppingCartModifier component. It also sets the success and error URLs.
Creating New Nicknamed Addresses
Users can create new nicknamed addresses in their address books on ship_to_multiple.jsp. If the user
creates a new nicknamed address, when the page reloads he has the option of sending his goods to this
nicknamed address. He then can associate an actual address with this nickname.
158 7 Order Processing
The handleCreateAddress() method of the ShoppingCartModifier handles creating a new nickname in
the address book. Here is the JSP to create a new address in the address book:
<!-- The user can enter the new address in this text box: --><dsp:input bean="ShoppingCartModifier.newShippingAddressName" size="20" type="text" value=""/><br>
<!-- Pushing the CREATE ADDRESS button will add the address to the user's Profile under the secondaryAddress attribute. The new nickname will also be added to the list of addresses in the drop-down list. --><dsp:input bean="ShoppingCartModifier.createAddress" type="submit" value="Create this address nickname"/>
<!-- GoTo this URL if NO errors are found during the CREATE ADDRESS button processing: --><dsp:input bean="ShoppingCartModifier.createAddressSuccessURL" type="hidden" value="ship_to_multiple.jsp"/> <!-- stay on same page -->
<!-- GoTo this URL if errors are found during the CREATE ADDRESS button processing: --><dsp:input bean="ShoppingCartModifier.createAddressErrorURL" type="hidden" value="ship_to_multiple.jsp"/> <!-- stay on same page -->
The newShippingAddressName property must be set for handleCreateNewAddress to work properly. Then,
when the user submits the form the address is automatically added to the user’s address book.
Populating Missing Address Information
Finally, users must enter any missing address information. For example, if a user creates a new nickname address
on the ship_to_multiple.jsp named “Summer House,” this address does not contain any information other
than the name “Summer House”. If the user elects to send a bike there, shipping_info_multiple.jsp needs
to obtain the actual address.
There are several things that are useful to note about the process for capturing this information. First, when a
user enters a new address, it needs to be inserted into two places: the shipping group that is part of the order
and the user’s address book. Second, if the address information is filled in, it is displayed; if not, a form appears
asking the user to provide it. Finally, to display the current arrangement of commerce items among shipping
groups, it is necessary to iterate through the shipping groups in the order and list the commerce items for each
one. This is done just as on ship_to_multiple.jsp.
First, the code determines if the address information is filled in, by testing for the presence of firstName. (We
decided that firstName is present in virtually any address so it is a good indicator that the address is filled in.)
<dsp:droplet name="IsEmpty"><dsp:param bean="ShoppingCartModifier.Order.ShippingGroups[param:shippingGroupIndex].shippingAddress.firstName" name="value"/>
If the address is missing, the user must supply it. The address is copied into the shipping group of the order
object and into the user’s address book by adding the name of the address (held in the description property)
to the addressesToCopy array property of the ShoppingCartModifier. When the user submits the form, all
addresses in this array get copied from the shipping groups in the order object to the user’s address book.
<%-- Mark this address to be copied from the Order to the Profile so we
7 Order Processing 159
can persist it --%><dsp:input bean="ShoppingCartModifier.AddressesToCopy" paramvalue="ShippingGroup.Description" type="hidden"/><%-- Set the ownerId field of the new address so that we know that this address belongs to the profile. --%><dsp:input bean="ShoppingCartModifier.Order.ShippingGroups[param:shippingGroupIndex].shippingAddress.ownerId" beanvalue="Profile.id" type="hidden"/>
Name<br>
<dsp:input bean="ShoppingCartModifier.Order.ShippingGroups[param:shippingGroupIndex].shippingAddress.firstName" size="15" type="text"/>
<dsp:input bean="ShoppingCartModifier.Order.ShippingGroups[param:shippingGroupIndex].shippingAddress.middleName" size="10" type="text"/>
<dsp:input bean="ShoppingCartModifier.Order.ShippingGroups[param:shippingGroupIndex].shippingAddress.lastName" size="15" type="text"/><br>
Street address <br>
<dsp:input bean="ShoppingCartModifier.Order.ShippingGroups[param:shippingGroupIndex].shippingAddress.address1" size="40" type="text"/><br>
<dsp:input bean="ShoppingCartModifier.Order.ShippingGroups[param:shippingGroupIndex].shippingAddress.address2" size="40" type="text"/><br>
City, State, Postal Code<Br>
<dsp:input bean="ShoppingCartModifier.Order.ShippingGroups[param:shippingGroupIndex].shippingAddress.city" size="20" type="text"/>
<dsp:select bean="ShoppingCartModifier.Order.ShippingGroups[param:shippingGroupIndex].shippingAddress.state"> <%@ include file="../../user/StatePicker.jspf" %></dsp:select>
<dsp:input bean="ShoppingCartModifier.Order.ShippingGroups[param:shippingGroupIndex].shippingAddress.postalCode" size="10" type="text"/><br>
Country<br>
<dsp:select bean="ShoppingCartModifier.Order.ShippingGroups[param:shippingGroupIndex].shippingAddress.country"> <%@ include file="../../user/CountryPicker.jspf" %></dsp:select><br>
If the address already exists, the page displays it unless it is private and owned by someone else. For example,
if the user is buying something for another person from a gift list, only the city and state of the address are
displayed. Please refer to the Gift Registry section of the Merchandising (page 193) chapter of this guide for
more information. The following JSP code determines whether or not the address is private.
<dsp:droplet name="Switch"> <dsp:param name="value" param="ShippingGroup.shippingAddress.ownerId"/> <dsp:getvalueof id="nameval4" bean="Profile.id" idtype="java.lang.String"> <dsp:oparam name="<%=nameval4%>">
160 7 Order Processing
<dsp:include page="../../user/DisplayAddress.jsp" flush="true"> <dsp:param name="address"bean="ShoppingCartModifier.Order.ShippingGroups[param:shippingGroupIndex].shippingAddress"/> <dsp:param name="private" value="false"/> </dsp:include> </dsp:oparam> </dsp:getvalueof> <dsp:oparam name="default"> <dsp:include page="../../user/DisplayAddress.jsp" flush="true"> <dsp:param name="address" bean="ShoppingCartModifier.Order.ShippingGroups[param:shippingGroupIndex].shippingAddress"/> <dsp:param name="private" value="true"/> </dsp:include> </dsp:oparam></dsp:droplet>
The last piece of information needed to create a shipping group is the shipping method. For example, customers
can choose Next Day delivery, Two Day delivery, etc. For instructions on setting the shipping method, see the
Basic Checkout section.
The final part of this process is the submission of the ship_info_multiple.jsp form and redirecting the
user to the payment screens. ship_info_multiple.jsp submits to handleShipToDone(), a method of the
ShoppingCartModifier that copies addresses from the order object to the profile. The submit fields look like
this:
<!-- CONTINUE button: --><!----------------------><!-- Pushing the CONTINUE button will move all commerce items to their new shipping groups, and move on to next page: --><dsp:input bean="ShoppingCartModifier.ShipToDone" type="submit" value="Continue---> "/>
<!-- Goto this URL if NO errors are found during the CONTINUE button processing: --><dsp:input bean="ShoppingCartModifier.ShipToDoneSuccessURL" type="hidden" value="payment_info_returning.jsp"/>
<!-- Goto this URL if errors are found during the CONTINUE button processing: --><dsp:input bean="ShoppingCartModifier.ShipToDoneErrorURL" type="hidden" value="shipping_info_multiple.jsp"/> <!-- stay on same page -->
Multiple Payment Methods
The advanced checkout process supports payment with multiple methods. For example, a customer can pay
with a combination of a credit card and a gift certificate. The payment screen allows a user to select from the
saved credit card list in his profile. The following section shows how users enter credit cards and gift certificates.
Entering a Credit Card
When a user enters a credit card he can either elect to use a credit card from the list of credit cards he saved
in his Profile or enter a new credit card. If he chooses to use an existing one, the ShoppingCartModifier
component copies all the fields for the credit card from the Profile to the payment group. The JSP to select an
existing credit card is:
7 Order Processing 161
<dsp:droplet name="ForEach"> <dsp:param bean="Profile.creditCards" name="array"/> <dsp:param name="elementName" value="creditCard"/> <dsp:oparam name="output"> <dsp:droplet name="Switch"> <dsp:param name="value" param="count"/> <dsp:oparam name="1"> <dsp:input bean="ShoppingCartModifier.selectedCreditCardName" paramvalue="key" name="creditCardToUse" type="radio" checked="<%=true%>"/> </dsp:oparam> <dsp:oparam name="default"> <dsp:input bean="ShoppingCartModifier.selectedCreditCardName" paramvalue="key" name="creditCardToUse" type="radio"/> </dsp:oparam> </dsp:droplet> <b><dsp:valueof converter="creditcard" param="key"/></b><br> </dsp:oparam></dsp:droplet>
This JSP sets the value of the selectedCreditCardName property of the ShoppingCartModifier to be
the selected credit card from the Profile. When the customer submits an order, the credit card information is
obtained from the Profile, using the nickname supplied in the selectedCreditCardName property.
When the user uses a new credit card, the information is inserted directly into the payment group
associated with the order and also copied to the Profile list of credit cards. There are two steps. First, the
selectedCreditCardName property of the ShoppingCartModifier must be set to the value new to flag the
ShoppingCartModifier that the card is a new one and should be copied to the user’s Profile. Second, the
credit card information is placed into the payment group. This includes the billing address, credit card number,
etc.
The following JSP snippet shows how we asked the user for payment information for each payment group in the
order. This code can be found in <ATG9dir>/PioneerCyclingJSP/
j2ee-apps/pioneer/web-app/en/checkout/full/payment_info_returning.jsp.
<dsp:droplet name="ForEach"><dsp:param bean="ShoppingCartModifier.creditCardPaymentGroups" name="array"/><dsp:oparam name="output"> <p><dsp:input bean="ShoppingCartModifier.selectedCreditCardName" name="creditCardToUse" type="radio" value="new"/> <b>New card below</b><p>
<b>Billing Address</b><p>
Name<br><dsp:input bean="ShoppingCartModifier.creditCardPaymentGroups[param:index].billingAddress.firstName" beanvalue="Profile.billingAddress.firstName" size="20" type="text"/>
<dsp:input bean="ShoppingCartModifier.creditCardPaymentGroups[param:index].billingAddress.middleName" beanvalue="Profile.billingAddress.middleName" size="10" type="text"/>
<dsp:input bean="ShoppingCartModifier.creditCardPaymentGroups[param:index].billingAddress.lastName" beanvalue="Profile.billingAddress.lastName" size="20" type="text"/><br>
162 7 Order Processing
Street address <br><dsp:input bean="ShoppingCartModifier.creditCardPaymentGroups[param:index].billingAddress.address1" beanvalue="Profile.billingAddress.address1" size="40" type="text"/><br>
<dsp:input bean="ShoppingCartModifier.creditCardPaymentGroups[param:index].billingAddress.address2" beanvalue="Profile.billingAddress.address2" size="40" type="text"/><br>
City, State, Postal Code<br><dsp:input bean="ShoppingCartModifier.creditCardPaymentGroups[param:index].billingAddress.city" beanvalue="Profile.billingAddress.city" size="20" type="text"/>
<%/* ------------------------------------------------------------ * The setvalue is a used to preload the value from * Profile.billingAddress into the address we are modifying. * After the form is submitted the actual value from the form * will be entered into the address. -------------------------------------------------------*/%> <dsp:setvalue bean="ShoppingCartModifier.creditCardPaymentGroups[param:index].billingAddress.state" beanvalue="Profile.billingAddress.state"/> <dsp:select bean="ShoppingCartModifier.creditCardPaymentGroups[param:index].billingAddress.state"> <%@ include file="../../user/StatePicker.jspf" %> </dsp:select>
<dsp:input bean="ShoppingCartModifier.creditCardPaymentGroups[param:index].billingAddress.postalCode" beanvalue="Profile.billingAddress.postalCode" size="10" type="text"/><br>
Country<br> <dsp:setvalue bean="ShoppingCartModifier.creditCardPaymentGroups[param:index].billingAddress.country" beanvalue="Profile.billingAddress.country"/> <dsp:select bean="ShoppingCartModifier.creditCardPaymentGroups[param:index].billingAddress.country"> <%@ include file="../../user/CountryPicker.jspf" %> </dsp:select><br>
Phone Number<br> <dsp:input bean="ShoppingCartModifier.creditCardPaymentGroups[param:index].billingAddress.phoneNumber" beanvalue="Profile.billingAddress.phoneNumber" size="20" type="text"/><br>
<p><b>Credit Card</b><p>
<!-- Set the type of card -->New card type <dsp:selectbean="ShoppingCartModifier.creditCardPaymentGroups[0].creditCardType"required="<%=true%>"><dsp:option value="Visa"/>Visa<dsp:option value="MasterCard"/>Master Card<dsp:option value="AmericanExpress"/>American Express
7 Order Processing 163
<dsp:option value="Discover"/>Discover</dsp:select><br>
<!-- Set card type and expiration date --> New card number and expiration date<br><dsp:input bean="ShoppingCartModifier.creditCardPaymentGroups[0].creditCardNumber" maxsize="20" size="20" type="text"/><br>
<!-- Set the month that the card will expire on -->Month: <dsp:select bean="ShoppingCartModifier.creditCardPaymentGroups[0].expirationMonth"><dsp:option value="1"/>January<dsp:option value="2"/>February<dsp:option value="3"/>March<dsp:option value="4"/>April<dsp:option value="5"/>May<dsp:option value="6"/>June<dsp:option value="7"/>July<dsp:option value="8"/>August<dsp:option value="9"/>September<dsp:option value="10"/>October<dsp:option value="11"/>November<dsp:option value="12"/>December</dsp:select><br>
<!-- Set the year that the card will expire on -->Year: <dsp:select bean="ShoppingCartModifier.creditCardPaymentGroups[0].expirationYear"><dsp:option value="2002"/>2002<dsp:option value="2003"/>2003<dsp:option value="2004"/>2004<dsp:option value="2005"/>2005<dsp:option value="2006"/>2006<dsp:option value="2007"/>2007<dsp:option value="2008"/>2008<dsp:option value="2009"/>2009<dsp:option value="2010"/>2010<dsp:option value="2011"/>2011<dsp:option value="2012"/>2012</dsp:select></dsp:oparam></dsp:droplet>
Note that credit card information is not inserted directly into ShoppingCartModifier.paymentGroup
because customers may use a combination of payment methods. The ShoppingCartModifier has a
CreditCard payment group as one of its properties. After the values for the new credit card are copied
to this location, the ShoppingCartModifier merges this new payment group into the right place in
the order. The property of the ShoppingCartModifier that contains these payment groups is the
creditCardPaymentGroups. This is an array property of credit card payment groups that defaults to a size of
one. This credit card is copied to the order as well as placed into the user’s Profile property creditCards. The
nickname for the credit card in the creditCards property of the Profile is the credit card number.
There are two final points about adding credit cards to an order that should be discussed. First, when a credit
card is added to the order, it always is given a relationship to the Order object of OrderAmountRemaining. This
means that the credit card is used to pay for any part of the order that is not accounted for by other payment
methods. For a discussion of relationships between payment groups and orders, consult the section on Using
Relationship Objects in Working with Purchase Process Objects chapter in the ATG Commerce Programming Guide.
164 7 Order Processing
ATG Consumer Commerce allows you to implement multiple credit card payment. However, the Pioneer Cycling
store only supports one credit card per order.
Paying with Gift Certificates
Paying with gift certificates is a relatively straightforward process. The single string property
ShoppingCartModifier.giftCertificateNumbers can hold any number of gift certificates. That is, the user
can enter as many gift certificate numbers as desired as long as they are delimited by white space. These gift
certificates are incorporated into new payment groups and added to the order in the following manner:
If you have gift certificates, type their codes below<br><dsp:input bean="ShoppingCartModifier.giftCertificateNumbers" size="50" type="text" value=""/><br>
Users obtain gift certificate claim numbers by purchasing them in the store. Refer to the section on Gift
Certificates in the Merchandising (page 193) chapter in this guide for more information.
Form Submission
Finally, all of these new payment methods must be submitted to the ShoppingCartModifier. The JSP to do
this is.
<dsp:input bean="ShoppingCartModifier.MoveToConfirmation" type="submit" value=" Continue -->" />
This code submits the form and the handleMoveToConfirmation() method creates and inserts payment
methods in the order.
Additional Order Capture Information
There are two pieces to the checkout process that have not been covered elsewhere in this guide. They are:
entering a valid credit card and displaying error messages.
Entering a Valid Credit Card
The checkout process attempts to validate a credit card when it is first entered. This includes things like
expiration date as well as a mod check against the number. All credit card numbers pass a particular
mathematical algorithm that validates them as real numbers. So, in order to actually complete the checkout
process a credit card number that passes this test must be supplied. ATG Consumer Commerce does not accept
random 16-digit numbers. You can use one of the following credit card numbers for testing:
• Visa: 4111111111111111
• MasterCard: 5555555555554444
• American Express: 378282246310005
7 Order Processing 165
• Discover: 6011111111111117
The expiration date for all cards can be any date in the future.
Displaying Error Messages
Most pages in the checkout process have their errorURL set to their own address. For example, if an error is
encountered during processing because a user enters an expired credit card, the user is returned to that same
page. All of the error messages print on that page and the user can correct the mistake on that page. Displaying
error messages to a user follows a standard pattern:
<!-- Check to see if there are any errors on the form --><dsp:droplet name="/atg/dynamo/droplet/Switch"><dsp:param bean="ShoppingCartModifier.formError" name="value"/><dsp:oparam name="true"> <font color=cc0000><STRONG><UL> <dsp:droplet name="/atg/dynamo/droplet/ErrorMessageForEach"> <dsp:param bean="ShoppingCartModifier.formExceptions" name="exceptions"/> <dsp:oparam name="output"> <LI> <dsp:valueof param="message"/> </dsp:oparam> </dsp:droplet> </UL></STRONG></font></dsp:oparam></dsp:droplet>
The ShoppingCartModifier stores all error messages that it encounters in the formExceptions array
property. If there are errors, the code iterates through the array to display the relevant messages.
Express Checkout
In the Pioneer Cycling store, the customer can place items in the shopping cart and then complete the purchase
in two clicks. Customers specify default values for necessary order information (credit card, shipping address,
and shipping method) and then can use the streamlined express checkout functionality for all subsequent
orders.
Enabling Express Checkout
In order to use the express checkout feature, the customer must have registered as a member, logged in as a
member, and entered at least one valid credit card and one shipping address. Once these conditions are met, a
customer may enable the express checkout feature by clicking see my express checkout ordering preferences
under “My commerce profile” on the “My Profile” page and then clicking the Set Your Express Checkout
Preferences button.
166 7 Order Processing
The hidden field in the form sample in express_checkout.jsp below sets the form handler’s
updateRepositoryId property. This property is used by theB2CProfileFormHandler to ensure that the
profile being edited is in fact the profile that is associated with the current session (in case a session expires or
otherwise becomes invalid while a person is filling in a form). We recommend using this hidden field in all forms
that invoke the handleUpdate method of ProfileFormHandler or its subclasses.
<dsp:form action="express_checkout.jsp"> <dsp:input bean="B2CProfileFormHandler.updateRepositoryId" beanvalue="B2CProfileFormHandler.repositoryId" type="hidden"/>
After the user checks the Set Your Express Checkout Preferences button, she is presented with a form that
asks for default shipping and billing information. The express checkout feature requires that at least one credit
card already be entered. If the customer’s profile contains no credit cards, she is presented with a message and
a link to the credit card entry page. After a customer has entered at least one credit card, she is presented again
with the page above.
7 Order Processing 167
Users without a valid credit card see the message above
Users with valid credit cards see a form that lets them select a default credit card. Additionally, the “Save These
Express Checkout Preferences” button is rendered.
168 7 Order Processing
Setting Express Checkout preferences
When the customer pushes the Save These Express Checkout Preferences button, the expressCheckout
property of the user’s profile is set. This ensures that the address, credit card, and shipping methods have been
set and prevents invalid data from causing errors at checkout time.
Setting the Express Checkout preferences is specialized Pioneer Cycling Store functionality. This code
can be found in <ATG9dir>/PioneerCyclingJSP/j2ee-apps/pioneer/web-app/en/user/
express_checkout_preferences.jsp.
7 Order Processing 169
The first form element on the page is the pull-down list for selecting the default shipping address.
The customer’s default shipping address is stored in the property Profile.shippingAddress.
The pull-down list in the screen shot above shows the list of all nicknames of addresses
stored in Profile.secondaryAddressess. The pull down list is linked to the property
ProfileFormHandler.editValue.defaultAddressNickname. When the form is submitted, the selected
value is set in the Profile.shippingAddress property. The following snippet demonstrates how the shipping
address is set.
Your current default shipping address is: <!-- <b><dsp:valueof param="key"/></b>.--><p><dsp:getvalueof id="pval0" bean="Profile.shippingAddress"><dsp:include page="DisplayAddress.jsp" flush="true"><dsp:param name="address" value="<%=pval0%>"/></dsp:include></dsp:getvalueof><p>
<dsp:droplet name="IsEmpty"> <dsp:param bean="Profile.secondaryAddresses" name="value"/> <dsp:oparam name="false">
Change it to:<dsp:select bean="B2CProfileFormHandler.editValue.defaultAddressNickname"> <dsp:droplet name="ForEach"> <dsp:param bean="Profile.secondaryAddresses" name="array"/> <dsp:oparam name="outputStart"> <dsp:option value=""/> Don't Change </dsp:oparam> <dsp:oparam name="output"> <dsp:droplet name="Switch"> <dsp:param name="value" param="key"/> <dsp:oparam name="editAddress"> </dsp:oparam> <dsp:oparam name="newBillingAddress"> </dsp:oparam> <dsp:oparam name="default"> <dsp:getvalueof id="option119" param="key" idtype="java.lang.String"><dsp:option value="<%=option119%>"/></dsp:getvalueof><dsp:valueof param="key"/> </dsp:oparam> </dsp:droplet> </dsp:oparam> </dsp:droplet></dsp:select>
The concepts involved in setting the default credit card are similar to setting the shipping address. The default
credit card is stored in Profile.defaultCreditCard, which is a reference to a RepositoryItem of type
credit_card that has the properties creditCardType and creditCardNumber. These are displayed on the
page next to the word “Default:” to remind the user of the current setting, if any. Just below that is a pull-down
list for selecting a new default credit card that shows a list of all credit cards on the user’s profile.
To display the current default value as pre-selected, we used the <dsp:setvalue> to set the form handler’s
value for the default credit card from the profile. To actually render the <dsp:select> drop-down list, we used
an IsEmpty servlet bean to determine if there is anything to render at all. The ForEach servlet bean has an
empty oparam that one might expect to be able to use for the case when the credit cards are empty and an
oparam="outputStart" for the <dsp:select> tag and oparam="outputEnd" for the </dsp:select>. The
ForEach servlet bean has been tuned for top performance and it would be too inefficient for the page compiler
to figure out which <dsp:select> and </dsp:select> and <dsp:option> tags go together. If we used a
170 7 Order Processing
loosely coupled group, like radio buttons, the IsEmpty would not be necessary, but since we used select, we
must help the page compiler by using IsEmpty.
Default: <dsp:valueof bean="Profile.defaultCreditCard.creditCardType"/><dsp:valueof bean="Profile.defaultCreditCard.creditCardNumber" converter="creditcard"/><p>
<dsp:setvalue bean="B2CProfileFormHandler.defaultCreditCardID" beanvalue="Profile.defaultCreditCard.id"/>
<dsp:droplet name="IsEmpty"> <dsp:param bean="Profile.creditCards" name="value"/> <dsp:oparam name="true"> <dsp:a href="credit_cards.jsp"> <font color=#cc0000> Please enter one or more credit cards in order to enable this feature </font> </dsp:a> <br> </dsp:oparam> <dsp:oparam name="false"> Choose from one of your saved credit cards<p> <dsp:select bean="B2CProfileFormHandler.defaultCreditCardID"> <dsp:droplet name="ForEach"> <dsp:param bean="Profile.creditCards" name="array"/> <dsp:oparam name="output"> <dsp:getvalueof id="option226" param="element.id" idtype="java.lang.String"><dsp:option value="<%=option226%>"/></dsp:getvalueof> <dsp:valueof converter="creditcard" param="key"/> <dsp:valueof param="element.creditCardType"/> <dsp:valueof converter="creditcard" param="element.creditCardNumber"/> </dsp:oparam> </dsp:droplet> </dsp:select> </dsp:oparam></dsp:droplet>
While credit cards and shipping addresses that must be drawn from the user’s profile, setting the shipping
method is simpler because the list of possible shipping methods is common to all users. We used the same
methods as above to pre-set the selected item on the pull-down list of shipping methods. The actual pull
down list is generated by the AvailableShippingMethods servlet bean that is described in the Shipping
Prices (page 130) section of the Pricing (page 127) chapter.
Default: <dsp:valueof bean="Profile.defaultCarrier"/><p>
<dsp:setvalue bean="B2CProfileFormHandler.defaultCarrier" beanvalue="Profile.defaultCarrier"/>
Choose from one of our shipping methods<p><!-- The droplet AvailableShippingMethods is able to give us a list of the --><!-- shipping methods that should be available to a user. --><dsp:droplet name="AvailableShippingMethods"> <dsp:param bean="ShoppingCartModifier.shippingGroup" name="shippingGroup"/> <dsp:param bean="UserPricingModels.shippingPricingModels" name="pricingModels"/>
7 Order Processing 171
<dsp:param bean="Profile" name="profile"/> <dsp:oparam name="output"> <dsp:select bean="B2CProfileFormHandler.defaultCarrier"> <dsp:droplet name="ForEach"> <dsp:param name="array" param="availableShippingMethods"/> <dsp:param name="elementName" value="method"/> <dsp:oparam name="output"> <dsp:getvalueof id="option313" param="method" idtype="java.lang.String"><dsp:option value="<%=option313%>"/></dsp:getvalueof> <dsp:valueof param="method"/> </dsp:oparam> </dsp:droplet> </dsp:select> </dsp:oparam></dsp:droplet>
The last form element on the page is the Save These Express Checkout Preferences submit button.
This button is rendered only if the list of credit cards in the profile is not empty. The form is submitted to
B2CProfileFormHandler.setExpressCheckoutPreferences, which takes the values from the form fields
and saves them to the profile.
<dsp:droplet name="IsEmpty"><dsp:param bean="Profile.creditCards" name="value"/><dsp:oparam name="false"> <dsp:input bean="B2CProfileFormHandler.setExpressCheckoutPreferences" type="submit" value="Save These Express Checkout Preferences"/> </dsp:oparam></dsp:droplet>
Deactivating Express Checkout
A customer can deactivate express checkout option by clicking the Deactivate Express Checkout button on
the “Express Checkout” page. The form below from express_checkout.jsp can be used to deactivate express
checkout by setting the value of B2CProfileFormHandler.value.expressCheckout to false.
<dsp:form action="express_checkout.jsp">
<dsp:input bean="B2CProfileFormHandler.updateRepositoryId" beanvalue="B2CProfileFormHandler.repositoryId" type="hidden"/> <dsp:input bean="B2CProfileFormHandler.editValue.expressCheckout" type="hidden" value="false"/> <dsp:input bean="B2CProfileFormHandler.setExpressCheckout" type="submit" value="Deactivate Express Checkout"/>
</dsp:form>
Modifying the Shopping Cart
We made two modifications to the cart.jsp page to enable express checkout. First, the Express Checkout
button is conditionally added to the form depending on the state of the Profile.expressCheckout property.
Conditional express checkout button incart.jsp:
172 7 Order Processing
<core:if value="Profile.expressCheckout"> <dsp:input bean="ShoppingCartModifier.expressCheckout" type="submit" value=" Express Checkout"/></core:if>
The Express Checkout button appears on cart.jsp when express checkout preferences have been set.
We also modified cart.jsp by adding a hidden input value containing the URL for a successful express
checkout:
<dsp:input bean="ShoppingCartModifier.expressCheckoutSuccessURL" type="hidden" value="full/co_confirm.jsp"/>
When express checkout is enabled, clicking the Express Checkout button creates an order using the items in
the user’s shopping cart and the pre-set default shipping address, credit card, and shipping method. The user is
then redirected to the final order confirmation page.
8 Order History 173
8 Order History
This chapter describes how the Pioneer Cycling store makes it easy for customers to view all the orders they
have placed on the Web site. They can see the details of what they ordered, where it was shipped, and the
order’s status; they can also use this section of the Web site to cancel orders that haven’t already been shipped.
This chapter includes the following sections:
Displaying a User’s Orders (page 173)
Describes how customers can review information from all the past and current orders.
Displaying a Single Order (page 176)
Describes how we display detailed information on a specific order.
Canceling Orders (page 179)
Describes how customers can cancel orders that haven’t already been shipped.
Displaying a User’s Orders
We used several servlet beans from ATG Consumer Commerce and one special servlet bean written especially for
the bike store to create the order history pages.
174 8 Order History
order_history.jsp, the Order History page in a customer’s profile
The following snippet of JSP, taken from order_history.jsp, demonstrates how the OrderLookup
servlet bean renders a customer’s list of open orders. OrderLookup is located at/atg/commerce/order/
OrderLookup. Please refer to the Configuring the Order Fulfillment Framework chapter in the ATG Commerce
Programming Guide for more information about OrderLookup.
The OrderLookup servlet bean can be used in several ways. In this case, it is passed the user’s profile ID and the
requested order state (“open”) and it outputs a list of orders in the result parameter. Then, a ForEach servlet
bean renders links to each of those orders in an ordered list.
The OrderStatesDetailed component, which is invoked for each order in the list, asks the fulfillment module
for detailed information about the state of an order.
<dsp:droplet name="OrderLookup"> <dsp:param bean="/atg/userprofiling/Profile.repositoryId" name="userId"/> <dsp:param name="state" value="open"/><dsp:param name="startIndex" param="openStartIndex"/><dsp:param name="numOrders" value="10"/>
<dsp:oparam name="output"> <dsp:droplet name="IsNull"> <dsp:param name="value" param="previousIndex"/> <dsp:oparam name="false"> <dsp:droplet name="Compare"> <dsp:param name="obj1" param="openStartIndex"/>
8 Order History 175
<dsp:param name="obj2" value="0"/> <dsp:oparam name="greaterthan"> <dsp:a href="order_history.jsp"><i>Previous orders</i> <dsp:param name="openStartIndex" param="previousIndex"/> <dsp:param name="closedStartIndex" param="closedStartIndex"/> <dsp:param name="cancelledStartIndex" param="cancelledStartIndex"/> </dsp:a> </dsp:oparam> </dsp:droplet> </dsp:oparam> </dsp:droplet>
<dsp:droplet name="ForEach"> <dsp:param name="array" param="result"/> <dsp:oparam name="outputStart"> <dsp:droplet name="IsNull"> <dsp:param name="value" param="nextIndex"/> <dsp:oparam name="false"> <dsp:a href="order_history.jsp"><i>More orders</i> <dsp:param name="openStartIndex" param="nextIndex"/> <dsp:param name="closedStartIndex" param="closedStartIndex"/> <dsp:param name="cancelledStartIndex" param="cancelledStartIndex"/> </dsp:a><br> </dsp:oparam> </dsp:droplet>
<OL> </dsp:oparam> <dsp:oparam name="outputEnd"> </OL> </dsp:oparam> <dsp:oparam name="empty"> No open orders. </dsp:oparam> <dsp:oparam name="output"> <LI> <dsp:a href="order.jsp"> <dsp:param name="orderId" param="element.id"/> #<dsp:valueof param="element.id">no order number</dsp:valueof></dsp:a> <dsp:valueof date="MMMMM d, yyyy" param="element.submittedDate"/> <dsp:droplet name="/atg/commerce/order/OrderStatesDetailed"> <dsp:param name="state" param="element.state"/> <dsp:oparam name="output"><dsp:valueof param="detailedState"/></dsp:oparam> </dsp:droplet>
</dsp:oparam> </dsp:droplet> </dsp:oparam>
<dsp:oparam name="error"> <span class=profilebig>ERROR: <dsp:valueof param="errorMsg">no error message</dsp:valueof> </span> </dsp:oparam></dsp:droplet>
176 8 Order History
Displaying a Single Order
When the customer chooses a link from the order history page to view a single particular order (such
as #o12345), he sees a page of detailed information about that order with a URL like this: http://
my.bikestore.com/path/order.jsp?orderId=o12345. The orderId parameter is generated by setting a
parameter between the <dsp:a> and </dsp:a> tags of the link on order_history.jsp like this:
<dsp:a href="order.jsp"> <dsp:param name="orderId" param="element.id"/> #<dsp:valueof param="element.id">no order number</dsp:valueof></dsp:a>
The customer is then shown the status and history of a single order (order.jsp with the information of order
number that corresponds to orderId). In the JSP code snippet from order.jsp below, instead of being passed
explicitly as a parameter to the OrderLookup servlet bean, orderId is set by the link from the order history
page.
<dsp:droplet name="/atg/commerce/order/OrderLookup">
<dsp:oparam name="error"> <p> <span class=profilebig>ERROR: <dsp:valueof param="errorMsg">no error message</dsp:valueof> </span> <p> </dsp:oparam>
<dsp:oparam name="output"> <dsp:setvalue paramvalue="result" param="order"/> <p> <span class=profilebig>order #<dsp:valueof param="order.id">no order id</dsp:valueof></span>
<p> <table cellspacing=0 cellpadding=0 border=0>
<!-- Setup gutter and make space --> <tr> <td><dsp:img height="1" width="100" src="../images/d.gif"/><br></td> <td> </td> <td><dsp:img height="1" width="400" src="../images/d.gif"/></td> </tr>
<dsp:droplet name="Switch"> <dsp:param name="value" param="order.stateAsString"/>
<dsp:oparam name="NO_PENDING_ACTION"> <tr valign=top> <td> <span class=help>Gosh. You should have this one by now.</span> </td>
<td></td>
<td> <table width=100% cellpadding=0 cellspacing=0 border=0> <tr><td class=box-top-profile>Order status</td></tr></table>
8 Order History 177
<p> This order was placed on <dsp:valueof date="MMMMM d, yyyy" param="order.submittedDate"/> and shipped on <dsp:valueof date="MMMMM d, yyyy" param="order.completedDate"/>.
<p> If there is a problem with this order, please <dsp:a href="contact_customer_service.jsp">contact a customer service representative</dsp:a>. <p> <br> </td> </tr> </dsp:oparam>
<dsp:oparam name="default"> <tr valign=top> <td> <span class=help>Since this order has not yet shipped, you may still change it.</span> </td>
<td></td>
<td> <table width=100% cellpadding=0 cellspacing=0 border=0> <tr><td class=box-top-profile>Order status</td></tr></table> <p> This order was placed on <dsp:valueof date="MMMMM d, yyyy" param="order.submittedDate"/>. <br> Its status is <dsp:valueof param="order.stateAsString">UNKNOWN STATUS</dsp:valueof>. <br> <p> > <dsp:a href="cancel_order.jsp"> <dsp:param name="orderId" param="order.id"/> cancel the order</dsp:a><br> <p> To make any other changes to this order, please <dsp:a href="contact_customer_service.jsp">contact a customer service representative</dsp:a>. </span> <p> <br> </td> </tr> </dsp:oparam> </dsp:droplet>
<tr valign=top> <td> <span class=help>This is the contents of the order.</span> </td> <td></td> <td> <table width=100% cellpadding=0 cellspacing=0 border=0> <tr><td class=box-top-profile>Order details</td></tr></table> <p>
<table cellspacing=2 cellpadding=0 border=0>
178 8 Order History
<tr><td></td><td> </td><td></td><td> </td><td></td></tr>
<tr valign=top> <td colspan=3>
<b>Order # <dsp:valueof param="order.id">no order id</dsp:valueof></b><br> <dsp:valueof date="h:mma MMMMM d, yyyy" param="order.submittedDate"/><br> Sales: [email protected] </td> <td colspan=2></td> </tr> <tr><td colspan=5><hr size=0></td></tr>
<tr valign=top> <td colspan=3> <dsp:getvalueof id="pval0" param="order" idtype=" atg.commerce.order.Order"> <dsp:include page="OrderBillingInfo.jsp" flush="true"> <dsp:param name="order" value="<%=pval0%>"/></dsp:include></dsp:getvalueof> </td> <td colspan=2></td> </tr>
<tr><td colspan=5><hr size=0></td></tr>
<dsp:getvalueof id="pval0" param="order" idtype=" atg.commerce.order.Order"> <dsp:include page="OrderShippingInfo.jsp" flush="true"> <dsp:param name="order" value="<%=pval0%>"/></dsp:include></dsp:getvalueof>
<tr><td colspan=5><hr size=0></td></tr>
</table> </td> </tr> </table> <P>
</dsp:oparam></dsp:droplet>
The OrderLookup servlet bean puts the order into the result parameter. Because we thought it would be
clearer, we renamed the parameter order using the setvalue statement of <dsp:setvalue param="order"
paramvalue="result"/>. Then any of the order properties can be accessed using order.<propertyname>.
Because order.jsp contains so much information, the JSP can become difficult to read. For easier reading we
separated out the OrderBillingInfo.jsp and OrderShippingInfo.jsp fragments and included them in
order.jsp using:
<dsp:include page="filename.jsp" flush="true"></dsp:include>
These pages display details of the order’s shipping and billing information.
8 Order History 179
There is no technical reason for separating the JSP fragments OrderBillingInfo.jsp and
OrderShippingInfo.jsp rather than including them inline in order.jsp. However, it is often easier to
manage a large number of small JSP files than a small number of larger JSP files.
It is important to remember that the OrderLookup servlet bean has a security feature that enables a customer
to look up only his own orders. If you would like disable this security feature so that anyone can look at any
order, you can set the property OrderLookup.enableSecurity=false.
Canceling Orders
The ATG Consumer Commerce product provides the ability to cancel and edit existing orders through the
Customer Service Module. Because there are many different ways to present the cancel order functionality
to the customer and there are many different business rules governing order cancellation restriction, this
functionality is implemented outside the core product. The ability of a user to cancel his own orders is a Pioneer
Cycling specific piece of functionality.
The business rules for Pioneer Cycling dictate that any order that still has actions pending may be cancelled, so
in order.jsp we used a Switch servlet bean on the order’s state to determine if it may be cancelled:
<dsp:droplet name="Switch"> <dsp:param name="value" param="order.stateAsString"/>
<dsp:oparam name="NO_PENDING_ACTION">
<!-- Snip -->
This order has been completed. You cannot cancel it. </dsp:oparam>
<dsp:oparam name="default">
<!-- Snip -->
<dsp:a href="cancel_order.jsp"> <dsp:param name="orderId" param="order.id"/> cancel the order</dsp:a><br> </dsp:oparam></dsp:droplet>
The link to cancel_order.jsp contains an orderId parameter to keep track of which order to cancel.
cancel_order.jsp is a very simple page. The form used to cancel the order exists for the sake of the user
interface. It gives the customer the chance to change her mind about the cancellation.
180 8 Order History
The next page actually does the canceling. Note the hidden orderId parameter that gets passed to the next
page with the form submission. When displaying data like this on the web there is always a security concern. In
order to thwart malicious users who might hit the order URL and try out different orderId parameters to gain
access to private data, we allowed users to see only the orders that they have placed. By default this security
feature is turned on, but you can disable the security feature by setting the property /atg/commerce/order/
OrderLookup.enableSecurity=false.
<dsp:form action="order_cancelled.jsp"> Are you sure you wish to Cancel Order #<dsp:valueof param="orderId"><i>none</i></dsp:valueof> <p> If you do not wish to cancel your order, you may use your browser's back buttonto return to the order. <input name="orderId" type="hidden" value='<dsp:valueof param="orderId"/>'> <BR><br> <input type="submit" value="CANCEL ORDER"></dsp:form>
The action of the form, order_cancelled.jsp, is the next stop when the customer clicks the “Cancel Order”
button. This is the page that actually cancels the order using the CancelOrder servlet bean.
8 Order History 181
If the cancellation is initiated without any problem, the successoparam is rendered. Otherwise the
erroroparam is rendered and the error message is displayed. (Canceling an order is an asynchronous process
in which the form handler sends a Java message to the fulfillment system that then performs the cancel order
operation. Since it is asynchronous, there is no guarantee that it will complete by the time the “This Order Has
Been Cancelled” page displays to the user. Also, it is possible that the fulfillment process might not be running.
The cancel request could be sent and the order would not actually be cancelled until the fulfillment system was
brought back up.) Generally, in the Pioneer Cycling store it takes only tens of seconds. We included a message to
users so that they will understand if orders do not show up as cancelled right away on their Order History pages.
Here is the relevant part of order_cancelled.jsp:
<dsp:droplet name="/atg/commerce/order/CancelOrder"> <dsp:oparam name="success"> The Cancel Order instruction has been sent to the order processor. <P> It may take several minutes for the order to disappear from the list of orders on your <dsp:a href="order_history.jsp">order history</dsp:a> page. </dsp:oparam> <dsp:oparam name="error"> <dsp:valueof param="errorMsg"/> </dsp:oparam></dsp:droplet>
The CancelOrder servlet bean extends the OrderLookup servlet bean and therefore has many similarities
in its configuration. The main difference is that it has an as additional property, the OrderCanceller, that is
the component that communicates with the fulfillment system to cancel the order. Here is /atg/commerce/
order/CancelOrder.properties:
$class=atg.projects.b2cstore.CancelOrder$scope=global
orderCanceller=OrderCancellerorderManager=OrderManager
openStates=\ submitted,\ processing,\
182 8 Order History
pending_merchant_action,\ pending_customer_action
closedStates=\ no_pending_action
profilePath=/atg/userprofiling/ProfileenableSecurity=true
The OrderCanceller used by CancelOrder is configured as follows in /atg/commerce/order/
OrderCanceller.properties:
$class=atg.projects.b2cstore.OrderCanceller$scope=global
transactionManager=/atg/dynamo/transaction/TransactionManagermessageSourceName=OrderCancellermodifyPort=ModifyOrderPort
OrderCanceller.java:
package atg.projects.b2cstore;
// Commerceimport atg.commerce.order.*;import atg.commerce.fulfillment.*;import atg.commerce.*;import atg.commerce.states.*;import atg.commerce.messaging.*;
// Java classesimport javax.jms.*;import javax.transaction.*;import javax.transaction.xa.*;import java.util.ResourceBundle.*;import java.util.*;
/** * This class will contain methods needed to cancel an order by sending * messages to the fulfillment subsystem. * * @version $Change: 228696 $$DateTime: 2002/02/11 13:07:58 $$Author: releng $ */public class OrderCanceller extends SourceSinkTemplate{ //------------------------------------- /** Class version string */ public static final String CLASS_VERSION = "$Change: 228696 $$DateTime:2002/02/11 13:07:58 $$Author: releng $";
//--------------------------------------------------------------------------- // property:MessageSourceName //---------------------------------------------------------------------------
/** the name used by this class when it acts as a message source **/ private String mMessageSourceName = "OrderCanceller";
//---------------------------------------------------------------------------
8 Order History 183
/** * Sets the name used by this class when it acts as a message source * so that it's messages can be identified. **/ public void setMessageSourceName(String pMessageSourceName) { mMessageSourceName = pMessageSourceName; }
//--------------------------------------------------------------------------- /** * Gets the name used by this class when it acts as a message source * so that it's messages can be identified. **/ public String getMessageSourceName() { return mMessageSourceName; }
/** Port name for sending modify order messages */ String mModifyPort = null;
//------------------------------------- /** * Sets Port name for sending modify order messages **/ public void setModifyPort(String pModifyPort) { mModifyPort = pModifyPort; }
//------------------------------------- /** * Returns Port name for sending modify order messages **/ public String getModifyPort() { return mModifyPort; }
//------------------------------------- /** * Assemble and send a message to cancel the order * @param orderId the id of the order to cancel. **/ public void sendCancelOrder(String pOrderId) {
Modification[] mods = new Modification[1]; ModifyOrder message = new ModifyOrder();
message.setOrderId(pOrderId); message.setSource(getMessageSourceName()); message.setOriginalSource(getMessageSourceName()); message.setOriginalId(message.getId());
GenericRemove gr = new GenericRemove(); gr.setTargetType(Modification.TARGET_ORDER); gr.setTargetId(pOrderId); mods[0] = gr;
message.setModifications(mods);
try { sendCommerceMessage(message, getModifyPort());
184 8 Order History
} catch(JMSException j) { logError(j); } }
} // end of class
CancelOrder.java:
package atg.projects.b2cstore;
import javax.servlet.*;import javax.servlet.http.*;
import atg.commerce.CommerceException;import atg.commerce.order.*;import atg.commerce.states.*;import atg.nucleus.naming.ParameterName;import atg.nucleus.naming.ComponentName;import atg.servlet.*;import atg.repository.RepositoryException;import atg.repository.RepositoryItem;
import java.io.*;import java.util.List;import java.util.Locale;
/** * This servlet cancels the order for the orderId paramteter passed in. * It takes as parameters: * <dl> * * <dt>orderId * * <dd>the id of the order to cancel * * </dl> * * It renders the following oparams: * <dl> * * <dt>success * * <dd>The oparam success is rendered once if the cancel was successful * * <dt>error * * <dd>error will be rendered if an error occurred * * </dl> * * It sets the following output params: * <dl> * * <dt>errorMsg * * <dd>if an error occurred this will be the detailed error message * for the user. *
8 Order History 185
* </dl> * * This droplet has a security feature that allows only the current user to * cancel his own orders. This feature is enabled by default. To disable * it, set the property enableSecurity=false */public class CancelOrder extends OrderLookup { //------------------------------------- // Class version string public static final String CLASS_VERSION = "$Id: CancelOrder.java,v 1.2 2000/06/07 19:17:43 cynthia Exp $";
//------------------------------------- // Constants //------------------------------------- public final static String SUCCESS = "success";
//------------------------------------- // property: orderCanceller OrderCanceller mOrderCanceller; public void setOrderCanceller(OrderCanceller pOrderCanceller) { mOrderCanceller = pOrderCanceller; } public OrderCanceller getOrderCanceller() { return mOrderCanceller; }
//------------------------------------- /** * service the request */ public void service(DynamoHttpServletRequest pReq, DynamoHttpServletResponse pRes) throws ServletException, IOException { // Get the orderId parameter from the request. It was set by the // hidden form field on the cancel_order.jsp page. String orderId = pReq.getParameter(ORDERID); if (orderId != null) { try { if (isLoggingDebug()) logDebug("orderId = " + orderId);
// Make sure that an order for that orderId exists by asking the //OrderManager with the orderExists(orderId) method. We then make double //sure by doing orderManager.loadOrder(orderId) and making sure it is not //null, but that is optional. Order order = null; if (getOrderManager().orderExists(orderId)) order = getOrderManager().loadOrder(orderId);
if (order == null) { String errMsg = OrderUserMessage.format(MSG_NO_SUCH_ORDER, getUserLocale(pReq, pRes)); pReq.setParameter(ERRORMESSAGE, errMsg); pReq.serviceLocalParameter(ERROR, pReq, pRes); return; }
186 8 Order History
// Make sure that the current logged in user is the owner of this order by // resolving the current user's profile id and comparing it to the order's //owner. if (isEnableSecurity()) { if (isLoggingDebug()) logDebug("checking ownership. current user is " + getCurrentProfileId(pReq) + " order owner is " +order.getProfileId()); if (!order.getProfileId().equals(getCurrentProfileId(pReq))) {String errMsg = OrderUserMessage.format(MSG_NO_PERMISSION_FOR_ORDER, getUserLocale(pReq, pRes)); pReq.setParameter(ERRORMESSAGE, errMsg); pReq.serviceLocalParameter(ERROR, pReq, pRes); return; } }
// Now we actually cancel the order getOrderCanceller().sendCancelOrder(orderId);
// Everything went well, so we will send out the success oparam pReq.serviceLocalParameter(SUCCESS, pReq, pRes); } catch (CommerceException e) { if (isLoggingError()) logError(e);
// Something went wrong. This will cause the error oparam to be rendered //and the errorMsg parameter to be set for the jsp page. String errMsg = OrderUserMessage.format(MSG_GENERAL_ERROR, getUserLocale(pReq, pRes)); pReq.setParameter(ERRORMESSAGE, errMsg); pReq.serviceLocalParameter(ERROR, pReq, pRes); } return; }
// Oops! It looks like somebody forgot to pass in an orderId parameter. if (orderId == null) { String errMsg = OrderUserMessage.format(MSG_NO_PARAM_SPECIFIED, getUserLocale(pReq, pRes)); pReq.setParameter(ERRORMESSAGE, errMsg); pReq.serviceLocalParameter(ERROR, pReq, pRes); return; } }}
9 Inventory 187
9 Inventory
This chapter describes the Pioneer Cycling inventory system. It includes the following sections:
Overview of the Pioneer Cycling Inventory System (page 187)
Describes how we used the ATG Consumer Commerce inventory system.
Implementing an Inventory System (page 188)
Describes how we implemented the inventory system.
Accessing Inventory Information (page 188)
Describes how inventory is displayed on product pages and checkout pages.
Overview of the Pioneer Cycling Inventory System
We used the standard inventory system that ships with ATG Consumer Commerce for the Pioneer Cycling store.
Using the inventory system, the store can show users the following information on any particular item:
• Availability status message
• Availability status
• Availability date
• Stock level
• Backorder stock level
• Preorder stock level
The Pioneer Cycling site displays the inventory status of a product on the product page and on the shopping
cart. For example, before a user adds a bike to his cart, we want to indicate to him if the item is out of stock. This
way, he can make the educated decision to purchase the item on backorder or purchase an alternate product.
To implement working inventory in the Pioneer Cycling store, first we actually created inventory information in
an inventory subsystem. Second, we set up access to this information via the various components supplied by
ATG Consumer Commerce.
188 9 Inventory
Implementing an Inventory System
ATG Consumer Commerce includes an inventory system adapter that provides an API to determine if an item is
in stock, back-ordered, or pre-ordered. That is, ATG Consumer Commerce provides a set of Inventory interface
classes that can be implemented to provide inventory functionality. ATG Consumer Commerce ships with an
example that is a repository-based inventory system. See the Inventory Framework chapter in the ATG Commerce
Programming Guide for more information.
Since the repository is provided by ATG Consumer Commerce (as an instance of a GSA repository), we were able
to use the existing component: /atg/commerce/inventory/InventoryRepository and the inventory XML
schema: /atg/commerce/inventory/inventory.xml. We simply used the ACC to enter information into the
repository. We supplied inventory information for only a few examples of the products in the Pioneer Cycling
store; for other products, the inventory information displays as “unknown.”
Accessing Inventory Information
Inventory information is used on product pages and checkout pages. The next section examines the
components we used to access information at each of these places.
Product Pages
Each product page displays the current availability of a particular product.
9 Inventory 189
The availability of a product is one of the values that can be returned by the InventoryLookup component:
• INSTOCK
• OUTOFSTOCK
• PREORDERABLE
• BACKORDERABLE
“Unknown” is displayed if there is no information available for a particular item.
To access this information, we used the InventoryLookup component located at /atg/commerce/
inventory/InventoryLookup. We made some slight changes to the way that InventoryLookup is
configured. Specifically, we changed it so that errors are not logged if inventory information is not found on
a particular item. Since not all items are in the inventory, we didn’t want an error to result if inventory level
information was unavailable. To do this, we set the following property in our properties file:
logInventoryExceptionsAsError=false
190 9 Inventory
The InventoryLookup component takes a catalogRefId property and then queries the inventory system,
returning several parameters. We used the inventoryInfo parameter to get access to the availability status of
an item. The JSP that performs this is:
<dsp:droplet name="/atg/commerce/inventory/InventoryLookup"> <dsp:param name="itemId" param="link.item.repositoryId"/> <dsp:param name="useCache" value="true"/> <dsp:oparam name="output"> <dsp:valueof param="inventoryInfo.availabilityStatusMsg">Unknown</dsp:valueof> </dsp:oparam> </dsp:droplet>
One thing to note about this usage is that we set the parameter useCache to be true. By default, the
Inventory servlet bean uses the regular InventoryManager, but the useCache parameter directs it to use the
CachingInventoryManager. This improves the performance, but as with any cache, there is the possibility of
getting stale data.
Checkout Pages
The Shopping Cart page in the Pioneer Cycling store, cart.jsp provides more information than the catalog
pages. Specifically, it indicates if:
• An item is out of stock
• An item is on backorder
• An item is on preorder
• If an item is in stock, but the quantity that the user is ordering is greater than the quantity in stock, we indicate
how many are left in stock.
9 Inventory 191
To get this information, the InventoryLookup component was again used but this time with a different JSP
usage. This is what the JSP looks like:
<%-- Display inventory information if any skus are not available --%> <% String stockLevel = drequest.getParameter("inventoryInfo.stockLevel"); String quantity = drequest.getParameter("CiRelationship.commerceItem.quantity"); String availStatus = drequest.getParameter("inventoryInfo.availabilityStatus"); %>
<core:switch value="<%= availStatus %>"> <core:case value="1001"> (Out) </core:case> <core:case value="1002"> (Preorder) </core:case> <core:case value="1003"> (Backorder) </core:case> <core:case value="INSTOCK">
<core:IfNotNull value="<%=stockLevel%>"> <core:ifGreaterThan object1="<%=quantity%>" object2="<%=stockLevel%>">
<%-- -1 is the code for infinite stock, so display nothing here. Customer is ordering more than are in stock, so display a message --%>
<core:ifNotEqual object1="<%=stockLevel%>" object2="-1"> (Only <dsp:valueof param="inventoryInfo.stockLevel"/> left) </core:ifNotEqual>
</core:ifGreaterThan> </core:IfNotNull> </core:case>
</core:switch>
192 9 Inventory
10 Merchandising 193
10 Merchandising
This chapter describes the merchandising features implemented in the Pioneer Cycling site and includes the
following sections:
Promotions (page 193)
Describes how to use various types of promotions in your site.
Gift Certificates (page 200)
Describes how to create and use gift certificates.
Coupons (page 201)
Describes how to create and use coupons.
Gift Registry (page 203)
Describes how to create and use a gift registry.
Promotions
Promotions are repository items. When a user is “given” a promotion, it means that a reference to the promotion
repository item is added to the user’s profile property activePromotions.
Promotions are a way of encouraging a user to make a purchase by highlighting products and offering
discounts. ATG Consumer Commerce provides flexibility in the types of promotions and the ways that they can
be delivered to users.
One typical way to deliver a promotion is to embed a promotion ID in a URL, which automatically gives the user
that promotion when he clicks on the URL. Another way is to create a scenario that uses the “Give Promotions”
action, and shows the promotion’s media in a slot. For example:
"upon login of person whose purchase history=null give promo 'buy 2 apples, get 1
free' and show items in slot MediaSlot 'ApplesPromotionMedia'."
It is important to realize that, although these two actions are conceptually related in the eyes of the customer,
there are distinct differences. The first gives a customer a promotion that discounts a product while the second
shows the customer a media item.
When prices are calculated for items, orders, tax, or shipping, the pricing engine uses the user profile’s
activePromotion list to calculate a personalized price. Discounts are applied when the order is priced, which is
any time that the customer looks at his shopping cart or checks out.
194 10 Merchandising
If there are several applicable discounts, the “order of application” attribute of the promotion determines the
order in which they get applied.
Implementing Promotions
A promotion is basically a pricing model with a piece of media. It consists of a condition, a discount, and the
entity being discounted.
Conditions for discounts could include: Types of Discounts Entities Discounted
attributes of the product
the whole order
person purchasing
time of day
percentage off
fixed price off
fixed price
individual products
whole orders
shipping
tax
We used the following types of promotions in the Pioneer Cycling site:
• Percentage amount off a particular product
• Percentage amount off a whole order
• Specific amount off a particular product
• Specific amount off a whole order
• Specific amount or percentage off a product based on an attribute
• Free product or free order (100% off or fixed price = 0)
• Discount a product in an certain category
• Discount an item or order based upon order attributes (such as number of items in an order or total price of
an order)
Some examples of the discounts we used in the Pioneer Cycling store are:
• “Always <> Apply discount to all items”
• “When Order’s priceInfo’s amount is greater than 80 <> Apply discount to: Order Total”
• “When Order contains at least 3 ( product in category named Helmets ) <> Apply discount to: up to 1
( product in category named Helmets )”
Pioneer Cycling Promotions
We created the following promotions for the Pioneer Cycling store.
• $20 off an order over $80 – coupon (used for coupon COUP1234.)
10 Merchandising 195
• $5 off sixth part
• 10 Francs off sixth part
• 10% off everything in the store
• 20% off order
• 25% off BMX bikes
• 25% off Helmet with purchase of bike
• 30% off Helmet with purchase of bike
• 40% off Helmet with purchase of bike
• 5% off everything in the store
• 5% off everything in the store – coupon (Used for coupon COUP1000)
• 50 Yen off sixth part
• Buy 2 helmets get 1 free
• Buy 2 lights get 1 free
• Free shipping for orders over $400
• Free water bottle with jersey
Targeting Content for Promotions
This section describes the various ways to target content for promotions. There are different methods for
targeting content. You can:
• Create DSS-based promotions (scenarios)
• Highlight products and content
• Cross sell and upsell
Scenarios
Scenarios can update or track user profiles and offer promotions. Store pages can include slots dedicated to
DSS-based promotions. A DSS scenario is set up to watch for the following types of conditions or events:
• Buyers with certain profile traits
• Products or content viewed or products added to cart
• Attributes of products or content viewed or of products added to cart
• Order amount or quantity
• Time
196 10 Merchandising
The scenario responds by performing any of the following actions:
• Displaying a piece of promotional media in one of the slots
• Giving the buyer a promotion that changes the order price
• Sending the buyer an e-mail
• Changing a profile attribute
In the Pioneer Cycling store, the store home page and the shopping cart page display promotional information.
For example, the store offers a 10% discount to new members.
You may want to push specific products in specific locations on your site. For example, in the Pioneer Cycling
store, we chose to highlight a particular type of bike jersey for a period of time on the clothing page.
The Pioneer Cycling store highlights various products. For example, a specific bike is highlighted on the
Mountain Bikes category page. Or, particular helmets and water bottle cages are featured on each bicycle
product page in the “Related Products” section. For more information, see Appendix A, Pioneer Cycling
Scenarios (page 229).
Highlighting products and content
You can create and manage a list of specified products to recommend to all customers or a group of specifically
targeted customers. You can assign a discount to products on the list. Examples of promotional lists are a list of
the top ten products or a list of award-winning products. We did not implement any promotional lists in Pioneer
Cycling, but you could do this in the following manner:
1. Create a new category or categories in your store to hold these special product lists. For example: “Store ->
Special -> Award Winning Products”. One of the advantages of ATG Consumer Commerce’s flexible category
system is that products may be located in many different areas of the store.
2. Include the special products by adding them to the new category’s fixedChildProducts attribute.
3. Create a global promotion that discounts this category. Set its attribute "Automatically give to all
customers" = "true" to make it global. Then set its discount rule to "Always discount items in
category Award Winning Products". Now, these items are discounted, regardless of whether the
customer views them in the special category or their original category.
4. Deliver some media to the customer to announce your new category, or simply place a link to the category
somewhere noticeable.
Cross-selling
ATG Consumer Commerce offers cross-selling features. Cross-selling promotes an add-on or accessory product
or service that, when combined with the primary product, makes a better or complete solution. Business
managers can use the commerce repository editor to identify products that are related (for example, a jersey
and some matching gloves) and have the catalog display a set of links to those products.
In the Pioneer Cycling site, cross-selling is accomplished by a related accessory list on a product page. Each
product has a list of related products. These products are chosen by a merchandiser, and placed into the
fixedRelatedProducts attribute. These related products are shown on a product template, with links to more
product information. A Get It button is also created that allows the customer to add that product to the cart
from that page. For example, in the following screenshot, a helmet and a water bottle cage are shown on the
page of a specific bike. The customer could add the helmet to her cart directly from this page rather than having
to go to the product page of the helmet.
10 Merchandising 197
Cross-selling a helmet and water bottle cage with a bike.
Upselling
The key to upselling is to promote a more powerful or higher-level product than the one the customer is
considering. It is key to portray both the current product and the promoted product as desirable for the
customer.
We didn’t specifically implement any upselling in Pioneer Cycling but you could easily do it in your web
application. The concept of upselling is to sell products of higher quality or better price to customers. For
example, if a customer looks at $1500 bike, we could also show a $2000 bike under Related Products on that
product page. We could add the text, “Bike X has the same safety and styling features as Bike Y, plus the added
features of a specially designed comfort seat and a titanium frame.”
This sort of upselling is accomplished with the same functionality as cross selling: using the
fixedRelatedProducts attribute.
You could also use a scenario to upsell products. For example, you could create a scenario that watches for
orders that contain an expensive bike frame, but economy tires. The scenario could offer an incentive for a better
set of tires, and perhaps a small discount. This scenario could potentially only be targeted at customers who
prefer more expensive products.
198 10 Merchandising
Slots
A slot is a type of component that you can create to hold items. When creating a slot, you specify certain
attributes about the slot and the type of item you can put in the slot. A slot can contain only one type of item
—for example, Products or Promotions, but not both. Slots must contain repository items; however, that item
could be a media-external item that links to anything.
For information about how to create slots, refer to the ATG Personalization Programming Guide.
Slots are typically used in the following way: One or more scenario processes puts items into the slot, and
another process looks in the slot and either displays the item in the slot or performs some other action on it.
The Pioneer Cycling site has several scenarios that populate slots with items. Then, various pages (including
store_home.jsp and many catalog display pages) dynamically display pages based on the contents of these
slots.
We used slots on the home page and among top-level pages. Slots can be filled with content, lists of products, or
promotions.
Creating a Slot
Before you can populate a slot, you must first create it. A slot is a named component so it needs a .properties
file. The slots properties files must reside in specific slot directories. The Pioneer Cycling store site has three
different types of slots, so we created separate named components:
• atg/repository/Slots/MediaSlot for media items
• atg/repository/Slots/ProductSlot for Product items
Here is an example of the MediaSlot.properties file:
#/atg/registry/Slots/MediaSlot$class=atg.scenario.targeting.RepositoryItemSlot$scope=sessiongeneration=0itemDescriptorName=media-externalmaxRenderSize=1repositoryName=ProductCatalogretrieval=1
As mentioned before, a slot can contain only one type of item. The itemDescriptorName defines what
item type is put into this slot in this case media-external. The media-external type is defined in the
ProductCatalog, so ProductCatalog is stored in the repositoryName attribute. The maxRenderSize
indicates the maximum number of items that can be pulled out of the slot at one time. See the Using Slots
chapter in the ATG Personalization Programming Guide for specifics on slots properties files.
We needed to create two slots for displaying promotions to our French and Japanese users: ProductSlotFr
and ProductSlotJa. These are similar to the original product slot, except that they refer to the different
repositories for the different language catalogs because each slot can only receive items from a single
repository.
The MediaSlot was localized in a different fashion. The same slot is displayed to all three languages, but the
media item that is placed into the slot checks the current locale, and renders the media in the appropriate
language.
10 Merchandising 199
Populating a Slot
We created some scenarios that populate the Pioneer Cycling site’s MediaSlot with zero or more image files
that contain promotional images we want to display at the top of store_home.jsp.
ATG Relationship Management Platform is very flexible and allows many options. We chose to create a scenario
called “New Customers” that populates the Pioneer Cycling site’s MediaSlot with an HTML file containing an
image that tells users they are eligible for 20% off their first purchase.
It accomplishes this with the “Show items in slot” action.
Displaying Items in a Slot
Once a slot is populated, you can do several things with the item in the slot. This section shows a simple slot
item example.
store_home.jsp simply displays whatever image is in the media-external item (an HTML file with a
reference to an image file) in the MediaSlot. The image is not clickable — the user just sees the image, but
clicking on the image does not result in any special behavior.
store_home.jsp displays the contents of the MediaSlot using the DisplayMediaSlot.jsp fragment.
The following JSP fragment is used to display the contents of the MediaSlot:
<DECLAREPARAM NAME="NumToDisplay" CLASS="java.lang.String" DESCRIPTION="NumToDisplay">
<dsp:droplet name="/atg/targeting/TargetingFirst"> <dsp:param name="howMany" param="NumToDisplay"/> <dsp:param bean="/atg/registry/Slots/MediaSlot" name="targeter"/> <dsp:param name="fireContentEvent" value="false"/> <dsp:param name="fireContentTypeEvent" value="false"/> <dsp:oparam name="output"> <dsp:getvalueof id="media_url" param="element.url" idtype="String"> <dsp:include page="<%=media_url%>" flush="true"/> </dsp:getvalueof> </dsp:oparam> <dsp:oparam name="empty"> No special offers today! </dsp:oparam>
</dsp:droplet>
This fragment displays the first item in the MediaSlot. Because the items in the slot are of type media-
external, they have a “url” property that can be used to display the media item. In this case, the “url” value in
the repository item points to the name of a file that contains the desired image.
In addition to MediaSlot, the Pioneer Cycling site uses the ProductSlot to display various products to
customers. Scenarios are created to populate the ProductSlot with products. Site developers can create
scenarios to populate a ProductSlot with some of the site’s newest, coolest products.
You could also create a scenario to populate a ProductSlot with products that are overstocked and are being
discounted to reduce stock. The Pioneeer Cycling site populates the ProductSlot with two simple scenarios. One
is the “Show Products” scenario that adds the new QW-33 Bike and some related products to the ProductSlot.
200 10 Merchandising
If the user views that particular bike, several other related parts are added to the ProductSlot via the “Cross
Sell” scenario.
The ProductSlot display code is similar to the MediaSLot code. You can view the JSP at
<ATG9dir>/PioneerCyclingJSP/j2ee-apps/pioneer/
web-app/en/common/DisplayProductSLotHome.jsp.
Gift Certificates
The Pioneer Cycling store allows customers to purchase and redeem gift certificates. A customer can pay for an
order using a gift certificate, credit card, or a combination of the two. There are three steps in the gift certificate
process: the customer purchases the gift certificate; Pioneer Cycling delivers the gift certificate to the designated
recipient, and the recipient redeems the gift certificate.
Purchase of Gift Certificate
Gift certificates are created in the Claimable repository for customers to claim when they check out. This
repository entry is typically generated when a user purchases the gift certificate. Please refer to the ATG
Consumer Commerce Claimable repository for more information. The Claimable repository contains items that
can be claimed by a user; it currently includes gift certificates and coupons.
For the Pioneer Cycling store, we created gift certificates as a product of the store. When the product was
created, all the information necessary to create the Gift Certificate was captured by the base product item-
descriptor definition. The gift certificate was created just as any other product and data was inserted with one
exception: the fulfiller field. Gift certificates are “shipped” electronically, so we used the SoftgoodFulfiller
instead of the standardHardgoodFulfiller used for tangible goods.
After we created the gift certificate item in the product catalog so that customers could view it, we
generated a purchase page. The purchase page differs from the pages used to purchase hard goods
in that the type of shipping group used to ship the gift certificate is different than hard goods. The
two main differences are that when the gift certificate is purchased it requires an e-mail address,
and the form submits to ShoppingCartHandler.handleAddSoftGoodToOrder() instead
of .ShoppingCartHandler.handleAddItemToOrder(), which is used for hard goods.
The recipient’s e-mail address must be specified by the property softGoodRecipientEmailAddress of the
ShoppingCartModifier component. This e-mail address is then used by the handleAddSoftGoodToOrder()
method to populate the shipping group associated with the gift certificate.
The JSP that places the gift certificate into the cart looks like:
Recipient's email address <dsp:inputbean="ShoppingCartModifier.softGoodRecipientEmailAddress" size="30"/><p><%/* ADD TO CART BUTTON: Adds this SKU to the Order*/%><dsp:input bean="ShoppingCartModifier.addSoftGoodToOrder" type="submit" value=" Add Gift Certificate to Cart "/>
Note that the amount of the gift certificate is set as the face value of the item times the quantity. So, if a store
offers gift certificates with a SKU price of $20 and a customer purchases five of them for one address, the
amount of the gift certificate is set to $100.
10 Merchandising 201
E-mail Delivery of Gift Certificates
Gift certificates are delivered to the user via the SoftGoodFulfiller. This service is defined by the component
at /atg/commerce/fulfillment/SoftgoodFulfiller. The delivery mechanism is e-mail. The component
that lives at /atg/commerce/fulfillment/SoftgoodFulfiller has several properties that can be set
to personalize the message sent by the fulfiller to the user. For the Pioneer Cycling site, we changed the
values of some of these properties so that the message would be appropriate for the store. This is what the
SoftgoodFulfiller.properties file for the Pioneer Cycling store looks like:
# email properties for customized messagedefaultSubject=A Gift Certificate for [email protected]=Here is a gift certificate claim number for you. You can go toPioneer Cycling store to use it.giftCertificateEmailTemplate=/PioneerCycling/en/emails/GiftCertificate.jsp
This sets the properties that control the contents of the e-mail sent to the gift certificate recipient.
Using Gift Certificates
The ShoppingCartModifier component handles claiming gift certificates and placing them into the purchase
information for an order. It relies on two key pieces: having the property giftCertificateNumbers be
populated with a String that represents white space separated gift certificate claim numbers and calling the
moveToConfirmation method of the ShoppingCartModifier on form submission. This is how we set up the
use of gift certificates in the Pioneer Cycling store:
In the two pages that deal with payment in the Pioneer Cycling store, PaymentInfo.jsp and
PaymentInfo2.jsp, we generated an input field where customers can type in the claim codes for their gift
certificates. Then, the submit value of the form gets set to moveToConfirmation. This is what the JSP looks
like:
If you have gift certificates, type their codes below<br><dsp:input bean="ShoppingCartModifier.giftCertificateNumbers" size="50" type="text" value=""/><br>
<%/* submition handle */%><dsp:input bean="ShoppingCartModifier.MoveToConfirmation" type="submit" value=" Continue --> "/>
Coupons
Coupons are a mechanism for delivering promotions. A customer receives a claim code for a coupon via
e-mail; she enters it during the checkout process and then the promotion is added to her Profile’s list of
activePromotions. The promotion becomes immediately available to her. There are two parts to the coupon
system: getting coupons into the system so they can be claimed and then the action of claiming the coupon.
202 10 Merchandising
Creating Coupons
Coupons exist in the claimable repository. The claimable repository is located at <ATG9dir>/home/atg/
commerce/en/claimable/ClaimableRepository. One of the item-descriptors defined in the claimable
repository is coupon. A coupon is essentially a claim code attached to a promotion. There are several ways to put
information into the repository: using the /atg/commerce/promotions/CouponDroplet or using the ACC to
populate the repository with an item. For the Pioneer Cycling store, we used the ACC to populate the repository
with two coupons:
• COUP1234: gives the customer a promotion called “5% off everything in the store - coupon”
• COUP1000: gives the customer a promotion called “$20 off orders over $80 - coupon”
We clicked on the Commerce component in the ACC and then selected the repository for “Gift Certificates and
Coupons.” After selecting this repository, we selected “New Item” and then entered the necessary information—
the promotion that we wanted to associate with the coupon.
Claiming Coupons
Claiming coupons is done via the /atg/commerce/promotions/CouponFormHandler component. This
component needs several properties set on a Java Server Page:
• couponClaimCode - the code to claim the coupon
• claimCouponSuccessURL - the URL to go to on successful claim
• claimCouponErrorURL - the URL to go to on claim failure
After the user has typed in a coupon claim code, the FormHandler attempts to claim it and add it to the user’s
list of promotions. Here is the JSP that can be used to claim a coupon.
<dsp:form action="<%=form40%>" method="post">
<% /* Where to go to on success or failure */%>
<dsp:input bean="CouponFormHandler.claimCouponSuccessURL" beanvalue="/OriginatingRequest.requestURI" type="hidden"/> <dsp:input bean="CouponFormHandler.claimCouponErrorURL" beanvalue="/OriginatingRequest.requestURI" type="hidden"/>
<%/* Get the coupon claim code */%> Coupon code: <dsp:input bean="CouponFormHandler.couponClaimCode" type="text"/>
<dsp:input bean="CouponFormHandler.claimCoupon" type="submit" value="Claim it"/></dsp:form>
10 Merchandising 203
Gift Registry
Overview
The Pioneer Cycling Store uses ATG Consumer Commerce gift lists to let customers keep lists of items they would
like to purchase in the future or receive as gifts and register upcoming events (such as birthdays or weddings).
ATG Consumer Commerce gift lists are repository-based. The repository is configured in the /atg/commerce/
gifts/Giftlists.properties file. ATG Consumer Commerce provides all the components required to
interface with the repository. The gift lists in the Pioneer Cycling Store use standard ATG Consumer Commerce
functionality and components; no custom code was written.
ATG Consumer Commerce Components
The ATG Consumer Commerce component hierarchy for the gift list functionality used in the Pioneer Cycling
Store is in atg.commerce.gifts. This package includes formHandlers and droplets that support:
• creating, editing, and publishing gift lists.
• adding items to gift lists.
• moving items between gift lists and shopping carts.
• searching for and purchasing from published gift lists.
For a complete description of the gift list repository, database, components, and configuration files, see the
chapter, Configuring Merchandising Services, in the ATG Commerce Programming Guide.
GiftlistFormHandler
The GiftlistFormHandler provides the primary interface between the customer and the gift list repository.
This form handler accepts input from the customer to create, update, and delete gift lists using information
provided. It is also used to add items to and remove items from gift lists. Properties in the handler are used to
store user input for processing as well as to store success and failure URLs to which the user is redirected after
processing.
LookupDroplets
The GiftlistLookupDroplet and GiftitemLookupDroplet components are instances of the
atg.repository.servlet.ItemLookupDroplet. These components provide an easy way to find items in the
repository based on id.
GiftlistDroplet
The GiftlistDroplet is a servlet bean that allows customers to add and remove gift lists (of other customers)
to their profile.
GiftitemDroplet
The GiftitemDroplet is a configurable servlet bean that allows customers to remove or buy items
from their own personal gift lists. Two droplets are provided with ATG Consumer Commerce gift lists:
RemoveItemFromGiftlist and BuyItemFromGiftlist.
204 10 Merchandising
GiftlistSearch
The GiftlistSearch form handler is used to search the repository for gift lists based on properties other than
id. The handler uses input from the customer such as owner name, event name, event type, and state to find gift
lists that other customers have published and to return a list of gift lists that match the given criteria.
Pioneer Cycling Gift Lists
We used ATG Consumer Commerce gift list functionality to create two types of lists in the Pioneer Cycling Store
that allow customers to register products and other information.
We called the first type of registry a wish list. The wish list is a default private list given to all customers to record
products to purchase at a later date. As customers shop through the store, they can add items to the wish list or
move items from the wish list to their shopping cart.
We called the other type of registry a gift list. Registered customers can create an unlimited number of these
lists to register upcoming events and to record pertinent information such as the event name, date, or shipping
address. They can add products to the list as they shop. Customers can publish their gift lists so that other
customers can search for and purchase items from them.
Creating Wish Lists
All Pioneer Store customers are given a default wish list when they visit the store. ATG Consumer Commerce
extends the user definition in the standard ATG Relationship Management Platform Profile Repository by adding
a wishlist property to userProfile.xml.
Creating and Displaying Gift Lists
All Pioneer Store registered customers can create and update an unlimited number of gift lists that are added to
their profiles. We used the standard gift lists provided by ATG Consumer Commerce that extend the customer’s
profile in the Profile Repository. Again, we used userProfile.xml. Refer to the Configuring Merchandising
Services chapter in the ATG Commerce Programming Guide for more information on how gift lists are created for
each user.
The GiftlistFormHandler manages wish lists and gift lists. The following wish list and saved gift lists code
sample shows how the Pioneer Cycling store displays wish lists and gift lists and the items in each. lists.jsp.
This sample has been edited for clarity. See <ATG9dir>/PioneerCyclingJSP/j2ee-apps/pioneer/web-
app/en/user/lists.jsp for the complete file.
<dsp:form action="lists.jsp" method="post"><p><span class=storebig>My Lists</span><p><span class=help>Lists allow you to save products you want for later, and shareyour giftlists with friends.</span><p>
<!-- First display the wishlist -->
<dsp:setvalue beanvalue="Profile.wishlist" param="wishlist"/><dsp:setvalue paramvalue="wishlist.giftlistItems" param="items"/><dsp:setvalue paramvalue="wishlist.id" param="giftlistId"/><dsp:input bean="GiftlistFormHandler.updateGiftlistItemsSuccessURL" type="hidden" value="./lists.jsp"/><dsp:input bean="GiftlistFormHandler.updateGiftlistItemsErrorURL" type="hidden"
10 Merchandising 205
value="./lists.jsp"/><dsp:input bean="GiftlistFormHandler.giftlistId" paramvalue="giftlistId" type="hidden"/><dsp:droplet name="IsEmpty"> <dsp:param name="value" param="items"/> <dsp:oparam name="false"> <dsp:droplet name="/atg/dynamo/droplet/ForEach"> <dsp:param name="array" param="items"/>
<dsp:oparam name="output"> <tr valign=top> <td><input name='<dsp:valueof param="element.id"/>' size="2" type="text" value='<dsp:valueof param="element.quantityDesired"/>'></td> <td></td> <td> <dsp:droplet name="/atg/commerce/catalog/ProductLookup"> <dsp:param name="id" param="element.productId"/> <dsp:param name="elementName" value="product"/> <dsp:oparam name="output"> <b>
<dsp:getvalueof id="templateUrl" idtype="String" param="product.template.url"> <dsp:a page="<%=templateUrl%>"> <dsp:param name="id" param="id"/> <dsp:param name="navAction" value="jump"/> <dsp:droplet name="/atg/commerce/catalog/SKULookup"> <dsp:param name="id" param="element.catalogRefId"/> <dsp:param name="elementName" value="giftSku"/> <dsp:oparam name="output"><dsp:valueof param="giftSku.displayName"/></dsp:oparam> </dsp:droplet> </dsp:a> </dsp:getvalueof> </b> </dsp:oparam> </dsp:droplet> </td> </dsp:oparam> <dsp:oparam name="outputEnd"> <dsp:input bean="GiftlistFormHandler.updateGiftlistItems" type="submit" value="Update wishlist quantities"/> </dsp:oparam> </dsp:droplet> </dsp:oparam>
<dsp:oparam name="true"> <b>Your wishlist is empty</b> </dsp:oparam></dsp:droplet></dsp:form>
<!-- Next display each giftlist -->
<dsp:droplet name="IsEmpty"> <dsp:param bean="Profile.giftlists" name="value"/> <dsp:oparam name="false">
<dsp:droplet name="/atg/dynamo/droplet/ForEach"> <dsp:param bean="Profile.giftlists" name="array"/>
206 10 Merchandising
<dsp:oparam name="output">
<blockquote> <dsp:form action="lists.jsp" method="post"> <table cellspacing=2 cellpadding=0 border=0 width=75%> <!-- Display giftlist name --> <tr><td colspan=10 class=box-top-viewmy> <dsp:valueof param="element.eventName"/> </td> </tr>
<!-- Display giftlist items if any --> <dsp:setvalue paramvalue="element.giftlistItems" param="items"/> <dsp:setvalue paramvalue="element.id" param="giftlistId"/> <dsp:setvalue paramvalue="element.eventName" param="listName"/>
<dsp:input bean="GiftlistFormHandler.updateGiftlistItemsSuccessURL" type="hidden" value="./lists.jsp"/> <dsp:input bean="GiftlistFormHandler.updateGiftlistItemsErrorURL" type="hidden" value="./lists.jsp"/> <dsp:input bean="GiftlistFormHandler.giftlistId" paramvalue="giftlistId" type="hidden"/>
<dsp:droplet name="IsEmpty"> <dsp:param name="value" param="items"/> <dsp:oparam name="false">
<!-- Display giftlist items if any --> </dsp:oparam> <dsp:oparam name="true"> </dsp:oparam> </dsp:droplet> </dsp:form><!--* Finally, check if customer is registered. If so allow them to create a * new giftlist using GiftlistFormHandler.createGiftlist. -->
The following screenshot shows how the above code sample displays the customer’s wish list, gift list(s) and
their contents.
10 Merchandising 207
Creating Gift Lists
In the above screenshot, one option for registered customers is to create and edit gift lists. The following code
sample shows how to test for registered customers and to display the box to create a new list:
lists.jsp
<dsp:droplet name="/atg/dynamo/droplet/Switch"> <dsp:param bean="Profile.transient" name="value"/> <dsp:oparam name="false"> <blockquote> <table cellspacing=0 cellpadding=0 border=0 width=75%> <tr><td colspan=6 class=box-top-viewmy>Make a new gift list</td></tr> <tr><td colspan=6 class=box>
208 10 Merchandising
<dsp:form action="lists_new.jsp" method="POST"> <dsp:input bean="GiftlistFormHandler.createGiftlistSuccessURL" type="hidden" value="./lists_new.jsp"/> <dsp:input bean="GiftlistFormHandler.createGiftlistErrorURL" type="hidden" value="./lists.jsp"/> Name of event <span class=help>(i.e. Harry's Wedding List)</span><br> <dsp:input bean="GiftlistFormHandler.eventName" size="25" type="text" value="New Event"/> <p> <dsp:input bean="GiftlistFormHandler.createGiftlist" type="submit" value="Make new gift list"/> </dsp:form> </td> </tr> </table> </blockquote> </dsp:oparam> <dsp:oparam name="default"> <blockquote> <b>Login or Register and create a gift list!</b><p> Registered customers at Pioneer Cycling are able to create giftlists and share them with their friends. Let everyone know what gear you want for your birthday. <p> > <dsp:a href="my_profile.jsp">Login or Register </dsp:a> now and start workingon your giftlist. </blockquote> </dsp:oparam></dsp:droplet>
When customers select the “Make New Gift list” button, they are directed to the “create gift list” page where
they enter information about the upcoming event such as date, type, and description. This form again uses the
GiftlistFormHandler to accept customer input and to call handlers to save the information in the repository.
The following code sample (from lists_create.jsp) shows how input is taken and handle methods are
called.
Note: The “. . .” markers in the following code sample indicate a place where code has been removed to clarify
the sample.
<%--This page allows a user to create a new giftlist--%>
<dsp:importbean bean="/atg/commerce/gifts/GiftlistFormHandler"/><dsp:importbean bean="/atg/dynamo/droplet/ForEach"/><dsp:importbean bean="/atg/dynamo/droplet/Switch"/><dsp:importbean bean="/atg/dynamo/droplet/IsEmpty"/><dsp:importbean bean="/atg/userprofiling/Profile"/>
<span class=storebig>Create new giftlist</span><dsp:form action="lists.jsp" method="POST"><dsp:input bean="GiftlistFormHandler.saveGiftlistSuccessURL" type="hidden" value="./lists.jsp"/><dsp:input bean="GiftlistFormHandler.saveGiftlistErrorURL" type="hidden" value="./lists_new.jsp"/>. . .
<b>Event Name</b><br>
10 Merchandising 209
<dsp:input bean="GiftlistFormHandler.eventName" size="40" type="text"/> <p> <b>Event Date</b> <dsp:select bean="GiftlistFormHandler.month"> <dsp:droplet name="ForEach"> <dsp:param bean="GiftlistFormHandler.months" name="array"/> <dsp:oparam name="output"> <dsp:getvalueof id="option564" param="index" idtype="java.lang.Integer"> <dsp:option value="<%=option564.toString()%>"/> </dsp:getvalueof> <dsp:valueof param="element">UNDEFINED</dsp:valueof> </dsp:oparam> </dsp:droplet> </dsp:select> <dsp:select bean="GiftlistFormHandler.date"> <dsp:droplet name="ForEach"> <dsp:param bean="GiftlistFormHandler.dates" name="array"/> <dsp:oparam name="output"> <dsp:getvalueof id="option583" param="element" idtype="java.lang.String"> <dsp:option value="<%=option583%>"/> </dsp:getvalueof> <dsp:valueof param="element">UNDEFINED</dsp:valueof> </dsp:oparam> </dsp:droplet> </dsp:select> <dsp:select bean="GiftlistFormHandler.year"> <dsp:droplet name="ForEach"> <dsp:param bean="GiftlistFormHandler.years" name="array"/> <dsp:oparam name="output"> <dsp:getvalueof id="option602" param="element" idtype="java.lang.String"> <dsp:option value="<%=option602%>"/> </dsp:getvalueof> <dsp:valueof param="element">UNDEFINED</dsp:valueof> </dsp:oparam> </dsp:droplet> </dsp:select> <p> <b>Event Type</b> <dsp:select bean="GiftlistFormHandler.eventType"> <dsp:droplet name="ForEach"> <dsp:param bean="GiftlistFormHandler.eventTypes" name="array"/> <dsp:oparam name="output"> <dsp:getvalueof id="option627" param="element" idtype="java.lang.String"> <dsp:option value="<%=option627%>"/> </dsp:getvalueof> <dsp:valueof param="element">UNDEFINED</dsp:valueof> </dsp:oparam> </dsp:droplet> </dsp:select><br>
<b>Comments</b><br> <dsp:setvalue bean="GiftlistFormHandler.comments" value=""/> <dsp:textarea bean="GiftlistFormHandler.comments" rows="4" cols="40"></dsp:textarea><%-- <dsp:textarea bean="GiftlistFormHandler.comments" maxlength="254" value="" rows="4" cols="40"></dsp:textarea></dsp:textarea> --%>
210 10 Merchandising
<p>
<b>Event Description</b><br> <dsp:setvalue bean="GiftlistFormHandler.description" value=""/><%-- <dsp:textarea bean="GiftlistFormHandler.description" maxlength="254" value="" rows="4" cols="40"></dsp:textarea> --%> <dsp:textarea bean="GiftlistFormHandler.description" rows="4" cols="40"></dsp:textarea> <p> <b>Extra information or special instructions</b><br> <%-- <dsp:textarea bean="GiftlistFormHandler.instructions" maxlength="254" value="" rows="4" cols="40"></dsp:textarea> --%> <dsp:textarea bean="GiftlistFormHandler.instructions" rows="4" cols="40"></dsp:textarea> <p> <b>Where should people ship the gifts?</b><p> <blockquote> <dsp:input bean="GiftlistFormHandler.isNewAddress" type="radio" checked="<%=true%>" value="false"/>Choose one of your saved shipping destinations.<br> <dsp:select bean="GiftlistFormHandler.shippingAddressId"> <dsp:droplet name="ForEach"> <dsp:param bean="GiftlistFormHandler.addresses" name="array"/> <dsp:oparam name="output"> <dsp:getvalueof id="option695" param="key" idtype="java.lang.String"> <dsp:option value="<%=option695%>"/> </dsp:getvalueof> <dsp:valueof param="element">UNDEFINED</dsp:valueof> </dsp:oparam> </dsp:droplet> </dsp:select><br> <p> <dsp:input bean="GiftlistFormHandler.isNewAddress" type="radio" value="true"/>New address below <p> If you want this address stored in your address book, please give it a nickname:<br> <dsp:input bean="GiftlistFormHandler.newAddressName" size="40"/><br> Name <br><dsp:input bean="GiftlistFormHandler.newAddress.firstName" name="firstName" size="15"/> <dsp:input bean="GiftlistFormHandler.newAddress.middleName" name="middleName" size="5"/> <dsp:input bean="GiftlistFormHandler.newAddress.lastName" name="lastName" size="15"/><br> Street address <br> <dsp:input bean="GiftlistFormHandler.newAddress.address1" name="address1" size="40"/><br> <dsp:input bean="GiftlistFormHandler.newAddress.address2" name="address2" size="40"/><br> <table border=0 cellspacing=0 cellpadding=0> <tr valign=top> <td>City<br> <dsp:input bean="GiftlistFormHandler.newAddress.city" name="city" size="15"/></td> <td>State<br> <dsp:select bean="GiftlistFormHandler.newAddress.state"> <%@ include file="StatePicker.jspf" %> </dsp:select></td> <td>Postal Code<br>
10 Merchandising 211
<dsp:input bean="GiftlistFormHandler.newAddress.postalCode" name="postalCode" size="15"/></td> </tr> </table>
Country <dsp:select bean="GiftlistFormHandler.newAddress.country"> <%@ include file="CountryPicker.jspf" %> </dsp:select> </blockquote>
<p> <br> </td> </tr>
<tr valign=top> <td width=30%> <span class=help> Decide if you want your list to be public yet. When you make your list public, your friends can find your list by searching. </span>
</td> <td></td> <td> <table width=100% cellpadding=0 cellspacing=0 border=0> <tr><td class=box-top-store>Gift list public?</td></tr></table> <p>
<dsp:input bean="GiftlistFormHandler.isPublished" name="published" type="radio" value="true"/> Make my list public now<br> <dsp:input bean="GiftlistFormHandler.isPublished" name="published" type="radio" checked="<%=true%>" value="false"/> Don't make my list public yet
<p> <br>
<dsp:input bean="GiftlistFormHandler.saveGiftlist" type="submit" value="Save gift list"/>
Here is the screenshot for lists_new.jsp. It takes the input from customers and saves it to the repository.
212 10 Merchandising
lists_new.jsp
Editing and Deleting Gift Lists
The implementations of editing and deleting are similar to creating gift lists. On the “My Lists” page, there is an
edit this list link below each gift list. This link directs the customer to the edit gift list page and passes the gift
list id through. For the Pioneer Cycling store, we did this in lists_edit.jsp. It calls GiftlistLookupDroplet
to get the gift list item from the repository and checks the owner of the gift list against the profile id. If they
match, then the gift list information is displayed in the same format as create and options to save and delete are
10 Merchandising 213
provided. The following code sample demonstrates how to call GiftlistLookupDroplet and how to check for
permissions.
Note: The “. . .” markers in the following code sample indicate a place where code has been removed to clarify
the sample.
lists_edit.jsp:
<dsp:droplet name="/atg/commerce/gifts/GiftlistLookupDroplet"> <dsp:param name="id" param="giftlistId"/> <dsp:oparam name="output"> <dsp:droplet name="IsEmpty"> <dsp:param name="value" param="element"/> <dsp:oparam name="false"> <dsp:setvalue paramvalue="element" param="giftlist"/>
<dsp:droplet name="/atg/dynamo/droplet/Switch"> <dsp:param bean="Profile.id" name="value"/> <dsp:getvalueof id="nameval3" param="giftlist.owner.id" idtype="java.lang.String"><dsp:oparam name="<%=nameval3%>"> <dsp:form action="lists.jsp" method="POST">
. . .
<b>Event Name</b><br> <dsp:input bean="GiftlistFormHandler.eventName" paramvalue="giftlist.eventName" size="40" type="text"/>
. . . <p> <br> <dsp:input bean="GiftlistFormHandler.deleteGiftlistSuccessURL" type="hidden" value="./lists.jsp"/> <dsp:input bean="GiftlistFormHandler.deleteGiftlistErrorURL" type="hidden" value="<%=thisPage.getNewUrl()%>"/> <dsp:input bean="GiftlistFormHandler.deleteGiftlist" name="delete" type="submit" value="Delete gift list"/> <dsp:input bean="GiftlistFormHandler.updateGiftlistSuccessURL" type="hidden" value="./lists.jsp"/> <dsp:input bean="GiftlistFormHandler.updateGiftlistErrorURL" type="hidden" value="<%=thisPage.getNewUrl()%>"/> <dsp:input bean="GiftlistFormHandler.updateGiftlist" name="update" type="submit" value="Save gift list"/> </core:createUrl> </td> </tr> </table> </dsp:form> </dsp:oparam> <dsp:oparam name="default"> You do not have permission to access the specified giftlist </dsp:oparam> </dsp:droplet> </dsp:oparam> <dsp:oparam name="true"> <font color=cc0000><STRONG><UL> Either no giftlist found or you do not have permission to access
214 10 Merchandising
it. </dsp:oparam> </dsp:droplet> </dsp:oparam></dsp:droplet>
Adding Items to Gift Lists
Customers can add items to any of their lists while they shop. In the Pioneer Cycling Store, we used the /atg/
commerce/gifts/GiftlistFormHandler component in the product display page with an option for the
customer to pick a list for the selected item.
The following code sample (from GiftlistGetItBox.jsp) demonstrates how to set the formHandler
properties and how to add the selected product and quantity to a gift list:
<dsp:importbean bean="/atg/commerce/gifts/GiftlistFormHandler"/><dsp:importbean bean="/atg/userprofiling/Profile"/>
<table cellpadding=0 cellspacing=0 border=0> <tr> <td class=box-top-store>Add to Gift List</td> </tr> <tr> <td class=box>
<dsp:getvalueof id="form24" bean="/OriginatingRequest.requestURI" idtype="java.lang.String"><dsp:form action="<%=form24%>" method="post"> <input name="id" type="hidden" value='<dsp:valueof param="id"/>'> <input type="hidden" name="itemType" value="product"> <input name="itemId" type="hidden" value='<dsp:valueof param="Product.repositoryId"/>'> <dsp:input bean="GiftlistFormHandler.addItemToGiftlistSuccessURL" type="hidden" value="../user/lists.jsp"/> <dsp:input bean="GiftlistFormHandler.addItemToGiftlistErrorURL" type="hidden" value="../user/lists.jsp"/> <dsp:input bean="GiftlistFormHandler.productId" paramvalue="Product.repositoryId" type="hidden"/>
<%/*Display any errors that have been generated during Cart operations: */%> <dsp:include page="../../common/DisplayGiftlistFormHandlerErrors.jsp" flush="true"></dsp:include>
Add <dsp:input bean="GiftlistFormHandler.quantity" size="4" type="text" value="1"/>
<dsp:select bean="GiftlistFormHandler.catalogRefIds"> <dsp:droplet name="/atg/dynamo/droplet/ForEach"> <dsp:param name="array" param="Product.childSKUs"/> <dsp:param name="elementName" value="sku"/> <dsp:param name="indexName" value="skuIndex"/> <dsp:oparam name="output"> <dsp:getvalueof id="option59" param="sku.repositoryId" idtype="java.lang.String"><dsp:option value="<%=option59%>"/></dsp:getvalueof><dsp:valueof param="sku.displayName"/>
10 Merchandising 215
</dsp:oparam> </dsp:droplet> </dsp:select> <BR> to <dsp:select bean="GiftlistFormHandler.giftlistId"> <dsp:getvalueof id="option73" bean="Profile.wishlist.id" idtype="java.lang.String"><dsp:option value="<%=option73%>"/></dsp:getvalueof>My Wishlist <dsp:droplet name="/atg/dynamo/droplet/ForEach"> <dsp:param bean="Profile.giftlists" name="array"/> <dsp:param name="elementName" value="giftlist"/> <dsp:oparam name="output"> <dsp:getvalueof id="option83" param="giftlist.id" idtype="java.lang.String"><dsp:option value="<%=option83%>"/></dsp:getvalueof><dsp:valueof param="giftlist.eventName">Undefined</dsp:valueof> </dsp:oparam> </dsp:droplet> </dsp:select> <dsp:input bean="GiftlistFormHandler.addItemToGiftlist" type="submit" value="I Want It"/> </dsp:form></dsp:getvalueof>
</td> </tr></table>
The following screenshot is from a product page that demonstrates how the above code renders on the product
page.
216 10 Merchandising
Gift Purchasing
Customers can publish gift lists so that other customers can search for them and view and purchase the items
listed on them. (Wish lists are similar to gift lists, except they are private and can be seen only by their owners.
They are not searchable.) In order to implement gift list searching and gift purchasing for the Pioneer Cycling
Store, we used the GiftlistSearch form handler to search for gift lists, GiftListDroplet to add and
remove gift lists from a profile, and GiftitemLookupDroplet to view and purchase gift items.
Searching for Gift Lists
The GiftlistSearch component is a configurable component used for gift list searching. For the
Pioneer Cycling Store, we use the standard configuration to search for gift lists. The searching criteria
used are: owner name, owner state, event name, and event type. The following configuration file,
GiftlistSearch.properties, configures an instance of the atg.commerce.gifts.SearchFormHandler
class.
/atg/commerce/gifts/GiftlistSearch.properties
$class=atg.commerce.gifts.SearchFormHandler$scope=session
doNameSearch=truenameSearchPropertyNames=owner.firstName,owner.lastName
10 Merchandising 217
doAdvancedSearch=trueadvancedSearchPropertyNames=eventType,eventName,state
doPublishedSearch=truepublishedSearchPropertyNames=public,published
profileRepository^=/atg/userprofiling/ProfileTools.profileRepositoryprofileTools=/atg/userprofiling/ProfileToolsitemTypes=gift-list
The next code sample is from giftlist_search.jsp. It demonstrates how we used the GiftlistSearch
component to find published gift lists. This file has been split into two parts to show what each section does. The
first half of the code displays previously found (if any) gift lists that have been added to the customer profile.
The following code can be found <ATG9dir>/PioneerCyclingJSP/j2ee-apps/pioneer/
web-app/en/catalog/giftlist_search.jsp:
<%/*Display the giftlists that customer is shopping for.*/%>
<dsp:droplet name="IsEmpty"> <dsp:param bean="Profile.otherGiftlists" name="value"/> <dsp:oparam name="false"> <b>You are shopping for these people</b><br> <hr size=0> <dsp:droplet name="/atg/dynamo/droplet/ForEach"> <dsp:param bean="Profile.otherGiftlists" name="array"/> <dsp:oparam name="output"> <p> <b><dsp:valueof param="element.owner.firstName"/> <dsp:valueof param="element.owner.middleName"/> <dsp:valueof param="element.owner.lastName"/></b></br> <dsp:valueof param="element.eventName"/> <dsp:valueof date="d-MMM-yyyy" param="element.eventDate"/><br> <b>Event Description</b><br> <dsp:valueof param="element.description"/><br> <b>Extra Information</b><br> <dsp:valueof param="element.instructions"/><br> > <dsp:a href="./giftlist_view.jsp"> <dsp:param name="giftlistId" param="element.id"/>View the items in this gift list</dsp:a> <br> > <dsp:a href="./giftlist_search.jsp"> <dsp:param name="giftlistId" param="element.id"/> <dsp:param name="action" value="remove"/> Stop shopping for this person</dsp:a> </dsp:oparam> </dsp:droplet> <hr size=0></dsp:oparam>
<dsp:oparam name="true"></dsp:oparam>
</dsp:droplet>
When rendered, the page lists all previously found gift lists and provides customers with a form to perform
additional searches.
218 10 Merchandising
The second half of giftlist_search.jsp as shown here does the searching. It takes input from the customer
and, on submit, searches the repository for published gift lists that meet the criteria.
The following code can be found in <ATG9dir>/PioneerCyclingJSP/j2ee-apps/pioneer/
web-app/en/catalog/giftlist_search.jsp:
<dsp:droplet name="ForEach"> <dsp:param bean="GiftlistSearch.searchResults" name="array"/> <dsp:oparam name="output"> <dsp:a href="./giftlist_search.jsp"> <dsp:param name="action" value="add"/> <dsp:param name="searching" value="false"/> <dsp:param name="giftlistId" param="element.id"/> <dsp:valueof param="element.owner.firstName"/> <dsp:valueof param="element.owner.middleName"/> <dsp:valueof param="element.owner.lastName"/> </dsp:a> <dsp:valueof param="element.eventName"/> <dsp:valueof param="element.eventDate"/> <br> </dsp:oparam></dsp:droplet>
10 Merchandising 219
</blockquote>
</dsp:oparam><dsp:oparam name="true">
Your search found no giftlists <p>
</dsp:oparam></dsp:droplet></tr></table>
</dsp:oparam></dsp:droplet>
<dsp:form action="giftlist_search.jsp"><p><b>Find someone's gift list</b><hr size=0>Name: <dsp:input bean="GiftlistSearch.searchInput" size="30" type="text"/><p>Optional criteria that may make it easier to find the right list:<p>
<dsp:droplet name="ForEach"><%-- For each property specified in GiftlistSearch.advancedSearchPropertyNames, retrieve all possible property values. This allows the customer to pick one to search on for advanced searching. --%><dsp:param bean="GiftlistSearch.propertyValuesByType" name="array"/><dsp:oparam name="output"><dsp:droplet name="Switch"> <dsp:param name="value" param="key"/> <%-- One property that a product in the store can have is weight range. In this case, if the property is weight range, we want to put all possible choices in a pulldown menu. --%> <dsp:oparam name="eventType"> Event Type <%-- property to store the customer's selection is propertyValues --%> <dsp:select bean="GiftlistSearch.propertyValues.eventType"> <dsp:option value=""/>Any <dsp:setvalue paramvalue="element" param="outerelem"/> <dsp:droplet name="ForEach"> <dsp:param name="array" param="outerelem"/> <dsp:oparam name="output">
<dsp:getvalueof id="eventTypeName" param="element" idtype="java.lang.String"> <dsp:option value="<%=eventTypeName%>"/> </dsp:getvalueof>
<dsp:valueof param="element">UNDEFINED</dsp:valueof> </dsp:oparam> </dsp:droplet> </dsp:select><br> </dsp:oparam>
<dsp:oparam name="eventName"> <b>Event Name <%-- property to store the customer's selection is propertyValues --%>
220 10 Merchandising
<dsp:input bean="GiftlistSearch.propertyValues.eventName" size="30" type="text" value=""/> <br> </dsp:oparam> <dsp:oparam name="state"> <b>State <%-- property to store the customer's selection is propertyValues --%> <dsp:input type="text" size="30" bean="GiftlistSearch.propertyValues.state" value=""/> <br> </dsp:oparam> </dsp:droplet> </dsp:oparam> </dsp:droplet>
Viewing Someone Else’s Gift Lists
The View the items on this gift list link takes the customer to a giftlist_view.jsp page. For this feature, we
used the GiftlistLookupDroplet to retrieve the gift list from the repository by Id. The following code sample
shows how we used the component and displayed each item on the list and information about it.
The following code can be found in <ATG9dir>/PioneerCyclingJSP/j2ee-apps/pioneer/
web-app/en/catalog/giftlist_view.jsp:
<%/* check if parameter giftlistId has been passed into page. if it has, then call GiftlistDroplet to do something */ %><dsp:droplet name="IsEmpty"><dsp:param name="value" param="giftlistId"/><dsp:oparam name="false"> <dsp:droplet name="/atg/commerce/gifts/GiftlistLookupDroplet"> <dsp:param name="id" param="giftlistId"/> <dsp:oparam name="output"> <dsp:droplet name="IsEmpty"> <dsp:oparam name="false"> <dsp:setvalue paramvalue="element" param="giftlist"/> <span class=storebig>Gift List for <dsp:valueof param="giftlist.owner.firstName"/> <dsp:valueof param="giftlist.owner.middleName"/> <dsp:valueof param="giftlist.owner.lastName"/> </span> <br> <p> <blockquote> <b>Event name:</b><dsp:valueof param="giftlist.eventName"/> <br> <b>Event date:</b><dsp:valueof date="d-MMM-yyyy" param="giftlist.eventDate"/> <p>
<table cellspacing=0 cellpadding=0 border=0> <tr valign=bottom> <td><b>Quantity<br>wanted</b></td> <td> </td> <td><b>Quantity<br>bought</b></td> <td> </td> <td><b>Product</b></td> </tr> <tr><td colspan=5><hr size=0></td></tr> <tr valign=top> <dsp:droplet name="/atg/dynamo/droplet/ForEach">
10 Merchandising 221
<dsp:param name="array" param="giftlist.giftlistItems"/> <dsp:oparam name="output"> <dsp:setvalue paramvalue="element" param="item"/> <dsp:droplet name="/atg/commerce/catalog/ProductLookup"> <dsp:param name="elementName" value="product"/> <dsp:param name="id" param="item.productId"/> <dsp:oparam name="output"> <td><dsp:valueof param="item.quantityDesired">quantity desired</dsp:valueof></td><td></td> <td><dsp:valueof param="item.quantityPurchased">quantity purchased</dsp:valueof></td><td></td> <td>
<dsp:droplet name="/atg/dynamo/droplet/IsNull"> <dsp:param name="value" param="product.template"/> <dsp:oparam name="true"> <dsp:valueof param="product.displayName">ERROR:no product name</dsp:valueof> </dsp:oparam> <dsp:oparam name="false"> <dsp:getvalueof id="templateUrl" idtype="String" param="product.template.url"> <dsp:a page="<%=templateUrl%>"> <dsp:param name="id" param="product.repositoryId"/> <dsp:param name="giftId" param="item.id"/> <dsp:param name="giftlistId" param="giftlistId"/> <dsp:param name="gift" value="true"/> <dsp:param name="navAction" value="jump"/> <dsp:valueof param="product.displayName">ERROR:no product name</dsp:valueof> </dsp:a> </dsp:getvalueof> </dsp:oparam> </dsp:droplet>
</td> </tr> </dsp:oparam> </dsp:droplet> </dsp:oparam> <dsp:oparam name="unset"> Your giftlist is empty </dsp:oparam> </dsp:droplet> </tr> </table> <p> <b>Event description:</b><br> <dsp:valueof param="giftlist.description"/><br> <p> <b>Ship to:</b> <blockquote> </blockquote> <p> </span> <% /* Where are the gifts going to be sent to? This person may be different from the owner of the giftlist, so we need to display the shipping address person, city and state. */ %>
222 10 Merchandising
<dsp:valueof param="giftlist.shippingAddress.firstName"/> <dsp:valueof param="giftlist.shippingAddress.middleName"/> <dsp:valueof param="giftlist.shippingAddress.lastName"/><br> <dsp:valueof param="giftlist.shippingAddress.city"/>, <dsp:valueof param="giftlist.shippingAddress.state"/><br> </blockquote> <p> </dsp:oparam> <dsp:oparam name="true"> Nothing to look at here. </dsp:oparam> </dsp:droplet> </dsp:oparam> </dsp:droplet></dsp:oparam></dsp:droplet>
The figure shows how the above code sample displays information about the event and items. For each item, it
shows quantity wanted and quantity purchased to date. A link to that product page displays the product and an
“add to cart” button.
giftlist_view.jsp
Address privacy
Security is always an important factor. When customers register a gift list on the Pioneer Cycling store, they
select one of their stored shipping addresses for the list. This way, friends know where to send their gifts. All
10 Merchandising 223
addresses in ATG Consumer Commerce have an owner ID. All pages where the address may appear (gift list
details, order summary) verify the owner ID before displaying complete address information. The current profile
ID is compared to the owner ID of the address. If they are not equal, only the city and state are displayed and
the street address is hidden. Because this code is at the page level, individual sites can select a security level
as required. The following code sample demonstrates how we used the switch servlet bean on the owner id
property of an address to show limited information.
The following code can be found in <ATG9dir>/PioneerCyclingJSP/j2ee-apps/pioneer/
web-app/en/Checkout/full/co_confirm.jsp:
<!-- If address owner id = profile id, then display street address --> <dsp:droplet name="Switch"> <dsp:param name="value" param="ShippingGroup.shippingAddress.ownerId"/>
<!-- If this user "owns" this address, pass in private=false: --> <dsp:getvalueof id="nameval3" bean="Profile.id" idtype="java.lang.String"><dsp:oparam name="<%=nameval3%>"> <dsp:getvalueof id="pval0" param="ShippingGroup.shippingAddress"> <dsp:include page="../../user/DisplayAddress.jsp" flush="true"> <dsp:param name="address" value="<%=pval0%>"/> <dsp:param name="private" value="false"/></dsp:include></dsp:getvalueof> </dsp:oparam></dsp:getvalueof>
<!-- Else pass in private=true: --> <dsp:oparam name="default"> <dsp:getvalueof id="pval0" param="ShippingGroup.shippingAddress"> <dsp:include page="../../user/DisplayAddress.jsp" flush="true"> <dsp:param name="address" value="<%=pval0%>"/> <dsp:param name="private" value="true"/></dsp:include></dsp:getvalueof> </dsp:oparam> </dsp:droplet>
en\user\DisplayAddress.jsp:
<dsp:importbean bean="/atg/dynamo/droplet/IsEmpty"/><dsp:importbean bean="/atg/dynamo/droplet/Switch"/>
<DECLAREPARAM NAME="address" CLASS="java.lang.Object" DESCRIPTION="A ContactInfo Repository Item to display">
<DECLAREPARAM NAME="private" CLASS="boolean" DESCRIPTION="If true, we will hide the details of the address.">
<dsp:valueof param="address.firstName"/><dsp:valueof param="address.middleName"/><dsp:valueof param="address.lastName"/><br><dsp:droplet name="Switch"> <dsp:param name="value" param="private"/> <dsp:oparam name="true"> </dsp:oparam> <dsp:oparam name="default"> <dsp:valueof param="address.address1"/><br>
224 10 Merchandising
<dsp:droplet name="IsEmpty"> <dsp:param name="value" param="address.address2"/> <dsp:oparam name="false"> <dsp:valueof param="address.address2"/><br> </dsp:oparam> </dsp:droplet> </dsp:oparam></dsp:droplet><dsp:valueof param="address.city"/>,<dsp:valueof param="address.state"/><dsp:valueof param="address.postalCode"/><br><dsp:valueof param="address.country"/>
Purchasing Items from Gift Lists
Each page that shows a specific gift list has links to each product on the list that bring the customer to the
corresponding product pages. Then, on each product page, the customer has the ability to add that gift item to
a shopping cart. To do this, we used the addGiftItemToOrder in the ShoppingCartModifier component to
add the gift to the customer’s shopping cart. Special handling instructions are created for a gift in a shopping
cart. See the section on Extending the Purchase Process for Gift Lists in the Configuring Merchandising Services
chapter of the ATG Commerce Programming Guide for a description of the purchase process. Customers can
either continue shopping or proceed to checkout after they add gifts to their shopping carts.
The following sample is from en\catalog\GetItForGiftListBox.jsp:
<%/*This fragment is placed on a product template. It allows a customerto purchase something off someone else's gift list. It assumes thatparam:product, param:giftlistId, and param:giftId are passed.*/%>
<dsp:importbean bean="/atg/commerce/order/ShoppingCartModifier"/><dsp:importbean bean="/atg/commerce/catalog/SKULookup"/>
<dsp:droplet name="/atg/commerce/gifts/GiftlistLookupDroplet"> <dsp:param name="id" param="giftlistId"/> <dsp:param name="elementName" value="giftlist"/>
<dsp:oparam name="output"> <table cellpadding=0 cellspacing=0 border=0> <tr> <td class=box-top-store>Get this gift for <dsp:valueof param="giftlist.owner.firstName">someone</dsp:valueof></td> </tr>
<tr> <td class=box>
<dsp:getvalueof id="form33" bean="/OriginatingRequest.requestURI" idtype="java.lang.String"><dsp:form action="<%=form33%>" method="post"> <input name="id" type="hidden" value='<dsp:valueof param="Product.repositoryId"/>'> <input type="hidden" name="itemType" value="Product"> <input name="itemId" type="hidden" value='<dsp:valueof param="Product.repositoryId"/>'> <dsp:input bean="ShoppingCartModifier.addGiftItemToOrderSuccessURL"
10 Merchandising 225
type="hidden" value="../checkout/cart.jsp"/> <dsp:input bean="ShoppingCartModifier.productId" paramvalue="Product.repositoryId" type="hidden"/> <dsp:input bean="ShoppingCartModifier.giftlistId" paramvalue="giftlist.id" type="hidden"/> <dsp:droplet name="/atg/commerce/gifts/GiftitemLookupDroplet"> <dsp:param name="id" param="giftId"/> <dsp:param name="elementName" value="giftitem"/> <dsp:oparam name="output"> <dsp:input bean="ShoppingCartModifier.giftlistItemId" paramvalue="giftitem.id" type="hidden"/> <dsp:input bean="ShoppingCartModifier.catalogRefIds" paramvalue="giftitem.catalogRefId" type="hidden"/>
<dsp:valueof param="giftlist.owner.firstName">firstname</dsp:valueof> wants <dsp:valueof param="giftitem.quantityDesired">?</dsp:valueof> <dsp:droplet name="SKULookup"> <dsp:param name="id" param="giftitem.catalogRefId"/> <dsp:param name="elementName" value="skuItem"/> <dsp:oparam name="output"> <dsp:valueof param="skuItem.displayName">sku</dsp:valueof><br> </dsp:oparam> </dsp:droplet> and so far people have bought <dsp:valueof param="giftitem.quantityPurchased">?</dsp:valueof>. <p>
</dsp:oparam></dsp:droplet>
<%/* URL to go to if user's session expires while he is filling out this form */%><dsp:input bean="ShoppingCartModifier.sessionExpirationURL" type="hidden" value="../../common/SessionExpired.jsp"/>
Buy <dsp:input bean="ShoppingCartModifier.quantity" size="4" type="text" value="1"/><dsp:input bean="ShoppingCartModifier.addGiftItemToOrder" type="submit" value="Add to Cart"/></dsp:form></dsp:getvalueof>
</dsp:oparam></dsp:droplet>
This screenshot shows the product page for an item on the gift list and the option to purchase one for the
customer:
226 10 Merchandising
Checkout with Gifts
During checkout, a customer may edit the shipping instructions for the gift. By default, the item ships to
the address in the gift list; however, the purchaser may ship the gift to a different address. Once the order is
processed and item shipped, the gift list is updated to display the quantity purchased for that item.
Bundles
Overview
ATG Consumer Commerce allows you to bundle SKUs into a single product as a merchandising package. The
bundle of SKUs is then treated as any other product, and ordered as a single item.
10 Merchandising 227
Bundle building example
In the Pioneer Cycling store, we created several bundles. One of them, a bundle called the “Adventure Bundle”
contains a mountain bike, a car rack, a water bottle holder, and a helmet. All four products are available
separately in the store. However, when they are purchased together as a package, they have a special price.
First, we created the four SKUs we wanted to bundle. Then we created a SKUlink item for each SKU. We did that,
and set the quantity attribute of each SKUlink to one because there is one of each SKU in the bundle.
We then created a product called “Adventure Bundle”, and placed it in the “Bundles” category. We wrote its
descriptions and attached media just as we would for other products. We then created a SKU for it, and attached
all the SKU links that we made.
We added up the prices of all the component SKUs and decided upon an appropriate discount. We put the
full price into the bundle SKU’s listPrice attribute, the discounted price into its salePrice attribute, and
changed the onSale attribute to “TRUE”. This allowed us to show the bundle savings on its product template.
The Adventure Bundle has only one SKU, which includes a medium bike. However, we could also have easily
created several versions of each bundle, each represented by its own SKU. For example we could have had two
other different ‘Adventure Bundles’, one that came with a small bike and another with a large bike. Each SKU
would have a slightly different set of SKU links, and possibly a different price.
Displaying bundles
A bundle product can be treated exactly as a normal product. However, since bundles have some special
properties, we decided to create a special template to display them in the Pioneer Cycling store,. You can
view this template in <ATG9dir>/PioneerCyclingJSP/j2ee-apps/pioneer/web-app/en/catalog/
product_bundle.jsp. This template displays the names of all the component SKUs, and their inventory status.
It tells customers exactly what is in each bundle, and assures them that every part of it is in stock. It also displays
the list price of each component SKU, so customers notice their savings.
228 10 Merchandising
To print out the names and list prices of all the component SKUs in a bundle SKU, we simply iterated over the
SKU’s bundleLinks attribute. For example, the bundle SKU in the bundleSKU parameter:
<dsp:droplet name="/atg/dynamo/droplet/ForEach"> <dsp:param name="array" param="Product.childSKUs[0].bundleLinks"/> <dsp:param name="elementName" value="link"/> <dsp:oparam name="output"> <dsp:valueof param="link.item.displayName">Nothing</dsp:valueof> <dsp:valueof converter="currency" param="link.item.listPrice">no price</dsp:valueof> <br> </dsp:oparam> </dsp:oparam></dsp:droplet>
Appendix A. Pioneer Cycling Scenarios 229
Appendix A. Pioneer Cycling Scenarios
The following is a detailed description of how business strategies for the Pioneer Cycling store were
implemented as scenarios in DSS. You can look at these scenarios in the ACC to understand the connection
between each scenario’s business goals and its technical implementation.
The Pioneer scenarios are classified in four folders:
• Customer Services scenarios send helpful notifications to customers at various times.
• Marketing scenarios implement the site’s marketing strategies and research.
• Profiling scenarios track customer behavior and manipulate user profiles accordingly.
• Sales scenarios deliver incentives and special promotions to customers.
Pioneer Cycling Scenarios viewed in the ACC.
230 Appendix A. Pioneer Cycling Scenarios
Customer Services
NewGear
This scenario alerts owners of BMX bikes that there are new products that might interest them. (The BuildProfile
scenario tracks each BMX bike purchase and records it in the user profile.) When the NewGear scenario is
triggered, it immediately finds all the appropriate people who wish to receive e-mail. An e-mail template is then
personalized with a small amount of profile information, and sent to each person.
NotifyByArea
This scenario contains three segments that illustrate simple geographic personalization. The first segment
sends e-mail to anyone in a ZIP code starting with “02” who orders a certain product. The second segment
sends an e-mail to anyone from the West Coast (defined as a user with a billing address state of CA or WA) who
uses the promotion offered to new customers. In both cases, the scenario simply waits for the right event and
then filters people by the respective fields in their billing addresses. The third segment, “Event in Boston”, is an
announcement for a local trade show. It looks for people returning to the site at a certain time who are from the
Boston area. It shows those people an ad in the “MediaSlot” slot encouraging them to attend the trade show.
PleaseComeBack
This simple scenario sends e-mail to customers who have registered on the site, but who do not return within
two weeks. The store sends a friendly e-mail reminding them of its existence. Notice how this scenario waits for
them to log in, and then ends if they do. We designed our scenarios to always check if a customer has indicated
whether or not she wants to receive e-mail.
Marketing
Customer Survey
The two segments of this scenario implement a simple customer survey. The first segment solicits responses,
and then the second rewards those who answer it. This scenario also demonstrates how you can use audit trail
recording to monitor the success of your initiatives.
The first segment begins on the first day of July. We decided to survey customers who shopped in the last
month, so the scenario looks for everyone with a “Last activity” timestamp from June. First, it filters out all
the people who cannot be sent e-mail. Then, it randomly selects 10% of the remaining customers to receive
the survey solicitation. After each e-mail is sent, an audit trail is recorded to a special data set created for this
scenario, CustSurveyDataset. Each audit contains a timestamp, the profile ID of the customer, and a special
label, in this case SentSurveyInvite. In this way, we know exactly who we invited to answer the survey. The
scenario then waits for an event that indicates that the user has visited the page with the survey. Its final job is to
record that fact into the audit trail.
The second segment, “GiveReward”, gives a promotion to every customer that takes the time to complete the
survey. In this case, it’s “5% off everything in the store”.
Appendix A. Pioneer Cycling Scenarios 231
You can look at Analysis > Chart Templates in the ACC and see a chart for the month of June 2000 called
“customerSurvey” that shows the success of our survey scenario, by gender. The first set of bars shows us how
many male customers received the survey invitation, and how many responded. The second shows statistics for
female customers. You can easily adapt this chart to examine your response rate by any profile attribute, such as
home state. (The actual survey page that we built for this example is for demonstration only.)
Helmet No Discount
In this scenario, marketers are testing the effectiveness of three different promotional media items. When a
customer views a bike product page, one of three different media items (Helmets Safety First, Helmets Keep
Cool, or Helmets Attractive) is put into the MediaSlot. When the customer goes to a page with MediaSlot,
such as the bike category page (category_all_bikes.jsp), the media item in the slot is displayed. An audit
trail then tracks and records when customers view and/or order any helmets after being shown each specific
media item.
Helmet Promotional Offer
In this scenario, marketers are performing an experiment in which they hope to discover which of three similar
promotions works best in their store. The premise is to increase sales of bikes by offering a discounted helmet
with the order of any bike. We would like to determine which discount is most effective: 25%, 30%, or 40% off.
This scenario waits for a customer to view a bike template. When that happens, the first thing we do is put a
helmet into the ProductSlot slot that we want to suggest to the customer. Next the customer is randomly
given one of the three offers. First the offer is shown in the MediaSlot, then the promotion is given to the
customer, and then everything is recorded into the audit trail. The scenario then waits for the customer to
submit an order that uses that promotion, and records that fact into the audit trail. If the customer does not use
the promotion, we remove it from her profile.
By looking at several factors, our marketers then determine which promotion was most effective. They can
examine issues such as whether 40% off attracts more customers than 30%or whether reduced margins are
compensated by more sales.
Loyalty
One of the surest ways to build customer loyalty is to reward your repeat customers. This scenario shows a
small example of accomplishing this in the Pioneer Store. Part of the scenario is responsible for recording the
information necessary to know who those customers are, and the other part gives the reward.
Two simple segments “TrackNumber” and “TrackAmount” record the number of orders a customer has placed,
and their cumulative dollar amount. This information is stored in the order repository as well, but it is convenient
for many reasons to keep this information in the profile. For example, we could build a profile group called
“Loyal customers” who had placed four or more orders totaling more than $400.
The main segment “GivePromo” waits for these loyal customers to log in. In this case, we’ve decided to reward
those who have placed four or more orders adding up to at least $100. When those customers log in, they see
the media we put into the MediaSlot slot telling them that they have this special promotion. They also see all
the prices in the store discounted, as the type of promotion we have given them is a percent-off discount on all
items. As a further detail, we’ve decided to reward those customers whose orders total above $500 with 10% off,
and everyone else with 5% off.
232 Appendix A. Pioneer Cycling Scenarios
New Customers
This scenario offers anonymous customers an incentive to register in the form of a one time 20% discount. The
scenario works by displaying a graphic with the incentive on the homepage when the customer first comes to
the site. Again, it’s a piece of media in the MediaSlot slot.
The second segment then waits to see what the customer does next: registers or logs in. If the segment sees a
registration event, then it gives the customer the promotion. Either way, the media is then removed from the
slot, as the customer is now a member and no longer needs to see the incentive.
Profiling
Browsing
This scenario keeps track of what the customer has been browsing in the store. One segment records the ID of
each product a customer browses and another records each category that is browsed. A third segment records
each search string.
These values are stored in a transient profile attribute, so that the customer always has a record of each product
and category that has been browsed in each session. A Java Server Page in the My Profile area, displays this
information to the customer. You can view the code of this page in <ATG9dir>/PioneerCyclingJSP/j2ee-
apps/pioneer/web-app/en/user/pages_viewed.jsp.
BuildProfile
This scenario has many segments that illustrate several ways that a profile may be modified by a scenario
based upon a customer’s actions. In the Pioneer store, we built some profile attributes to track a customer’s
preferences for certain kinds of bikes. The attribute pricePreference tells whether a customer favors economy
or expensive products, and weightPreference tells whether a customer prefers sturdy or light products. These
are actually implemented as simple integer traits that begin at 0, and then indicate the customer’s bias by being
positive or negative.
Each segment catches a certain event, and then increments or decrements these values by an appropriate
amount. For example, browsing a cheap product may lower the pricePreference by one. However, adding a
very expensive item to the cart increases it by ten. In this way, we can make rough estimates about a customer’s
preferences.
The last segment records the types of bikes that a customer purchases. This enables the site to again roughly
classify its customers according to the types of bikes that they ride.
Appendix A. Pioneer Cycling Scenarios 233
Sales
Buy2Get1Free
Customers who browse the Accessories category page are shown an incentive to buy two accessories and get
the third free. However, a different incentive is shown to male customers than to female customers. Men are
urged to purchase lights and women to purchase helmets. Appropriate media was created to enhance this
targeted message. Note also that each gender group is given a promotion only redeemable for that category of
products.
Default
This scenario simply waits for an event called “Items requested”, which indicates that a page is requesting items
from a slot, but it is empty. This scenario sticks a generic advertisement into the slot in that case.
FreeGift
This scenario makes use of the “Add Item to Order” action, which enables a scenario to directly add items into a
customer’s shopping cart. The advantages of this are apparent when you consider the case when the site would
234 Appendix A. Pioneer Cycling Scenarios
like to offer a customer a free gift. In this case, Pioneer Cycling offers customers a free water bottle when they
purchase a jersey.
The first segment “ShowIncentive” waits for customers to browse the clothing category. When they do, they are
told about the free gift offer through media placed in the “MediaSlot” slot.
The next segment, “FreeGift”, begins when the customer places a jersey into the shopping cart. First, the
customer is given the promotion that discounts the water bottle to free. Next, the order is tested to see if it
already contains any other free water bottles. If it doesn’t, then the “Add Item to Order” action, puts one into the
cart. When the customer adds the jersey to the order, the cart shows both the jersey and the water bottle, with
the bottle marked as free. If the customer were to try to outwit our scenario by removing the jersey and taking
off with the free water bottle, he would notice that the water bottle without the jersey now costs $20.00 – its
regular price. This happens because of a safeguard in the promotion rule that states “Discount exactly 1 water
bottle when Order contains at least 1 jersey.”
FreeShipping
This scenario offers free shipping to customers in the USA who place orders over $400.
The first segment “GivePromo” waits for an item to be added to the order. First it checks to make sure that the
locale is en_US. Then, whenever the cart’s total exceeds $400.00, a message is displayed in the “MediaSlot” slot
informing the customer that shipping is now free. Notice that this segment fires every time that an item is added
to the order, but that the promotion is given only once and the slot holds only one copy of this media. This
works because one of the attributes of this promotion is that it can only be given to a customer once. The slot
also does not allow duplicate items. Recognizing tricks like this can help make the logic in your scenarios much
simpler.
The second segment in this scenario, “RemovePromo,” takes care of a special case. What if the customer removes
some items from the cart, and is no longer eligible for the shipping discount? The promotion is simply not
be applied because it knows to look for the order’s total price. However our slot would still be showing the
congratulatory message. To avoid this mix-up, this segment watches for the “Item Removed from Order” event,
and removes the media if the order’s total drops below $400.00.
IncreasePurchaseSize
This scenario offers some amount off a sixth product when you buy five others. It is interesting in that the
scenario delivers a different promotion for each locale, so that the discount is 5 dollars, 10 Francs, or 50 yen,
depending upon which locale the customer is shopping in.
ShowProducts
This scenario simply fills the “ProductSlot” slots of each locale with featured products using a targeter. These
products are displayed on the homepage.
The contents of the targeter are shown in the slot both when anonymous users visit the home page and at login
time. The reason for this is that the targeter may turn up some new items depending upon which user logs in,
but also has a pool of products to show to everybody.
Appendix A. Pioneer Cycling Scenarios 235
Scenario Templates
There are several prefabricated scenario templates that you can use as the beginnings of your own scenarios.
Scenario Template Description
Coupon Creates scenarios that give customers a promotion whenever their
orders reach a certain state
Cross-sell_promotion Cross-sells products against others
Free_gift Creates scenarios like the “FreeGift” one described above
Group_targeted_e-mail Sends e-mail to all members of a profile group
Group_targeted_promotion Delivers a promotion to all members of a group
Profile_targeted_e-mail Sends e-mail to all customers with a certain profile attribute
Targeter_based_promotion Shows the contents of a targeter in a slot
Changing URLs in E-mail Templates
The Pioneer Cycling store uses several scenarios with e-mail templates that get sent to users. The e-mail
templates in the following scenarios provide users with URLs:
user/NewBMXGear.jsp
user/PleaseComeBack.jsp
users/customer_survey.jsp
In the Pioneer Cycling store, the host and port are set to http://localhost:8840. You should change this
to reflect the host and port of the http server that is serving your site. For example, if your http server is named
webserver1 and is running on port 8080, then you should change:
http://localhost:8840/PioneerCyclingJSP
to the following:
http://webserver1:8080/PioneerCyclingJSP
236 Appendix A. Pioneer Cycling Scenarios
Appendix B. Optimizing Performance 237
Appendix B. Optimizing Performance
The performance of your web site depends on many factors, including the hardware on which it runs , the
performance of any back end systems, the proper tuning of caches, and the efficiency of the ATG services and
servlet beans. This appendix discusses the performance of Java Server Pages. For more tips and procedures for
testing your web site’s performance and how to improve it, see the performance-related chapters of the ATG
Installation and Configuration Guide.
JSPs are compiled into Java servlets at run time and are executed when a user requests a page that invokes
one of these servlets. The design and implementation of JSP code can make a huge difference in the overall
performance of a web site. We explain some guidelines that help you to tune your Java Server Pages to run as
accurately and as quickly as possible.
This appendix is divided into the following parts
Prioritizing Web Site Concerns (page 237)
Streamlining JSP Construction (page 238)
Using the Broadest Scope Possible (page 242)
Reloading the Catalog Repository Cache (page 242)
Prioritizing Web Site Concerns
A common problem for many developers is that they concentrate on efficiency issues too early in the
development process. We suggest that the first priority of any web site is that it functions. If people can’t access
your site or if the site is difficult to use and poorly designed, users aren’t going to care how fast it runs. ATG
products can help you to build a site that runs fast, but testing for accuracy and functionality is more important.
Since JSP is relatively easy to work with, performance optimizations can be considered after you stabilize basic
functionality.
Working on the most used pages in the site should be another high priority. Obviously a page that people only
visit if they sign up for a special promotion should get less attention than then site home page. Focusing on
tuning the most popular pages in the site so that they run smoothly and load quickly makes your web site more
attractive to users.
After your web site is up and running you can evaluate it and decide how to make it work even better. The rest
of this appendix offers some suggestions on how to write JSP code so that it provides optimal performance for
your site.
238 Appendix B. Optimizing Performance
Streamlining JSP Construction
There are several ways that you can improve your web site through writing “clean” JSP code. Some JSP functions
slow down the speed at which a page is compiled and served. Others make site development and maintenance
easier when they are used in certain ways. This section describes several methods that we recommend using to
make your site run as well as possible.
Limit Excess Droplet Invocations
To a certain degree, we recommend sectioning the Java Serve Pages so that often used fragments can be
separated into individual, reusable JSP files that can be included with <dsp:include>. While this sectioning
makes site development and maintenance easier, it also can make the site perform poorly if overused. However,
you don’t have to sacrifice all modularity in your Java Server Pages for better performance.
Each droplet invocation, either by <dsp:include page="filename.jsp" flush="true"/> or
<dsp:droplet name="/atg/dynamo/droplet/BeanName">, is compiled into a SubServlet invocation.
Another option for sectioning Java Server Pages is using JSP include directives, which work similarly to
droplet src. The syntax is as follows:
<dsp:include file="RelativeURLSpec.jsp">
This directive includes the JSP fragment at RelativeURLSpecJSP to be “in-lined” in the JSP in which this line
is written. The in-lining occurs before page compilation and the two files are treated as one fragment rather
than as the two fragment invocations you would get using the <dsp:inclide="RelativeURLSpec.jsp"></
dsp:include> syntax.
The use of the IsEmpty and IsNull components gives the developer security from NullPointerExceptions
but invoking each of these servlet beans takes time. To avoid this problem you can eliminate excess IsNull
invocations when you know that a value really can or cannot be null. For example, while iterating over a list of
SKUs for a product with a ForEach and displaying some properties for each SKU, it is a good idea to perform an
IsNull check on each of them. However, there are situations where you can be virtually certain that none of
the SKUs will be null. One of these situations is when each SKU on the list is retrieved as a product.childSKU.
If your database is in good shape and if you test to ensure that the list has no nulls in it, you can be virtually
assured that it never will have nulls. In cases like this, you can comfortably eliminate the IsNull check to
improve performance.
The following code sample is an example in which you would likely use ForEach to iterate over an array. If you
assume that it is possible, but not very likely, that the array has no elements, it is best to invoke ForEach without
checking ahead of time if the array is empty. Instead, use the <dsp:oparam name="empty"> of ForEach as
shown here:
The contents of someArray:<P><dsp:droplet name="ForEach"> <dsp:param name="array" param="param:someArray"/> <dsp:oparam name="empty"> <i>someArray has no elements!</i> </dsp:oparam> <dsp:oparam name="outputStart"> </OL>
Appendix B. Optimizing Performance 239
</dsp:oparam> <dsp:oparam name="outputEnd"> </OL> </dsp:oparam> <dsp:oparam name="output"> <LI><dsp:valueof param="element"></dsp:valueof> </dsp:oparam></dsp:droplet>
If you assume that most of the time the array is empty and only sometimes has elements, it may be more
efficient to check if the array is empty before invoking ForEach, as in this example:
The contents of someArray:<P><dsp:droplet name="IsEmpty"> <dsp:param name="value" param="param:someArray"/> <dsp:oparam name="empty"> <i>someArray has no elements!</i> </dsp:oparam> <dsp:oparam name="false"> <OL> <dsp:droplet name="ForEach"> <dsp:param name="array" param="param:someArray"/> <dsp:oparam name="output"> <LI><dsp:valueof param="element"></dsp:valueof> </dsp:oparam> </dsp:droplet> </OL> </dsp:oparam></dsp:droplet>
The efficiency gained here is the difference between the time it takes to invoke ForEach, to figure out what
kind of object someArray is, and to inquire if it is empty versus the time is takes to invoke IsEmpty, which goes
through roughly the same calculations. For both situations, the former code snippet performs approximately as
well or better than the latter.
Use the Cache Servlet Bean
Using the Cache servlet bean can save a lot of time when you use it properly. When used incorrectly, it can slow
rendering of a Java Sever Page and even cause the page to display incorrect data. It is important to understand
how Cache works in order to avoid mistakes that can slow your pages or make them wrong.
(If you are not familiar with Cache, you may want to read more about it in Appendix B: ATG Servlet Beans of the
ATG Page Developer’s Guide.
Let’s examine an example where Cache actually slows down your web site. The following example uses Cache
on a page called hello.jsp:
<dsp:droplet name="Cache"> <dsp:oparam name="empty"> Hello world! </dsp:oparam></dsp:droplet>
240 Appendix B. Optimizing Performance
Cache stores the value “Hello world!” in its cache so that future retrievals can come from that cache. This is
not a good place to use Cache from an efficiency point of view. Without Cache, the Java code generated to
print a static string is very fast and simple. The use of Cache in this example causes an unnecessary servlet
invocation and the unnecessary storage of more bytes. The Cache servlet bean is only useful when you are
caching dynamic content.
Here is another example in which Cache causes incorrect results. The dynamic pricing engine is among the most
“costly” pieces of functionality used in the Pioneer Cycling Store JSP code because it calculates pricing on the
fly and can run slowly. Determining a dynamic price for each SKU personalized for each user takes many more
computation resources than finding and displaying static prices. It is dynamic, so one way to make it run faster is
to cache it. Every SKU has a different price, so you can use the SKU ID as a key into the cache.
<dsp:droplet name="/atg/dynamo/droplet/Cache"> <dsp:param name="key" param="Sku.repositoryId "/> <dsp:oparam name="output">
<dsp:droplet name="/atg/commerce/pricing/PriceItem"> <dsp:param name="item" param="Sku"/> <dsp:param name="elementName" value="PricedSku"/>
<dsp:oparam name="output"> <dsp:droplet name="/atg/dynamo/droplet/Compare"> <dsp:param name="obj1" param="PricedSku.priceInfo.ListPrice"/> <dsp:param name="obj2" param="PricedSku.priceInfo.amount"/>
<%/*ListPrice is greater, so display it first in strikout mode: */%> <dsp:oparam name="greaterthan"> <span class="strikeout"> <dsp:valueof converter="currency" param="PricedSku.priceInfo.ListPrice"/> </span> </dsp:oparam> </dsp:droplet>
<%/*Display their price: */%> <dsp:valueof converter="currency" param="PricedSku.priceInfo.amount"/> </dsp:oparam></dsp:droplet> <%/*PriceItem droplet*/%>
</dsp:oparam></dsp:droplet>
In this code example, Cache caches a string representing the formatted price of the SKU, for example, “$42”. It
is much faster to let Cache render the string “$42” than to let the PriceItem servlet bean calculate a price, so
caching this string seems like a good idea. However, PriceItem calculates personalized prices, so if a user has a
promotion for 20% off everything and he is the first to invoke the cached PriceItem for SKU123, then his price
is cached and even users who don’t have the 20% off promotion see that price.
A solution might be to key the cache a combination of the profile ID and the SKU ID. The problem remains that
the string manipulation needed to construct a key out of those two pieces of data would cost time and that the
hit ratio on such a cache would likely be so low as to render the cache useless. Worst of all, if the user is granted a
promotion before the cache times out, the user sees the incorrect price.
Another situation to consider is caching content that contains links. In order to perform session tracking, the
session ID must be carried by every HTTP request either in a cookie or in the URL link, and inserted by on-the-
fly link rewriting. Cache cannot cache content that needs to have link rewriting performed on it. If you choose
Appendix B. Optimizing Performance 241
to use Cache to cache content that contains links and if a good portion of your user population does not accept
session cookies, then invoking Cache on content that contains link wastes time.
We have now explored in detail some situations where you should not use the Cache servlet bean:
1. When the content is static.
2. When the cached content would be incorrect for the situation.
3. When it takes more time to construct the key than to render the content.
4. When the cache hit ration would be low.
5. When link rewriting is required.
You should use Cache when the string you would use for a key is readily available and the content is always the
same for each key. You should also use Cache when the content either contains no links or the when the bulk of
the user population is expected to accept session cookies.
Avoid “Expensive” Functionality
Sometimes there is a piece of functionality that runs as efficiently as it possibly can but just isn’t fast enough
to use on every page of the site. For example, the PriceItem servlet bean performs a dynamic personalized
pricing calculation on a SKU based on its price attributes. The promotions granted to the user and any other
pricing models that might apply are calculated efficiently, but not quickly. On a page that displays a product
with four SKUs and the price of each SKU is displayed, PriceItem is invoked four times. If you find that the page
is unacceptably slow, one solution is to change the functionality so that the static price, list price, and sale price
are the only prices displayed on the product pages. Personalized pricing can be displayed on the Shopping Cart
page.
Writing your own Servlet Beans
Sometimes a site requires functionality that cannot be achieved from the servlet beans already included in ATG,
so as a developer you can write your own. To achieve desired performance, you should first ensure that the
servlet bean itself does its job efficiently; then, minimize the need for “helper” droplets.
For example, some servlet beans fail if given a null value in an input parameter. Make your servlet bean accept
null input parameters. If it takes input parameters, do the quick comparison to null or empty inside the service
method rather than using the Java Server Page to invoke IsNull or IsEmpty on the parameter value before
invoking your servlet bean.
Another example is if your servlet bean outputs an array that is then iterated by ForEach. This common pattern
works well because it is usually efficient and it is easily maintainable and readable by people who have used
ForEach before. However, if ForEach then requires a Switch servlet bean on each element and in some cases
invokes another servlet bean and so on and so on, there may be a point where it is better to do the iteration
within your own servlet bean.
To summarize, we do not recommend creating a whole site of customized servlet beans to gain performance.
Nor do we recommend that site developers begin by thinking about where they can and cannot use Cache
and how much code they can write inline. Instead, we recommend that site development begin with modular
JSP fragments used sensibly in an overall site structure driven by functionality and design. When your site is
somewhat functional, you should begin testing to see if it meets your performance goals and needs. If it does
not, then you should identify the slowest and most often used parts of the site and improve their performance.
242 Appendix B. Optimizing Performance
Using the Broadest Scope Possible
Three scopes are available to ATG components: global, session, and request. A global component is instantiated
once and is shared by all users. A session component is created once for a given session and is shared by all
requests of that session. Request scoped components are freshly instantiated for each request that uses them.
Resolving the path to an existing component takes a little time, but instantiating a new object is comparatively
more costly. To build a high performance web site one must be conscious of the number of objects that are
allocated on each request and the amount of memory each user consumes. Therefore it important to think
about the scope of a component when it is defined in the system.
In general it is fairly obvious when a component should be globally scoped versus session or request scoped. If
a component can be shared, without synchronization problems, by all users, then it should be globally scoped.
It is a somewhat more difficult decision to decide if a component should be session or request scoped. The
decision balances on minimizing the number of objects that are allocated on every request and reducing the
total amount of memory that is used per session. If a component is going to be used on many requests within
a session then in general it should be session scoped. However if an object is only going to be occasionally
used within the session then it should be declared request scoped. After the request the Java Virtual Machine is
allowed to garbage collect the object, versus holding onto it for the life of the session.
Form handlers are a special case of component; they can be used quite often, but in general are request scoped.
As a design pattern, messages relayed back to the user (such as error messages) are made available as properties
of the form component. It becomes increasingly difficult to manage this sort of component, with messages, as
session scoped, because your application needs to be cognizant of when to clear the messages. As a request
scoped component the error messages are automatically “cleared” after the request when the object is garbage
collected. Notice that the component /atg/commerce/order/ShoppingCartModifier, which manages the
shopping cart order, is by default declared to be a request scoped component. Its performance in request scope
is sufficient for most sites, but a site that needs an extra performance boost might benefit by changing that form
handler to session scoped.
Reloading the Catalog Repository Cache
The performance of the catalog can be improved by preloading the catalog repository when ATG starts up. The
cache is loaded by performing queries, just as during normal operation. Cache preloading differs from normal
operation because it normally involves querying for all items of a certain type and then doing nothing with the
results. This bulk query is easily achieved by addingquery-items tags to the repository definition XML file.
In Pioneer Cycling, we added these tags to a separate file:
PioneerCycling/config/atg/commerce/catalog/preloadCatalogCache.xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE gsa-template PUBLIC "-//Art Technology Group, Inc.//DTD General SQL Adapter//EN" "http://www.atg.com/dtds/gsa/gsa_1.0.dtd">
<gsa-template>
<header> <name>Pioneer Cycling Default Catalog Cache Pre-loader</name> <author>The Pioneers</author>
Appendix B. Optimizing Performance 243
<version>$Id: $</version> </header>
<query-items item-descriptor="category">ALL</query-items> <query-items item-descriptor="product">ALL</query-items> <query-items item-descriptor="sku">ALL</query-items> <query-items item-descriptor="media">ALL</query-items> <query-items item-descriptor="manufacturer">ALL</query-items> <query-items item-descriptor="sku-link">ALL</query-items> <query-items item-descriptor="folder">ALL</query-items></gsa-template>
This file can be invoked from the product catalog definition file at /PioneerCycling/config/
atg/commerce/ProductCatalog.properties with the following property setting that adds
preloadCatalogCache.xml to the end of the list of definitonFiles.
definitionFiles+=\ /atg/commerce/catalog/preloadCatalogCache.xml
In Pioneer Cycling, we commented this setting out to improve the speed of startup. In a live site, it is often
desirable to trade off slower startup for better performance but in development and demonstration situations,
fast startup is generally preferable.
It should also be noted that in Pioneer Cycling, the cache preloading is provided for the default English language
catalog only. In an actual online store, you would provide query-items tags for all of the repositories and item-
types for which the performance boost is needed. In this implementation we did not provide query-items tags
for the French or Japanese catalogs.
Please see the section on SQL Repository Caching in the SQL Repository Architecture chapter of the ATG Repository
Guide for more information about reloading a repository cache.
244 Appendix B. Optimizing Performance
Index 245
Index
AACC
users, 14
Address Book page, 29
Address nicknames, 157
address_book.jsp, 37
Addresses
alternate shipping, 30
billing, 29
missing information, 158
privacy, 222
shipping, 30
user, 24
Advanced
checkout, 149
Advanced checkout, 153
Adventure bundle, 227
Anonymous users, 14
AvailableShippingMethods, 170
Bb2c_product table, 59
b2c_product_catalog_dll.sql, 58
B2CemailFormHandler class, 38
B2CProfileFormHandler, 16, 35, 166
B2CSearchFormHandler, 48
B2CUserResources, 40
Billing addresses, 29
in checkout, 152
Browsing scenario, 47
Bundles, 226
Business users’ overview, 5
CCache servlet bean, 239
cancel_order.jsp, 179
CancelOrder servlet bean, 181
cart.jsp, 137, 171
Catalog
browsing, 45
extending, 57
overview, 6
repository background, 57
searching, 48
Catalog repository
reloading cache, 242
CatalogSearch, 48, 49
CategoryBrowsed, 46
Checkout
advanced, 149, 153
entering information for, 146
express, 165
secure, 147
Checkout pages, 190
CommerceProfileFormHandler, 16
Cookies, 14, 240
Coupons, 201
claiming, 202
creating, 202
Credit card page, 39
Credit cards
collecting information, 39
in checkout, 153
selecting default, 167
validating, 42, 164
Cross-selling, 196
Customer service page, 37
DDatabase schema, 55
Databases
performance, 59
Discounts, 194
DMS messages, 46, 47, 49
Droplet invocation, 238
dynamoMessagingSystem.xml, 50
EEmail
templates, 235
EmailFormHandler component, 38
Error messages
displaying, 165
Express checkout
deactivating, 171
enabling, 165
express_checkout.jsp, 166
express_checkout_preferences.jsp, 168
FForm handlers, 242
Fragments, 238
246 Index
GGift certificates
email delivery of, 201
paying with, 164
purchasing, 200
using, 201
Gift lists, 204
adding items to, 214
creating and displaying, 204
editing and deleting, 212
purchasing items from, 224
searching for, 216
viewing someone else’s, 220
Gift Registry, 203
GiftListFormHandler, 203
Gifts
checkout with, 226
purchasing, 216
HhandleCreateNewCreditCard() method, 40
handleLogin() method, 16
History page, 44
IInventory, 187
accessing information, 188
caching information, 190
implementing a system, 188
InventoryLookup component, 189
IsEmpty component, 238
IsNull component, 238
item types
describing, 51
Item types
adding new, 61
JJSP include directives, 238
JSP pages
sectioning, 238
JSP Performance, 237
Kkeyword attribute, 61
LLogin page, 14
LoginFragment.jsp, 15
MManufacturer item type, 61
Media
delivering, 196
MediaSlot, 198
Merchandising, 193
overview, 10
My Profile page, 26
my_profile.jsp, 14
NNicknames
for addresses, 157
OOrder History, 173
Order processing
overview, 11
order_cancelled.jsp, 180
order_history.jsp, 174
OrderLookup, 174
Orders
cancelling, 179
changing quantities, 142
deleting items from, 143
displaying, 173
displaying a single, 176
modifying, 144
processing, 135
reviewing and editing, 135
viewing subtotal, 140
OrderStatesDetailed, 174
Overview, 5
PPage fragments, 238
Patch Bay, 47, 50
Payment methods, 160
Performance
optimizing, 237
Personalization, 51
Pricing
calculating, 139
displaying in checkout, 137
overview, 11
Pricing engine, 193
ProductBrowsed, 46
ProductCatalog, 58
Products
availability, 189
highlighting, 196
related accessories, 196
Index 247
slots, 198
Profile Management, 13
Profile Repository extensions, 51
Profile servlet bean, 14
Profile traits, 23
ProfileFormHandler, 16
ProfileFragment.jsp, 16
Profiles
overview, 8
Promotions, 193
implementing, 194
targeting content for, 195
Properties
adding, 58
displayName, 62
Qquery-item tags, 242
RRegistration, 14, 16
customized, 25
Repository API, 52
ResourceBundle, 40
SScenarios, 195
Browsing, 47
delivering promotions with, 193
Pioneer Cycling, 229
templates, 235
upselling with, 197
Scopes, 242
SearchEventSender, 49
SearchFormHandler, 48
Searching
keeping track of, 47
simple, 48
Servlet beans
writing your own, 241
Setting up email, 2
Shipping addresses, 30
multiple, 154
Shipping information
in checkout, 150
Shipping methods
choosing in checkout, 151
setting default, 170
Shopping cart
modifying, 171
Shopping cart page, 137
ShoppingCartModifier, 165
SKU
adding properties, 60
SKU Bundles, 226
Slots
creating, 198
populating, 199
SMTPEmail, 39
SoftGoodFulfiller, 200
Speed of loading, 237
SQL Profile Repository, 52
SQL tables, 59
Starting
Pioneer Cycling, 2
stock status, 190
Style item type
adding, 63
Subtypes
creating, 63
TTargeting, 195
overview, 10
UUpselling, 197
User item types, 51
User profile, 21
User profiles, 13
userProfile.xml, 45, 51
extending, 27
Users
anonymous, 14
VvalidateCreditCard, 42
WWish lists, 204
XXML files, 57
248 Index