Technical Blog

6 Posts tagged with the dynamic_content tag

In 6.1.2, we added a slick condition builder widget to the Dynamic Content Delivery wizard. If you're thinking of using the Tagging Framework for your own purposes, you too can take advantage of this widget. In this article, we'll look at how to create a custom tag dictionary and a custom condition builder dialog to allow users to build conditions using the tags in a custom tag dictionary.

 

Before we get into coding, let's take a quick look at the new Dynamic Content Delivery dialog to see how the widget actually works.

 

How it works

Designing an intuitive UI for building conditions was a challenge for the design team. Most business users don't have a lot of experience with software applications that let them design complex rules for things like dynamic content and promotions. Usually, that falls in the domain of software programmers. So how do you give users almost code-level flexibility without completely scaring them away with its complexity? We banged our collective head against the wall for a long time over the course of multiple design sessions until finally we'd come up with something everyone was pleased with.

 

condbuilder_with_callouts1.png

As you can see, there are a lot of different parts here. The basic building block of a condition is a statement. A statement consists of

  • the tag
  • the tag operator
  • the tag value.

A condition must contain at least one statement. Creating a condition with a single statement is pretty straightforward. For example, assume you want to build a condition for shoppers who are 60 or over. You'd click add statement, expand the tag group, and select the tag, which in this case is "are of age". (Note that the "are of age" tag is located in the "Customer Profile" group. Tag groups were added in 6.1.2 as a way to organize tags in the condition builder.)

1add_age_tag.png

Next, you'd select the tag operator. In this case, we want "greater than or equal to".

2select_operator.png

Finally, you set a tag value of 60, which means you only want to match shoppers whose "are of age" tag contains a value that is greater than or equal to 60.

3enter_tag_value.png

That's a simple example, but we can create much more complicated conditions by combining multiple statements in a statement block.

 

A statement block can contain one or more statements. In the previous example, we had a default statement block containing a single statement. We can add additional statements by clicking the add statement link. The statements within a statement block are combined by either the AND or the OR operator. Only one operator can be used within a statement block. If you need to mix AND and OR, you can add additional statement blocks.

 

Creating the tag dictionary, tag group, and tags

 

First, we create a custom tag dictionary and some custom tags. Here's the SQL (mind the hard-coded primary keys).

/* Create a tag dictionary: */     
     insert into TTAGDICTIONARY(uidpk, guid, name, purpose)
          values(4, 'CUSTOM', 'Custom', 'Used to store custom tags and conditions');

/* Create a tag group: */
     insert into ttaggroup(uidpk, guid)
          values(5, 'CUSTOM_GROUP');


/* Create custom tags: */
     insert into TTAGDEFINITION(uidpk, guid, name, description, tagvaluetype_guid, taggroup_uid)
          values(22, 'CUSTOM_TAG_1', 'Custom 1', 'A custom tag', 'text', 5),
          (23, 'CUSTOM_TAG_2', 'Custom 2', 'A custom tag', 'text', 5),
          (24, 'CUSTOM_TAG_3', 'Custom 3', 'A custom tag', 'text', 5),
          (25, 'CUSTOM_TAG_4', 'Custom 4', 'A custom tag', 'text', 5);


/* Map custom tags to custom dictionary: */
     insert into TTAGDICTIONARYTAGDEFINITION(tagdictionary_guid, tagdefinition_guid)
          values('CUSTOM', 'CUSTOM_TAG_1'),
          ('CUSTOM', 'CUSTOM_TAG_2'),
          ('CUSTOM', 'CUSTOM_TAG_3'),
          ('CUSTOM', 'CUSTOM_TAG_4');


/* Create language resources for the tags and tag group: */
     insert into TLOCALIZEDPROPERTIES (UIDPK,LOCALIZED_PROPERTY_KEY,VALUE,TYPE,OBJECT_UID)
          values (50062,'tagDefinitionDisplayName_en','Custom tag 1','TagDefinition',22),
          (50063,'tagDefinitionDisplayName_en','Custom tag 2','TagDefinition',23),
          (50064,'tagDefinitionDisplayName_en','Custom tag 3','TagDefinition',24),
          (50065,'tagDefinitionDisplayName_en','Custom tag 4','TagDefinition',25),
          (50066,'tagGroupDisplayName_en','Custom group','TagGroup',5);

 

Creating the condition builder UI

Now, you want to create a CM client plugin with a dialog that will allow users to create and edit conditions using only the tags in this dictionary. I won't get into the details of creating a CM client plugin; you can look at the attached source code to see how I did it. The part we're interested in is the dialog that extends AbstractEpDialog. In my createEpDialogContent implementation, I add a text box to contain the name of the condition, a label for the text box, and the code that displays the condition builder composite.

