Sei sulla pagina 1di 254

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.

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

ATG Consumer Commerce Reference Application Guide

iii

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 Users 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

iv

ATG Consumer Commerce Reference Application Guide

Targeting Content for Promotions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Slots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Gift Certificates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Purchase of Gift Certificate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . E-mail Delivery of Gift Certificates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Using Gift Certificates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Coupons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Creating Coupons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Claiming Coupons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Gift Registry . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ATG Consumer Commerce Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Pioneer Cycling Gift Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Creating Wish Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Creating and Displaying Gift Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Adding Items to Gift Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Gift Purchasing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Bundles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Bundle building example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Displaying bundles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . A. Pioneer Cycling Scenarios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Customer Services . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . NewGear . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . NotifyByArea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . PleaseComeBack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Marketing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Customer Survey . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Helmet No Discount . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Helmet Promotional Offer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Loyalty . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . New Customers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Profiling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Browsing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . BuildProfile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Sales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Buy2Get1Free . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Default . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . FreeGift . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . FreeShipping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . IncreasePurchaseSize . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ShowProducts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Scenario Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Changing URLs in E-mail Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . B. Optimizing Performance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Prioritizing Web Site Concerns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Streamlining JSP Construction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Limit Excess Droplet Invocations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Use the Cache Servlet Bean . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Avoid Expensive Functionality . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Writing your own Servlet Beans . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Using the Broadest Scope Possible . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Reloading the Catalog Repository Cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

195 198 200 200 201 201 201 202 202 203 203 203 204 204 204 214 216 226 226 227 227 229 230 230 230 230 230 230 231 231 231 232 232 232 232 233 233 233 233 234 234 234 235 235 237 237 238 238 239 241 241 242 242

ATG Consumer Commerce Reference Application Guide

Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245

vi

ATG Consumer Commerce Reference Application Guide

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.

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
config

Description contains atg/dynamo/service/j2ee/J2EEContainer.properties, which installs the PioneerCyclingJSP application in Dynamos J2EE container contains directories that hold individual J2EE applications contains MANIFEST.MF, which declares the PioneerCyclingJSP application dependent on the Pioneer Cycling module, and specifies other configuration information for the Dynamo module

j2ee-apps META-INF

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 email 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

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/webapp/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 its 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 interdependenciesso 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.

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 customers 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.

1 Introduction

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 sites features according to a customers 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

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

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.

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 customers: name and contact info shipping and billing information credit card numbers checkout preferences wish lists or gift registries

2 Business Users Overview

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.

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 visitors 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 users 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.

10

2 Business Users Overview

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.

2 Business Users Overview

11

12

2 Business Users Overview

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

3 Profile Management

13

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">

14

3 Profile Management

<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"

3 Profile Management

15

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 users 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 tags 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"/>

16

3 Profile Management

<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>&nbsp;&nbsp;</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>&nbsp;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>

3 Profile Management

17

<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>&nbsp;<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>&nbsp;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>&nbsp;State<br> &nbsp; <dsp:select bean="B2CProfileFormHandler.value.billingAddress.state"> <%@ include file="StatePicker.jspf" %> </dsp:select> </td> <td>&nbsp;Postal Code<br> &nbsp; <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" %>

18

3 Profile Management

</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>&nbsp;<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>&nbsp;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>&nbsp;State<br> &nbsp;<dsp:select bean="B2CProfileFormHandler.value.shippingAddress.state"> <%@ include file="StatePicker.jspf" %>

3 Profile Management

19

</dsp:select> </td> <td>&nbsp;Postal Code<br> &nbsp;<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>&nbsp;<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>&nbsp;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>&nbsp;<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>

20

3 Profile Management

<table width=100% cellpadding=0 cellspacing=0 border=0> <tr><td class=box-top-profile>&nbsp;All done?</td></tr> </table> <p> <p>&nbsp;<br> <!-- Submit form to handleCreate() handler --> <dsp:input bean="B2CProfileFormHandler.create" type="submit" value=" Register --> "/> <p> &nbsp;<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 users point of view is the same as leaving the last name blank.

3 Profile Management

21

top half of register.jsp

22

3 Profile Management

bottom half of register.jsp

Profile Trait
firstName middleName lastName email login password

