Warning your Content Editors

I was recently in a situation where Content Editors could add a widget which needs a datasource, otherwise it would display the wrong information. Even though we’ve had meetings, trainings, wikis and more this is still something that could be forgotten, or the datasource could still be in workflow and therefore not be live yet resulting in incorrect information. Normally, I would suggest not displaying it at all if it’s not there (rather than displaying ‘incorrect’ data), but that would lead to questions about why it’s not there, while on our staging environment it all looks good (in case of Workflow at least). I’m loving the Rules Engine, and figured this would be a good usage of said engine.

Sitecore has a very easy way to display alerts in the Content Editor when an item is selected – Content Editor Warnings. When adding a rule to /sitecore/system/Settings/Rules/Content Editor Warnings/Rules Sitecore will run these rules when you select an item.

Now, on to the new rule. In our case, two new Conditions need to be created. One to check whether the datasource of the widget is empty and one to check whether the item the datasource is linking to is in Workflow.

Checking whether the datasource is set

I’ve created a new Folder called Conditions in /sitecore/system/Settings/Rules/Content Editor Warnings/Conditions, and below that created a new Condition.

The Text of the condition: where the datasource for [RenderingId,Tree,root={EB2E4FFD-2761-4653-B052-26A64D385227},specific] is not set

Where the GUID points to the Layout item. Please note that you can also specify a path if that’s more to your liking – I prefer using GUIDs as those don’t change, while paths may. I’ve also filled the Type field to point to my class and assembly.

Now the code. I’ve created a new class called EmptyDataSourceWarning, and overridden the Execute method:

public class EmptyDataSourceWarning<T> : StringOperatorCondition<T> where T : ContentEditorWarningsRuleContext
{
  public string RenderingId { get; set; }

  protected override bool Execute(T ruleContext)
  {
    Assert.ArgumentNotNull(ruleContext, "ruleContext");
    foreach (RenderingReference reference in ruleContext.Item.Visualization.GetRenderings(Sitecore.Context.Device, false))
    {
      if (base.Compare(reference.RenderingID.ToString(), RenderingId)
      {
        if (string.IsNullOrEmpty(reference.Settings.DataSource))
        {
          return true;
        }
      }
    }

    return false;
  }
}

I think this speaks for itself for the most part – the RenderingId gets passed in from Sitecore (this is the [RenderingId,Tree,root={EB2E4FFD-2761-4653-B052-26A64D385227},specific] bit in the condition).

Because the rendering can be added to the page multiple times I’m using a foreach loop to go through all renderings. If the rendering’s ID and RenderingId match AND the datasource of the rendering is not set, the action needs to be triggered.

For the action I just have the ‘show editor warning’ which comes out of the box with Sitecore.

rule1

Don’t mind the ‘and where true’, which I just used for debugging purposes.

The result:

homedatasourcenotset

In my case there was just one widget that actually needs to have its datasource set, but if all of them need to be set it (or the rule needs to be inverted) that’s easily changed.

Checking the workflow status of the datasource item

Another Condition needs to be created, this time to check the datasource item’s workflow status.

The Text of the Condition: where the datasource item for [RenderingId,Tree,root={EB2E4FFD-2761-4653-B052-26A64D385227},specific rendering] is in workflow

The code:

public class DataSourceItemInWorkflow<T> : StringOperatorCondition<T> where T : ContentEditorWarningsRuleContext
{
  public string RenderingId { get; set; }

  protected override bool Execute(T ruleContext)
  {
    Assert.ArgumentNotNull(ruleContext, "ruleContext");

    foreach (RenderingReference reference in ruleContext.Item.Visualization.GetRenderings(Sitecore.Context.Device, false))
    {
      if (base.Compare(reference.RenderingID.ToString(), RenderingId))
      {
        var item = Factory.GetDatabase("master").GetItem(reference.Settings.DataSource);
        if (item != null)
        {
          IWorkflow wf = Factory.GetDatabase("master").WorkflowProvider.GetWorkflow(item);
          return !wf.IsApproved(item)
        }
      }
    }

    return false;
  }
}

Mind you, this only checks if the latest version is approved, it will not check if there’s an older version that’ll go live, nor does it check things like publishable etc.

I’ve actually created a new rule in the same rule item I already had:

rules

And the result when I do set a datasource, but point to an item which is in workflow, as expected:

homedatasourceworkflow

Of course, this is a reasonably simple example, but it does demonstrate that without too much effort we can improve the warnings to our Content Editors.

Advertisements

Contextual Ribbons In Page Editor – Part 2

While I was creating another Page Editor ribbon  was hoping to find another way of making the ribbons contextual. I did find an easier way to achieve the same goal, which I will explain here.

In the Core database, in /sitecore/content/Applications/WebEdit/Ribbons/WebEdit, I created a new item based on the /sitecore/templates/System/Ribbon/Strip template.

I set the Type field to my custom class (TestApplication.Commands.Strips.TestStrip).

Here’s the code for the class.

namespace TestApplication.Commands.Strips
{
    using System.Web.UI;
    using Sitecore.Data.Items;
    using Sitecore.Diagnostics;
    using Sitecore.Shell.Framework.Commands;
    using Sitecore.Shell.Web.UI.WebControls;
    using Sitecore.Web.UI.WebControls.Ribbons;

    public class TestStrip : RibbonStrip
    {
        public override void Render(HtmlTextWriter output, Ribbon ribbon, Item strip, CommandContext context)
        {
            Assert.ArgumentNotNull(output, "output");
            Assert.ArgumentNotNull(ribbon, "ribbon");
            Assert.ArgumentNotNull(strip, "strip");
            Assert.ArgumentNotNUll(context, "context");

            if (Helper.DescendantOrSelf(context.Items[0], TestConstants.TestID))
            {
                ribbon.RenderChunks(output, strip, context, false);
            }
        }
    }
}

The call to Helper.DescendantOrSelf is a simple test class I created to check whether context.Items[0] is a descendant (or self) of an item with the GUID specified. If that’s the case, the strip will be rendered, otherwise it will not appear.

Easy, no? Sure, it’s theoretically not the same as Contextual Ribbons – you don’t have to set anything in Sitecore, and in this case has hardcoded when to display – but it does do the trick.

While looking for this however, I also noticed (in the Strip item) the ‘Contextual Header’ field. I haven’t been able to find an example of it filled and haven’t yet experimented with it to see how it works. If anyone has any clue or pointers on that, please let me know in the comments.