Sunday, February 14, 2016

Integrating AddThis with Sitecore goals

Social buttons


We faced the requirement to add social buttons to a Sitecore 8.1 site and track the interaction with a goal in xDB. Oh yes, and do it fast.

Social Connected

Our first thought was using the social buttons provided by Sitecore (in 'social connected'). But, we bumped into some issues (well, actually just not enough options) and had to write quite some button-code ourselves.

AddThis

So we started looking for alternatives and got back to a tool which we had used before and was known to our customer : AddThis. Problem was that it is not integrated within Sitecore and therefor did not create a goal. After discussing pros and cons we went for the flexibility of AddThis and started writing the code to trigger a goal.

Trigger a goal in a MVC controller

Some people have already blogged about not being able to get to the current Tracker from within WebApi ..  With SSC (Sitecore.Services.Client) it should be possible - as Mike Robbins has shown:


but as we did not have that information yet and we could not afford to take any risks, we went for the solution that seemed easy: create a mvc controller and a HttpPost action.

The controller action


[HttpPost]
public ActionResult TriggerGoal(string goal)
{
 if (!Tracker.IsActive)
 {
  return Json(new { Success = false, Error = "Tracker not active" });
 }

 if (string.IsNullOrEmpty(goal))
 {
  return Json(new { Success = false, Error = "Goal not set" });
 }

 var goalItem = ... // get goal item from Sitecore based on goal string
 if (goalItem == null)
 {
  return Json(new { Success = false, Error = "Goal not found" });
 }

 var visit = Tracker.Current;
 if (visit == null)
 {
  return Json(new { Success = false, Error = "Current tracker is null" });
 }

 var page = Tracker.Current.Session.Interaction.PreviousPage;
 if (page == null)
 {
  return Json(new { Success = false, Error = "Page is null" });
 }

 var registerTheGoal = new PageEventItem(goalItem);
 var eventData = page.Register(registerTheGoal);
 eventData.Data = goalItem["Description"];
 eventData.ItemId = goalItem.ID.Guid;
 eventData.DataKey = goalItem.Paths.Path;
 Tracker.Current.Interaction.AcceptModifications();

 Tracker.Current.CurrentPage.Cancel(); // Abandon current request so that it doesn't appear in reports

 return Json(new { Success = true });
}

We try to find the goal (Sitecore item) based on the post parameter. If found, we use that data to register a PageEvent to the PreviousPage of the current Tracker. We end by 'saving' the changes and cancelling the current request. The return values are an indication of what happened.

Remember:  Do not forget to register the route to your controller... ;)


The javascript

(function(global){

 var jQuery = global.jQuery;
 var addthisComp = {
  $instance: null,
  
  init: function(){
   addthisComp.$instance = jQuery('.addthis_sharing_toolbox');
   if(addthisComp.$instance.length){
    
    // Listen for the ready event
    if(global.addthis){
     if(global.addthis.addEventListener) 
          global.addthis.addEventListener('addthis.menu.share', addthisComp._share);
    }

   }
  },

  // Dispatched when the user shares
  _share: function(evt) {
      jQuery.ajax({
    type: "POST",
    url: '...', // url - route to your controller action
    data: {"goal": evt.data.service},
    success: function(obj){ 
    },
    dataType: 'json'
   });
  }
 };

 global.addthisComp = addthisComp;
   jQuery(document).ready(function(){
     global.addthisComp.init();
 });

})(window);

This is just attaching an ajax post request to out controller when the share functions of addthis are used. Of course, the post action could be attached to any event you want..


Sunday, January 24, 2016

Sitecore 8.1 encode name replacements

<encodeNameReplacements>

In Sitecore 8.1 you will find an extra entry in the <encodeNameReplacements> section of the Sitecore config (now located in /App_Config/Sitecore.config): 
<replace mode="on" find=" " replaceWith="-" />

This addition will replace all spaces in the generated urls (so either in display name or name) with a '-'.  Looks very nice, our seo-friends happy, but...

Two issues have been noticed which you might need to tell your editors about:

1. Items with a " " and a "-" in the (display) name

Our editors created several items with both characters in the name.. they all resulted in a 404 page because the item could not be resolved by Sitecore. Two ways to handle this:
- turn off the replacement
- tell your editors not to do that (optionally create a rule with a regular expression to validate item names)

We went for option 2..  


2. Wildcard items

When you have a wildcard item in a folder (item named "*") this replacement will give additional issues..  As soon as you create an item with a space in it, it will not be found and directed to the wildcard instead. Apparently the check for wildcards is done before the handling of replacements which causes this behavior. 