Defined In DPS1 DPS DPS1 DPS DPS2 DPS2

3 Profile Management

23

Profile Trait
gender dateOfBirth daytimeTelephoneNumber size

Defined In DPS DPS ATG Commerce 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
homeAddress billingAddress shippingAddress

Defined in DPS ATG Commerce 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
firstName middleName lastName address1 address2 city

Collected? no no no yes yes yes

Required? yes* yes* yes* yes no yes

24

3 Profile Management

Address Field
state postalCode country telephone

Collected? yes yes yes sometimes

Required? no yes yes no

*These three fields are copied from the users 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, address2 city, 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.

3 Profile Management

25

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.

26

3 Profile Management

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 customers shopping experiences on this site, including shopping cart information, alternate shipping addresses, saved credit cards, lists of recently viewed items, and the customers 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

3 Profile Management

27

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>

28

3 Profile Management

<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.

3 Profile Management

29

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 users 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 forms 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.

30

3 Profile Management

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>&nbsp; </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>&nbsp;Done?</td></tr> </table> <p>&nbsp;<br> <!-- Submit form to handleUpdate() handler --> <dsp:input bean="B2CProfileFormHandler.newAddress" type="submit" value=" Add Address --> "/> . . .

3 Profile Management

31

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

32

3 Profile Management

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.

3 Profile Management

33

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.

34

3 Profile Management

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();

3 Profile Management

35

// save nickname in editValue map - the updateAddress handler will need it edit.put("nickname", nickname); // ...as will, perhaps, the changeAddressNickname handler edit.put("newNickname", nickname); String[] addressProps = getAddressProperties(); // Store each property in the map, for use by edit_secondary_address.jsp Object 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

36

3 Profile Management

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 users 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.

3 Profile Management

37

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 visitors profile) and to set the recipient to the customerServiceEmailAddress property. (By default, this property is pioneer@example.com.)

38

3 Profile Management

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

3 Profile Management

39

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 abbreviations MasterCard=MC Visa=VISA American_Express=AMEX Discover=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,

40

3 Profile Management

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?

3 Profile Management

41

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) {

42

3 Profile Management

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,

3 Profile Management

43

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.

44

3 Profile Management

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 visitors 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" />

3 Profile Management

45

<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 members 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.jsp catalog/category_bikes.jsp catalog/category_bundle.jsp catalog/category_clothing.jsp catalog/category_generic.jsp catalog/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.jsp catalog/product_bundle.jsp catalog/product_generic.jsp catalog/product_gift_certificate.jsp

46

3 Profile Management

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 users 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.

3 Profile Management

47

As in the case of recently viewed categories and products, we added a transient set property to the visitors 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.

48

3 Profile Management

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

3 Profile Management

49

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 -->

50

3 Profile Management

<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.

3 Profile Management

51

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
size

Description The customers clothing size. We use this property for targeting sizeappropriate clothing to the user. 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. 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. 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. 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 customers 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. 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. A list of keywords the customer picks up based on his activities in the store. This propertys value is populated by DSS. When a user purchases a product, that products 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 users experience. A list of the IDs of items purchased by the user. This transient property keeps track of a list of the catalog categories recently visited by the user. This transient property keeps track of a list of the products recently visited by the user.

weightPreference

pricePreference

numOrders

cumulativeOrderAmount

bikesOwned

userKeywords

itemsBought categoriesViewed

productsViewed

52

3 Profile Management

Property
recentSearches

Description 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"

3 Profile Management

53

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"

54

3 Profile Management

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) clothing_size INT wants_prod_reviews TINYINT weight_preference INTEGER price_preference INTEGER num_orders INTEGER cum_order_amt DOUBLE PRECISION PRIMARY KEY(id) ); CREATE TABLE b2c_user_keyword ( id VARCHAR(40) sequence_num INTEGER keyword VARCHAR(50) PRIMARY KEY(id, sequence_num) ); CREATE TABLE b2c_item_bought ( id VARCHAR(40) sequence_num INTEGER item VARCHAR(40) PRIMARY KEY(id, sequence_num) ); CREATE TABLE b2c_bike_owned ( id VARCHAR(40) sequence_num INTEGER bike VARCHAR(40) PRIMARY KEY(id, sequence_num) );

