Thursday, June 18, 2009

ATG Add Custom properties to SKU

Extending Commerce item

In some situations we have to add some additional properties to existing commerce object (commerceItem) and we need to integrate it with ATG commerce.

For eg in configuring a ring we may have to add some additional properties to ring added in Cart.

Say a ring may need some additional properties like Metal Finish, Stone Color, and Engraving etc. In effect this means we should be able to “Configure the ring”.

In configuring a window blinds we need to pass some additional properties to window blinds commerceitem

Like
Room name : (Any user input value)
Color: (User select from a set of options provided)
Height: (User select from a set of options provided)
Width: (User select from a set of options provided)
Etc….

There is a chance that some of these properties are not added to the commerce item while adding to cart because user may not need that. So the question one having in mind are

1) Do we need to extend the commerceItem to include each and every additional properties?.
2) Can we do this Dynamically at run time?.

Modifying commerce Item for each and every properties will be cumbersome as sonner or later there is a chance of additional properties or some properties are going to filter out. So we can simplify this situation by considering a MAP. i.e java.util.Map
Which is having key and values.

So we have to extend commerceItem to reflect the Map. And all additional properties names with values can be added into the Map as key value pairs while adding to Shopping cart.

This indeed solves the multiplicity problem of properties.But how we add the map to OOTB commerceitem? IS there any complexities while adding the modified/extended commerceitem to order/cart?. If the properties need repricing how we add that?

One of the Easiest ways to handle this situation is by modifying Purchase Process. This includes a sequence of steps.




Step 1 of 7:Extend class CommerceItemImpl

First we need to extend the commerce object hierarchy by subclassing an existing object and adding new primitive data type properties. When we add primitive data type properties, you don't need to write any code to save the properties to or load the properties from the Order Repository.Here we are adding a Map object. Which is not primitive datatype So we have to add some code to reflect the props in order.

import atg.commerce.order.CommerceItemImpl;


public class BlindsCommerceItemImpl extends CommerceItemImpl {

static final long serialVersionUID = 1;


public BlindsCommerceItemImpl() {
      super();
}


public Map getSelectedOptions()
{
       return (Map) getPropertyValue("selectedOption");
}


public void setSelectedOptions(Map selectedOptions)
{
      setPropertyValue("selectedOption", selectedOptions);
}


public double getBlindsPrice() {
      return ((Double) getPropertyValue("designedPrice")).doubleValue();
}


public void setBlindsPrice(double blindsPrice) {
      setPropertyValue("designedPrice", new Double(blindsPrice));
}

}



We have added 2 properties here
1)getters and setters for selected Options.
This holds the Map property.

2) getters and setters for blindsPrice.
This is a value holder for Price. Which we will discuss later.


In the code example above, note the calls to getPropertyValue() and setPropertyValue(). These methods retrieve and set the values of properties directly on the repository item objects; they are part of the atg.commerce.order.ChangedProperties interface. In a commerce object that supports the ChangedProperties interface, every get() method needs to call the getPropertyValue() method, and similarly every set() method needs to call the setPropertyValue() method.


Step 2 of 7- Extend the Order Repository Definition File
Extend the Order Repository definition file, orderrepository.xml, to add the new properties in MyCommerceItemImpl to the existing commerceItem item descriptor. In this example, the new property to add is the shortDescription property.
The orderrepository.xml file is found in the CONFIGPATH at /atg/commerce/order/orderrepository.xml. To extend the file, create a new orderrepository.xml file at /atg/commerce/order/ in your localconfig directory. The new file should define the shortDescription property for the commerceItem item descriptor. During startup, the ATG platform uses XML file combination to combine the orderrepository.xml files in the CONFIGPATH into a single composite XML file.



We have added new table called BLINDS_ITEM_FEATURES which is of type multi to hold the MAP. And also added a transient property “designedPrice” to hold the custom price.


We created the new myCommerceItem item descriptor, defining both its properties which is a map and the database table and columns that store those properties. Now we need to modify accordingly the Order Repository database schema.
The following DDL statement creates the database table and columns specified in the orderrepository.xml file that you created in step 2.

The database script is

CREATE TABLE BLINDS_ITEM_FEATURES
(
COMMERCE_ITEM_ID VARCHAR2(40 BYTE) NOT NULL,
FEATURE_ID VARCHAR2(254 BYTE),
OPTION_ID VARCHAR2(254 BYTE)
)
LOGGING
NOCOMPRESS
NOCACHE
NOPARALLEL
MONITORING;


ALTER TABLE BLINDS_ITEM_FEATURES ADD (
PRIMARY KEY
(COMMERCE_ITEM_ID, FEATURE_ID));


ALTER TABLE BLINDS_ITEM_FEATURES ADD (
FOREIGN KEY (COMMERCE_ITEM_ID)
REFERENCES DCSPP_ITEM (COMMERCE_ITEM_ID));


Step 3 of 7 - Modify the OrderTools Configuration File
The OrderTools component controls many aspects of the purchase process, such as mapping between commerce object types and class names, defining the default commerce object types, and mapping between commerce objects and item descriptors. You need to modify the OrderTools configuration file to support the new MyCommerceItemImpl class and myCommerceItem item descriptor.
To modify the OrderTools configuration file, layer on a configuration file by creating an OrderTools.properties file at /atg/commerce/order/ in your localconfig directory.
Add new entry to beanNameToItemDescriptorMap which declares our extended Commerce item.

beanNameToItemDescriptorMap=\

atg.projects.b2cblueprint.order.BlueprintOrderImpl=order,\

atg.commerce.order.CommerceItemImpl=icommerceItem,\

blinds.commerce.order.BlindsCommerceItemImpl=commerceItem,\

Also edit the commerceItemTypeClassMap to add our commerce item as default entry unless we don’t specify this in CartFormHandler

commerceItemTypeClassMap+=\

giftWrapCommerceItem=atg.projects.b2cblueprint.order.GiftWrapCommerceItem,\

default=blinds.commerce.order.BlindsCommerceItemImpl




Step 4 of 7 – Extend CommerceItemInfo class

This is a info class which acts like a property holder which hold the latest info about the commerce item configured which we are going to add to cart. So the values we are going to pass to the commerce items are 1) the Map 2) The Price So the calss will looks like

import atg.commerce.order.purchase.AddCommerceItemInfo;



public class BlindsAddCommerceItemInfo extends AddCommerceItemInfo

{


public BlindsAddCommerceItemInfo() {

      super();


}



private Map selectedOptions;

private double blindsPrice;



public double getBlindsPrice() {

       return blindsPrice;

}
 


public void setBlindsPrice(double blindsPrice) {

      this.blindsPrice = blindsPrice;

}
 


public Map getSelectedOptions() {

      return selectedOptions;

}
 


public void setSelectedOptions(Map selectedOptions) {

      this.selectedOptions = selectedOptions;

}


}




Step 5 of 7 – Extend PurchaseProcessHelper class

Extend Purchase Process Helper Class
Override the method setCommerceItemProperties . Here we transfer the properties form AddCommerceItemInfo class to actual CommerceItem.

import atg.commerce.order.purchase.PurchaseProcessHelper;


public class BlindsPurchaseProcessHelper extends PurchaseProcessHelper {



public BlindsPurchaseProcessHelper() {

      super();


}

}




protected void setCommerceItemProperties(CommerceItem pItem,

AddCommerceItemInfo pItemInfo) throws CommerceException {


     super.setCommerceItemProperties(pItem, pItemInfo);

     if (!(pItem instanceof BlindsCommerceItemImpl)) {

     throw new CommerceException(

                           "CommerceItem not an instance of BlindsCommerceItemImpl");

                         }

      if (!(pItemInfo instanceof BlindsAddCommerceItemInfo)) {

     throw new CommerceException(

                           "AddCommerceItemInfo not an instance of BlindsAddCommerceItemInfo");

}
 


if (isLoggingDebug()) {

              logDebug("INSIDE BlindsPurchaseProcessHelper.setCommerceItemProperties");


 


BlindsCommerceItemImpl blindsItem = (BlindsCommerceItemImpl) pItem;


BlindsAddCommerceItemInfo blindsInfo = (BlindsAddCommerceItemInfo) pItemInfo;

if (isLoggingDebug()) {

      logDebug(" BLinds info from purchase process=="+blindsInfo);

}




if (isLoggingDebug()) {

         logDebug("The Map got==="+blindsInfo.getSelectedOptions());

}


blindsItem.setSelectedOptions(blindsInfo.getSelectedOptions());


}

}
Make this a component by adding PurchaseProcess.properties in the path atg/commerce/order/Purchase. Note that the path is very important else ATG will take the default one.

Step 6 of 7-Extend CartFormHandler