protected void createEpDialogContent(final IEpLayoutComposite dialogComposite) {
          
     /* 
     * Set the color on the top level composite. This recursively sets the color of  
     * the condition builder's constituent controls.
     */     
     final Shell shell = this.getShell();
     final RGB rgb = new RGB(255, 255, 255);
     final Color color = new Color(shell.getDisplay(), rgb);           
     shell.setBackground(color);
     color.dispose();
                  
     // Add the condition name field label
     final IEpLayoutData labelData = dialogComposite.createLayoutData(IEpLayoutData.FILL, IEpLayoutData.FILL, true, true, 2, 1);
     dialogComposite.addLabelBoldRequired(MyConditionBuilderMessages.ConditionName, EpState.EDITABLE, labelData);
          
     // Add the condition name field
     final IEpLayoutData fieldData = dialogComposite.createLayoutData(IEpLayoutData.FILL, IEpLayoutData.FILL, true, false, 2, 1);
     conditionNameText = dialogComposite.addTextField(EpState.EDITABLE, fieldData);
     conditionNameText.setTextLimit(CONDITION_NAME_TEXT_LIMIT);
 
     // Add the condition builder composite
     final Composite composite = displayConditionBuilder(shell);
     composite.setParent(dialogComposite.getSwtComposite());
}

 

The actual implementation of displayConditionBuilder is where the real work happens.

private Composite displayConditionBuilder(final Composite parentComposite) {
     
     ElasticPath elasticPath = Application.getInstance().getElasticPath();
 
     // get the tag operator service
     TagOperatorService tagOpService = (TagOperatorService)
               elasticPath.getBean(ContextIdNames.TAG_OPERATOR_SERVICE);
     
     // get tag definitions from the custom dictionary
     TagDictionaryService dictionaryService = (TagDictionaryService)
               elasticPath.getBean(ContextIdNames.TAG_DICTIONARY_SERVICE);
     TagDictionary tagDictionary = dictionaryService.findByGuid(CUSTOM_DICTIONARY);
     Set<TagDefinition> tagDefinitions = tagDictionary.getTagDefinitions();
     
     // get the tag groups for those tag definitions
     TagGroupService tagGroupService = (TagGroupService)
               elasticPath.getBean(ContextIdNames.TAG_GROUP_SERVICE);                    
     Set<TagGroup> tagGroups = new HashSet<TagGroup>();          
     for (TagDefinition tagDefinition : tagDefinitions) {
          if (tagDefinition.getGroup() == null) { continue; }
          tagGroups.add(tagGroupService.findByGuid(tagDefinition.getGroup().getGuid()));
     }
     
     ConditionBuilderFactoryImpl factory = new ConditionBuilderFactoryImpl();
     factory.setDataBindingContext(this.dataBindingCtx);
     factory.setLocale(CorePlugin.getDefault().getDefaultLocale());
     factory.setTagOperatorService(tagOpService);
     factory.setConditionBuilderTitle(""); //$NON-NLS-1$
     factory.setTagGroupsList(new ArrayList<TagGroup>(tagGroups));
     
     factory.getResourceAdapterFactory().setResourceAdapterForLogicalOperator(
               new ResourceAdapter<LogicalOperatorType>() {
                    public String getLocalizedResource(final LogicalOperatorType object) {
                         return AdminGCMessages.getMessage(object.getMessageKey());
                    } });
 
     factory.getResourceAdapterFactory().setResourceAdapterForUiElements(
               new ResourceAdapter<String>() {
                    public String getLocalizedResource(final String object) {
                         return AdminGCMessages.getMessage(object);
                    } });
 
     factory.setListenerForRefreshParentComposite(
               new ActionEventListener<Object>() {
                    public void onEvent(final Object object) {
                         parentComposite.pack();
                         parentComposite.layout();
                    } });
     
     Composite composite = factory.createFullUiFromModel(parentComposite, SWT.FLAT, this.logicalOperator);
     return composite;
}

That looks like a lot of code, but we'll break it down by sections so we can make more sense of it.

 

In the first section, we get references to some of the tag-related services. The TagOperatorService is used internally by the condition builder factory.

     TagOperatorService tagOpService = (TagOperatorService)
               elasticPath.getBean(ContextIdNames.TAG_OPERATOR_SERVICE);

Next, we use the TagDictionaryService to get the tags from the custom dictionary.

     TagDictionaryService dictionaryService = (TagDictionaryService)
               elasticPath.getBean(ContextIdNames.TAG_DICTIONARY_SERVICE);
     TagDictionary tagDictionary = dictionaryService.findByGuid(CUSTOM_DICTIONARY);
     Set<TagDefinition> tagDefinitions = tagDictionary.getTagDefinitions();

 