NOT NULL, NULL, NULL, NULL, NULL, NULL, NULL,

NOT NULL, NOT NULL, NOT NULL,

NOT NULL, NOT NULL, NOT NULL,

NOT NULL, NOT NULL, NOT NULL,

3 Profile Management

55

56

3 Profile Management

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 cant 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

4 Extending the Product Catalog

57

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
available highlightPriority

Description This property indicates whether this product is available in the current locale. 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. 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 products 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.)

priceRange

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.

58

4 Extending the Product Catalog

Property
style

Description 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. This property is a reference to an item of type Manufacturer that indicates the company that makes the product.

manufacturer

The standard ATG Consumer Commerce product catalog stores the product item type attributes in the dcs_product table. We didnt 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) available TINYINT manufacturer VARCHAR(40) highlight_priority INTEGER price_range INTEGER weight_range INTEGER style VARCHAR(40) PRIMARY KEY(product_id) );

NOT NULLREFERENCES dcs_product(product_id), NULL, NULL REFERENCES b2c_manufacturer(manufacturer_id), NULL, NULL, NULL, NULL REFERENCES b2c_style(style_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"/>

4 Extending the Product Catalog

59

<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
color exactWeight

Description The color of the SKU. 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 color VARCHAR(100) NULL, exact_weight DOUBLE PRECISION NULL, PRIMARY KEY(sku_id) );

REFERENCES dcs_sku(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>

60

4 Extending the Product Catalog

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
id displayName description longDescription image keywords

Description Unique identifier Name of manufacturer Description of the manufacturer Detailed description of the manufacturer Manufacturer logo 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) manufacturer_name VARCHAR(200) description VARCHAR(200) long_description VARCHAR(40) image VARCHAR(40)

NOT NULL, NULL, NULL, NULL REFERENCES dcs_media_txt (media_id), 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).

4 Extending the Product Catalog

61

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:

62

4 Extending the Product Catalog

<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 doesnt. 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-accessorysku. 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 frameproducts. If the product was a part that fits on a particular bike frame (such as a crankset), we made it of type frame-fitproduct. 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.

4 Extending the Product Catalog

63

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"/>

64

4 Extending the Product Catalog

<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 sequence_num INTEGER NOT NULL, frame INTEGER NOT NULL, PRIMARY KEY(product_id, sequence_num) );

REFERENCES dcs_product(product_id),

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 partand-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:

4 Extending the Product Catalog

65

Property
size proof material

Description Indicates the size of a garment (S,M, L, XL) Used for storing what a garment is impervious to (for example, waterproof or windproof ). 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 REFERENCES dcs_sku(sku_id), clothing_size proof material PRIMARY KEY(sku_id) );

VARCHAR(40) INTEGER INTEGER INTEGER

NOT NULL NULL, NULL, NULL,

Here is the XML definition for the clothing-sku item-descriptor. Notice that by specifying supertype="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"/>

66

4 Extending the Product Catalog

<option value="Kevlar" <option value="Nylon" <option value="Polyester Blend" <option value="Silk" <option value="Wool" <option value="Goretex" </property> </table> </item-descriptor>

code="3"/> code="4"/> code="5"/> code="6"/> code="7"/> code="8"/>

Bike-SKU and Part-and-Accessory-SKU


The new bike-sku and the part-and-accessory-sku item types have the following attributes:

Property
metal dimensions

Description The type of metal used to make the bike. 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) metal VARCHAR(40) PRIMARY KEY(sku_id) );

NOT NULL NULL

REFERENCES dcs_sku(sku_id), REFERENCES

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) tag VARCHAR(100) measurement DOUBLE PRECISION PRIMARY KEY(sku_id, tag) );

NOT NULL REFERENCES dcs_sku(sku_id), NOT NULL, NULL,

Here is the XML file for creating the bike-sku item type:

4 Extending the Product Catalog

67

<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 SKUs 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.

68

4 Extending the Product Catalog

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 dont necessarily have to change the product catalog template pages.

5 Displaying and Accessing the Product Catalog

69

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 JSPs include directive). This makes your template pages easier to maintain. By creating page fragments and using the dsp:include tag or JSPs 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 mediainternal-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.

70

5 Displaying and Accessing the Product Catalog

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.)

