Tuesday, 6 December 2016

Firebug Stopped working!

I rely on Firebug as a Firefox add-in and was seriously dismayed when I installed Firefox 50 to find that Firebug had stopped working.  I believe that this is because from version 50 Firefox is multi-process.

Anyway I found a little work-around that gets Firebug working again.  Install Firequery after you upgrade to Firefox 50 and Firebug will start working again. Simple!

http://firequery.binaryage.com/

Monday, 28 November 2016

Chrome Extension for Testing GeoIP

Recently I've been working on a project that used GeoIP.  In particular the site has a location finder map, that uses your location as a starting point.  

Not being able to test different locations was an issue for me.  And then we were asked to develop some personalization that used the State/Region value obtained via GeoIP! 

How do you easily test for different scenarios, and more importantly how would the end user do effective UAT?

Whilst doing some research into the issue I came across the following chrome browser plug-in. It solves the problem by allowing you to override the appropriate browser data that contains the IP address value and trick the GeoIP detection into thinking you are somewhere you are not.  In effect spoofing an IP Address.

Here's a link to the tool:

https://chrome.google.com/webstore/detail/sitecore-analytics-testin/pecalkbdlhhhcoenmcjnmhgnncnkdgak?utm_source=chrome-app-launcher-info-dialog

Once installed you will see a Sitecore logo top-right.  Click on the logo and under GeoIP you will see the name of the selected profile - Default is the one set up for you on install.



Click on drop-down arrow to the right of the Default text, and select Manage Profiles


Here you can then add new profiles.  In the text boxes give it a name and enter the appropriate external IP address and click the Add Profile button:





Now open a new tab and navigate to your Sitecore site, click on the Sitecore logo and select the profile you want to use and then select New Contact. The page will refresh and your IP address will be changed to the selected one.

N.B. One point to note is that in order for this to work, you will need to update the Analytics.ForwardedRequestHttpHeader value in
Sitecore.Analytics.Tracking.config file.  You should use whatever value you have configured in the GeoIP settings in the tool:






Typical choices are X-Forwarded-For or X-Real-IP.

Note that the instructions supplied in the GeoIP settings section are wrong for Sitecore version 8.1 the setting is not in Sitecore.Analytics.config file.




Friday, 23 September 2016

Using the x-path builder in Sitecore 7.5 +

The Developer Tools link has disappeared from Sitecore 7.5

Here's how you get to the developer tools:

http://[Site Host Name]/sitecore/shell/default.aspx?xmlcontrol=IDE

Wednesday, 11 May 2016

ERROR Error in FileWatcher. Internal buffer overflow

On one installation of Sitecore 8.1 update 2 that I recently did, I noticed lots of errors in the logs like:

5404 15:52:21 ERROR Error in FileWatcher. Internal buffer overflow.
Exception: System.IO.InternalBufferOverflowException
Message: Too many changes at once in directory: D:\inetpub\...\Website\.

As explained here: http://mikael.com/2016/01/sitecore-file-watcher-internal-buffer-overflow-exception/ 

The resolution oddly enough is to move the license file to a subfolder of the data directory.

Email Experience Manager 3.2.1 Rev. 160127 Issue with Sample Package

I recently had to install the Email Experience Manager (ECM) for a client on Sitecore 8.1 update 2, and came across an issue when following the steps to smoke test the installation:

https://doc.sitecore.net/email_experience_manager/configuring_the_delivery_process/performance/testing_exm_performance_in_emulation_mode

The steps although not explicitly saying install the supplied Sample Newsletter package do refer to the same.  So I went ahead and installed the Email Experience Manager Sample Newsletter 3.2.1 rev 160127.zip that is supplied as part of this ECM package.  

