25 August 2015

Sitecore 8 tracking media download

As part of the implementation, we sometime have question about tracking. It happened few time in the past where clients wanted to track PDF download on there website. Using Sitecore 6 was a bit tricky as it was kind of tricky. In the past, I tried a few things including overriding the  Media Handler to trigger an event "Download PDF". Eventhough this was working I did not like the idea of overridding the media handler as it is always painful for Sitecore upgrade and else.

Well I have to say that I was quite pleased to see that this is now handled out of the box on Sitecore 8. This is done through Media Assets. You can find more about it in the following link: https://doc.sitecore.net/sitecore%20experience%20platform/marketing%20taxonomies/classify%20a%20media%20item%20as%20a%20digital%20marketing%20asset

So what do you need to do:

1- Open your Marketing Control Panel and add a New Taxonomy Asset Item:


2- Deploy your Media Asset folder:


3- You can now use your Media Asset on your Media Item directly:

4- Now the only thing left to do is to setup the Event tracking on your media item:

5- setting up the event on the media item, means that everytime someone will request the media item, sitecore will track the Media Assets with the corresponding event: Download. The event already exist in sitecore and is already hooked to the Marketing report:

There you go you are now tracking the Media requests.

Just a quick look at the magic behind, if you quickly uncompile the Sitecore DLL, look for the MediaRequestHandler class that processes '~/media' requests raise 'media:request' event.



Analytics defined in 'Sitecore.Analytics.RobotDetection.Media.MediaRequestHandler' handler that is supposed to do processing will checks:
  1. If Analytics is enabled for the site
  2. If requested item has some data in a tracking field
  3. Launches 'Tracker.StartTracking()' API which would initialize tracker ( 'startAnalytics' pipeline )
Happy coding

16 August 2015

Loop through renderings in presentation and get the associated datasource

Today I was asked for how you can programmatically loop through all the renderings for an item and get the datasource information for each of the rendering. That sounded like an interesting question so I decided to post it here... What we wanted to do was to push the datasource items through workflow when the main item is beeing approved. We will split this in 2 posts so it is not too long here. So on this first post we will just see some code that will loop through the presentation and retrieve the list of item set in the renderings datasource. To make it a little different, lets try to find those where the rendering is inside a specific placeholder and a device

Well I first started to create a context class that will have method like the following:  
        /// 
        /// Return renderings for a specific Placeholder for the current item and a specific device
        /// 
        /// 
        /// 
        /// 
        public IEnumerable GetRenderings(string placeholderKey, string deviceName)
        {
            var currentItem = Sitecore.Context.Item;

            Sitecore.Data.Fields.LayoutField layoutField = new Sitecore.Data.Fields.LayoutField(currentItem);
            Sitecore.Layouts.RenderingReference[] renderings = layoutField.GetReferences(GetDeviceItem(deviceName));

            var filteredRenderingList = new List();

            foreach (var rendering in renderings)
            {
                if (rendering.Placeholder.Contains(placeholderKey))
                {
                    filteredRenderingList.Add(new RenderingItemWrapper(rendering.RenderingItem));
                }
            }

            return filteredRenderingList;
        }

        /// 
        /// Return the settings including datasource ID for the renderings in a specific placeholder 
        /// For current item and specific device
        /// 
        /// 
        /// 
        /// 
        public IEnumerable GetRenderingsSettings(string placeholderKey, string deviceName)
        {
            var currentItem = Sitecore.Context.Item;

            Sitecore.Data.Fields.LayoutField layoutField = new Sitecore.Data.Fields.LayoutField(currentItem);
            Sitecore.Layouts.RenderingReference[] renderings = layoutField.GetReferences(GetDeviceItem(deviceName));

            var filteredRenderingList = new List();

            foreach (var rendering in renderings)
            {
                if (rendering.Placeholder.Contains(placeholderKey))
                {
                    filteredRenderingList.Add(new RenderingSettingsWrapper(rendering.Settings));
                }
            }

            return filteredRenderingList;
        }

        /// 
        /// Get the Device from the context database and the Device name
        /// 
        /// 
        /// 
        private Sitecore.Data.Items.DeviceItem GetDeviceItem(string deviceName)
        {
            if (Sitecore.Data.ID.IsID(deviceName))
            {
                return Sitecore.Context.Database.Resources.Devices.GetAll().Where(d => d.ID.Guid == new Guid(deviceName)).First();
            }
            else
            {
                return Sitecore.Context.Database.Resources.Devices.GetAll().Where(d => d.Name.ToLower() == deviceName.ToLower()).First();
            }
        }