In Blinds.com the CartFormHandler Extends the default CartModifierFormHandler for custom functionality.
This class holds all the handle methods for the buttons on the cart
page. Since all buttons need to perform similar functionality,
including updating item quantities, adding gift wrap/gift message
and moving to the checkout process, all the methods have been
captured in this class. In the case of ExpressCheckout, this
class does the preliminary duties of modifying cart contents, and
then calls the ExpressCheckoutFormHandler to run the express
checkout pipeline.


import atg.commerce.order.purchase.CartModifierFormHandler;

public class BlindsCartModifierFormHandler extends CartModifierFormHandler {



private Map featureOptionMap;



public void setFeatureOptionMap(Map featureOptionMap)

{

           this.featureOptionMap = featureOptionMap;

}

public Map getFeatureOptionMap()

{

           return featureOptionMap;

}



protected void addItemToOrder(DynamoHttpServletRequest pRequest,

DynamoHttpServletResponse pResponse)

throws ServletException, IOException

{


if (isLoggingDebug()) {

         logDebug("Start BlindsCartModifierFormHandler addItemToOrder");

}





try {

          boolean valid = mergeItemInputForAdd(pRequest, pResponse,

          getCommerceItemType(), false);

          if (valid) {



          if (isLoggingDebug()) {

                    logDebug("VALID adding item and    commerceItemType=="+getCommerceItemType());

             }



Object someObj = getItems()[0];



if (isLoggingDebug()) {

               logDebug("Cast it to BlindsAddCommerdeItemInfo");

}




BlindsAddCommerceItemInfo itemInfo = (BlindsAddCommerceItemInfo) getItems()[getItems().length-1];



if (isLoggingDebug()) {

       logDebug("After Cast");

}

//Get the Map and set to AddItemInfo



if (isLoggingDebug()) {

        logDebug(" Feature option map from CartFormHandler = "+getFeatureOptionMap());

}


itemInfo.setSelectedOptions(getFeatureOptionMap());


double price = getBlindsPrice(getFeatureOptionMap(), itemInfo.getCatalogRefId());

itemInfo.setBlindsPrice(price);



if (isLoggingDebug()) {

           logDebug("Done adding item item info from cart Form Handler=="+itemInfo);

}



}

}catch (Exception re) {

if (isLoggingDebug()) {

logDebug("Error in addItemToOrder" + re.getMessage());

}



re.printStackTrace();

}


super.addItemToOrder(pRequest, pResponse);




// super.addItemToOrder(pRequest,pResponse);

if(getFormError())

{

           //mark the transaction for roll back

           TransactionManager tm = getTransactionManager();

           if(tm != null)

           {

               try

                    {

                        Transaction t = tm.getTransaction();

                         if(t != null)

                                      t.setRollbackOnly();

                    }

                  catch (SystemException exc) {

                         if (isLoggingError())

l                                     ogError(exc);

                   }



}

}

}





}



In the Jsp page Point all the feature and option to featureOptionMap.
In the properties(CartFormHandler.properties)) file add an entry to specify which is the CommerceItemInfo class we are using

addItemInfoClass=blinds.commerce.order.BlindsAddCommerceItemInfo

Also note that we are setting the 2 items into BLindsAddCommerceItemInfo Class
1)The FeatureOption which is the MAP.
2)The Price calculated




Step 7 of 7- Create pre-Calculator for pricing


ATG Commerce provides several preconfigured pricing calculators. We can extend these calculators to fit your site's requirements, and you can also create new pricing calculators if necessary.

We are extending atg.commerce.pricing.ItemPricingCalculator .

package blinds.commerce.pricing.calculators;



import atg.commerce.pricing.priceLists.ItemPriceCalculator;

import atg.commerce.pricing.ItemPriceInfo;

import atg.commerce.pricing.PricingException;





public class BlindsItemListPriceCalculator extends ItemListPriceCalculator {



public static final String PAYMENT_TYPE = "paymentType";



public static final String CREDIT = "1";







protected void priceItem(double pPrice, ItemPriceInfo pPriceQuote, CommerceItem pItem, RepositoryItem pPricingModel, Locale pLocale, RepositoryItem pProfile, Map pExtraParameters) throws PricingException {




BlindsCommerceItemImpl blindsItem = (BlindsCommerceItemImpl)pItem;

pPrice = blindsItem.getBlindsPrice();

super.priceItem(pPrice, pPriceQuote, pItem, pPricingModel, pLocale, pProfile, pExtraParameters);



} // end priceItem



}

Code blindsItem.getBlindsPrice(); will retrieve the price from the commerceitem and will pass it to pricingEngine through the method priceItem();

Make the calculator a component by adding the ItemListPriceCalculator.properties in the location atg/commerce/pricing/calculators/.

