Thursday, 10 December 2020

Custom Header Icon for your items?

Custom Header Icon for your Items?

On many occasions I have thought it would be nice to be able to display a different icon in the header of an item, rather than the one chosen for the template.  For example you might want a different icon based on the some status or perhaps an image relating to the item context.  For example a head-shot of a member of staff.

In this example I will show you how I change the header icon based on an image field on that item.  But hopefully you will see that the possibilities are endless.

The example I am using is based on an import I built, that created faculty/staff items in the content tree, for an educational institution.  I thought it would be a nice touch to display the faculty member's image as the header icon, if there was an image attached to the item.

What am I changing?

This processor will switch out the icon shown top left of the image below with a custom image:


And the end result:
 

How do you do it?

We need to add our pipeline processor to the Render Content Editor Header pipeline, which needs to come before the Add Title pipeline processor:
<pipelines>
	<renderContentEditorHeader>
		<processor type="MyProject.Feature.Image.Processors.GetEditorIcon, MyProject.Feature.Image"
		           patch:before="processor[@type='Sitecore.Shell.Applications.ContentEditor.Pipelines.RenderContentEditorHeader.AddTitle, Sitecore.Client']" resolve="true" />
	</renderContentEditorHeader>
</pipelines>
And here's the custom processor:
namespace MyProject.Feature.Image.Processors
{
	public class GetEditorIcon
	{
		private readonly ISitecoreService _sitecoreService;

		public GetEditorIcon(ISitecoreServiceFactory sitecoreServiceFactory)
		{
			_sitecoreService = sitecoreServiceFactory.CreateInstance();
		}

		public void Process(RenderContentEditorHeaderArgs args)
		{
			Assert.ArgumentNotNull(args, "args");
			var item = args.Item;
			using (var htmlTextWriter = new HtmlTextWriter(new StringWriter()))
			{
				var imageBuilder = new ImageBuilder();

				if (item.TemplateID == Constants.MemberDetailsPage)
				{
					var provider = _sitecoreService.GetItem<FacultyStaffMember>(item.ID.Guid);
                    
					if (!string.IsNullOrEmpty(provider?.FacultyMemberProfileImage?.Src))
					{
						var src = provider.FacultyMemberProfileImage.Src;

						var urlString =
							new UrlString(src + "?height=32&width=32")
							{
								["rev"] = item[FieldIDs.Revision],
								["la"] = item.Language.ToString()
							};
						imageBuilder.Src = urlString.ToString();
						imageBuilder.Class = "scEditorHeaderIcon";
						htmlTextWriter.Write("<span class=\"scEditorHeaderIcon\">");
						htmlTextWriter.Write(imageBuilder.ToString());
						htmlTextWriter.Write("</span>");

						//Remove other icon added
						args.Parent.Controls.Clear();
						args.EditorFormatter.AddLiteralControl(args.Parent, 
                        	                    htmlTextWriter.InnerWriter.ToString());
					}
				}
			}
		}
	}
}
The process method does the following:
  • Checks the template of the item to ensure that it is using the template we are wanting to update.
  • We then get the item (in this case using GlassMapper), but you could just use the Sitecore item
  • We then build the image HTML and replace the Sitecore one in the header

This processor gets called every time the item is refreshed in the Content Editor.

Thursday, 12 November 2020

Why I use a Mac for development

Why I use a Mac for Sitecore development.

This may seem a little odd but please hear me out...
 
For the past 10 years, or so I have been doing Windows .Net development on a Mac of one sort or another.  It started because I wanted to be able to continue developing for clients who forced me to be on a VPN and still be able to get my mail, use communication tools etc., without having to come off the VPN.  At that time running VMs on a Windows laptop was unheard of and so I invested in a Mac Book Pro and Parallels desktop.  

With each release of Parallels you can find that some issues previously fixed start to reappear, but over time I still find it the best platform to use.
 
It becomes especially important in that it allows me to have VMs for each of my clients.  With some clients having more than one; depending on the number of projects and even the number of branches in the code, etc.
 
One of Parallels best features is coherence mode where you can run multiple VMs (depending on how much memory you have) at the same time and have the VM's desktop and windows integrated into the Macs, allowing for seamless switching between Mac and Windows applications.  Coherence also allows you to use all your screens with your VMs.
 
