Technical Blog

5 Posts tagged with the dynamic_content_delivery tag

Rumba is the code name for Elastic Path Commerce 6.1.2, which was officially released to customers early last week. The development work on this release began in late April and finished in August. Dubbed a feature release, it has a bit of everything included in it.

 

The key focus of Rumba was rounding out the tagging framework and dynamic content functionality. The tagging framework was enhanced to include tag value types, which allow for easy definition of UI helpers for various tags, validation of tag values and localization among other things. The reusable generic condition builder underwent a major facelift, when we introduced an easy to follow UI, nested conditions and a combination of AND and OR operators, which will allow users to take their shopper segmentation to the next level. Shopper segmentation was further enhanced by the introduction of several new tagging events and corresponding tags such as cart subtotal and in store search terms. We also provided a sample GEO IP 3rdparty integration with Quova. Through the integration with Quova, we developed several highly useful GEO IP tags such as country code tag, state/province tag, top level domain tag etc. All of these tags will make GEO targeting a very appealing prospect for our clients.

 

During the second half of Rumba development, we focused on usability enhancements and client requested fixes to existing functionality. We added column sorting to frequently used areas of the CM Client such as Order and Product Searches. This usability work will allow Customer Service Reps, Catalog Managers and other CM Client users to be much more efficient in their day to day tasks. We also improved the save message prompts in CM Client to include additional information about the objects being saved, which will allow CM Client users to make faster and more informed decisions.

Another area of the system that experienced a facelift is the permissions structure. Previously, everyone with access to CM Client had read permission on everything and read, update and write permissions on explicitly assigned areas of the CM Client. In 6.1.2, we've implemented a restricted view access policy, so that users who are not assigned specific activities, catalogs, stores and warehouses will not have access to them at all. Finally, the tax calculations for inclusive tax jurisdictions were improved, by addressing bugs for edge cases. As a result, taxes are now covered by extensive automated tests, which will serve as a model of how we can better automate the testing of key aspects of our platform in the future.

 

There were also some technical improvements. We upgraded to Solr 1.3, and we are back on a mainline release. Previously we had customized Solr and Lucene to get it to do what we wanted. The new version now provides exactly what we need, so it's going to be easier to upgrade Solr and get bug fixes in the future. Solr 1.3 brings a lot of potential: more flexible config, better performance, runtime index creation/copying and more. Also, our build scripts are now leveraging antlion to provide build avoidance. Now, if the core engine is up to date and you rebuild storefront, you will only rebuild storefront, reducing overall build time considerably.

0 Comments Permalink

Give them an inch, they take a mile. Any time you give users the ability to enter free form text, there's a potential for something to go wrong. Thankfully, we've already got a variety of pieces in Elastic Path Commerce to validate user input. As of 6.1.2., the Not authorized to view the specified document 1262 includes a validation feature, so when users are creating Dynamic Content Delivery rules, the condition builder dialog can provide instant feedback and prevent them from submitting invalid values. To see this in action, fire up the CM client, go to the Store Marketing activity, and edit a Dynamic Content Delivery. When you get to the SHOPPERS screen (Step 4 of 6), add the "have a cart subtotal" tag. The tag requires a numeric value. If you try to enter alphabetical or special characters, the text field turns red. If you click the red x next to the field, you'll see an explanation of the error.

tag-validation.png

 

If you're creating your own tag definitions, you'll want to learn how to incorporate tag validation. Since I like to learn by doing, I gave it a try, and in this article, I'll walk you through the steps I followed to define a tag, capture values for it, and enable validation on it.

 

Before diving in, I should provide a very brief overview of how tag validation works. When you create a tag definition, you associate it with a tag value type. You can think of a tag value type as high-level data type. For example, in 6.1.2, there are over a dozen out-of-the-box tag value types, including time, age, gender, text, money, city, product category. Each tag value type has its own set of validation constraints, which determine whether a given value qualifies as a valid instance of that type. For example, the default validation constraints for the age tag value type require the value to be a number between 0 and 120. The validation constraints are used most notably in the Dynamic Content Delivery wizard, to ensure that users enter sensible values when creating SHOPPER segment conditions. Behind the scenes, we use Spring Validation (or Valang) to interpret the validation constraints. We'll look a bit more at this later on, since this is probably the most interesting part of it.

 

The first thing I needed to do was to decide what kind of tag I wanted to create. For the purpose of testing validation, I figured a phone number tag might be a good one to try. I created a tag value type in the database (identified by the GUID phone_number) and specified the operators it needed to support.

insert into TTAGVALUETYPE(uidpk, guid, java_type)
     values(17, 'phone_number', 'java.lang.String');

insert into TTAGVALUETYPEOPERATOR(tagvaluetype_guid, tagoperator_guid)
     values('phone_number', 'includes'),
          ('phone_number', 'greaterThan'),
         ('phone_number', 'lessThan'),
          ('phone_number', 'notIncludes');

Then, I created a basic validation constraint.

insert into TVALIDATIONCONSTRAINTS(uidpk, object_uid, error_message_key, validation_constraint, type)
     values(17, 17,     'validationTagPhoneNumberError', 
     '{ condition : isValidConditionType(this) is true AND match(\'[0-9]\\\\d{2}-\\\\d{3}-\\\\d{4}\', tagValue) is true : \'value must be a valid phone number (only numbers)\' }',
     'TagValueType');

Okay, that might look a bit scary. The object_uid and type columns together identify the tag value type (the uidpk in the TAGVALUETYPE table). The validation_constraint column contains the interesting part. The format of the validation constraint is as follows:

{ condition: <constraint> : <error message> }

The first part of the constraint, isValidConditionType(this), checks to make sure the value is not null, that its Java type matches the tag value type's Java type, and a few other checks. As a rule, you should always include this in your constraints.

 

The second part is where it gets interesting. If you remove some of the escape sequences, you're left with a condition that includes a simple regular expression to match North American telephone numbers.

match('[0-9]\\d{2}-\\d{3}-\\d{4}', tagValue) is true

match is a Valang function that returns true if the string in the second argument matches the regular expression in the first argument. The tagValue is a placeholder for the value that's being evaluated. There are a number of other functions available in Valang and you'll want to get familiar with them by reading the Valang documentation, but the syntax is pretty straightforward.

 

The last step for setting up the validation constraint is to create a localized error message for it.

insert into TLOCALIZEDPROPERTIES(uidpk, object_uid, localized_property_key, value, type)
     values(100174, 17, 'validationTagPhoneNumberError_en',
        'The phone number must match the format XXX-XXX-XXXX',
        'TagValueConstraint');

 

With this in place, I could define tags and set their tag value type to phone_number. To demonstrate that it worked, I created a customer telephone number tag, attached it to the SHOPPER tag dictionary, and then went into the Dynamic Content Delivery dialog to try it out.

invalid-phone-number.png

When I tried to enter letters or special characters other than the dash, the field background turns red and I could click the x to find out what I'd done wrong.

 

This is just a simple example. A quick look at the Valang documentation will reveal how flexible and powerful it is. I was also really surprised at how easy it was to set up a tag value type and associate it with validation constraints. In an upcoming article, I'll take things one step further and show how to replace the tag value's free-form text box with a drop-down list or combo box.

0 Comments Permalink

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

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

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