Embed Renderings (Maps, Media, etc.) in Sitecore Rich Text Editor Fields

Introduction

This post describes one technique that you can use the Sitecore Web Content Management System to embed rendering in Rich Text Editor (RTE) fields. CMS users can position videos, maps, Flash resources, and other multimedia and dynamic items using the WYSIWYG editor. A snippet in the RTE allows the CMS user to insert markup representing a rendering control, and the renderField pipeline processes controls in RTE field values.

Embedding renderings in the RTE fields violates one of the primary principles of Web content management, which is to separate content from presentation. Neither Sitecore nor I explicitly support this enhancement, which could have unpredictable effects. I published this blog post without any significant testing, and only with XSL and Web controls (not sublayouts, especially considering code-behind). See the Additional Considerations at the end of this post before choosing to implement this technique.

Someone asked about this after my presentation at dreamcore 2010.

Check the HtmlEditor.SupportWebControls Setting

The first step is to ensure that the value of the HtmlEditor.SupportWebControls setting in the web.config file is true, which is the default for this setting.

Create a Media (or Any Other) Rendering

Create a rendering that outputs the required markup, such as the code to embed YouTube video or any other type of media. This rendering should typically retrieve all of the data it needs from its data source, such as the media file to render. I used the YouTube Integration Shared Source module, which most importantly gives me the URL of a flash resource in a field named Url. The base WebControl class exposes Width and Height properties.

Here is my prototype Web control:

namespace Sitecore.Sharedsource.Web.UI
{
  using System;
  using System.Web.UI;

  public class YouTube : Sitecore.Web.UI.WebControl
  {
    protected override void DoRender(HtmlTextWriter output)
    {
      Sitecore.Data.Items.Item youTube = this.GetItem();

      if (youTube == null
        || youTube.TemplateName.ToLower() != "youtube video"
        || String.IsNullOrEmpty(youTube["url"]))
      {
        return;
      }

      string markupTemplate = @"<object width=""{0}"" height=""{1}"">"
                              + @"<param name=""movie"" value=""{2}""></param>"
                              + @"<param name=""allowFullScreen"" value=""true""></param>"
                              + @"<param name=""allowscriptaccess"" value=""always""></param>"
                              + @"<embed src=""{2}"" type=""application/x-shockwave-flash"""
                              + @" allowscriptaccess=""always"" allowfullscreen=""true"""
                              + @" width=""{0}"" height=""{1}""></embed></object>";
      string markup = String.Format(
        markupTemplate,
        Sitecore.StringUtil.GetString(this.Width, "480"),
        Sitecore.StringUtil.GetString(this.Height, "385"),
        youTube["url"]);
      output.WriteLine(markup);
    }
  }
}

If needed, add the appropriate tag prefix to the web.config file. For example, I added the following within the /configuration/system.web/pages/controls element in my web.config file:

<add tagPrefix="ss" namespace="Sitecore.Sharedsource.Web.UI" assembly="assembly"/>

You can read more about ASP.NET tag prefixes in the Presentation Component Cookbook on the Sitecore Developer Network.

Register the Rendering Snippet

You can create a snippet to allow CMS users to insert the rendering in RTE fields. You can read more about snippets in the Client Configuration Cookbook on the Sitecore Developer Network.

To enable snippet in the default RTE profile:

  1. In the Core database, copy the /Sitecore/System/Settings/Html Editor Profiles/Rich Text Full/Toolbar 1/Insert Snippet item to /Sitecore/System/Settings/Html Editor Profiles/Rich Text Default/Toolbar 1. To control who can insert snippets, you can apply access rights to the Insert Snippet command in the RTE to control who can insert snippets.
  2. Within the /Sitecore/System/Settings/Html Editor Profiles/Rich Text Default item, insert an item named Snippets using the Common/Folder data template.
  3. In the new /Sitecore/System/Settings/Html Editor Profiles/Rich Text Default/Snippets item, assign insert options to allow the user to create items using the System/Html Editor Profiles/Html Editor Snippet data template.