This approach seems to have been born out as the right way to go, especially now as Sitecore installations become more complicated (Docker, SXA, Commerce, etc) and quite often the easiest way to get someone up and running is to share a VM.  It's also useful to keep older VMs lying around as they often contain pieces of code that you can reuse (being able to actually view them running is great).  And of course being able to partition the work you do for clients has many benefits, from when you have to demo through to ensuring that you can have the correct versions of the component that the project needs without interference or breakage from other projects (Solr is a good example of this).

Obviously if most of your colleagues are using Windows Hyper-V to host VMs, then you will need to convert the VM, and unfortunately there is no out of the box way to do that with Parallels (not sure why), but that's now relatively straight forwards to do and just requires a bit of googling.


I would highly recommend that for any new projects, you use some sort of VM system to contain your project, website and development resources.

Thursday, 5 March 2020

Error when using ajax.js (Commerce) and JSON.parse

I found an issue today with the ajax.js file that comes with Sitecore Commerce in Sitecore 9.2.  

I was trying to get a third party js component (Chute) working in Sitecore which loads scripts dynamically, but the component wouldn't display any content.  This particular component displays images in either a carousel or grid.  But after the initial load nothing appeared except for an error in my browser console:

TypeError: data is null  ajax.js:  156:24


This same component worked without any issue, in a non-commerce environment.

After some investigations, it became apparent that the issue was down to the fact that somewhere in the Js files supplied with Commerce the standard JSON.parse function was replaced with a custom function. 

Unfortunately it was missing a null check inside the new function.  I know that as developer we all do this...  However, I wanted to highlight the issue, to save someone some grief when trying to figure out what is going on...

The file in question can be found in the Sitecore content tree here: /sitecore/media library/Base Themes/Commerce Services Theme/Scripts/ajax

Once you download the media content you will get a script called ajax.js.

The offending code @ line 156, can be found just after the JSON.parse equals statement in the block of code shown below:

(function (root, factory) {
    'use strict';
    if (typeof define === 'function' && define.amd) {
      // use AMD define funtion to support AMD modules if in use
      define(['exports'], factory);
    } else if (typeof exports === 'object') {
      // to support CommonJS
      factory(exports);
    }

    // browser global variable
    var AjaxService = {};
    root.AjaxService = AjaxService;
    factory(AjaxService);
  })(this, function (AjaxService) {
    'use strict';

    // Hack >>
    // When debugging is on, Sitecore is returning extra information, we need to strip it from the JSON string
    var jsonParseRef = JSON.parse;

    JSON.parse = function (data) {

      var debugIndex = data.indexOf('<div title="Click here to view the profile."');
      if (debugIndex > 0) {
        data = data.substring(debugIndex, 0, debugIndex);
      }

      return jsonParseRef(data);
    };



You will see in the above function that JSON.parse is updated with a new function, unfortunately the data variable is not checked for null before being used.

Changing the JSON.parse function to: 

JSON.parse = function (data) {

      if(data==null)return null;

      var debugIndex = data.indexOf('<div title="Click here to view the profile."');

      if (debugIndex > 0) {
        data = data.substring(debugIndex, 0, debugIndex);
      }

      return jsonParseRef(data);
    };

Fixes the issue.

Hopefully this might help someone.

Friday, 31 January 2020

TDS Model Inheritance

Team Development for Sitecore Model Inheritance

For a long time now I have been trying to find a way to have TDS models inherit across the Helix architecture.

I found this link on StackOverflow:

https://stackoverflow.com/questions/31711197/sitecore-tds-multi-project-properties-base-template-reference-not-working-for-me

And thought it worth re-posting - Thanks to Rohan for figuring out how to do this!

All you have to do is to update Helpers.tt in the TDS project that needs to inherit base, as follows:


public static string GetNamespace(string defaultNamespace, SitecoreItem item, bool includeGlobal = false)
{
    List namespaceSegments = new List();
    // add the following line
    namespaceSegments.Add(!item.ReferencedItem ? defaultNamespace : "[BaseProjectNameSpace]");
    // remove this line - namespaceSegments.Add(defaultNamespace);
    namespaceSegments.Add(item.Namespace);
    string @namespace = AsNamespace(namespaceSegments); // use an extension method in the supporting assembly

    return (includeGlobal ? string.Concat("global::", @namespace) : @namespace).Replace(".sitecore.templates", "").Replace("_", "");
} 

Make sure that you replace the above [BaseProjectNameSpace] with your base projects namespace. 

Also make sure you update the Multi-Project  properties for your TDS project so that it references the base project: