Friday, November 25, 2011

Enable Telerik Rad Editor Lite for FireFox and other than IE browsers in Sharepoint

Sharepoint has own default editor for RichTextField. However if you are not satisfied with the functionality offered by this html editor, there are several other implementations available from other vendors. One of the popular editors for Sharepoint is Telerik Rad Editor Lite. Advantage of this control is that it is free (it was developed by Telerik using agreement with MS). However it works only in IE and according to information from forums from Telerik site there are no plans to support other browsers in free version.

The good news is that even if it is not officially supported it is still possible to show Rad Editor in other than IE browsers (there is not guarantee that all features will work properly in this case, but most of basic features work). We will need little reflection and knowledge of how rendering templates work in Sharepoint.

At first lets check from where problem comes. If you investigate code of RadHtmlListField in Reflector (from RadEditorSharePoint.dll), you will find that it has the following hierarchy:

RadEditor > MOSSRadEditor > RadHtmlListField

RadHtmlListField has property IsSupportedBrowser:

   1: internal bool IsSupportedBrowser
   2: {
   3:     get
   4:     {
   5:         this.CheckBrowserCapabilities();
   6:         return this._isSupportedBrowser;
   7:     }
   8: }

If you will check another class RadEditorRenderer you will find that this property is used in order to determine will Rad Editor will be rendered as html editor or as text area without any toolbars:

   1: private void RenderConditional()
   2: {
   3:     this._editor.Page.VerifyRenderingInServerForm(this._editor);
   4:     if (!this._editor.HasPermission)
   5:     {
   6:         this.RenderNonEditableContent();
   7:     }
   8:     else if (!this._editor.Editable)
   9:     {
  10:         this.RenderNonEditable();
  11:     }
  12:     else if (this._editor.RenderAsTextArea || !this._editor.IsSupportedBrowser)
  13:     {
  14:         this.RenderAsTextArea(this._editor.GetPostbackErrorMessage());
  15:     }
  16:     else
  17:     {
  18:         this.RenderEditable(this._editor.GetPostbackErrorMessage());
  19:     }
  20: }

(see line 12). Let’s check how Rad Editor determines browser capabilities. The logic is implemented in CheckBrowserCapabilities() method which is called from IsSupportedBrowser property (see above):

   1: private void CheckBrowserCapabilities()
   2: {
   3:     if (!this._browserCapabilitiesRetrieved)
   4:     {
   5:         this._browserCapabilitiesRetrieved = true;
   6:         if (base.IsDesignMode)
   7:         {
   8:             this._isSupportedBrowser = true;
   9:             this._isIe = true;
  10:             this._isSafari = false;
  11:         }
  12:         else
  13:         {
  14:             HttpRequest request = this.Context.Request;
  15:             HttpBrowserCapabilities browser = request.Browser;
  16:             if (browser.Browser == "IE")
  17:             {
  18:                 double num = browser.MinorVersion + browser.MajorVersion;
  19:                 this._isSupportedBrowser = num >= 5.5;
  20:                 this._isIe = true;
  21:             }
  22:             else if ((browser.Browser.ToLower() == "opera") &&
  23:                 (browser.MajorVersion == 9))
  24:             {
  25:                 this._isSupportedBrowser = true;
  26:             }
  27:             else
  28:             {
  29:                 string input =
  30:                     (request.UserAgent != null) ? request.UserAgent.ToLower() : "";
  31:                 if (Regex.Match(input, @"rv:((1\.(3|4|5|6|7|8|9|10))|((2|3|4|5)\.\d))",
  32:                     RegexOptions.Compiled | RegexOptions.IgnoreCase).Success)
  33:                 {
  34:                     this._isSupportedBrowser = true;
  35:                 }
  36:                 else if ((input.IndexOf("safari") > 0) && (input.IndexOf("gecko") > 0))
  37:                 {
  38:                     this._isSafari = true;
  39:                     this._isSupportedBrowser = true;
  40:                 }
  41:                 else
  42:                 {
  43:                     this._isSupportedBrowser = false;
  44:                 }
  45:             }
  46:         }
  47:     }
  48: }

This method is the root of all problems. When you use last version of FireFox (I used FF 8) request.Browser contains “Firefox” and user agent contains “2 mozilla/5.0 (windows nt 5.2; rv:8.0) gecko/20100101 firefox/8.0”. In older FF versions (I tested with 3.6) Rad Editor worked – so it passed another values in specified fields (I didn’t check what exact values before to update it to version 8).

Now when we know the problem let’s find solution. We need to check if browser is Firefox and if yes set _isSupportedBrowser field to true. This field is private so we can’t just inherit the RadHtmlListField class and modify it. We need to use reflection. I created the following helper class:

   1: public static class ReflectionHelper
   2: {
   3:     public static void SetFieldValue(object obj, string name, object value)
   4:     {
   5:         BindingFlags bf = 0 | BindingFlags.Instance | BindingFlags.Static |
   6:             BindingFlags.Public | BindingFlags.NonPublic;
   7:         FieldInfo fi = obj.GetType().BaseType.BaseType.BaseType.FindMembers(MemberTypes.Field,
   8:             bf, Type.FilterName, name)[0] as FieldInfo;
   9:         fi.SetValue(obj, value);
  10:     }
  11: }
 
Now we need to create custom class – inheritor of RadHtmlListField and override OnLoad() method. In the overridden version we will check is the browser is Firefox and if yes – will set private fields by reflection:
 
   1: public class AllBrowsersHtmlListField : RadHtmlListField
   2: {
   3:     private void checkForFirefox()
   4:     {
   5:         HttpRequest request = HttpContext.Current.Request;
   6:         HttpBrowserCapabilities browser = request.Browser;
   7:         if ((browser.Browser.ToLower() == "firefox"))
   8:         {
   9:             ReflectionHelper.SetFieldValue(this, "_browserCapabilitiesRetrieved", true);
  10:             ReflectionHelper.SetFieldValue(this, "_isSupportedBrowser", true);
  11:         }
  12:     }
  13:  
  14:     protected override void OnLoad(EventArgs e)
  15:     {
  16:         this.checkForFirefox();
  17:         base.OnLoad(e);
  18:     }
  19: }
 
Very important to set also _browserCapabilitiesRetrieved field. Otherwise code in base class will be again executed and _isSupportedBrowser will be false.
 
Now there is only one step remaining. When you activate feature “Use RadEditor to edit list items” it copies new rendering template RadEditorList.ascx for RichTextField into ControlTemplate folder (it overrides default rendering template). You need to modify this file in order to use your own class AllBrowsersHtmlListField:
   1: <%@ Control Language="C#" AutoEventWireup="false" %>
   2: ...
   3: <%@ Register TagPrefix="customradeditor" Assembly="CustomTelerikField, Version=1.0.0.0, Culture=neutral, PublicKeyToken=..." Namespace="CustomTelerikField" %>
   4: <SharePoint:RenderingTemplate ID="RichTextField" runat="server">
   5:         <Template>
   6:                 <customradeditor:AllBrowsersHtmlListField id="RadTextField" runat="server" FontSizeCoef="7" />
   7:                 ...
   8:         </Template>
   9: </SharePoint:RenderingTemplate>

It is also worth to make RadEditorList.ascx read only so when “Use RadEditor to edit list items” will be activated next time it won’t override your version. Code which copies file is inside try/catch block so it won’t fail feature activation and site creation if it is stapled with this feature.

After you will perform iisreset Sharepoint will use your own control and those will display Rad Editor in FireFox. If you will have similar problems with another browsers you can fix them by the same way.

No comments:

Post a Comment