There is a support fix available for this (452602 - patching the ItemResolver), but another (and maybe easier) way of handling it is not using spaces in folders with wildcard items.


That is for now - tell your editors to avoid getting (a lot of) bug reports for broken urls ;)

Update

Apparently the issue(s) has been fixed in Sitecore 8.1 Update 2 (see http://sitecorefootsteps.blogspot.be/2016/03/item-url-replacement-improvements.html)

Wednesday, December 23, 2015

Sitecore Lucene index and DateTime fields

[Sitecore 8.1]

DateTime field in Lucene index

I was trying to create an index search for an event calendar that would give me items (from a template etc..)  that have a datefield:
  • from today onwards (today included) 
  • up until today
The field is Sitecore is a date field (so no time indication), but our query seemed to have issues with the time indications. The code to create the predicate looks like this:

private Expression<Func<EventItem, bool>> GetDatePredicate(OverviewMode mode)
{
  var predicate = PredicateBuilder.True<EventItem>();
  switch (mode)
  {
 case OverviewMode.Future:
 {
  var minDate = DateTime.Today.ToUniversalTime();
  predicate = predicate.And(n => n.StartDate > minDate);
  break;
 }
 case OverviewMode.Past:
 {
  var maxDate = DateTime.Today.ToUniversalTime();
  var minDate = DateTime.MinValue.ToUniversalTime();
  predicate = predicate.And(n => n.StartDate < maxDate).And(n => n.StartDate > minDate);
  break;
 }
 default:
 {
  return null;
 }
  }
  return predicate;
}


This did not work correctly with events "today". We had to add "AddDays(-1)" after the Today before we set it to UTC. So why?

The first reason is that Sitecore stores its DateTimes in UTC which was an hour difference with our local time. So, our dates shifted a day back: "12/12/2015" becomes "12/11/2015 23:00". This is known and should be no issue as we also shift to UTC in our predicate.

But still.. we did not get the correct results.

The logs

So we look at the logs. Sitecore logs all requests in the Search log file. We saw that our predicate was translated into something like this:
"+(+date_from:[* TO 20151111t230000000z} +date_from:{00010101t000000000z TO *])"

Looks fine, but note that the "t" in the dates is lowercase. In my index however they are all uppercase. If I try the query with Luke it does give me the wrong results indeed.. When I alter the query in Luke to use uppercase T it works correctly..

Support, here we come!


Solution(s)

Support gave us 2 possible solutions, next to the one we already had (skipping a day).

1. Format

We could alter our index to use a format attribute:
<field fieldName="datefrom" storageType="YES" indexType="UNTOKENIZED" vectorType="NO" boost="1f" 
format="yyyyMMdd" type="System.DateTime" 
settingType="Sitecore.ContentSearch.LuceneProvider.LuceneSearchFieldConfiguration, Sitecore.ContentSearch.LuceneProvider"/>

After rebuilding our index, the "DateFrom" field values, stored in the index, will contain only dates (like "20151209"), so search by dates should return results as expected (since there are no "T" and "Z" symbols). 
This works if you really don't need the times..

2. Custom Converter

Another solution is to override the "Sitecore.ContentSearch.Converters.IndexFieldUtcDateTimeValueConverter" class to store dates in lower case to the index.

Add your converter to the index config:
<converters hint="raw:AddConverter">
  ...
  <converter handlesType="System.DateTime" 
         typeConverter="YourNamespace.LowerCaseIndexFieldUtcDateTimeValueConverter, YourAssembly" />
  ...
</converters>

As a result, all dates should be stored to the index in lower case. As the search query is in lower case, all expected results should be found.


Future solution

Since currently search queries are always generated in lower case and this behavior is currently not configurable (the "LowercaseExpandedTerms" property of the "Lucene.Net.QueryParsers.QueryParser" class is always set to true, which lowers parameters in a search query string), a feature request for the product was made so that it can be considered for future implementations. That should make these tweaks unnecessary..

Wednesday, December 16, 2015

Sitecore WFFM 8.1 and multilingual save actions

WFFM Save actions

Every Sitecore developer that had the 'pleasure' of working with WFFM knows about save actions, and probably also knows that save actions by default are shared. And so: not multilingual. In some cases this is no issue, but if you want to send an email to your visitor you might want to do that in his own language.

KB : Solution 2

A solution for this issue is given in https://kb.sitecore.net/articles/040124. Most people use "Solution 2":

Apply the following customization:
  • Navigate to /sitecore/templates/Web Forms for Marketers/Form
  • Uncheck "Shared" checkbox for the Save Action field.
After this change, you must add Save Actions to each language version of the form item. This means that each language of the form item will keep its own list of Save Actions.

The error

This works up until Sitecore 8.0, but when we tried this in Sitecore 8.1 with WFFM 8.1 we got this:

When we go to a form and switch to another language this nice error appears. Apparently the reason is simple: by making the save action field un-shared we caused empty values in some languages for that field. Sounds very logical indeed.

But Sitecore does not expect an empty value in that field, it expects some xml.

The fix (workaround)

  1. Go to an affected form (in a working language)
  2. Switch to "Raw values"
  3. Open a needed language version of a form item.
  4. Insert the following value into the Save Actions field:
<?xml version="1.0" encoding="utf-16"?>
 <li xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <g id="{E5EABB1F-40BC-45BB-8D87-3B6C239B521B}" displayName="Actions" 
     onclick="javascript:return scForm.postEvent(this,event,'forms:addaction')" />
 </li>
Switch back to normal view (and save). This xml will insert an empty save actions block and you are good to go.

Monday, December 7, 2015

Sitecore Lucene index with integers

The situation

We recently discovered an issue when using a facet on an integer field in a Sitecore (8.1) Lucene index. We had a number of articles (items) with a date field. We had to query these items, order them by date and determine the number of items in each year.

The code

We created a ComputedField "year" and filled it with the year part of the date:
var dateTime = ((DateField)publicationDateField).DateTime;
return dateTime.Year;
We added the field to a custom index, and created an entry in the fieldmap to mark it as System.Int32. We rebuild the index, check the contents with Luke and all is fine. So we create a class based on SearchResultItem to use for the query:

class NewsItem : SearchResultItem
{
    [IndexField("title")]
    public string Title { get; set; }

    [IndexField("publication date")]
    public DateTime Date { get; set; }

    [IndexField("category")]
    public Guid Category { get; set; }

    [IndexField("year")]
    public int PublicationYear { get; set; }
}

The query

When we use this class for querying, we get not results when filtering on the year.. apparently integer fields need to be tokenized to be used in searches (indexType="TOKENIZED"). Sounds weird as this is surely not true for text fields, but the NumericField constructor makes it clear:

Lucene.Net.Documents.NumericField.NumericField(string name, int precisionStep, Field.Store store, bool index) : base(name, store, index ? Field.Index.ANALYZED_NO_NORMS : Field.Index.NO, Field.TermVector.NO)

So, we changed the field in the fieldmap and set it tokenized. We add an analyzer to prevent the integer being cut in parts (Lucene.Net.Analysis.KeywordAnalyzer or Sitecore.ContentSearch.LuceneProvider.Analyzers.LowerCaseKeywordAnalyzer).

Success?

Yeah! We have results! We got the news items for 2015! And 2014..  But... there is always a but or this post would be too easy. We still needed a facet. And there it went wrong. The facet resulted in this:


Not what we expected actually...

So back to our query and index..  Sitecore Support found out that this happens because of the specific way the numeric fields are indexed by Lucene, they are indexed not just as simple tokens but as a tree structure (http://lucene.apache.org/core/2_9_4/api/all/org/apache/lucene/document/NumericField.html).

Unfortunately, Sitecore cannot do faceting on such fields at this moment - this is now logged as a bug.

The Solution

The solution was actually very simple. We threw out the field from the fieldmap and changed the int in our NewsItem to string. If we want to use them as an integer we need to cast them afterwards, but for now we don't even need that.
Luckily for us, even the sorting doesn't care as our int's are years. So we were set.. queries are working and facets are fine.

Sunday, November 29, 2015

SOLID Sitecore templates

Every programmer in an object-oriented environment should know the S.O.L.I.D. principles:
  • Single Responsibility
  • Open/closed
  • Liskov substitution
  • Interface segregation
  • Dependency inversion


More information about this topic can be found all over the internet, on wikipedia.

What I want to talk about here is using these principles when working with Sitecore, and Sitecore templates in particular. It will come down to a basic conclusion.
I hope that for most of I will not tell anything new, but I've seen quite a lot of code already that does not apply to these principles and the reason is almost always: we had to do it "fast".

They are lots of blog posts already over solid principles in C# which of course also apply to a Sitecore based solution. I will not repeat that but focus on the architecture of the Sitecore templates - not quite the same as oo-coding but as you will see the solid principles even have their meaning here.

Single Responibility

"A template should have only a single responsibility"

Do not use a template called "News" for something else then a news article. Even if your other item has the same fields. You will create confusion and the maintenance of the templates will become harder. You might consider using base templates if your items have the same fields, but there also goes the single responsibility principle.

Base templates
Do not create base templates that have more than a single responsibility. If you want to re-use fields for SEO purposes, create a seo base template. Do not mix them with e.g. fields for your navigation in a single global "page" base template. If you really need such a 'base', create it from several other base templates.
A base template that has more than a single section should get you thinking...

Open/Closed

"An entity should be open for extension, but closed for modification"

For this one my example does not apply to the template itself, but to functions that switch on template(-id)'s. Quite often I get to see code that has a set of if-statements (or a switch) to identify the template of an item and do something somehow related but still different per template. After a while you might even get to see these if/switch statements more than once in the same class. If you create a new template you might break those function(s) because your new template will not be set in the selections.

How can we make this better maintainable? Create an interface (if you don't already have one, as you should) and use that interface to the outside world. Implement the interface for each (set of) template(s) that you need - use a base class with virtual functions if needed. In front of those classes you create a façade (class) that does the template detection and uses the matching implementation of the interface. The consumers of your code will always see the interface and the implementing classes do not need to check templates anymore.
If you create a new template you need to check your façades and maybe create a new implementation of an interface. But you don't need to scan your solution for template detecting statements anymore.


Liskov substitution

"Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program"

Applied on templates this comes down to a few simple rules that everyone finds very obvious and still.. the "fast" way, you know.

  • Do not "overwrite" fields from a base template: if a base template already has a field "Title" do not create another "Title" field. Also make sure you do not have multiple base templates that include the same fieldname. 
  • Do not re-use a base template field for a purpose it was not designed. You will not only confuse your fellow programmers, but also you content editors.

Interface segregation

"Specific interfaces are better than one general-purpose interface"
"Clients should not be forced to implement interfaces they don't use"

Applying this on Sitecore templates comes down to using base templates. Use base templates, and use them wisely. Create a base template for every need and make sure that your templates do not end up with fields they do no use. 
Unused fields will confuse your content editors, it might give them the impression that they lack functionality. 


Dependency inversion

"One should depend upon Abstractions. Do not depend upon concretions. A high-level modules or class should not depend upon low-level modules or classes"

This principle is mostly implemented with Dependency Injection. As there are already lots of blog posts already on that subject (also in combination with Sitecore) I won't go into details here. And I couldn't find an example related to templates..

One thing worth mentioning however is not to think that you are done when using inversion of control. If you want you classes/function independent and testable you must also make sure they do not use Sitecore.Context. A simple (but often forgotten) one is using the "current language". Your business functions should ask for the language if they need it (a parameter) and not depend on the Sitecore.Context!


Conclusion

Wrapping this up for using templates one could say:

  • use base templates
  • use base templates wisely
  • use small base templates wisely
and try to keep your business logic code independent of Sitecore.Context and templateId's.


Friday, November 27, 2015

Preview.ResolveSite and the disabled Shared Layout button in Sitecore Experience Editor

After installing some Sitecore 8.1 sites, we noticed something weird in one of the new features in the experience editor: the final/shared layout button, which quite some editors were waiting for.

The symptoms:

The Final/Shared button is disabled.




Some more testing came up with the following results;

  • It happened on some Sitecore 8.1 installations, but not on all of them
  • It happened on all browsers, on different client machines
  • It happened only if we went from the LaunchPad to the Content Editor and then via the Publish ribbon to the Eperience Editor
  • It did not happen if we went from the LaunchPad immediately to the Experience Editor - and strangely enough once we had visited the Experience Editor this way the button was always enabled throughout the entire session

It took us quite a while and help from Sitecore Support (thanks Yuriy) to figure it out, but then they pointed us at a the Preview.ResolveSite setting. A small explanation is also found at Kirkegaard's blog.

The preview resolving settings

On a multisite solution, when one opens a page in the Experience Editor, by default, Sitecore resolves the corresponding site using the value of the Preview.DefaultSite setting from the \App_Config\Sitecore.config file. The default value there is "website".

However if one sets Preview.ResolveSite setting value to "true" (it is located in the same file and by default it is "false"), Sitecore tries to resolve the root item and the context site based on the current content language and the path to the item. If Sitecore cannot resolve the context site, it uses the site that is specified in the Preview.DefaultSite setting.

So, if Sitecore renders the page on the solution in context of the "website", the Final Layout button is disabled. Why that happens is still a mystery as the rest of the Experience Editor is working fine.

But anyway: setting the Preview.ResolveSite to true fixed the issue.