Then, we use the TagGroupService to get the groups associated with those tags. In order to include a tag in the condition builder, it must be associated with a group.

     TagGroupService tagGroupService = (TagGroupService)
               elasticPath.getBean(ContextIdNames.TAG_GROUP_SERVICE);                    
     Set<TagGroup> tagGroups = new HashSet<TagGroup>();          
     for (TagDefinition tagDefinition : tagDefinitions) {
          if (tagDefinition.getGroup() == null) { continue; }
          tagGroups.add(tagGroupService.findByGuid(tagDefinition.getGroup().getGuid()));
     }

The next step is to instantiate the condition builder factory and configure it with the TagOperatorService and the list of tag groups.

// configure the condition builder factory
ConditionBuilderFactoryImpl factory = new ConditionBuilderFactoryImpl();
factory.setDataBindingContext(this.dataBindingCtx);
factory.setLocale(CorePlugin.getDefault().getDefaultLocale());
factory.setTagOperatorService(tagOpService);
factory.setConditionBuilderTitle(""); //$NON-NLS-1$
factory.setTagGroupsList(new ArrayList<TagGroup>(tagGroups));

We need to specify how the condition builder gets the language strings for its UI elements and labels. We do this by configuring generic resource adapters on the condition builder factory.

factory.getResourceAdapterFactory().setResourceAdapterForLogicalOperator(
          new ResourceAdapter<LogicalOperatorType>() {
               public String getLocalizedResource(final LogicalOperatorType object) {
                    return AdminGCMessages.getMessage(object.getMessageKey());
               } });
 
// configure the resource adapter that provides the UI labels for the other UI components
factory.getResourceAdapterFactory().setResourceAdapterForUiElements(
          new ResourceAdapter<String>() {
               public String getLocalizedResource(final String object) {
                    return AdminGCMessages.getMessage(object);
               } });

The resource adapters delegate to the AdminGCMessages utility class, which defines constants for the language resources. The actual language strings are stored in com.elasticpath.cmclient.admin.gc.AdminGCPluginResources.properties.

public final class AdminGCMessages {
 
     /* Property file binding. */
     private static final String BUNDLE_NAME = "com.elasticpath.cmclient.admin.gc.AdminGCPluginResources"; //$NON-NLS-1$
 
...
 
     /* Generic condition builder UI Labels */
     public static String LogicalOperator_AND;
     public static String LogicalOperator_OR;
     
     public static String ConditionBuilder_Title;
     public static String ConditionBuilder_AddConditionButton;
     
     public static String ConditionBuilder_Add_Rule_label;
     public static String ConditionBuilder_Remove_Rule_label;
 
     
     static {
          NLS.initializeMessages(BUNDLE_NAME, AdminGCMessages.class);
     }
     
...
     public static String getMessage(final String messageKey) {
          try {
               final Field field = AdminGCMessages.class.getField(messageKey);
               return (String) field.get(null);
               
          } catch (final Exception e) {
               return messageKey;
          }
     }     
}

We also need to configure a listener to ensure that the parent composite gets resized whenever statements and statement blocks are added or removed from the widget.

factory.setListenerForRefreshParentComposite(
          new ActionEventListener<Object>() {
               public void onEvent(final Object object) {
                    parentComposite.pack();
                    parentComposite.layout();
               } });

The last step is to call the factory's createFullUiFromModel method and return the Composite that contains the condition builder.

Composite composite = factory.createFullUiFromModel(parentComposite, SWT.FLAT, this.logicalOperator);
return composite;

The first argument to createFullUiFromModel  is the Composite object that will contain the condition builder. The third argument is a LogicalOperator object that represents the condition model.

 

Note that conditions are persisted as ConditionalExpression objects. If you look at the attached source code for the dialog, you'll see that the dialog constructor takes a ConditionalExpression object and creates a LogicalOperator from it.

public GCDialog(final Shell parentShell, final ConditionalExpression expression, final String title, final Image image) {
     super(parentShell, 2, false);
     this.title = title;
     this.titleImage = image;
     this.expression = expression;
     this.dataBindingCtx = new DataBindingContext();
     
     // convert the expression to a DSL string and populate the logical operator
     ConditionDSLBuilder dslBuilder = (ConditionDSLBuilder) 
               Application.getInstance().getElasticPath().getBean(ContextIdNames.TAG_CONDITION_DSL_BUILDER);
               
     String conditionString = this.expression.getConditionString();
     
     if (conditionString == null) {
          this.logicalOperator = new LogicalOperator(LogicalOperatorType.AND);
          this.logicalOperator.addLogicalOperator(new LogicalOperator(LogicalOperatorType.OR));                    
     } else {
          this.logicalOperator = dslBuilder.getLogicalOperationTree(conditionString);
     }          
                              
     setAuthorized();                    
}

A ConditionalExpression object is passed in by the action that creates the dialog. In the constructor, we use the expression's getConditionString method to return the condition as a string value. (You'll see some neat Groovy code if you take a look at it.) Then, we use the ConditionDSLBuilder service's getLogicalOperationTree method to convert that string to a LogicalOperator object.

 

