Adding YouTube Videos to the Sitecore Rich Text field

NOTE: This post has been updated 04-12-2012 with new iOS 5.x compatible object tags.

This post is not only about adding YouTube videos to Sitecore content. It’s actually about replacing content from the Rich Text editor on runtime using the RenderField pipeline.

Have you ever tried to add <object> tags or other content to the Sitecore Rich Text Editor?

Pasting Object HTML directly to the Rich Text Editor

Pasting Object HTML directly to the Rich Text Editor

And have you then experienced that after pasting the code, the Rich Text Editor will produce JavaScript errors:

Java Script Errors

Java Script Errors

Or have you experienced that the Rich Text Editor will remove the <object> tag, leaving the embed tag, making your HTML invalid?

Or is it simply too tricky for the end user to open then HTML and paste in HTML code directly?

There is several solutions to these problems. One of them is to allow the user to write one thing in the editor and display another on the web site. For example, if the user wants to link to a YouTube video, why not just let the user paste a link to the video in the editor. But when the page is rendered, replace the link with the embedded YouTube video player?

I know that there is JavaScript functions to do this, but I would like to create a version that will run without Javascript.

To do this, you need to create a new RenderField processor.

When the replacer is in place you can write an URL to the YouTube video:

Link to YouTube video

Link to YouTube video

And when displayed on the website, the link is replaced with an embedded player:

Displaying embedded YouTube video

Displaying embedded YouTube video

The processor is added to the RenderField pipeline in web.config:

<renderField>
<processor type="Sitecore.Pipelines.RenderField.SetParameters, Sitecore.Kernel" />
...
...
<!-- our processor -->
<processor type="MyCode.Pipelines.RenderField.RenderYouTubeVideo, MyDll"/>
<!-- our processor -->
<processor type="Sitecore.Pipelines.RenderField.AddBeforeAndAfterValues, Sitecore.Kernel" />
<processor type="Sitecore.Pipelines.RenderField.RenderWebEditing, Sitecore.Kernel" />
</renderField>

The YouTube replacer uses the HtmlAgilityPack that is shipped with Sitecore. This pack allows me to traverse the HTML as it were XML.

using System;
using System.Text;
using Sitecore.Text;

namespace Oti.DigiZuite.Renderer.Pipelines.RenderField
{
  public class RenderYouTubeVideo
  {
    public void Process(RenderFieldArgs args)
    {
      Assert.ArgumentNotNull(args, "args");
      Assert.ArgumentNotNull(args.FieldTypeKey, "args.FieldTypeKey");

      // Do not modify output if the field is not a rich text field,
      // or if the page is in page editor mode
      if (args.FieldTypeKey != "rich text" ||
          String.IsNullOrEmpty(args.FieldValue) ||
          Context.PageMode.IsPageEditorEditing)
      {
        return;
      }

      // Load the HTML into the HtmlAgilityPack
      HtmlDocument doc = new HtmlDocument();
      doc.OptionWriteEmptyNodes = true;
      doc.LoadHtml(args.Result.FirstPart);

      // Search for all links
      HtmlNodeCollection aTag = doc.DocumentNode.SelectNodes("//a");
      if (aTag == null || aTag.Count == 0)
        return;

      foreach (HtmlNode node in aTag)
      {
        // Look for links to YouTube
        if (node.Attributes["href"].Value.StartsWith("http://www.youtube.com"))
        {
          // Get the video ID and create an object tag
          UrlString urlString = new UrlString(node.Attributes["href"].Value);
          string videoIdentifier = urlString.Parameters["v"];
          string youtubePlayer = RenderVideo(videoIdentifier, 640, 385);
          node.ParentNode.InnerHtml = node.ParentNode.InnerHtml.Replace(node.OuterHtml, youtubePlayer);
        }
      }
      // Replace the Rich Text content with the modified content
      args.Result.FirstPart = doc.DocumentNode.OuterHtml;
    }

    private string RenderVideo(string videoIdentifier, int width, int height)
    {
      StringBuilder sb = new StringBuilder();
      sb.Append(@"<div class=""Video"">");
      sb.AppendFormat(@"<object width=""{0}"" height=""{1}"">", width, height);
      sb.AppendFormat(@"<param name=""movie"" value=""http://www.youtube.com/v/{0}&color1=0xb1b1b1&color2=0xcfcfcf&hl=en_US&feature=player_embedded&fs=1""></param>", videoIdentifier);
      sb.Append(@"<param name=""allowFullScreen"" value=""true""></param>");
      sb.Append(@"<param name=""allowScriptAccess"" value=""always""></param>");
      sb.AppendFormat(@"<embed src=""http://www.youtube.com/v/{0}&color1=0xb1b1b1&color2=0xcfcfcf&hl=en_US&feature=player_embedded&fs=1"" type=""application/x-shockwave-flash"" allowfullscreen=""true"" allowScriptAccess=""always"" width=""{1}"" height=""{2}""></embed>", videoIdentifier, width, height);
      sb.Append(@"</object>");
      sb.Append(@"</div>");
      return sb.ToString();
    }
  }
}