You will note the difference between the IRenderingItemWrapper and IRenderingSettingsWrapper
The later has all the information about the Datasource Item.

Then you can have a method that will loop through your IEnumerable then check your datasource information:

        /// 
        /// Get the Datasources for all renderings inside a placeholders
        /// 
        /// 
        /// 
        public IEnumerable GetRenderingDatasources(string placeholderKey)
        {
            var renderingDatasources = new List();

            IEnumerable renderingsSettings = _presentationContext.GetRenderingsSettings(placeholderKey);
            foreach (var renderingSettings in renderingsSettings)
            {
                if (renderingSettings.DataSource != null)
                {
                    Sitecore.Data.Items.Item datasourceItem;
                    if (Sitecore.Data.ID.IsID(renderingSettings.DataSource))
                    {
                        datasourceItem = _sitecoreContext.GetItem(new Guid(renderingSettings.DataSource));
                    }
                    else
                    {
                        datasourceItem = _sitecoreContext.GetItem(renderingSettings.DataSource);
                    }

                    if (datasourceItem == null)
                        continue;

                    renderingDatasources.Add(datasourceItem);
                }
            }

            return renderingDatasources;
        }


Hope that help anyone...

25 July 2015

Site search and Predictive

I had an interesting requirement about a predictive search. The requirement was to propose terms when a user start entering text on the Search Box:


After a few thoughts we have considered the following approach let's make those terms propositions coming from the website content. As a matter of fact why not using the index to search for the term and return the short list required. What would we need:

  1. WebAPI call from the View
  2. A search Service on the .NET side that will query the index and bring the result
So It sounds quite simple.

So let's setup the Controller for your WebAPI. this could be something like:

namespace XXX.Web.Controllers.XXX.API
{
    [RoutePrefix("webapi/v1/search")]
    public class SuggestiveSearchController : ServicesApiController
    {
        private readonly ISuggestiveSearch _searchService;

        public SuggestiveSearchController(ISuggestiveSearch suggestiveSearchServices)
        {
            _searchService = suggestiveSearchServices;
        }

        [HttpGet]
        [Route("autocomplete")]
        public AutoCompleteSuggestionList Autocomplete(string query)
        {
            if (string.IsNullOrEmpty(query))
                return null;

            return _searchService.AutoCompleteTerm(query);
        }
    }
}


Now, on your View you will have something that calls your WebApi:

            $(document).ready(function () {
                $("#keyword.autocomplete").autocomplete({
                    serviceUrl: '/webapi/v1/search/autocomplete'
                });
            });


Make sure your route will resolve and Sitecore will not resolve your WebAPI route. For that you could use the Attribute routing as per the really nice article from Bart Bovendeerdt on the following link: http://wp-bartbovendeerdtcom.azurewebsites.net/sitecore-8-webapi-v2-mvc-and-attribute-routing/

Then on your service level, you will then retrieve your list of suggestion through Sitecore Content Search - here would be a quick draft...

        public AutoCompleteSuggestionList AutoCompleteTerm(string term)
        {
            var result = new AutoCompleteSuggestionList();
            var list = new List();

            if (String.IsNullOrEmpty(term))
                return result;

            result.query = term;

            var sitecoreService = new SitecoreService(_databaseContext.GetDatabaseName());
            var home = sitecoreService.GetItem(_siteContext.GetStartPath());
            var index = _contentSearchContext.GetIndex(new SitecoreIndexableItem(home));

            using (var context = index.CreateSearchContext(SearchSecurityOptions.DisableSecurityCheck))
            {
                var suggestionsKeywords = context.GetTermsByFieldName("keywords", term);
                foreach (var suggestion in suggestionsKeywords 
                {
                    list.Add(suggestion.Term);
                }
            }
            if (list.Count <= 0)
            {
                result.suggestions = list;
                return result;
 
            }

            result.suggestions = list.Distinct().Take(10).ToList();
            return result;
        }
    }

9 July 2015

Sitecore 8 Upload Zip File with Unpack options broken