5 Displaying and Accessing the Product Catalog

71

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 didnt 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 products property values.

The following code shows how we displayed the products 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 products 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>

72

5 Displaying and Accessing the Product Catalog

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">

5 Displaying and Accessing the Product Catalog

73

<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 items 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/

74

5 Displaying and Accessing the Product Catalog

web-app/en/Common/DisplaySKUPrice.jsp

<ATG9dir>/PioneerCyclingJSP/j2ee-apps/pioneer/web-app/en/DisplayProductPrice.jsp.

List Price versus Users Price


In the Pioneer Cycling store, we made a distinction between the list price for a product and the users price: list price: the full, undiscounted price of a product. users 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 Mothers Day the Pioneer Cycling store offers all women 20% off. The list price of a helmet is $100; the users 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 users 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. Orderlevel 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>

5 Displaying and Accessing the Product Catalog

75

<%/*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 products list price to the customers 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 users price is always displayed. Inside PriceItem, the list price is displayed like this:

<dsp:valueof converter="currency" param="PricedSku.priceInfo.ListPrice"/>

The users 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>

76

5 Displaying and Accessing the Product Catalog

The list price and users 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>

5 Displaying and Accessing the Product Catalog

77

</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"/>

78

5 Displaying and Accessing the Product Catalog

<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 "/>

5 Displaying and Accessing the Product Catalog

79

<%/* 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 users 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 handlers 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.

80

5 Displaying and Accessing the Product Catalog

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 SKUs 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"

5 Displaying and Accessing the Product Catalog

81

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"/>

82

5 Displaying and Accessing the Product Catalog

</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>

5 Displaying and Accessing the Product Catalog

83

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.

84

5 Displaying and Accessing the Product Catalog

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"/>

5 Displaying and Accessing the Product Catalog

85

<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>&nbsp;&nbsp;</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"/> . . .

86

5 Displaying and Accessing the Product Catalog

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 SKUs 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 SKUs 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 SKUs 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.

5 Displaying and Accessing the Product Catalog

87

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

88

5 Displaying and Accessing the Product Catalog

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 category in 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"/>

5 Displaying and Accessing the Product Catalog

89

<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>&nbsp;&nbsp;</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"/>

90

5 Displaying and Accessing the Product Catalog

<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 doesnt have to be passed again here. The users 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

5 Displaying and Accessing the Product Catalog

91

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 users 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.

92

5 Displaying and Accessing the Product Catalog

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"/>

5 Displaying and Accessing the Product Catalog

93

<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>

94

5 Displaying and Accessing the Product Catalog

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

5 Displaying and Accessing the Product Catalog

95

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"> &nbsp; &nbsp; <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.

96

5 Displaying and Accessing the Product Catalog

-- 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 Mens Jerseys page, and then to the product page for a mens Black Arrow Jersey, the navigation history (at the top of the page) looks like this:

5 Displaying and Accessing the Product Catalog

97

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 catalogs 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"/>

98

5 Displaying and Accessing the Product Catalog

<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"> &gt; </dsp:oparam> </dsp:droplet> <dsp:getvalueof id="countStr" param="count" idtype="Integer">

5 Displaying and Accessing the Product Catalog

99

<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>

100

5 Displaying and Accessing the Product Catalog

</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 searchingsimple 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 customers 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.

5 Displaying and Accessing the Product Catalog

101

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.

102

5 Displaying and Accessing the Product Catalog

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 forms submit method is invoked. The following example shows SimpleSearchFragment.jsp:
<!-This page fragment is intended to be imbedded on any page that requires a search capability. The FormAction parameter is the name of 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 components 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

5 Displaying and Accessing the Product Catalog

103

* 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

104

5 Displaying and Accessing the Product Catalog

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 components 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 theyre 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 search that potentially returns both category and product repository items. The one paramater, ResultArray, accepts a HashMap that contains elements with the keys "category" and "product". The values of these keys are collections of category or product repository items found in the search.

5 Displaying and Accessing the Product Catalog

105

----------------------------------------------------------------*/ %> <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>

106

5 Displaying and Accessing the Product Catalog