To save changes made by the user, we convert the LogicalOperator back to a condition string and update the ConditionalExpression object. You can see this in the okPressed method of the dialog:

protected void okPressed() {
     dataBindingCtx.updateModels();          
     
     ElasticPath elasticPath = Application.getInstance().getElasticPath();
     
     // convert the logical operator to a condition string so it can be persisted
     ConditionDSLBuilder dslBuilder = elasticPath.getBean(ContextIdNames.TAG_CONDITION_DSL_BUILDER);               
     String conditionString;
     try {
          conditionString = dslBuilder.getConditionalDSLString(this.logicalOperator);
          this.expression.setConditionString(conditionString);
          
          // set the condition name
          this.expression.setName(this.conditionNameText.getText());
          
          // set the dictionary name
          this.expression.setTagDictionaryGuid(CUSTOM_DICTIONARY);
 
          // make sure it's saved as a named condition
          this.expression.setNamed(true);
          
          super.okPressed();
          
     } catch (InvalidConditionTreeException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
     }
}

 

The actual persistence code is in the Action code that opened the dialog (the EditGCAction and CreateGCAction classes):

public void run() {
     ElasticPath elasticPath = Application.getInstance().getElasticPath();  
     ConditionalExpression expression = (ConditionalExpression) elasticPath.getBean(ContextIdNames.CONDITIONAL_EXPRESSION);                    
     boolean dialogOk = GCDialog.openCreateDialog(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), 
               expression);
          
     if (dialogOk) {               
          TagConditionService conditionService = (TagConditionService) elasticPath.getBean(ContextIdNames.TAG_CONDITION_SERVICE);          
          conditionService.saveOrUpdate(expression);          
          listView.refreshViewerInput();               
     }
}

There's definitely a lot going on in several different places and I've tried to touch as many of the key areas as possible. Hopefully, the attached code will answer some of the outstanding questions, but I'm sure there are others. Please post any questions you have here.

0 Comments Permalink

6.1.2 brings bug fixes and some enhancements to the Elastic Path Commerce platform, mainly in the areas of the Tagging Framework and CM client usability (list sorting and searching). At a glance:

  • Geo IP tags: 6.1.1 introduced Dynamic Contentand the Tagging Framework, which gave marketers the ability to personalize the shopping experience based on who the customer is (age, referring site, search engine terms). With the addition of Geo IP tags in 6.1.2, it's now possible to target content based on the shopper's geographic location (city, state, country, region, etc.).
  • Visited categories tag: a new tag in the Tagging Framework that keeps track of the categories that the the shopper has visited.
  • Cart subtotal tag: tracks the shopping cart subtotal.
  • In-site search terms tag: contains the search terms entered in the storefront's search box.
  • Generic condition builder: 6.1.1 included a simple UI for building Dynamic Content Delivery conditions. 6.1.2 expands on that with a more flexible, intuitive UI. (If you've worked with Google Analytics' advanced segments feature, it should look familiar.) This new UI was developed as an RCP UI widget, so it can easily be leveraged for other scenarios that require a condition builder to configure tag evaluation.
  • Tag value types and validation constraints: you can now configure critieria for validating the tag values entered in the Dynamic Content Delivery dialog.
  • Selectable values for tags: you can use drop-down lists to set tag values in the Dynamic Content Delivery dialog.
  • Saved conditions: it is now possible to create Dynamic Content Delivery conditions and save them for use in other Dynamic Content Deliveries.
  • Tag groups: tags can now be assigned to tag groups, to provide better visual organization in the Dynamic Content Delivery dialog.
  • Tag localization: tags, tag groups, and validation constraints now support localization through the TLOCALIZEDPROPERTIES table.

 

There'll be some blog posts to go in depth on some of these topics, so keep watching this space.

1 Comments Permalink

In a recent post on the Get Elastic blog, Linda talked about personalizing content based on the customer's sorting behavior. For example, price-conscious shoppers might sort categories and search results by price from lowest to highest, so you want to show them promotional banners that highlight inexpensive or discounted items. Or maybe you have shoppers who place a higher value on popularity. These shoppers tend to sort by top sellers, and you want to target specific content to them.

 

But are there any ecommerce tools that can do this type of personalization out of the box? None that I know of. However, Elastic Path Commerce 6.1.1 has two new features that give us almost everything we need. In this article, we'll look at how to add sort-by personalization. And it's really quite easy, I promise.

 

Before we get started, be sure to read my previous posts on Dynamic Content and the Tagging Framework (Dynamic Content 101, Tagging Framework 101, and Targeting Dynamic Content for Mobile Device Users). For more in-depth technical information, you can check out the 6.1.1 Developer Guide or see the Commerce Manager User Guide for the marketing user perspective.

The Solution

