Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Version 9.4
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
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
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
Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245
vi
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.
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.
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
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
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.
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.
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
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).
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
11
12
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.
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
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
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> </td> <td><dsp:img height="1" width="300" src="../images/d.gif"/></td> </tr> <tr valign=top> <td width=30%> <dsp:droplet name="Switch"> <dsp:param bean="B2CProfileFormHandler.formError" name="value"/> <dsp:oparam name="true"> <span class=registrationerror> <span class=help>There were problems with your registration:</span><p> <UL> <dsp:droplet name="ProfileErrorMessageForEach"> <dsp:param bean="B2CProfileFormHandler.formExceptions" name="exceptions"/> <dsp:oparam name="output"> <LI> <dsp:valueof param="message"/> </dsp:oparam> </dsp:droplet> </UL> </span> </dsp:oparam> </dsp:droplet> </td> <td></td> <td> <!-- 1. I am: --> <table width=100% cellpadding=0 cellspacing=0 border=0> <tr><td class=box-top-profile> Me</td></tr> </table> <p> Name (first, middle, last)<br> <dsp:input bean="B2CProfileFormHandler.value.firstName" maxlength="40" size="15" type="text" required="<%=true%>"/> <dsp:input bean="B2CProfileFormHandler.value.middleName" maxlength="40" size="10" type="text"/> <dsp:input bean="B2CProfileFormHandler.value.lastName" maxlength="40" size="15" type="text" required="<%=true%>"/> <p> Email address <br>
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> <br> </td> </tr> <tr valign=top> <!-- 2. Billing/Main Address --> <td width=30%> <span class=help>Please provide us with the address where you wish to be billed.<p> You may use this same address for shipping, or enter a separate shipping address below.</span> </td> <td></td> <td> <table width=100% cellpadding=0 cellspacing=0 border=0> <tr><td class=box-top-profile> My billing address</td></tr> </table> <p> Street address <br> <!-- Store the ProfileID in ownerId field of the address. This tells us this address "belongs to" (and can be edited) by the user. --> <dsp:input bean="B2CProfileFormHandler.value.billingAddress.ownerId" beanvalue="Profile.id" type="hidden"/> <dsp:input bean="B2CProfileFormHandler.value.billingAddress.address1" maxlength="30" size="40" type="text" required="<%=true%>"/><br> <dsp:input bean="B2CProfileFormHandler.value.billingAddress.address2" maxlength="30" size="40" type="text"/><br> <table cellpadding=0 cellspacing=0> <tr> <td>City<br> <dsp:input bean="B2CProfileFormHandler.value.billingAddress.city" maxlength="30" size="20" type="text" required="<%=true%>"/></td> <td> State<br> <dsp:select bean="B2CProfileFormHandler.value.billingAddress.state"> <%@ include file="StatePicker.jspf" %> </dsp:select> </td> <td> Postal Code<br> <dsp:input bean="B2CProfileFormHandler.value.billingAddress.postalCode" maxlength="10" size="10" type="text" required="<%=true%>"/></td> </tr> </table> <table cellpadding=0 cellspacing=0> <tr> <td>Country<br> <dsp:select bean="B2CProfileFormHandler.value.billingAddress.country" required="<%=true%>"> <%@ include file="CountryPicker.jspf" %>
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> <br> </td> </tr> <tr valign=top> <!-- 3. Ship it here: --> <td width=30%> <span class=help> If you know that you will want to ship products to a different address, then you may enter an alternate shipping address here. <p>If you will always ship products to your billing address, then indicate that here. </span> </td> <td></td> <td> <table width=100% cellpadding=0 cellspacing=0 border=0> <tr><td class=box-top-profile> My main shipping address</td></tr> </table> <p> <dsp:input bean="B2CProfileFormHandler.shipToBillingAddress" type="radio" checked="<%=true%>" value="true"/> Use billing address for shipping<br> <dsp:input bean="B2CProfileFormHandler.shipToBillingAddress" type="radio" value="false"/> Use the new address below for shipping <p> Street address <br> <!-- Store the ProfileID in ownerId field of the address. This tells us this address "belongs to" (and can be edited) by the user. --> <dsp:input bean="B2CProfileFormHandler.value.shippingAddress.ownerId" beanvalue="Profile.id" type="hidden"/> <dsp:input bean="B2CProfileFormHandler.value.shippingAddress.address1" maxlength="30" size="40" type="text"/><br> <dsp:input bean="B2CProfileFormHandler.value.shippingAddress.address2" maxlength="30" size="40" type="text"/><br> <table cellpadding=0 cellspacing=0> <tr> <td>City<br> <dsp:input bean="B2CProfileFormHandler.value.shippingAddress.city" maxlength="30" size="20" type="text"/></td> <td> State<br> <dsp:select bean="B2CProfileFormHandler.value.shippingAddress.state"> <%@ include file="StatePicker.jspf" %>
3 Profile Management
19
</dsp:select> </td> <td> Postal Code<br> <dsp:input bean="B2CProfileFormHandler.value.shippingAddress.postalCode" maxlength="10" size="10" type="text"/></td> </table> Country<br> <dsp:select bean="B2CProfileFormHandler.value.shippingAddress.country" required="<%=true%>"> <%@ include file="CountryPicker.jspf" %> </dsp:select> <p> <br> </td> </tr> <tr valign=top> <!-- 4. Username/password: --> <td width=30%> <span class=help>When you return to the site, log in with this username and password to manage your account information.</span> <p> </td> <td></td> <td> <table width=100% cellpadding=0 cellspacing=0 border=0> <tr><td class=box-top-profile> My profile</td></tr> </table> <p> Choose a username <span class=help>( i.e. 'joeshmoe22' )</span><br> <dsp:input bean="B2CProfileFormHandler.value.login" maxlength="20" size="15" type="text"/> <p> Choose a password <span class=help>( i.e. 'pA$$weRD22' )</span><br> <dsp:input bean="B2CProfileFormHandler.value.password" maxlength="35" size="15" type="password"/><br> Enter password again to be sure you have it right<br> <dsp:input bean="B2CProfileFormHandler.value.confirmPassword" maxlength="35" size="15" type="password"/> <p> <br> </td> </tr> <tr valign=top> <!-- 5. Optional Personal Data: --> <td width=30%> <span class=help>These questions are optional, but it's nice if you tell us.</span> </td> <td></td> <td> <%@ include file="../common/UserPreferencesFormFragment.jspf" %> </td> <tr valign=top> <!-- 6. Submit: --> <td width=30%> <td></td> <td>
20
3 Profile Management
<table width=100% cellpadding=0 cellspacing=0 border=0> <tr><td class=box-top-profile> All done?</td></tr> </table> <p> <p> <br> <!-- Submit form to handleCreate() handler --> <dsp:input bean="B2CProfileFormHandler.create" type="submit" value=" Register --> "/> <p> <br> </td> </tr> </table> </dsp:form> </dsp:getvalueof>
3 Profile Management
21
22
3 Profile Management
Profile Trait
firstName middleName lastName email login password
3 Profile Management
23
Profile Trait
gender dateOfBirth daytimeTelephoneNumber size
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%>"/>
Profile Trait
homeAddress billingAddress shippingAddress
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
24
3 Profile Management
Address Field
state postalCode country telephone
*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>
3 Profile Management
29
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> </td> </tr> <tr valign=top> <!-- 6. Submit: --> <td></td> <td></td> <td> <table width=100% cellpadding=0 cellspacing=0 border=0> <tr><td class=box-top-profile> Done?</td></tr> </table> <p> <br> <!-- Submit form to handleUpdate() handler --> <dsp:input bean="B2CProfileFormHandler.newAddress" type="submit" value=" Add Address --> "/> . . .
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);
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:
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.
<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.
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>
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.
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
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.
44
3 Profile Management
pages_viewed.jsp
<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
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>
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> . . .
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.
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
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>
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) );
3 Profile Management
55
56
3 Profile Management
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
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.
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
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"/>
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>
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
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).
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:
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:
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
<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.
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
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:
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) );
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
<option value="Kevlar" <option value="Nylon" <option value="Polyester Blend" <option value="Silk" <option value="Wool" <option value="Goretex" </property> </table> </item-descriptor>
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) );
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) );
Here is the XML file for creating the bike-sku item type:
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
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.
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.
<ATG9dir>/PioneerCyclingJSP/j2ee-apps/pioneer/ web-app/en/catalog/product_bike.jsp.
<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.
<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
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:
72
<% /* ------------------------------------------------------* 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">
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>
74
web-app/en/Common/DisplaySKUPrice.jsp
<ATG9dir>/PioneerCyclingJSP/j2ee-apps/pioneer/web-app/en/DisplayProductPrice.jsp.
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>
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:
76
The list price and users price for each SKU is displayed on the product page.
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>
77
</dsp:getvalueof>
78
<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 "/>
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>
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.
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"/>
80
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.
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"
81
value="../checkout/cart.jsp"/>
The ShoppingCartModifier is described in detail in the Order Processing (page 135) chapter.
82
</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>
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>
84
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:
<DECLAREPARAM NAME="Product" CLASS="java.lang.Object" DESCRIPTION="A Product repository Item - REQUIRED."> <dsp:importbean bean="/atg/commerce/pricing/PriceItem"/>
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> </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:
86
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>
<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>
87
These features are covered in detail below in another section so we can continue to focus on the process of creating template pages.
88
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"/>
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> </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. */ %>
90
<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>
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
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 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.
categories of bikes, with a link for each bike, a picture, and a description.
92
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"/>
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
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
95
childCategories of parts. For each child category of parts, a nested ForEach component renders a link to
<dsp:droplet name="/atg/commerce/catalog/CategoryLookup"> <dsp:param bean="/atg/dynamo/servlet/RequestLocale.locale" name="repositoryKey"/> <dsp:oparam name="output"> <dsp:droplet name="/atg/dynamo/droplet/ForEach"> <dsp:param name="array" param="element.childCategories"/> <dsp:param name="elementName" value="child"/> <dsp:param bean="/OriginatingRequest.requestLocale.locale" name="repositoryKey"/> <dsp:oparam name="output"> <dsp:include page="../common/ItemLink.jsp" flush="true"> <dsp:param name="Item" param="child"/> <dsp:param name="navAction" value="jump"/> <dsp:param name="Image" param="child.thumbnailImage"/> <dsp:param name="DisplayText" param="child.displayName"/> </dsp:include> <br> <dsp:droplet name="/atg/dynamo/droplet/ForEach"> <dsp:param name="array" param="child.childCategories"/> <dsp:param name="elementName" value="grandChild"/> <dsp:oparam name="output"> <dsp:include page="../common/ItemLink.jsp" flush="true"> <dsp:param name="Item" param="grandChild"/> <dsp:param name="navAction" value="jump"/> <dsp:param name="Image" param="grandChild.thumbnailImage"/> <dsp:param name="DisplayText" param="grandChild.displayName"/> </dsp:include> <br> </dsp:oparam> </dsp:droplet> </dsp:oparam> </dsp:droplet> </dsp:oparam> </dsp:droplet>
You could also use a recursive algorithm to display the whole tree. The JSP code sample below retrieves the category on the top-most level with the CategoryLookup component and invokes itself to recurse through the child categories. A category ID is passed in as the ID parameter in the URL. The major drawback of this implementation is that if your tree has any circular references (for example, a category containing an ancestor category as a child category), it will recurse forever.
<dsp:importbean bean="/atg/dynamo/droplet/ForEach"/> <dsp:importbean bean="/atg/commerce/catalog/CategoryLookup"/> <!-- display all products under this category and subcategories.
96
-- 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:
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.
98
<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.
<dsp:droplet name="/atg/dynamo/droplet/Switch"> <dsp:param name="value" param="displaybreadcrumbs"/> <dsp:oparam name="true"> <dsp:droplet name="/atg/dynamo/droplet/ForEach"> <dsp:param bean="CatalogNavHistory.navHistory" name="array"/> <dsp:param name="elementName" value="crumb"/> <dsp:oparam name="output"> <dsp:droplet name="/atg/dynamo/droplet/Switch"> <% /* ------------------------------------------------* We want to put a separator between the items in the navHistory. In * this example we put a greater than sign between them. We use a * switch droplet to identify the first item in the array because we * don't want to render a separator before the first item. * ------------------------------------------------- */ %> <dsp:param name="value" param="count"/> <dsp:oparam name="1"> </dsp:oparam> <dsp:oparam name="default"> > </dsp:oparam> </dsp:droplet> <dsp:getvalueof id="countStr" param="count" idtype="Integer">
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
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.
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
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
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
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.
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
<%/*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.
107
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
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>
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> </td> <td class=box> Match these attributes
110
<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>
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
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.
filtering.jsp:
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
<dsp:oparam name="output"> <dsp:getvalueof id="option116" param="element" idtype="java.lang.String"> <dsp:option value="<%=option116%>"/> </dsp:getvalueof><dsp:valueof param="element"/> </dsp:oparam> </dsp:droplet> </dsp:oparam> </dsp:droplet> </dsp:select> <br> <br> </td> <td class=box> </td> <td class=box> <dsp:input bean="FilterCreator.priceSelectionType" type="radio" value="allPrices"/> Show products of all prices<br> <dsp:input bean="FilterCreator.priceSelectionType" type="radio" value="priceCategory"/> Show products that are priced<br> <dsp:select bean="FilterCreator.priceCategory" size="4"> <dsp:droplet name="RepositoryValues"> <dsp:param name="itemDescriptorName" value="product"/> <dsp:param name="propertyName" value="priceRange"/> <dsp:oparam name="output"> <dsp:droplet name="ForEach"> <dsp:param name="array" param="values"/> <dsp:oparam name="output"> <dsp:getvalueof id="option166" param="element" idtype="java.lang.String"> <dsp:option value="<%=option166%>"/> </dsp:getvalueof><dsp:valueof param="element"/> </dsp:oparam> </dsp:droplet> </dsp:oparam> </dsp:droplet> </dsp:select> <br> <P> <%/* Set these hidden params to cause the action to return to the * previous page */%> <input name="navCount" type="hidden" value='<dsp:valueof bean="/atg/commerce/catalog/CatalogNavHistory.navCount"/>'> <input type="hidden" name="navAction" value="jump"> <input type="hidden" name="id" value="cat10005"> <dsp:input bean="FilterCreator.submit" type="submit" value=" Catalog "/> </td> </tr> </table> <p> </dsp:form> </td> Filter
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
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
The PartsFilterFormHandler overrides the getRuleRepresentation() methods of its parent class. This allows it to specify an SGML targeting rule.
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.
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
description="An array of repository items"> <%/* Filter the array of repository items in the page param, "array" */%> <dsp:droplet name="RuleBasedRepositoryItemGroupFilter"> <dsp:param name="inputItems" param="array"/> <dsp:param bean="FilterCreator.repositoryItemGroup" name="repositoryItemGroup"/> <dsp:oparam name="output"> <%/* Display each filtered item */%> <dsp:droplet name="ForEach"> <dsp:param name="array" param="filteredItems"/> <dsp:param name="elementName" value="childProduct"/> <dsp:oparam name="outputStart"> <table cellspacing=3 cellpadding=0 border=0> </dsp:oparam> <dsp:oparam name="output"> <tr valign=top> <td> <dsp:valueof param="childProduct.manufacturer.displayName"/> </td> <td> </td> <td colspan=3> <b> <dsp:include page="../common/ItemLink.jsp" flush="true"> <dsp:param name="Item" param="childProduct"/> <dsp:param name="Image" param="childProduct.thumbnailImage"/> <dsp:param name="DisplayText" param="childProduct.displayName"/> <dsp:param name="navAction" value="push"/> </dsp:include> </b> </td> </tr> <%/* List all child SKUs of the product */%> <dsp:droplet name="ForEach"> <dsp:param name="array" param="childProduct.childSKUs"/> <dsp:param name="elementName" value="childSku"/> <dsp:oparam name="output"> <tr valign=top> <td colspan=2></td> <td> <dsp:valueof param="childSku.displayName"/> </td> <td> </td> <td align=right> <dsp:include page="../common/DisplaySkuPrice.jsp" flush="true"> <dsp:param name="Sku" param="childSku"/> <dsp:param name="Image" param="childSku.thumbnailImage"/> <dsp:param name="navAction" value="push"/> <dsp:param name="DisplayText" param="childSku.displayName"/> </dsp:include> </td> </tr> </dsp:oparam> </dsp:droplet> <tr> <td colspan=5><hr size=0></td> </tr> </dsp:oparam>
119
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.
120
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:
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>
<!-- 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:
122
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: -->
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.
124
<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>
125
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
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.
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.
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
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.
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.
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.
7 Order Processing
135
snapshot of cart.jsp
136
7 Order Processing
snapshot of cart_edit.jsp
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> <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> <br>Shipping to you:<hr size=0></td></tr> </dsp:oparam> <dsp:oparam name="output"> <dsp:setvalue param="itemName" value='<%="item" + request.getParameter("shippingGroupIndex") + ":" +request.getParameter("index")%>'/> <%@ include file="CartLineItem.jspf" %> </dsp:oparam> </dsp:droplet> </dsp:oparam> <dsp:oparam name="true"> <dsp:droplet name="ForEach"> <dsp:param name="array" param="ShippingGroup.CommerceItemRelationships"/> <dsp:param name="elementName" value="CiRelationship"/> <dsp:param name="indexName" value="index"/> <dsp:oparam name="outputStart"> <tr><td colspan=13> <br>Gifts <dsp:droplet name="GiftlistLookupDroplet"> <dsp:param name="id" param="giftlistId"/> <dsp:param name="elementName" value="giftlist"/> <dsp:oparam name="output"> for <dsp:valueof param="giftlist.owner.firstName"/> <dsp:valueof param="giftlist.owner.lastName"/> </dsp:oparam> </dsp:droplet>: <hr size=0></td></tr> </dsp:oparam> <dsp:oparam name="output"> <dsp:setvalue param="itemName" value='<%="item" + request.getParameter("shippingGroupIndex") + ":" +request.getParameter("index")%>'/> <%@ include file="CartLineItem.jspf" %> </dsp:oparam> </dsp:droplet> </dsp:oparam> </dsp:droplet> </core:defaultCase> </core:switch> </dsp:oparam> </dsp:droplet>
7 Order Processing
139
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>
140
7 Order Processing
Again in the Pioneer Cycling, store, we wanted to show customers their subtotals both before discounts and after discounts.
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.
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"/> <!-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.)
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.
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"/>
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.
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.
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"/>
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.
<!-- 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.
<!-- 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.
<!-- 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.
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>
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>
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.
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
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.
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.
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 -->
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.
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.
This code submits the form and the handleMoveToConfirmation() method creates and inserts payment methods in the order.
164
7 Order Processing
Discover: 6011111111111117 The expiration date for all cards can be any date in the future.
<!-- 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.
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.
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=""/> 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"/> <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>
7 Order Processing
171
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.
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> </dsp:oparam> </dsp:droplet> </dsp:oparam> </dsp:droplet> <dsp:droplet name="ForEach"> <dsp:param name="array" param="result"/> <dsp:oparam name="outputStart"> <dsp:droplet name="IsNull"> <dsp:param name="value" param="nextIndex"/> <dsp:oparam name="false"> <dsp:a href="order_history.jsp"><i>More orders</i> <dsp:param name="openStartIndex" param="nextIndex"/> <dsp:param name="closedStartIndex" param="closedStartIndex"/> <dsp:param name="cancelledStartIndex" param="cancelledStartIndex"/> </dsp:a><br> </dsp:oparam> </dsp:droplet> <OL> </dsp:oparam> <dsp:oparam name="outputEnd"> </OL> </dsp:oparam> <dsp:oparam name="empty"> No open orders. </dsp:oparam> <dsp:oparam name="output"> <LI> <dsp:a href="order.jsp"> <dsp:param name="orderId" param="element.id"/> #<dsp:valueof param="element.id">no order number</dsp:valueof></dsp:a> <dsp:valueof date="MMMMM d, yyyy" param="element.submittedDate"/> <dsp:droplet name="/atg/commerce/order/OrderStatesDetailed"> <dsp:param name="state" param="element.state"/> <dsp:oparam name="output"><dsp:valueof param="detailedState"/></dsp:oparam> </dsp:droplet> </dsp:oparam> </dsp:droplet> </dsp:oparam> <dsp:oparam name="error"> <span class=profilebig>ERROR: <dsp:valueof param="errorMsg">no error message</dsp:valueof> </span> </dsp:oparam> </dsp:droplet>
8 Order History
175
The customer is then shown the status and history of a single order (order.jsp with the information of order number that corresponds to orderId). In the JSP code snippet from order.jsp below, instead of being passed explicitly as a parameter to the OrderLookup servlet bean, orderId is set by the link from the order history page.
<dsp:droplet name="/atg/commerce/order/OrderLookup"> <dsp:oparam name="error"> <p> <span class=profilebig>ERROR: <dsp:valueof param="errorMsg">no error message</dsp:valueof> </span> <p> </dsp:oparam> <dsp:oparam name="output"> <dsp:setvalue paramvalue="result" param="order"/> <p> <span class=profilebig>order #<dsp:valueof param="order.id">no order id</dsp:valueof></span> <p> <table cellspacing=0 cellpadding=0 border=0> <!-- Setup gutter and make space --> <tr> <td><dsp:img height="1" width="100" src="../images/d.gif"/><br></td> <td> </td> <td><dsp:img height="1" width="400" src="../images/d.gif"/></td> </tr> <dsp:droplet name="Switch"> <dsp:param name="value" param="order.stateAsString"/> <dsp:oparam name="NO_PENDING_ACTION"> <tr valign=top> <td> <span class=help>Gosh. You should have this one by now.</span> </td> <td></td> <td> <table width=100% cellpadding=0 cellspacing=0 border=0> <tr><td class=box-top-profile>Order status</td></tr></table>
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> <br> </td> </tr> </dsp:oparam> <dsp:oparam name="default"> <tr valign=top> <td> <span class=help>Since this order has not yet shipped, you may still change it.</span> </td> <td></td> <td> <table width=100% cellpadding=0 cellspacing=0 border=0> <tr><td class=box-top-profile>Order status</td></tr></table> <p> This order was placed on <dsp:valueof date="MMMMM d, yyyy" param="order.submittedDate"/>. <br> Its status is <dsp:valueof param="order.stateAsString">UNKNOWN STATUS</dsp:valueof>. <br> <p> > <dsp:a href="cancel_order.jsp"> <dsp:param name="orderId" param="order.id"/> cancel the order</dsp:a><br> <p> To make any other changes to this order, please <dsp:a href="contact_customer_service.jsp">contact a customer service representative</dsp:a>. </span> <p> <br> </td> </tr> </dsp:oparam> </dsp:droplet> <tr valign=top> <td> <span class=help>This is the contents of the order.</span> </td> <td></td> <td> <table width=100% cellpadding=0 cellspacing=0 border=0> <tr><td class=box-top-profile>Order details</td></tr></table> <p> <table cellspacing=2 cellpadding=0 border=0>
8 Order History
177
<tr><td></td><td> </td><td></td><td> </td><td></td></tr> <tr valign=top> <td colspan=3> <b>Order # <dsp:valueof param="order.id">no order id</dsp:valueof></b><br> <dsp:valueof date="h:mma MMMMM d, yyyy" param="order.submittedDate"/><br> Sales: 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:
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:
8 Order History
181
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
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.
9 Inventory
187
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
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 )
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
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).
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
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:
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.
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.
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
# 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.
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.
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.
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
<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> <br> </td> </tr> <tr valign=top> <td width=30%> <span class=help> Decide if you want your list to be public yet. When you make your list public, your friends can find your list by searching. </span> </td> <td></td> <td> <table width=100% cellpadding=0 cellspacing=0 border=0> <tr><td class=box-top-store>Gift list public?</td></tr></table> <p> <dsp:input bean="GiftlistFormHandler.isPublished" name="published" type="radio" value="true"/> Make my list public now<br> <dsp:input bean="GiftlistFormHandler.isPublished" name="published" type="radio" checked="<%=true%>" value="false"/> Don't make my list public yet <p> <br> <dsp:input bean="GiftlistFormHandler.saveGiftlist" type="submit" value="Save gift list"/>
Here is the screenshot for lists_new.jsp. It takes the input from customers and saves it to the repository.
10 Merchandising
211
lists_new.jsp
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> <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
<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.
216
10 Merchandising
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:
<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>
<%/* check if parameter giftlistId has been passed into page. if it has, then call GiftlistDroplet to do something */ %> <dsp:droplet name="IsEmpty"> <dsp:param name="value" param="giftlistId"/> <dsp:oparam name="false"> <dsp:droplet name="/atg/commerce/gifts/GiftlistLookupDroplet"> <dsp:param name="id" param="giftlistId"/> <dsp:oparam name="output"> <dsp:droplet name="IsEmpty"> <dsp:oparam name="false"> <dsp:setvalue paramvalue="element" param="giftlist"/> <span class=storebig>Gift List for <dsp:valueof param="giftlist.owner.firstName"/> <dsp:valueof param="giftlist.owner.middleName"/> <dsp:valueof param="giftlist.owner.lastName"/> </span> <br> <p> <blockquote> <b>Event name:</b><dsp:valueof param="giftlist.eventName"/> <br> <b>Event date:</b><dsp:valueof date="d-MMM-yyyy" param="giftlist.eventDate"/> <p> <table cellspacing=0 cellpadding=0 border=0> <tr valign=bottom> <td><b>Quantity<br>wanted</b></td> <td> </td> <td><b>Quantity<br>bought</b></td> <td> </td> <td><b>Product</b></td> </tr> <tr><td colspan=5><hr size=0></td></tr> <tr valign=top> <dsp:droplet name="/atg/dynamo/droplet/ForEach">
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"/>
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
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
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
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.
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
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.
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.
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
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
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
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
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
235
236
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)
237
<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
</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.
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
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.
241
<?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
<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.
243
244
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