<%/*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.

5 Displaying and Accessing the Product Catalog

107

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=session doKeywordSearch=true keywordsPropertyNames=keywords doTextSearch=true textSearchPropertyNames=description,displayName doAdvancedSearch=true advancedSearchPropertyNames=weightRange,manufacturer,childSKUs minScore=1 catalogTools=CatalogTools itemTypes^=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.

108

5 Displaying and Accessing the Product Catalog

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 components advancedSearchPropertyNames property. In this case, the values are weightRange, manufacturer, and childSKUs. The search forms 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 products 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>

5 Displaying and Accessing the Product Catalog

109

<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>&nbsp;</td> <td class=box> Match these attributes

110

5 Displaying and Accessing the Product Catalog

<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>

5 Displaying and Accessing the Product Catalog

111

</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>

112

5 Displaying and Accessing the Product Catalog

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 parts general price category. Filtering functionality is composed of three pieces: The Java Server Page where the filters 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>

5 Displaying and Accessing the Product Catalog

113

<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"/>

114

5 Displaying and Accessing the Product Catalog

<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>&nbsp;<br> </td> <td class=box>&nbsp;&nbsp;</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=" Catalog "/> </td> </tr> </table> <p> </dsp:form> </td> Filter

5 Displaying and Accessing the Product Catalog

115

</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.PossibleValues repository^=CatalogTools.catalog

The FilterCreator component configures the PartsFilterFormHandler classs repository property with the store catalog and sets the itemDescriptor to product, since parts are products.
FilterCreator component

$class=atg.projects.b2cstore.PartsFilterFormHandler $scope=session loggingIdentifier=Parts Filter Form Handler repository^=CatalogTools.catalog itemDescriptor=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.

116

5 Displaying and Accessing the Product Catalog

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>

5 Displaying and Accessing the Product Catalog

117

</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[]"

118

5 Displaying and Accessing the Product Catalog

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>&nbsp;&nbsp;&nbsp;&nbsp;</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>&nbsp;&nbsp;&nbsp;&nbsp;</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>

5 Displaying and Accessing the Product Catalog

119

<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 thats 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-byside comparison against attributes such as color, price, and weight.

120

5 Displaying and Accessing the Product Catalog

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:

5 Displaying and Accessing the Product Catalog

121

<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 handlers 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 handlers 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"/>

122

5 Displaying and Accessing the Product Catalog

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 handlers 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 librarys <dsp:select> tag for creating a dropdown list. The indexes (not the repository IDs) of the SKUs they select are stored in the form handlers SelectedIndex1 and SelectedIndex2 properties. Here is the code for the first SKU dropdown list:
<!-- First dropdown list: -->

5 Displaying and Accessing the Product Catalog

123

<!-- 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"/>

124

5 Displaying and Accessing the Product Catalog

<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"/>

5 Displaying and Accessing the Product Catalog

125

. . . <!- 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>

126

5 Displaying and Accessing the Product Catalog

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 Platforms 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 customers 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.

6 Pricing

127

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 users price: list price: the full, undiscounted price of a product. users 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. Heres another example: on Mothers Day the Pioneer Cycling store offers all women 20% off. The list price of a helmet is $100; the users 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 users 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">

128

6 Pricing

<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>

6 Pricing

129

</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.FixedPriceShippingCalculator atg.commerce.pricing.PriceRangeShippingCalculator atg.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 method shippingMethod=Ground # cost of shipping method amount=5.0

130

6 Pricing

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 method shippingMethod=Two Day # cost of shipping method amount=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 method shippingMethod=Next Day # range of costs ranges=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 shipping preCalculators=shipping/Ground,\

6 Pricing

131

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 customers 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

132

6 Pricing

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.

6 Pricing

133

134

6 Pricing

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.

7 Order Processing

135

snapshot of cart.jsp

136

7 Order Processing

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 customers orders. (ATG Consumer Commerce allows a customer to have more than one shopping cart

7 Order Processing

137

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 customers current Order object:
<dsp:valueof bean="ShoppingCartModifier.Order"/>

On the Shopping Cart page, the contents of the customers 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>&nbsp;<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">

138

7 Order Processing

<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>&nbsp;<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>&nbsp;<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 items onSale status. The following JSP code demonstrates the simplest case of displaying an items price, assuming that the commerce item has a parameter named commerceItem.

7 Order Processing

139

<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.

140

7 Order Processing

<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">

7 Order Processing

141

<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.

142

7 Order Processing

<!-- RECALCULATE Order button: --> <!-------------------------------> <dsp:input bean="ShoppingCartModifier.setOrderByRelationshipId" type="submit" value="Recalculate"/> &nbsp; &nbsp; <!-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 Developers 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

7 Order Processing

143

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 ordersetting 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

144

7 Order Processing

.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>

7 Order Processing

145

</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.

146

7 Order Processing

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.

7 Order Processing

147

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.

148

7 Order Processing

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.

7 Order Processing

149

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

150

7 Order Processing

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 users 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 users 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 <dsp:importbean <dsp:importbean <dsp:importbean <dsp:importbean <dsp:importbean . . .

bean="/atg/commerce/pricing/UserPricingModels"/> bean="/atg/commerce/pricing/AvailableShippingMethods"/> bean="/atg/userprofiling/Profile"/> bean="/atg/commerce/order/ShoppingCartModifier"/> bean="/atg/dynamo/droplet/ForEach"/> 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">

7 Order Processing

151

<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>

152

7 Order Processing

</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

7 Order Processing

153

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/j2eeapps/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 users 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.

154

7 Order Processing

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: -->

7 Order Processing

155

<!-- 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>

156

7 Order Processing

<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.

7 Order Processing

157

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 users 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 users 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 users 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 users address book.
<%-- Mark this address to be copied from the Order to the Profile so we

158

7 Order Processing

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%>">

7 Order Processing

159

<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:

160

7 Order Processing

<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 users 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>

7 Order Processing

161

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:select bean="ShoppingCartModifier.creditCardPaymentGroups[0].creditCardType" required="<%=true%>"> <dsp:option value="Visa"/>Visa <dsp:option value="MasterCard"/>Master Card <dsp:option value="AmericanExpress"/>American Express

162

7 Order Processing

<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 users 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.

7 Order Processing

163

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

164

7 Order Processing

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.

7 Order Processing

165

The hidden field in the form sample in express_checkout.jsp below sets the form handlers 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 customers 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.

166

7 Order Processing

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.

7 Order Processing

167

Setting Express Checkout preferences When the customer pushes the Save These Express Checkout Preferences button, the expressCheckout property of the users 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.

168

7 Order Processing

The first form element on the page is the pull-down list for selecting the default shipping address. The customers 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=""/>&nbsp;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 users profile. To display the current default value as pre-selected, we used the <dsp:setvalue> to set the form handlers 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

7 Order Processing

169

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"/> &nbsp; <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 users 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"/>

170

7 Order Processing

<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:

7 Order Processing

171

<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 users 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.

172

7 Order Processing

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 orders status; they can also use this section of the Web site to cancel orders that havent already been shipped. This chapter includes the following sections: Displaying a Users 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 havent already been shipped.

Displaying a Users 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.

8 Order History

173

order_history.jsp, the Order History page in a customers profile The following snippet of JSP, taken from order_history.jsp, demonstrates how the OrderLookup servlet bean renders a customers 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 users 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"/>

174

8 Order History

<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>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </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"/> &nbsp;&nbsp; <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>

8 Order History

175

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>&nbsp;&nbsp;</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>

176

8 Order History

<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> &nbsp;<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> &nbsp;<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>

8 Order History

177

<tr><td></td><td>&nbsp;&nbsp;</td><td></td><td>&nbsp;&nbsp;</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: orders@example.com </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 orders shipping and billing information.

178

8 Order History

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 orders 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. </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> You cannot cancel it.

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.

8 Order History

179

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 button to 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.

180

8 Order History

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=OrderCanceller orderManager=OrderManager openStates=\ submitted,\ processing,\

8 Order History

181

pending_merchant_action,\ pending_customer_action closedStates=\ no_pending_action profilePath=/atg/userprofiling/Profile enableSecurity=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/TransactionManager messageSourceName=OrderCanceller modifyPort=ModifyOrderPort

OrderCanceller.java:

package atg.projects.b2cstore; // Commerce import atg.commerce.order.*; import atg.commerce.fulfillment.*; import atg.commerce.*; import atg.commerce.states.*; import atg.commerce.messaging.*; // Java classes import 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"; //---------------------------------------------------------------------------

182

8 Order History

/** * 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());

8 Order History

183

} catch(JMSException j) { logError(j); } } } // end of class

CancelOrder.java:

package atg.projects.b2cstore; import javax.servlet.*; import javax.servlet.http.*; import import import import import import import import atg.commerce.CommerceException; atg.commerce.order.*; atg.commerce.states.*; atg.nucleus.naming.ParameterName; atg.nucleus.naming.ComponentName; atg.servlet.*; atg.repository.RepositoryException; 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. *

184

8 Order History

* </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; }

8 Order History

185

// 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; } } }

186

8 Order History

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.

9 Inventory

187

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.

188

9 Inventory

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 didnt 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

9 Inventory

189

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.

190

9 Inventory

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>

9 Inventory

191

192

9 Inventory

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 users 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 promotions 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 profiles 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.

10 Merchandising

193

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: attributes of the product the whole order person purchasing time of day

Types of Discounts percentage off fixed price off fixed price

Entities Discounted 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 Orders priceInfos 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.)

194

10 Merchandising

$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

10 Merchandising

195

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 Commerces 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 categorys 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.

196

10 Merchandising

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 didnt 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.

10 Merchandising

197

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=session generation=0 itemDescriptorName=media-external maxRenderSize=1 repositoryName=ProductCatalog retrieval=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.

198

10 Merchandising

Populating a Slot
We created some scenarios that populate the Pioneer Cycling sites 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 sites 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 mediaexternal, 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 sites 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.

10 Merchandising

199

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 itemdescriptor 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 recipients 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:input bean="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.

200

10 Merchandising

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 message defaultSubject=A Gift Certificate for You! defaultFromAddress=pioneer@example.com defaultMessageBody=Here is a gift certificate claim number for you. You can go to Pioneer 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 Profiles 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.

10 Merchandising

201

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 users 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>

202

10 Merchandising

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.

10 Merchandising

203

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 customers 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/webapp/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 share your 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"

204

10 Merchandising

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"/>

10 Merchandising

205

<dsp:oparam name="output"> <blockquote> <dsp:form action="lists.jsp" method="post"> <table cellspacing=2 cellpadding=0 border=0 <!-- Display giftlist name --> <tr><td colspan=10 class=box-top-viewmy> <dsp:valueof param="element.eventName"/> </td> </tr>

width=75%>

<!-- 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 customers wish list, gift list(s) and their contents.

206

10 Merchandising

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>

10 Merchandising

207

<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 working on 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 <dsp:importbean <dsp:importbean <dsp:importbean <dsp:importbean bean="/atg/commerce/gifts/GiftlistFormHandler"/> bean="/atg/dynamo/droplet/ForEach"/> bean="/atg/dynamo/droplet/Switch"/> bean="/atg/dynamo/droplet/IsEmpty"/> 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>

208

10 Merchandising

<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> --%>

10 Merchandising

209

<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>

210

10 Merchandising

<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>&nbsp;<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>&nbsp;<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.

10 Merchandising

211

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

212

10 Merchandising

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>&nbsp;<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

10 Merchandising

213

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"/>

214

10 Merchandising

</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.

10 Merchandising

215

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=true nameSearchPropertyNames=owner.firstName,owner.lastName

216

10 Merchandising

doAdvancedSearch=true advancedSearchPropertyNames=eventType,eventName,state doPublishedSearch=true publishedSearchPropertyNames=public,published profileRepository^=/atg/userprofiling/ProfileTools.profileRepository profileTools=/atg/userprofiling/ProfileTools itemTypes=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.

10 Merchandising

217

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>

218

10 Merchandising

</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 --%>

10 Merchandising

219

<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 Elses 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>&nbsp;</td> <td><b>Quantity<br>bought</b></td> <td>&nbsp;&nbsp;&nbsp;</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">

220

10 Merchandising

<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. */ %>