When a visitor to the storefront selects one of the sorting options from the drop-down list, a request is sent to Elastic Path with a sorter parameter in the URL. For example, if the shopper is browsing a category and sorts by price from lowest to highest, the URL will look like the following:

 

http://www.elasticpath.com/storefront/browse.ep?cID=100009&filters=c90000003&sorter=price-asc

 

The sorter parameter indicates which sorting option was selected by the shopper. Elastic Path 6.1.1 includes a TARGET_URL tag, which contains the query string, but this value is only captured at the time the shopper's session is created. So, we'll need to do a few things:

  • Create a QUERY_STRING tag and map it to the "who" tag dictionary.

  • Create a tag event listener interface and a tagger implementation to put the query string in a tag and add it to the shopper's tag set.

  • Create a filter to intercept the HTTP request, invoke the tag event listener, and pass it the request and customer session information.

  • Update the storefront request filter chain to include the new filter.

Once this is done, marketing users will be able to configure Dynamic Content Delivery to display specific content based on whether the QUERY_STRING tag includes the sorter parameter and what value it contains.

Create the QUERY_STRING Tag

To create the QUERY_STRING tag, you'll need to add it to the ttagdefinition table in your database:

 

insert into ttagdefinition(uidpk, guid, name, description, data_type)
    values(10, 'QUERY_STRING', 'QUERY_STRING',
    'The parameters in the request URL.', 'java.lang.String');

 

Next, you need to add the new tag to the "who" tag library:

 

insert into ttagdictionarytagdefinition(tagdictionary_guid, tagdefinition_guid)
    values('WHO', 'QUERY_STRING');

 

Now, marketing can use the QUERY_STRING tag when they're building rules in the Commerce Manager for displaying Dynamic Content. However, we still need to tell Elastic Path how to get the query string and put it in the shopper's tag set. That's coming next...

Create the Tag Event Listener and Tagger

The Tagging Framework uses a lightweight event listener model. It doesn't require you to implement any particular interfaces, but it's a good idea from a design perspective. There are two tag event listener interfaces defined in the Elastic Path Commerce core library, under com.elasticpath.commons.listeners:

  • NewHttpSessionEventListener, which receives notifications when the shopper arrives at the storefront.

  • CustomerLoginEventListener, which receives notifications when the shopper signs in to their store account.

Both interfaces expose an execute method that takes a CustomerSession and an HttpServletRequest as parameters.

 

For this customization, we'll follow the model of these existing listeners. Because we need to listen for all HTTP requests, not just when it's a new session or when the customer logs in, you'll create an interface named HttpRequestEventListener. Like the other tag event listener interfaces, it only needs an execute method that takes the customer session and HTTP request objects as parameters:

 

public interface HttpRequestEventListener {
    public void execute(final CustomerSession session, final HttpServletRequest request);
}

 

In the storefront web application, the com.elasticpath.sfweb.listeners package contains several Tagger classes that implement one or both of the listener interfaces. These classes are responsible for populating the tags with the data they need. We need to create our own tagger class that implements the new interface, takes the query string from the request, and puts it in the QUERY_STRING tag in the customer's tag set:

 

public class QueryStringTagger implements HttpRequestEventListener {
    private static final String QUERY_STRING = "QUERY_STRING";
    private static final Logger LOG = Logger.getLogger(QueryStringTagger.class);    

    public void execute(final CustomerSession session,
final HttpServletRequest request) {
        String queryString = request.getQueryString();
        if (LOG.isDebugEnabled()) {
            LOG.debug("Populating customer session with the query string: " + queryString);

        }
        TagSet tagSet = session.getCustomerTagSet();
        tagSet.addTag(QUERY_STRING, new Tag(queryString));
    }
}

 

Keep in mind that the QueryStringTagger is going to get called on every request, so you want to avoid doing too much processing in here. The key things to note:

You get the tag set from the CustomerSession object by calling getCustomerTagSet.

  • You create a tag and set its value by calling the Tag constructor and passing it the value you want to assign.

  • You use the addTag method on the TagSet object to add the tag to the shopper's tag set.

We now have a tagger class that can receive notification of HTTP requests and put the query string in the shopper's tag set. The next step is to create the filter that will notify the tagger class and pass it the customer session and HTTP request information.

Create the Filter

We'll create the QueryStringFilter in the storefront web app in the com.elasticpath.sfweb.filters package. The filter needs to include a HttpRequestEventListener collection. We'll define the filter and the tagger bean definitions in the storefront web app's filter-config.xml:

 

<bean id="queryStringFilter" class="com.elasticpath.sfweb.filters.QueryStringFilter">

<property name="requestHelper">

<ref bean="requestHelper" />

</property>

<property name="httpRequestEventListeners">

<list>

<ref bean="queryStringTagger"/>

</list>

</property>

</bean>

<bean id="queryStringTagger" class="com.elasticpath.sfweb.listeners.QueryStringTagger"/>

 