Interresting topic for the day. I went on a client side for a training today and I show Sitecore capability for batch upload. Which I used quite all the time on Sitecore 7. So quite confidently, I decided to go ahead and demo it on the fly. Well unfortunately for me this did not go as planned...

I prepared my zip files with the different images, folder and subfolders



Went to the media library to upload the Zip file


Selected the unpack option


And... quite abbarassing


Well this has been reported as a bug and the workaround has been provided. If you face the same issue, you will need to edit the content of the MediaFolder.js file in the location /Website/Sitecore/Shell/Applications/Media/MediaFolder 
replace the line 320 with the following:



 //Sitecore.Support.439231
    this.yUploader.upload(file, this.destination, params);
 //Sitecore.Support.439231
 


12 June 2015

Experience Editor Edit Frame Button

On a recent project using Sitecore 8, we have extensively used the Experience Editor, which I have to say was really nice to work with. As usual, we have used MVC along side with GlassMapper. For one of our template page, we had some of the fields that needed to be editable but should not show on the page. This is where WebEdits frame buttons came quite handy. That is something you would have never used if you are not using the Experience editor.

So what is the web edit frame buttons. Well those are the buttons that appears when you select an element on your experience editor and allows you to edit either external elements and/or fields that are not displayed on your page. They appears as per the below:


Once you click on the button then you will see a new popup to edit whatever fields you want on the item you want:


To configure it is quite simple:

Go to the Core Database and locate the WebEdit section: 


Locate the folder called Edit Frame Buttons (/sitecore/content/Applications/WebEdit/Edit Frame Buttons), this will give you a good sample of the buttons, especially the Edit one.

You can duplicate the folder and rename it as you would like. Then on the edit button you will be able to edit the following field:


You will notice that on the "Fields" field you will be able to add the "|" separated list of the fields you want to allow user to edit on the frame as per the below:


After defining the fields list available on your Edit button, you will need to do a bit of coding to ensure the webedit frame appears on the Experience Editor. 

1- go to your view rendering and locate the section where you want the frame to appear
2- add the following using:

    using (BeginEditFrame(XXX.Common.Constants.ButtonItemPaths.Common.AddThisButtons, Model.Path))
    {
        
[+ Edit Add This for the Page]
}

The first parameter of your BeginEditFrame will be the path to the Folder you created on the Core DB:
XXX.Common.Constants.ButtonItemPaths.Common.AddThisButtons

will be something like
/sitecore/content/Applications/WebEdit/XXX/Edit Frame Buttons/Common/Add This Buttons

The second Parameter will be the path to the item you would like to edit:
Model.Path

will be something similar to
/sitecore/content/XXX/Home/About Us

There you go... you now have your Webedit buttons on your experience editor.

29 May 2015

Sitecore MVC and Castle.Windsor

I just wanted to add do a quick post today about Castle.Windsor, Sitecore MVC and your own project solution. I am usually using Castle.Windsor as IOC. I am quite sure a few of us encountered the following issue when running Sitecore some application on the Sitecore Client:




4976 16:45:28 ERROR Application error.
Exception: Sitecore.Mvc.Diagnostics.ControllerCreationException
Message: Could not create controller: 'Media'. 
The current route url is: 'api/sitecore/{controller}/{action}'. 
Source: Sitecore.Mvc
   at Sitecore.Mvc.Controllers.SitecoreControllerFactory.CreateController(RequestContext requestContext, String controllerName)
   at System.Web.Mvc.MvcHandler.ProcessRequestInit(HttpContextBase httpContext, IController& controller, IControllerFactory& factory)
   at System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, Object state)
   at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
   at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
 
Nested Exception
 
Exception: Castle.MicroKernel.ComponentNotFoundException
Message: No component for supporting the service Sitecore.Controllers.MediaController was found
Source: Castle.Windsor
   at Castle.MicroKernel.DefaultKernel.Castle.MicroKernel.IKernelInternal.Resolve(Type service, IDictionary arguments, IReleasePolicy policy)
   at DPE.Business.DI.WindsorControllerFactory.GetControllerInstance(RequestContext requestContext, Type controllerType) in r:\Projects\XXX\Trunk\XXX.Business\DI\WindsorControllerFactory.cs:line 34
   at System.Web.Mvc.DefaultControllerFactory.CreateController(RequestContext requestContext, String controllerName)
   at Sitecore.Mvc.Controllers.SitecoreControllerFactory.CreateController(RequestContext requestContext, String controllerName)