After you have created a new calculator, you must associate it with its corresponding pricing engine. You can do this in either by Adding the calculator to an engine's list of pre- or post-calculators. The configuration invokes the calculator on the price of every item that passes through the engine.

So Create the ItemPricingEngine.properties inside atg/commerce/pricing
Add the line calculator

Wednesday, June 17, 2009

AutoLoad Classes in ATG DAS


I was trying to enable autoreload functionality for dynamo like we have in JBoss (loading war or ear based on 'scanPeriod') or 'touch' web.xml to reload classes however I couldnt find a solution for that. Fortunately its possible to reload nucleus components including the changes in properties file and java using Disposable Class Loader.

In case of Dynamo we need to follow these steps

1) Modify C:\ATG\DAS6.3.0\home\localconfig\environment.bat



Add reference to the classes we need to load to a System parameter defined by ATG i.e. 'atg.nucleus.class.path'

Eg:- set JAVA_ARGS=%JAVA_ARGS% -Datg.nucleus.class.path=file:///C:\ATG\ATG7.1\whitelabel\framework\classes



2) While DAS is running load ACC and browse to the required component & ‘Stop’ it .

3) Go to Tools -> Make new Class Loader



4) Start the component



However this will only allow to change existing methods or properties not for adding new methods .



On 2006.3 and up wecan add $reloadable=true to the .properties file for your component. In that case Nucleus will create a proxy for your component using cglib.If wego to the admin page for your "reloadable" component it'll now show a link for "Reload". When you click reload, the "real" object that your proxy was pointing at will be thrown away and a new one will be loaded.That'll give the effect of loading a new class.

Monday, June 15, 2009

Customizing LightBox

Overview

Lightbox JS is a simple, unobtrusive script used to overlay images on the current page. It's a snap to setup and works on all modern browsers.

The biggest advantage is this is working on the top of prototype that which is used in most of the applications. So lightbox is just a piece of script using this framework.
To create the 'shadow' effect over the page, we need to use a PNG file and some extra CSS with the original version of lightbox from Lokesh Dharkar (http://www.huddletogether.com/projects/lightbox/)

Dhakar’s method, however, while fantastic, was a bit too specific for major purposes and so Chris Campbell created an implementation which is a bit more flexible for extending a web site’s interface. This document covers how to create a cutom modal window by extending LighBox.

For more details please refer to http://particletree.com/features/lightbox-gone-wild/



Implementation Steps

The current js folder already includes all the necessary js so it’s just a matter to implement it.

1) Make sure prototype scripts are included in the page

All the prototype js are included

<script src="/js/published/protoaculous.js" type="text/javascript"></script>



2) Include lightbox script.


<script src="/js/lightbox.js" type="text/javascript"></script>


These scripts are the base scripts to display lightbox.


3) Extend the script to create Our functionality
By default lightbox only comes with the script which displays lightbox when we clicks on a particular link. Most of the cases we need a functionality like it should get displayed when we calls a javascript function. The function should also take a filename which it should displays in the box. Following is the sample code done which extended lightbox.




<script type="text/javascript">
/* - - - - - - - - - - - - - - - - - - - - -
Title : Customized Lightbox Extended Script
- - - - - - - - - - - - - - - - - - - - - */
// subclassing the standard LightBox for adding additonal user defined methods
var CustomLightBox = Class.create(lightbox, {
// add the empty constructor
initialize: function() {
},

clickOk: function()
{
alert(“I’m in OK”);
},

clickCancel: function(){
document.location.href = "/jsp/shoppingCart/shoppingCart.jsp"
this.deactivate();
}
});
// trigger the display of LightBox pass the jsp/html file to display within
function displayModalBox(fileName){
addLightboxMarkup();
var myLightbox = new CustomLightBox ();
myLightbox.content = fileName;
myLightbox.activate();
}
</script>





“displayModalBox(fileName)” is the function which we need to call to display lightbox. filename is the jsp or html page which gets displayed within the lightbox. Copy this script and modify for the purpose we need.


4) Map the custom functions to the links

clickOk & clickCancel are the extended methods or custom fucntions to invoke when user clicks a link inside the displaybox. However we need to map the links to these methods.

for eg:-


<a href="#" class="lbAction" rel="clickOk"><input type="button" value="<c:out value='${continue}'/>" class="submitbtn"></a>
<a href="#" class="lbAction" rel="clickCancel"><input type="button" value="<c:out value='${cancel}'/>" class="submitbtn"></a>




The only thing we need to take care is the class it should be lbAction for the link.