10 Merchandising

221

<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

222

10 Merchandising

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>

10 Merchandising

223

<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 customers 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 customer to purchase something off someone else's gift list. It assumes that param: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"

224

10 Merchandising

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:

10 Merchandising

225

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.

226

10 Merchandising

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 SKUs 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.

10 Merchandising

227

To print out the names and list prices of all the component SKUs in a bundle SKU, we simply iterated over the SKUs 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>

228

10 Merchandising

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 scenarios 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 sites 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.

Appendix A. Pioneer Cycling Scenarios

229

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, its 5% off everything in the store.

230

Appendix A. Pioneer Cycling Scenarios

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, weve 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, weve decided to reward those customers whose orders total above $500 with 10% off, and everyone else with 5% off.

Appendix A. Pioneer Cycling Scenarios

231

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, its 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/j2eeapps/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 customers actions. In the Pioneer store, we built some profile attributes to track a customers 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 customers 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 customers 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.

232

Appendix A. Pioneer Cycling Scenarios

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 customers shopping cart. The advantages of this are apparent when you consider the case when the site would

Appendix A. Pioneer Cycling Scenarios

233

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 doesnt, 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 carts 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 orders 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 orders 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.

234

Appendix A. Pioneer Cycling Scenarios

Scenario Templates
There are several prefabricated scenario templates that you can use as the beginnings of your own scenarios.