This is just one example of how to allow users to add one thing and display another. In the upcomming version of DAM for Sitecore, users will, when adding a video to their editor, have an thumbnail image inside the Rich Text editor, but still have a video player on the website. This eliminates the JavaScript errors that pops up when adding <object> tags to the editor.

Sitecore: Linking to Sitecore editor from an .aspx page

This tip comes from Thomas Stern, who got the tip from the Sitecore support team. I am not very well founded in the Sitecore XAML development, so I often find myself using an .aspx page to extend the Sitecore shell.
And sometimes I need my own .aspx page to open the Sitecore editor at a certain Sitecore item. I thought that was impossible, untill Thomas Stern showed me how to do that.

In order to run the Sitecore content editor from an .aspx page, you need to be logged into Sitecore in the shell domain. Then you call the Sitecore client like this:

/sitecore/shell/Applications/Content Manager/Default.aspx?fo={AA7D559F-ABA5-8F6A-ABCD-0ABDF7E74EE1}

This opens the Sitecore editor at the item specified in the “fo” parameter.
There are more parameters:

/sitecore/shell/Applications/Content%20Manager/default.aspx?fo={id}[&la={language}][&vs={version}]&mo=preview

  • fo = The item to open the Sitecore editor at
  • la = The language to open
  • vs = The version to open
  • mo=preview. This parameter removes the content tree at the left side.

To open the Sitecore editor in a new window by clicking a button from your own .aspx page, you can do the following: Create an ordinary asp:Button control and extend the OnClientClick property:

Item myItem = MySitecoreItemToOpen();
myButton.OnClientClick = "javascript:window.open('/sitecore/shell/Applications/Content Manager/Default.aspx?fo=" + myItem.ID + "'); return false;";

 

Save Sitecore Item from .aspx editor

Sitecore is an extendable platform, no doubt about it. For example it allows you to add an external (.aspx) page, to an item:

Advanced Sitecore Google Maps Editor

Advanced Sitecore Google Maps Editor

The page will then show up on your item. But what if you wish to modify the actual Sitecore item from the page? Like in this case:
This is one of the editors for the new Advanced Sitecore Google Maps (WCAG Edition) module. The page allows a user to apply a Google Maps map to a Sitecore item. When the user press “Save map”, the map center and zoom-level is saved to the Sitecore item:

Save map center and zoom to the current Sitecore item

Save map center and zoom to the current Sitecore item

This can only be achieved by hooking into some of Sitecore’s Javascript classes. When the “Save map” button is pressed I hook into the window.parent.scForm Javascript object and call item:load and item:refreshchildren.

All of this can be achieved from code-behind. On the button’s On_Click event I modify the current Sitecore item, and then use RegisterStartupScript to register the Javascript events:

protected void btnSave_Click(object sender, EventArgs e)
{
  // Get the Sitecore item to store map center and zoom level
  Sitecore.Data.Database db = Sitecore.Configuration.Factory.GetDatabase(Request.QueryString["database"]);
  Item item = db.GetItem(Request.QueryString["id"]);
  item.Fields["zoom"] = myZoomLevel;
  item.Fields["center"] = myCenter;
  // Now register the client startup script to be executed
  // When page posts back to save the modified Sitecore item
  SitecoreHelper.RegisterRefreshScript(Page, Request.QueryString["id"]);
}

The final function, SitecoreHelper.RegisterRefreshScript, is my own helper function that will register the startup script:

public static void RegisterRefreshScript(Page page, string id)
{
  StringBuilder message = new StringBuilder();
  message.Append("var parentScForm = window.parent.scForm;" + Environment.NewLine);
  message.Append(string.Format("parentScForm.postRequest('','','','item:load(id={0})');", id) + Environment.NewLine);
  message.Append(string.Format("parentScForm.postRequest('','','','item:refreshchildren(id={0})');", id) + Environment.NewLine);
  if (!page.ClientScript.IsStartupScriptRegistered("RefreshItem"))
  {
    page.ClientScript.RegisterStartupScript(typeof(string), "RefreshItem", message.ToString(), true);
  }
}

Thanks to the Sitecore support team to help me out with this one. BTW: It works with both Sitecore 5 and Sitecore 6.

Follow

Get every new post delivered to your Inbox.

Join 92 other followers