The issue there is if you are defining your IOC, you may need to ensure that This is resolving your Sitecore controllers correctly as well. here is the few controller I needed on my Initialisation:

            container.Register(Classes.FromAssemblyNamed("Sitecore.Speak.Client").BasedOn().LifestylePerWebRequest());
            container.Register(Classes.FromAssemblyNamed("Sitecore.Mvc").BasedOn().LifestylePerWebRequest());
            container.Register(Classes.FromAssemblyNamed("Sitecore.Mvc.DeviceSimulator").BasedOn().LifestylePerWebRequest());
            container.Register(Classes.FromAssemblyNamed("Sitecore.Marketing.Client").BasedOn().LifestylePerWebRequest());
            container.Register(Classes.FromAssemblyNamed("Sitecore.Client.LicenseOptions").BasedOn().LifestylePerWebRequest());

5 May 2015

Passing Invalid ID in the Rendering Datasource Break your page

Using Sitecore 8 MVC and GlassMapper has been awesome (Thanks to Mike Edwards great work). This is one of the must have for Sitecore MVC. I usually have my Rendering Datasource field pointing at a "Data" Item on the tree:


On the View code I can then bind my view with the Model using GlassMapper as per the following Code:

@inherits Glass.Mapper.Sc.Web.Mvc.GlassView<DPE.Data.Interfaces.SiteSettings.ISiteSettings>

I like to use Interfaces when defining the Models...

While this is great and working correctly when all is setup correctly, I have had a small issue on a latest project where editors deleted the datasource item without removing the links. Which left the Presentation of the item with a broken link:



Although this was quite simple to fix and instruct the client to select the option to either remove the links and/or editing the links manually. This was pointing at another case scenario:

what if you forgot to publish the data source item... Indeed in this situation you will end up with the same broken link in your datasource field on the web database and it will break the page with the following error:



The issue there is that sitecore is trying to create the default model during the pipeline action:

Sitecore.Mvc.Pipelines.Response.GetModel.CreateDefaultRenderingModel, Sitecore.Mvc

While trying to implement a custom action to replace the above pipeline, I found this great post from Hiral Desai. Really great source of information to implement the way around the issue:

The work around was quite simple: replacing the GetViewRenderer pipeline action. In order to do that we need to add the following config entry in our custom patch config files:


  < sitecore>
    
      
        
             
    
  

Then on the code side, we need to implement our new pipeline acttion with the following code:
public class GetViewRendererWithItemValidation : GetViewRenderer
    {        
        protected override Renderer GetRenderer(Rendering rendering, GetRendererArgs args)
        {           
            var viewRenderer = base.GetRenderer(rendering, args) as ViewRenderer;
            if (viewRenderer == null)
                return null;

            // Ignore item check when in page editor
            // Also this will break if the item for the datasource has been deleted without removing the link.
            if (Context.PageMode.IsPageEditor || Context.PageMode.IsPageEditorEditing)
                return viewRenderer;

            // Override renderer to null when there is an unpublished item refererenced by underlying view
            return viewRenderer.Rendering.Item != null && viewRenderer.Rendering.RenderingItem.InnerItem != null
                ? viewRenderer
                : null;
        }
    }

Hoping that will help anyone.


17 April 2015

Sitecore 8 Experience Editor Nav Bar disappear when scrolling

One weird issue we had today, which I am surprise we did not picked it up earlier is:

Where is the Children on the Nav Bar??
when opening the page and try to navigate, it all looks fine:

But then when starting to scroll down the page then trying to navigate again... it all disappear:


Well that is caused by the breadcrumb beeing positioned absolute instead of fixed. It has been reported as a bug with the reference 428971 and the workaround provided for the issue is to update 2 files;

\Website\sitecore\shell\client\Sitecore\Speak\Ribbon\Controls\Breadcrumb\Breadcrumb.js

\Website\sitecore\shell\client\Sitecore\Speak\Ribbon\Controls\LargeDropDownButton\LargeDropDownButton.js



For whoever needs this fix, please let me know and I will send you those 2 files...

Cheers