To create a snippet:

  1. Use the Developer Center to drag the rendering onto a scratch sublayout or layout.
  2. After adding, updating, or removing attributes as required, copy the control markup to the Windows clipboard.
  3. In the Core database, under the /Sitecore/System/Settings/Html Editor Profiles/Rich Text Default/Snippets item, insert a snippet definition item named after the rendering (for example, YouTube) using the System/Html Editor Profiles/Html Editor Snippet data template. To control who can insert the individual snippet, you can apply access rights to the snippet definition item.
  4. In the snippet definition item, set the Header field in the Data section to the text that the user should click to insert the snippet, such as the name of the rendering (YouTube).
  5. In the snippet definition item, set the value of the Value field in the Data section of the snippet definition item to the markup previously copied from the Developer Center (<ss:youtube runat="server" />).

The appropriate Value to embed my YouTube rendering is as follows:

<ss:youtube runat="server" />

The following screen shot of the RTE shows what it looks like to use the snippet (the arrow to the right of the second toolbar command from the right):

The following screen shot of the RTE shows a rendering in the RTE with no parameters.

The renderField Pipeline

Sitecore uses the renderField pipeline to transform field values to runtime markup. You can add a processor to the renderField pipeline to transform renderings embedded in RTE fields to markup. Here is my processor prototype:

namespace Sitecore.Sharedsource.Pipelines.RenderField
{
  using System;

  public class GetRTEFieldValue
  {
    public void Process(Sitecore.Pipelines.RenderField.RenderFieldArgs args)
    {
      Sitecore.Diagnostics.Assert.ArgumentNotNull(args, "args");
      Sitecore.Diagnostics.Assert.ArgumentNotNull(args.FieldTypeKey, "args.FieldTypeKey");

      if (args.FieldTypeKey != "rich text"
        || String.IsNullOrEmpty(args.FieldValue)
        || Sitecore.Context.PageMode.IsPageEditorEditing
        || !Sitecore.Configuration.Settings.HtmlEditor.SupportWebControls)
      {
        return;
      }

      this.TransformWebControls(args);
    }

    protected void TransformWebControls(Sitecore.Pipelines.RenderField.RenderFieldArgs args)
    {
      System.Web.UI.Page page = new System.Web.UI.Page();
      page.AppRelativeVirtualPath = "/";
      System.Web.UI.Control control = page.ParseControl(args.Result.FirstPart);
      args.Result.FirstPart = Sitecore.Web.HtmlUtil.RenderControl(control);
    }
  }
}

I added this processor to the renderField pipeline definition in web.config immediately after the default GetDocxFieldValue processor using 6.2.0 100104:

<renderField>
  <processor type="Sitecore.Pipelines.RenderField.SetParameters, Sitecore.Kernel"/>
  <processor type="Sitecore.Pipelines.RenderField.GetFieldValue, Sitecore.Kernel"/>
  <processor type="Sitecore.Pipelines.RenderField.ExpandLinks, Sitecore.Kernel"/>
  <processor type="Sitecore.Pipelines.RenderField.GetImageFieldValue, Sitecore.Kernel"/>
  <processor type="Sitecore.Pipelines.RenderField.GetLinkFieldValue, Sitecore.Kernel"/>
  <processor type="Sitecore.Pipelines.RenderField.GetInternalLinkFieldValue, Sitecore.Kernel"/>
  <processor type="Sitecore.Pipelines.RenderField.GetMemoFieldValue, Sitecore.Kernel"/>
  <processor type="Sitecore.Pipelines.RenderField.GetDateFieldValue, Sitecore.Kernel"/>
  <processor type="Sitecore.Pipelines.RenderField.GetDocxFieldValue, Sitecore.Kernel"/>

  <processor type="Sitecore.Sharedsource.Pipelines.RenderField.GetRTEFieldValue, assembly"/>

  <processor type="Sitecore.Pipelines.RenderField.AddBeforeAndAfterValues, Sitecore.Kernel"/>
  <processor type="Sitecore.Pipelines.RenderField.RenderWebEditing, Sitecore.Kernel"/>