Note the QueryStringTagger object in the httpRequestEventListeners collection. In the QueryStringFilter's doFilter method, we'll call the execute method of each event listener in the collection:

 

public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain filterChain)

throws IOException, ServletException {

 

   if (!(request instanceof HttpServletRequest)) {
       filterChain.doFilter(request, response);
       return;
    }

 

    HttpServletRequest httpServletRequest = (HttpServletRequest) request;

    CustomerSession customerSession = getCustomerSession(httpServletRequest);

 

    // Notify the request event listeners

    Collection<HttpRequestEventListener> listeners = getHttpRequestEventListeners();

    for (HttpRequestEventListener listener : listeners) {

        listener.execute(customerSession, httpServletRequest);

    }

    filterChain.doFilter(request, response);

}

 

CustomerSession getCustomerSession(final HttpServletRequest request) {

     return requestHelper.getShoppingCart(request).getCustomerSession();

}

Update the Filter Chain

Finally, we need to add the new filter to the filter chain in acegi.xml.vm and acegi.xml:

 

            <value>

                CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON

                \A/google-callback.ep.*\Z=channelProcessingFilter, basicProcessingFilter, basicExceptionTranslationFilter, basicFilterInvocationInterceptor

                \A/.*\Z=channelProcessingFilter, httpSessionContextIntegrationFilter, logoutFilter, logoutCustomerSessionFilter, onePageLogoutFilter, authenticationProcessingFilter, queryStringFilter, exceptionTranslationFilter, filterInvocationInterceptor

            </value>

 

Note the location of the queryStringFilter. Make sure you add it to the chain at some point after the customer session has been created, which generally occurs in the authenticationProcessingFilter.

 

Now that we've created the QUERY_STRING tag and the query string is getting stored in each visitor's tag set, it's time to let the marketing team test it out.

 

Using the example mentioned earlier, if you have a piece of Dynamic Content that you want to display  when shoppers sort a category or search results on price from lowest to highest, you can add a condition in the Dynamic Content Delivery like this:

dcd_query_string_tag_circled.png

Now, assuming you've got the proper Content Spaces in your category and search Velocity templates, the Dynamic Content will appear whenever your visitors sort by price from lowest to highest. And you can easily add others for different sorting habits, such as top sellers.

 

As you can see, a lot can be accomplished with just a small amount of initial coding. Be sure to download the attached source code and try it out for yourself.

0 Comments Permalink

What if your online store could identify shoppers with mobile devices and deliver content based on the device they're using? With the arrival of Dynamic Content and the Tagging Framework in Elastic Path Commerce 6.1.1, you can do just that.

 

This article takes a practical approach to extending the Tagging Framework. We'll look at how a developer can add a tag to identify mobile device users and how to create a tagging event listener to capture the information we need to store in that tag. And it's really quite easy, I promise. (Before diving in, you'll need a basic understanding of the Tagging Framework and Dynamic Content, so you'll want to take a look at my earlier posts and check out the 6.1.1 documentation.)

 

Solution Overview

When a visitor views a page on your Elastic Path storefront, the HTTP request includes a User-Agent header. The User-Agent header contains information about the application that's requesting
the page. This could be an automated web crawler, such as Googlebot or, if there's a person using that application to view web pages, a web browser. In the case of requests coming from mobile devices, like the iPhone, the user agent header also includes the name of the device.


We need to store this information in a tag and attach it to the shopper so that it can be accessed later on when we need to determine what Dynamic Content to display. Elastic Path Commerce doesn't have a tag that contains this information by default, so we need to do three things:

  • define a USER_AGENT tag in the "who" tag dictionary
  • create a tagging event listener (a tagger) class to get the User-Agent header from the request and store it in a USER_AGENT tag in the shopper's tag set
  • add the listener to the collection of event listeners that are invoked each time an HTTP session is created in Elastic Path Commerce.

 

Once this is done, marketing users will be able to target Dynamic Content based on the type of mobile device the shopper is using.

Defining the USER_AGENT Tag

First, we need to define the USER_AGENT tag. To do this, we need to insert a row into the TTAGDEFINITION table of the Elastic Path Commerce database:

insert into ttagdefinition(uidpk, guid, name, description, data_type)
values(10, 'USER_AGENT', 'USER_AGENT', 'The User-Agent request header.', 'java.lang.String');

Next, we need to add the tag to one of the three tag libraries ("who", "when", and "where"). The user agent is a piece of information that helps us identify the shopper, so we need to add it to the "who"
library. To do this, we need to insert a row into the TTAGDICTIONARYTAGDEFINITION table:

insert into ttagdictionarytagdefinition(tagdictionary_guid, tagdefinition_guid)
values('WHO', 'USER_AGENT');