Scenario Template
Coupon

Description Creates scenarios that give customers a promotion whenever their orders reach a certain state Cross-sells products against others Creates scenarios like the FreeGift one described above Sends e-mail to all members of a profile group Delivers a promotion to all members of a group Sends e-mail to all customers with a certain profile attribute Shows the contents of a targeter in a slot

Cross-sell_promotion Free_gift Group_targeted_e-mail Group_targeted_promotion Profile_targeted_e-mail Targeter_based_promotion

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

Appendix A. Pioneer Cycling Scenarios

235

236

Appendix A. Pioneer Cycling Scenarios

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 sites 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 cant access your site or if the site is difficult to use and poorly designed, users arent 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.

Appendix B. Optimizing Performance

237

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 dont 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>

238

Appendix B. Optimizing Performance

</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 Developers Guide. Lets 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>

Appendix B. Optimizing Performance

239

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 dont 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-thefly link rewriting. Cache cannot cache content that needs to have link rewriting performed on it. If you choose

240

Appendix B. Optimizing Performance

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 isnt 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.

Appendix B. Optimizing Performance

241

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>

242

Appendix B. Optimizing Performance

<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 itemtypes 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.

Appendix B. Optimizing Performance

243

244

Appendix B. Optimizing Performance

Index

A
ACC 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

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