</renderField>

Rendering Parameters

The most important parameter for the media rendering is its data source. The CMS user can double-click on a rendering in the RTE field to access rendering properties:

To select a data source for the rendering, the user clicks Browse. The data source item should contain field values to meet the requirements of the media rendering.

By default, the user interface control that allows the user to select a data source is rooted at the /Sitecore/Content item. If your users need to select an item from another branch, such as the media library, then you will have to override this UI. To do so:

  1. Copy the file /sitecore/shell/applications/item browser/item browser.xml to create /sitecore/shell/override/item browser.xml.
  2. In /sitecore/shell/override/item browser.xml, set the Root attribute to the /control/ItemBrowser/FormDialog/DataContext element with value “/sitecore”:

<DataContext ID="DataContext" Root="/sitecore" />

If you know that the user must select a media item as the data source, then you could set Root to /sitecore/media library, but you may subsequently find that other applications that use this user interface expect the default. So /sitecore might be the most appropriate value for the Root attribute.

In rendering properties, the user can click the Attributes tab to specify rendering parameters. For example, the prototype I wrote supports Height and Width parameters.

Here’s a screen shot of an RTE field after selecting a data source and entering height and width parameters for two instances of the YouTube rendering:

Here is the item containing that field rendered within the Page Editor:

Additional Considerations

Be careful not to embed a rendering that invokes the renderField pipeline for RTE fields or you could end up in an infinite loop.

Renderings embedded in RTE fields do not experience the full ASP.NET component lifecycle. Most solutions invoke the renderField pipeline at the rendering stage in the ASP.NET page lifecycle. Renderings that you embed in RTE fields should only implement functionality within the rendering stage of the ASP.NET page lifecycle.

You might need to generate different markup for different devices.

Consider whether you can or should cache the output of renderings that output the values of RTE fields that contain renderings, and/or whether you can or should cache the output of each of those embedded renderings.

You shouldn’t process renderings in RTE fields while the user is in the Page Editor. To edit RTE fields containing renderings in the Page Editor, CMS users should click the Edit Text command for the field. You won’t see media controls when inline editing in the Page Editor (only when navigating).

If you try to use my example, be sure to publish the YouTube templates and items.

This solution does not support parameters templates – the user must know the names of properties such as Height and Width.

I thought about writing something like a publishItem processor to process Web controls in RTE values and store the result inline after publishing, but I am not sure of the best way to prevent Sitecore from assigning a new revision ID, which would confuse smart publishing. A replacer would probably be the right way. This should reduce publishing efficiency but increase delivery efficiency.

Update: How a Rendering Can Determine Its Order

Someone asked me how the YouTube video could determine its order in the RTE field value. This solution in the DoRender() method of the YouTube Web control seemed to work:

protected override void DoRender(HtmlTextWriter output)
{
  Control parent = this.Parent;
  int position = -1;

  if (parent != null)
  {
    int counter = 0;

    foreach(Control control in parent.Controls)
    {
      if (control is LiteralControl)
      {
        continue;
      }

      counter++;

      if (this == control)
      {
        position = counter;
        break;
      }
    }
  }

The key is to ignore instances of LiteralControl, which represent each HTML or text element in the field value.

Also, I didn’t test this, but to enable a sublayout instead of an Web control, I think the snippet markup would be:

<sc:sublayout runat=”server” path=”/layouts/file.ascx” />

And similar for an XSL rendering:

<sc:xslfile runat=”server” path=”/xsl/file.xslt” />

This entry was posted in Obsolete. Bookmark the permalink.

23 Responses to Embed Renderings (Maps, Media, etc.) in Sitecore Rich Text Editor Fields

  1. kevin says:

    Thanks for posting this. I didn\’t ask the question but when you said it was possible at dream core that stuck in my head and today I plan on trying this out.Thanks again,Kevin

  2. kevin says:

    This worked, the one thing I\’m stuck on is how come you can configure a webcontrol from the html editor but it\’s not supported and we need to add a processor to get it to work?Not complaining but this is pretty useful and it would be very nice to see it fully supported in a future release.A good use I can think of would be for personalizing messages. Have a CustomerName control and now the editors can insert the customer name anywhere in the content. Can be used to call out to other system in the middle of your content too.Thanks again,Kevin

  3. John says:

    Kevin: I will raise the issue with product management, but I suspect Sitecore would not consider this best practice and wouldn\’t bake it into the product due to potential issues (for instance, someone drags in a control that requires the full ASP.NET lifecycle, and doesn\’t work under these conditions). I don\’t think it\’s too much of a customization for the people who need this feature.I personally would rather see it support parameters templates, so I will raise that potential feature as well.

  4. Kyle says:

    Have you tried testing this with a sublayout since posting this? I was able to get this working with a web control, but not with sublayouts.

  5. Yann says:

    That was really helpful. I had this working quite well on 6.2. However, tried it on sitecore 6.4 and does not seems to work. I can see my snippet HTML markup on the HTML view on the editor but cannot click on it to set the datasource…

    • commodore73 says:

      @Yann: I found the same issue with 6.4 and have not been able to resolve it. I suggest filing a case at http://support.sitecore.net to increase the weight behind this issue (the more people that file, the sooner it will be fixed). If you file a case, reference my case #337106. Sorry for the inconvenience.

      • Anthony says:

        @commodore73: Did you get an answer from Sitecore about this ? I can’t find your case in http://support.sitecore.net
        I have to implement exactly the same thing, any help would be really helpful.

        Thanks,
        Anthony

      • commodore73 says:

        For some reason commodore73 can’t reply to Anthony in this chain. I have not received a response from customer service; your comment prompted me to escalate this issue to product management.

  6. Andrew says:

    Hi.

    I’ve been trying to get this to work on 6.2 rev 100507m but the following happens:

    – Sitecore inserts “xmlns:smart=”http://www.sitecore.net/xhtml”” as a property into the markup I placed in the snippet (incidentally, it also breaks the self-closingness, adding a separate closing tag). Removing this has no effect – it is put back in again as soon as the editor is closed.

    – Sitecore shows it as being invalid HTML and the suggested fix is to remove it.

    – Nothing is written to the page, simply an empty space between the paragraphs.

    – It looks absolutely fine in preview mode.

    – In any other case, the breakpoint at the start of the class is not hit – only in preview mode is it hit.

    Do you have any suggestions as to what might be going wrong here?

  7. Samuli says:

    FYI, I also filed a case to http://support.sitecore.net on the erratic behavior on 6.4, referencing the case 337106.

  8. Holy cow. This is really powerful.

    I think this is what I’ve been looking for, but it’s going to take some research.

    Thanks for the YouTubeIntegration module reference, we were using the Video Embed option, and they did not update it for Sitecore 6.4.1 which is really irritating.

  9. commodore73 says:

    I can’t get Sitecore to commit to supporting this feature – product management keeps pointing me at these and saying every customer should implement their own solution:

    http://www.telerik.com/community/forums/aspnet-ajax/editor/embed-youtube-video-in-radeditor.aspx
    http://demos.telerik.com/aspnet-ajax/editor/examples/customdialogs/defaultcs.aspx

    Really sorry about that. I highly recommend that you file a support case with Sitecore AND complain to your local sales rep – Sitecore shouldn’t remove features on which customers depend when upgrading.

    This is one of the few cases where I am really unhappy with Sitecore.

    • JammyKam says:

      Thanks for the info and the tweet John. As briefly mentioned, the issue is that I need it to embed a custom rendering into the RTE. In my case to add in common values in between some text, eg. telephone number. There is some additional logic involved since the value shown may depend on the user logged in, ala personalization.

      I’ve read your other article about Replace Tokens in RTE [http://bit.ly/p09HKg] and think I could use that along with snippets. Of coourse I may well have 30+ tokens as well other snippets, so that clutters up my drop list! Your suggestion for the adserve of including item path in the token is a good one but not very user friendly for the non-technical content editors, unlike the embed option which was much more intuitive IMO.

      The only issue I have with using the renderfield pipeline is concern about performance. Obviously there could be a lot of text in the RTE and a lot of replaceable tokens. Do you know if there is going to be much of a performance hit with this, even with a clever bit of regex, there’s still the possibility of a lot of replacements/lookups.

      thanks

  10. commodore73 says:

    @JammyKam: I think that if a renderField pipeline processor and token replacement can meet your requirements and does not present usability problems, the performance impact should actually be very minimal, especially if you can cache the output of the renderings that retrieve those fields by the fewest possible VaryBy criteria.

    • JammyKam says:

      Thanks, still at early stages and looking at the various caching options that will be needed. Some of the tokens will vary by login (e.g. different telephone number for different customers) so need to think about this more. Shame there isn’t a vary by role option!

      I don’t think the usuability will be as good, I need to look over how many tokens I will need tokenized. I will continue chasing Sitecore for this, since I know for a fact that there a numerous projects out there that use this functionality and it will break code for A LOT of people wishing to upgrade and use the new features.

      Thanks

  11. commodore73 says:

    @JammyKam: I think you *can* build a VaryByRoles option. One trick would be to create a string containing the names of all roles for the user in alphabetic order, so that the string is exactly the same for all users that have exactly the same roles. Then add that to the caching key for that control. This would not address issues where you deny access rights to a user whose roles allow those rights, but you shouldn’t deny access rights to individual users anyway.

    http://www.sitecore.net/Community/Technical-Blogs/John-West-Sitecore-Blog/Posts/2011/05/Custom-Caching-Criteria-with-the-Sitecore-ASPNET-CMS.aspx

    Maybe not as easy as it could be, and certainly not easy to add a checkbox in the VaryBy options in the UI, but possible, and possibly worth it for performance. If you build the infrastructure, you could apply it to any other renderings.

  12. Rick Cabral says:

    John, I’m wondering if the method for embedding controls as “snippets” has changed in Sitecore 6.5 (I’ve been testing in update 4) – With the pipline handler set up, the snippet defined in Core, I can successfully insert the snippet, but the RTE does not render the nice yellow box with all the right-click options I’m expecting.

    • commodore73 says:

      Hi Rick,

      Check the comments on this post – I think Sitecore broke this completely with the RTE updates in 6.4, and now I don’t have a good solution. This has been really upsetting for me and a number of other people who depended on this solution; I don’t know what most people are doing for this now. I strongly recommend that you file a case with Sitecore, and if you get a solution, describe it here.

      -John

      • Rick Cabral says:

        Thanks John, I suspected this, but given the age of this post I figured asking the question was the first inroad. I’ve got it on the SDN forum as well, but I will gladly open a support ticket and something in the MVP forum.

      • JammyKam says:

        I asked the techs here at Sitecore UK at one of the user group meeets, apparently it was “never used” in the field and so it was removed, although I have used it and seen it used in most projects I was work on… I got the feeling that it was not going to be added back in any time soon either!

        The solution… well, not much of a solution really! I was told to have media fields in the item itself which should then be placed in the flow of text as needed. Of course this makes it VERY inflexible when you do not know exactly where to items will be placed or how many times!

        Not sure if it will be possible to write a custom user defined type to do something a bit prettier with some sort of interface.

Leave a reply to commodore73 Cancel reply