Now, marketing users can log in to the Commerce Manager client, create Dynamic Content, and configure the Dynamic Content Delivery to evaluate the USER_AGENT tag, but at this point that wouldn't be very useful, because we haven't yet set up the tagging event listener to get that information and store it in the shopper's tag set.

Creating the Tagger

There are two tagging event listener interfaces defined in the Elastic Path Commerce core library, under com.elasticpath.commons.listeners:

  • NewHttpSessionEventListener, which is fired when the visitor arrives at the storefront and an HTTP session is created.
  • CustomerLoginEventListener, which is fired when the visitor signs in to their store account.

 

Both interfaces expose an execute method that takes a CustomerSession and an HttpServletRequest as parameters.

 

In the storefront web application, the com.elasticpath.sfweb.listeners package contains several Tagger classes that implement one or both of the listener interfaces. These classes are responsible for populating the tags with the data they need. We can get the User-Agent header at the same time as the session is created, so we'll create a UserAgentTagger class that extends NewHttpSessionEventListener. The interface defines only one method: execute. We'll implement the execute method to capture the user agent header string and put it in a tag:

package com.elasticpath.sfweb.listeners; 
 
 
import org.apache.log4j.Logger;
import  javax.servlet.http.HttpServletRequest;
import com.elasticpath.commons.listeners.NewHttpSessionEventListener;
import com.elasticpath.domain.customer.CustomerSession;
import com.elasticpath.tags.Tag;
import com.elasticpath.tags.TagSet;
 
public class UserAgentTagger implements NewHttpSessionEventListener {
     private static final String USER_AGENT = "USER_AGENT";
     private static final Logger LOG = Logger.getLogger(UserAgentTagger.class);
 
     public void execute(CustomerSession session, HttpServletRequest request) {
 
          // get the user agent header string
          String userAgent = request.getHeader("User-Agent");
          
          if (LOG.isDebugEnabled()) {
               LOG.debug("User-Agent header contents: " + userAgent);
          }
 
          // get the shopper's tag set
          TagSet tagSet = session.getCustomerTagSet();
 
          // set the user agent in the shopper's tag set
 
          Tag userAgentTag = new Tag();
          userAgentTag.setValue(userAgent);
          tagSet.addTag(USER_AGENT, userAgentTag);
     }
}

Adding the Tagger to the Event Listeners Collection

When an HTTP session is created, the handleFilterRequest method of webCustomerSessionService calls each listener's execute method. So, we need to add the user agent tagger to the newHttpSessionEventListeners collection on the webCustomerSessionService bean. Open the storefront web app's serviceSF.xml file (located in WEB-INF/conf/spring/service) and add a bean definition for the user agent tagger:

 

<bean id="userAgentTagger" class="com.elasticpath.sfweb.listeners.UserAgentTagger"/>

 

Next, find the webCustomerSessionService bean definition. Inside bean definition, find the newHttpSessionEventListeners property and add a reference to the userAgentTagger bean:

 

 

We can now test this by enabling debug logging on the storefront and then accessing the storefront with various browsers. Each time a new customer session is created, a debug message is logged with the complete user agent header string. For example, when I use my iPod Touch to access my storefront, the following message is logged:

2009-04-22 17:48:26,246 [http-8080-Processor23] DEBUG com.elasticpath.sfweb.listeners.UserAgentTagger - User-Agent header contents: Mozilla/5.0 (iPod; U; CPU iPhone OS 2_2_1 like Mac OS X;
en-us) AppleWebKit/525.18.1 (KHTML, like Gecko) Version/3.1.1 Mobile/5H11 Safari/525.20

There's a lot more information in there than we really need, but we see that it contains the word iPhone. So, if we want to target content at shoppers who are using an iPhone or an iPod, we can now do that.

Now, when we're creating the Dynamic Content Delivery, we can select the USER_AGENT tag and use its value as one of the criteria for displaying content. For example, if you're configuring Dynamic Content Delivery to show a different banner to iPhone users, add the USER_AGENT tag with the operator set to includes and the value set to iPhone.

dcd-user-agent.png

This is just one way you can extend the Tagging Framework. You could also create a tag to get information from a cookie or a specific request parameter. And you're not limited to information in the HTTP request; for example, you could tag customers with products and categories they've viewed. Going forward, we'll be building more tags into the framework, so if you've got more ideas, we'd love to hear about them.

 

I know I've probably glossed over a few details, so if you have any questions or comments, please don't hesitate to post them here.

0 Comments Permalink

Dynamic Content 101

Posted by Paul Monk May 6, 2009

Dynamic Content is an exciting new feature in Elastic Path 6.1.1. The idea behind it is to give the Marketing team more control over site content without having to make changes to code (Java or HTML). There is still some initial work for the web developer to enable Dynamic Content in the storefront, and if your organization has special requirements, it's definitely possible for developers to extend the Dynamic Content framework (more on this in a future post), but control over Dynamic Content is mainly in the hands of Marketing. In a nutshell, here's how it works:

 

  • The web developer sets up some locations (called Content Spaces) within the web page for displaying Dynamic Content.
  • The EP administrator registers the Content Space in the Commerce Manager.
  • The Marketing person selects some content (a banner image, a Flash object) to display in the storefront.
  • The Marketing person chooses the Content Space where the content will appear and sets the conditions for displaying it (the who, when, and where).

 