N.B. The sample package like the main ECM package is part of a collection of 7 zip files contained in the zip file that you download from  (you'll need a Sitecore Login) the Downloads -> Email Experience Manager Link found on this page: https://dev.sitecore.net/Downloads/Email_Experience_Manager/Email_Experience_Manager_32/Email_Experience_Manager_32_Update1.aspx

When clicking on the Sample Newsletter item I got the following error:

Requested value 'Drafts' was not found. 

The error appears in a popover dialogue that it launched when the item is selected.

I used Resharper's dotPeek and was able to see what was going on:


The error I am seeing is happening in the MessageItemSource class where its calls MessageStateToString - it looks like the sample item is passing in a MessageType of Drafts - which doesn’t exist!

Simple answer is don't install the package until Sitecore fix this issue, but instead go ahead and create your own Sample Newsletter.
 

Tuesday, 10 May 2016

Sitecore WFFM Error on 8.1 update 2

On Sitecore 8.1 CD server after using the supplied SwitchMasterToWeb.config and having installed Web Forms For Marketers 8.1 rev 160304 - I get the following error when trying to access a form:

Unable to cast object of type 'System.String' to type 'Sitecore.Analytics.Reporting.ReportDataProvider'

The error occurs because Sitecore is trying to connect to the master Db and is getting a failure because it is not accessible. I suspect that SQL is returning a string error message instead of the expected object.

If you open up the SwitchMasterToWeb.config and comment out the following block it will resolve the issue:

<reporting>
  <dataprovider>
 <datasources>
   <add key="collection">
  <filtersfactory>
    <param desc="definitionDatabaseName" />web
  </filtersfactory>
   </add>
   <add key="reporting">
  <filtersfactory>
    <param desc="definitionDatabaseName" />web
  </filtersfactory>
   </add>
 </datasources>
  </dataprovider>
</reporting>

Wednesday, 16 March 2016

Custom Sitecore Fields Part 1 of 2

Custom Fields - Part 1 - A Google Maps Field

Abstract 

One of the best features of Sitecore (in my opinion) is how extensible it is.  Almost every aspect of the CMS can be extended, overwritten or enhanced.  This includes fields.

In this two part  blog I am going to cover some different types of fields you can create and how to put them together.

All of the examples and screenshots will be taken from Sitecore version 8.1, but the fields themselves should work in 7.x as well.

We'll start by looking at how you add a custom field to Sitecore, then move on to two  examples of different types of custom fields and finally show you how implement the fields in Sitecore templates, etc.

This article assumes that you have some basic knowledge of Sitecore development and that you are able to create a new Class Library project in Visual Studio and reference all the the necessary Sitecore dlls (perhaps create a nuGet package with all the Sitecore dlls, so that you can import those straight into your project).

Setting up Sitecore

To add a custom field to Sitecore, you need to navigate to the core database.  

From the dashboard click on the desktop icon:

















Once in the desktop, click on the databases icon bottom right and select core.

Then click on the Sitecore icon bottom left, and from the menu click on Content Editor:

























You should now see a content tree similar to the above.

Navigate to /sitecore/system/Field types.

I recommend creating your own field types sub-folder, and putting your fields in there, that way they will be all grouped together when you come to add them to your templates.

To do this: right-click on the field types node and click on insert and then select folder - give it a meaningful name.

Navigate to your newly created folder and right click on the node, select insert and then select Insert from template.  You need to add an item of the following template type: /sitecore/templates/System/Templates/Template field type.

Once added, the only fields we are interested in are the Assembly and Class fields, these will be filled out once we have created the code for our new field in Visual Studio.

Creating a Google Maps Field

This custom field will enable us to display a Google Maps image on a template and configure it to pick information from an address field on the same item.  This could be used by a content author to verify address information being added to an item.

Here's an example of it being used to show the location of a partner, based on the Partner Address field on the same item:

















To start lets create a new class called GoogleMapField.

It needs to inherit from: Sitecore.Web.UI.HtmlControls.Control, and needs to implement:
Sitecore.Shell.Applications.ContentEditor.IContentField

 
public class GoogleMapField : Control, IContentField
{
}

We need to add some properties which will get populated at run time.


public class GoogleMapField : Control, IContentField
{
     //Constants
     private const int MAP_HEIGHT = 300;
     private const int MAP_WIDTH = 600;
     private const string CONTENT_EDITOR_SAVE = "contenteditor:save";
     private const int DEFAULT_ZOOM = 14;
     private const string OVERRIDEZOOM = "OverrideZoom";
     private const string ISDEBUG = "Debug";
     private const string ADDRESSFIELDNAME = "AddressField";

     private Image _mapImageCtrl;

     private int _mapZoomFactor = DEFAULT_ZOOM;
     private bool Debug { get; set; }

     //Needed for a custom field 
     public string Source { get; set; }
     public string ItemID { get; set; }

     //Parameter properties
     protected int OverrideZoom { get; private set; }
     protected string AddressFieldName { get; private set; }

}

Next we'll create a method to parse the parameters  that will be passed into it from the source field in the template where it is used:



In this example the source field parameters are: &Debug=false&AddressField=Partner Address.
  • Debug [Optional] - turns on off display of the of the Google URL above the map.
  • AddressField [Required] - Name of the field containing the address information
  • OverrideZoom [Optional] - An integer value used to override the default zoom level of 14 

private void ParseParameters(string source)
{
     if (string.IsNullOrEmpty(source))
     {
         return;
     }

     var parameters = new UrlString(source);
     if (!string.IsNullOrEmpty(parameters.Parameters[OVERRIDEZOOM]))
     {
         int zoomValue;
         int.TryParse(parameters.Parameters[OVERRIDEZOOM], out zoomValue);

         if (zoomValue < 0 || zoomValue > 20) zoomValue = DEFAULT_ZOOM;

         OverrideZoom = zoomValue;
      }

      if (!string.IsNullOrEmpty(parameters.Parameters[ADDRESSFIELDNAME]))
      {
          AddressFieldName = parameters.Parameters[ADDRESSFIELDNAME];
      }

      if (!string.IsNullOrEmpty(parameters.Parameters[ISDEBUG]))
      {
           Debug = MainUtil.GetBool(parameters.Parameters[ISDEBUG], false);
      }
}
 
Next we need to add a method that extracts the address information and parses it into a format that we can push to Google: 

private string GetGeoData(Item item, ref string centre)
{
       var location = "";

       location = item[AddressFieldName].Replace("
", ", ");

       location = HttpUtility.UrlEncode(location);

       if (string.IsNullOrEmpty(location))
       {
           centre = location;
       }
       return location;
}
Next we need to add a method that will generate the URL:

private string GetGoogleMapImageUrl(string location, string centre)
{
    return
    string.Format(
          !string.IsNullOrEmpty(location)
               ? "http://maps.googleapis.com/maps/api/staticmap?&q={0}&zoom={1}&size={2}x{3}&sensor=false&maptype=roadmap&markers=color:blue%7Clabel:1%7C{0}"
               : "http://maps.googleapis.com/maps/api/staticmap?center={4}&zoom={1}&size={2}x{3}&sensor=false&maptype=roadmap&markers=color:blue%7Clabel:1%7C{0}",
               location, _mapZoomFactor, MAP_WIDTH, MAP_HEIGHT, centre);
}

Finally we override the Render method to put it all together:

protected override void Render(HtmlTextWriter output)
 {
     if (!string.IsNullOrEmpty(Value))
     {
          int.TryParse(Value, out _mapZoomFactor);
     }

     ParseParameters(Source);
     if (!string.IsNullOrEmpty(AddressFieldName))
     {
          base.Render(output);

          var db = Factory.GetDatabase("master") ?? Factory.GetDatabase("web");

          var id = !string.IsNullOrEmpty(ItemID)
               ? ItemID
               : ControlAttributes.Substring(ControlAttributes.IndexOf("//master/", StringComparison.Ordinal) + 9,
                 38);
          var item = new ID(id).ToSitecoreItem(db);

          //render other control

           var centre = "";
     
           var location = GetGeoData(item, ref centre);


           _mapImageCtrl = new Image
           {
               ID = ID + "_Img_MapView",
               CssClass = "imageMapView",
               Width = MAP_WIDTH,
               Height = MAP_HEIGHT,
               ImageUrl = GetGoogleMapImageUrl(location, centre)
            };
            _mapImageCtrl.Style.Add("padding-top", "5px");

            if (Debug)
            {
                output.Write(
                    "<div style="background-color: silver; color: white;">{0}</div>",_mapImageCtrl.ImageUrl);
            }

           _mapImageCtrl.RenderControl(output);
      }
}

What about the buttons? - For those we need to override the HandleMessage method:

public override void HandleMessage(Message message)
{
    if (!string.IsNullOrEmpty(Value))
    {
        int.TryParse(Value, out _mapZoomFactor);
    }

    string messageText;
    if ((messageText = message.Name) == null)
    {
        return;
    }

    if (messageText.Trim() == "contentfile:zoom")
    {
        _mapZoomFactor = _mapZoomFactor + 1 > 20 ? _mapZoomFactor : _mapZoomFactor + 1;
        Value = _mapZoomFactor.ToString();
        Sitecore.Context.ClientPage.Dispatch(CONTENT_EDITOR_SAVE);
        return;
    }

    if (messageText == "contentfile:zoom-")
    {
        _mapZoomFactor = _mapZoomFactor - 1 < 0 ? _mapZoomFactor : _mapZoomFactor - 1;
        Value = _mapZoomFactor.ToString();
        Sitecore.Context.ClientPage.Dispatch(CONTENT_EDITOR_SAVE);
        return;
    }

    if (messageText == "contentfile:reset")
    {
        _mapZoomFactor = _mapZoomFactor - 1 < 0 ? _mapZoomFactor : _mapZoomFactor - 1;
        Value = DEFAULT_ZOOM.ToString();
        Sitecore.Context.ClientPage.Dispatch(CONTENT_EDITOR_SAVE);
        return;
    }
    base.HandleMessage(message);
}

Finishing off Sitecore Field Setup

Now that we have created our Google Maps field, lets go back to Sitecore and finish setting up the field.
















As  mentioned before fill out the Assembly and Class fields with the appropriate information taken from your solution.

Next we need to add the three buttons:



















You need to fill out the following for each of the buttons:
  • The Display Name - Text you want to appear in Sitecore
  • Message  - This should match the Message Text value used in the HandleMessage method above.  You should also add the (id=$Target) to tell Sitecore to target the current item. 
  • Show in Field Editor (not shown) - This should be checked.

 

How could we enhance this?

For one project I worked on, we imported a geo database for North America into Sitecore.  We added the Google Maps Field to the template used to store the geo data items.  I then added some enhancements to enable the Google Maps Field to try and get the information from different fields on the item:
  • Lat & Long
  • Post Code
  • Full Address
private static string GetGeoData(BaseItem item,  ref string centre)
{
    double lat;
    double lng;
    double.TryParse(item["latitude"], out lat);
    double.TryParse(item["longitude"], out lng);
    var location = string.Format("{0},{1}", lat, lng);
            
    if (lat == 0.00 && lng == 0.00)
    {
         location = item["postcode"];
    }
    if (string.IsNullOrEmpty(location))
    {
         centre = string.Format("{0}{1}{2}{3}{4}{5}",
             !string.IsNullOrEmpty(item[LOCALITY]) ? item[LOCALITY] + "," : ""
             , !string.IsNullOrEmpty(item[REGION4]) ? item[REGION4] + "," : ""
             , !string.IsNullOrEmpty(item[REGION3]) ? item[REGION3] + "," : ""
             , !string.IsNullOrEmpty(item[REGION2]) ? item[REGION2] + "," : ""
             , !string.IsNullOrEmpty(item[REGION1]) ? item[REGION1] + "," : ""
             , !string.IsNullOrEmpty(item[COUNTRY]) ? item[COUNTRY] : "");
    }
    return location;
 }

Hopefully this will give you some ideas on how you too, could use this field!

I have the full source (including the Sitecore Package) here if you would like to download it: Sitecore Fields

In the next article we will look at a more complex field and also how to use these fields in Sitecore.