D
Database schema, 55 Databases performance, 59 Discounts, 194 DMS messages, 46, 47, 49 Droplet invocation, 238 dynamoMessagingSystem.xml, 50

B
b2c_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

E
Email templates, 235 EmailFormHandler component, 38 Error messages displaying, 165 Express checkout deactivating, 171 enabling, 165 express_checkout.jsp, 166 express_checkout_preferences.jsp, 168

C
Cache servlet bean, 239 cancel_order.jsp, 179 CancelOrder servlet bean, 181 cart.jsp, 137, 171 Catalog browsing, 45

F
Form handlers, 242 Fragments, 238

Index

245

G
Gift 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 elses, 220 Gift Registry, 203 GiftListFormHandler, 203 Gifts checkout with, 226 purchasing, 216

M
Manufacturer item type, 61 Media delivering, 196 MediaSlot, 198 Merchandising, 193 overview, 10 My Profile page, 26 my_profile.jsp, 14

N
Nicknames for addresses, 157

O
Order 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

H
handleCreateNewCreditCard() method, 40 handleLogin() method, 16 History page, 44

I
Inventory, 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

P
Page 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

J
JSP include directives, 238 JSP pages sectioning, 238 JSP Performance, 237

K
keyword attribute, 61

L
Login page, 14 LoginFragment.jsp, 15

246

Index

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

Q
query-item tags, 242

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

T
Targeting, 195 overview, 10

R
Registration, 14, 16 customized, 25 Repository API, 52 ResourceBundle, 40

U
Upselling, 197 User item types, 51 User profile, 21 User profiles, 13 userProfile.xml, 45, 51 extending, 27 Users anonymous, 14

S
Scenarios, 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

V
validateCreditCard, 42

W
Wish lists, 204

X
XML files, 57

Index

247

248

Index

Potrebbero piacerti anche