And Elastic Path Commerce takes care of the rest. Let's take a closer look at how this works in practice.

 

Setting up a Content Space

The web developer creates the HTML page layout of the storefront in the Velocity templates. Within the HTML, the web developer can include the contentspace macro. This looks something like the following:

#contentspace('contentSpaceID', '<span>Alternative Content</span>')

This defines a Content Space, which can be used to display Dynamic Content. The first parameter is the Content Space ID, which will need to be registered before it can be used. The second parameter is the static content to display if there's no Dynamic Content mapped to the space.

 

If you have 6.1.1 installed and you've set up the demo store data, you can see some examples of Content Spaces in the storefront home page template (themes\electronics\default\templates\velocity\index.vm under your assets directory). For more information on setting up Content Spaces, see the Developer Guide.

 

Registering a Content Space

The EP administrator registers the Content Space in the Commerce Manager so that it can be used as a target for Dynamic Content. To do this, log in to Commerce Manager and choose Activity->Configuration. Then click Content Spaces under Targeted Selling. Click the Create Content Space button. Set the name to the Content Space ID used in the Velocity template. You should also enter a meaningful description, something that will help users determine where the Content Space is, its preferred dimensions, etc. For more details, see the Developer Guide or the Commerce Manager User Guide.

 

Creating Dynamic Content

Next, Marketing selects the type of content they want to display in the storefront. Out of the box, Elastic Path Commerce supports several types. For example, there are types for displaying images and Flash objects. There's also a type for displaying HTML. And developers can easily create more types. These types, also called Content Wrappers, aren't simply static content. They can include Groovy code to do just about anything you can imagine. And you don't need to restart the storefront to make changes to them. (You can find out more about custom Content Wrappers in the Content Wrappers section in the Developer Guide.)

dc_adding_dc4.png

After selecting a type, there's usually some configuration required. For example, for a clickable image type, you'd need to select an image file and set the URL where shoppers will be redirected when they click the image.

 

There's a tutorial that will walk you through the process of creating Dynamic Content. (For the tutorial, you'll need to either install the Trial Installer or to set up the demo store data in your dev environment.)

 

Setting up the Dynamic Content Delivery

The last step is to set up the conditions for displaying the content. We want to make sure that the content will be displayed in the right place (which Content Space), to the right shoppers, at the right time, and in the right store (for example, display this content to shoppers who followed a link from Google after searching for the keyword "clogs").

dc_adding_dcd6.png

The details of configuring Dynamic Content Delivery are covered in the tutorial on creating Dynamic Content and in the Commerce Manager User Guide.

 

Behind the scenes, Dynamic Content Delivery relies heavily on the Tagging Framework to get information about the shopper and decide which pieces of content to display. In future posts, we'll dig a bit deeper into the customizable aspects of both Dynamic Content and the Tagging Framework. Until then, be sure to check out the 6.1.1 documentation on the Elastic Path doc site for more in-depth coverage of Dynamic Content.

0 Comments Permalink

Elastic Path Commerce 6.1.1 is now available for download. This release includes bug fixes, performance enhancements, and some new features. Here's what's new and noteworthy:

  • Import/Export tool improvements (performance improvements, support for import/export of promotions and configuration settings)
  • Support for load balancing of search server requests
  • Dynamic Content
  • Tagging and tag evaluation (a.k.a., the Tagging Framework).

You can find out more by checking out the 6.1.1 Relase Notes and the 6.1.1 documentation on the Elastic Path documentation site.

 

Over the next few days, we'll be posting articles on the Grep technical blog to highlight some of the cool things you can do, particularly with Dynamic Content and the Tagging Framework. Dynamic Content is about personalizing the shopping experience; Marketers can configure what content to display to which shoppers, where it will appear in the storefront, and the time period during which it will be shown. The Tagging Framework is what enables Dynamic Content to be targeted at specific shoppers. When a shopper first arrives at the storefront, Elastic Path Commerce starts collecting information and storing that information in tags, including:

  • the URL of the site where the shopper came from
  • the storefront URL where the shopper arrived
  • the search terms that were entered in the search box (if the shopper came from a search engine page like Google or Yahoo)

And when the customer logs in, the Tagging Framework can pull in other information from the customer's account, such as gender and age. Marketers can create rules in the Commerce Manager to target specific Dynamic Content at specific shoppers based on the information in each shopper's tag set.

 

This is pretty exciting stuff, so be sure to stay tuned for blog posts!

0 Comments Permalink