Wednesday 4 May 2016

Sitecore Custom Editor - Jump to Datasource

I recently worked with a client who was very new to Sitecore and CMSs in general, and was struggling to get their head around the concept of a component (with a datasource) being shown on the page, so that the content was not coming from the page item itself, but from the datasource content. To help them out I decided to look into whether I could add a custom tab which would show a preview of the page (yes, there is already a preview editor) but with the components with datasources highlighted (in a coloured border) and a clickable link to the datasource itself.

The new editor


First things first, we'll need a custom editor! There are a few blog entries already on how to do this, and it's very straightforward.  Now let's have a look at what we need to put in our new editor's aspx file.

As I mentioned, there's already a preview editor, but can we reuse or extend that?  Having a look at the item (in the core db) /sitecore/content/Applications/Content Editor/Editors/Layouts/Preview we can see the relevant page that is being shown is /sitecore/shell/~/xaml/Sitecore.Shell.Applications.ContentEditor.Editors.Preview.aspx which actually corresponds to the file sitecore\shell\Applications\Content Manager\Editors\Preview\Preview.xaml.xml which references the class Sitecore.Shell.Applications.ContentEditor.Editors.Preview.PreviewPage in the Sitecore.Client assembly.

Now I know how to read XML and XSLT, but I know I certainly prefer to write .NET, so the first thing I did was copy the necessary code over to C#.  I've created a gist for the CustomEditor.aspx and CustomEditor.aspx.cs files, which are based on the original PreviewPage, but not identical - I only included the code I deemed necessary, and on line 109 of the .cs file I have added a custom URL parameter so that we can identify that the page is being viewed in our custom editor: urlString["custom_editor"] = "true";

At this stage we can add our custom "editor" to the page and see a preview.

Highlighting the datasources


My plan to highlight the datasources and turn them into a link was to wrap the relevant renderings in a
<div class="rendering" data-datasource-id="{the-datasource-id}"> </div>

and then use javascript/CSS to highlight and make the region clickable (opening the datasource item in the content editor).

Fortunately wrapping a rendering is quite simple.  A quick Google search sent me to a page on ctor.io where I found exactly what was needed, except that the Title field in that example was changed to ID so that it can be used in the data- attribute of the HTML above.  We also only want to wrap the renderings in this way when we're in our custom editor, so we need to check the URL and ensure it contains our "custom_editor" parameter (above).
public class RendererWrapper : GetRendererProcessor
{
  public override void Process(GetRendererArgs args)
  {
    // Make sure the page is being viewed in the correct place
    if (Context.GetSiteName() == "shell") return;
    if (!Context.RawUrl.Contains("custom_editor")) return;
    if (!(args.Result is ViewRenderer)) return;

    if (args.Rendering == null || !ID.IsID(args.Rendering.DataSource)) return;

    Item dataSourceItem = args.PageContext.Database.GetItem(args.Rendering.DataSource);
    if (dataSourceItem == null) return;

    // Add wrapper rendering
    WrapperModel model = new WrapperModel
    {
      Renderer = (ViewRenderer)args.Result,
      Id = dataSourceItem.ID
    };

    args.Result = new ViewRenderer
    {
      Model = model,
      Rendering = args.Rendering,
      ViewPath = "/Views/Common/RenderingWrapper.cshtml"
    };
  }
}
public class WrapperModel
{
  public ViewRenderer Renderer { get; set; }
  public ID Id { get; set; }
}

and our RenderingWrapper.cshtml:
@model Sitecore.Common.Website.Enhancements.WrapperModel

<div class="rendering" data-datasource-id="@Model.Id">
    @Html.Partial(Model.Renderer.ViewPath, Model.Renderer.Model)
</div>

and of course we need to include the processor in the pipeline:
<pipelines>
  <mvc.getRenderer>
    <processor 
      patch:after="processor[@type='Sitecore.Mvc.Pipelines.Response.GetRenderer.GetViewRenderer, Sitecore.Mvc']"
      type="Sitecore.Common.Website.Enhancements.RendererWrapper, Sitecore.Common.Website"/>
  </mvc.getRenderer>
</pipelines>

at this point when we preview the page in our custom editor, our renderings which have datasources should be wrapped in our new div tag.  A little CSS for divs with the class "rendering" can quickly add a coloured border and turn the cursor in to a pointer.

Linking it together


The final step is to make the divs clickable - ie. opening the selected datasource in the Content Editor.  Digging around I found that the way, in Content Editor, to open an item using javascript is scForm.postEvent("", "", "item:load(id={item-id},language=en)"), where item-id is the ID of the item to open.  The main issue is that our page is being displayed in an IFrame, which is displayed in an IFrame (the custom editor tab) which is in the Content Editor frame; so the event needs to propagate from the page up 2 levels to the Content Editor where it should call the aforementioned postEvent() method.  Not the biggest issue in the world, we can use the javascript parent variable to access the parent frame.

So on our page we have the click handler:
$(function () {
  $('.rendering').click(function (e) {
    if (parent != null) {
      e.preventDefault();
      parent.renderingClicked($(this).data('datasourceId'));
    }
  });
});

which calls the handler in our custom editor .aspx (in the gist above)
function renderingClicked(datasourceId) {
  parent.scForm.postEvent("", "", "item:load(id=" + datasourceId + ",language=en)");
}

which tells the Content Editor to display our item!

No comments:

Post a Comment