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