January 3, 2013

Add a richtext webedit button with a custom dialog

I have already create a post about how to add a custom button into the richtext editor.
But if you do that you will maybe also be interested on how to insert a button into the webedit ribbon. Inserting the button is easy but in this post I will also explain how to open a dialog box to interact with webedit.
  1. Insert the new button in the toolbar:

    1. First of all, switch into the core database and add your button in the corresponding richtext editor profile. If you need to add it into the “Full” profile, insert a new item based on the template /sitecore/templates/System/WebEdit/WebEdit Button below: /sitecore/system/Settings/Html Editor Profiles/Rich Text Full/WebEdit Buttons/
    2. Set the properties of your item. The tree important fields here are:
      • Icon: the icon to display into the toolbar
      • Click: The event id you need to catch into your javascript (in my example I have set it to ‘chrome:field:linkcolorbox’)
      • Tooltip: the tooltip text when you stay on the button
  2. Handle the javascript event:

    1. This time we cannot avoid to modify a sitecore javascript file. I have been in contact with the sitecore support and ask to change it as a change request. So edit the file \sitecore\shell\Applications\Page Modes\ChromeTypes\FieldChromeType.js Add your own event: search for the line "handleMessage: function (message, params) {" and add a case with your own event.
      In my example here I add this:
      case "chrome:field:linkcolorbox":
       this.insertLinkcolorbox();
       break;
    2. And the related function in the same javascript is:
      insertLinkcolorbox: function () {
       var selectionText;
      
       // Not MSIE.
       if (window.getSelection && window.getSelection().getRangeAt) {
        this.selection = window.getSelection().getRangeAt(0);
        selectionText = this.selection.toString();
       }
        // MSIE.
       else if (document.selection && document.selection.createRange) {
        this.selection = document.selection.createRange();
        selectionText = this.selection.text;
       }
      
       if ($sc.trim(selectionText) == "") {
        alert(Sitecore.PageModes.Texts.SelectSomeText);
        return;
       }
      
       var chars = this.characteristics();
       this.editControl(chars.itemId, chars.language, chars.version, chars.fieldId, this.controlId(), "webedit:linkcolorbox");
      },
      You see in this function that we get the selected text and the important line is
      this.editControl(chars.itemId, chars.language, chars.version, chars.fieldId, this.controlId(), "webedit:linkcolorbox");
      where we have the webedit:linkcolorbox this is the event I will trigger.
    3. Now in a config file we need to handle this event triggered by the javascript:
      <configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
        <sitecore>
          <commands>
            <command name="webedit:linkcolorbox" type="ColorBox4Sitecore.Webedit.WebeditColorboxLink, ColorBox4Sitecore"/>
          </commands>
        </sitecore>
      </configuration>
      Notice that the name must be the same as the one in the javascript and of course the type value correspond to a class in my project who inherit from WebEditCommand.
  3. Handle the webedit command:

    Here is my class:
    using Sitecore;
    using Sitecore.Resources;
    using Sitecore.Shell.Applications.WebEdit.Commands;
    using Sitecore.Shell.Framework.Commands;
    using Sitecore.Text;
    using Sitecore.Web.UI.Sheer;
    
    namespace ColorBox4Sitecore.Webedit
    {
        public class WebeditColorboxLink : WebEditCommand
        {
            // Methods
            public override void Execute(CommandContext context)
            {
                Context.ClientPage.Start(this, "Run", context.Parameters);
            }
    
            protected static void Run(ClientPipelineArgs args)
            {
                if (args.IsPostBack)
                {
                    if (args.HasResult)
                    {
                        //The response to the javascript
                        SheerResponse.Eval("window.parent.Sitecore.PageModes.ChromeManager.handleMessage('chrome:field:colorboxlinkinserted',{{colorboxClass:{0}}})", new object[] { args.Result });
                    }
                }
                else
                {
                    //The dialog to run
                    UrlString str = ResourceUri.Parse("control:RichText.ColorboxLink").ToUrlString();
                    str.Add("mo", "webedit");
                    str.Add("la", args.Parameters["language"]);
                    SheerResponse.ShowModalDialog(str.ToString(), true);
                    args.WaitForPostBack();
                }
            }
        }
    }
    In this class you will need to change 2 lines:
    UrlString str = ResourceUri.Parse("control:RichText.ColorboxLink").ToUrlString();
    The value in the parse here is the dialog to display the id must correspond to the first xml tag just below the control tag (in the example here my tag is <RichText.ColorboxLink>). The second thing to change is the line:
    SheerResponse.Eval("window.parent.Sitecore.PageModes.ChromeManager.handleMessage('chrome:field:colorboxlinkinserted',{{colorboxClass:{0}}})", new object[] { args.Result });
    The value here is the message we will send back to the javascript when the user will click on the on button of my dialog. I will come back to this step later.
  4. Create the dialog:

    The files for my dialog are the same as in my previous post this code is common for the webedit and the normal richtext (I have just change a little bit the c# code to enable the webedit)
    <?xml version="1.0" encoding="utf-8" ?>
    <control xmlns:def="Definition" xmlns="http://schemas.sitecore.net/Visual-Studio-Intellisense">
      <RichText.ColorboxLink>
        <FormDialog Icon="Network/32x32/link.png" Header="Insert a Colorbox Link"
          Text="Select the colorbox class." OKButton="Link">
    
          <script Type="text/javascript" Language="javascript" Src="/sitecore/shell/Controls/Rich Text Editor/ColorboxLink/ColorboxLink.js">.</script>
    
          <CodeBeside Type="ColorBox4Sitecore.UI.sitecore_modules.Shell.Controls.Rich_Text_Editor.ColorboxLink.ColorboxLink, ColorBox4Sitecore.UI"/>
    
          <Border Padding="4px 0px 4px 0px">
            <GridPanel Width="100%" Columns="2">
              <Border Padding="0px 4px 0px 0px">
                <Literal Text="Colorbox class:"/>
              </Border>
    
              <Edit ID="ColorboxClass" Width="100%" GridPanel.Width="100%"/>
            </GridPanel>
          </Border>
        </FormDialog>
      </RichText.ColorboxLink>
    </control>
    
    The c# code is:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using Sitecore;
    using Sitecore.Data;
    using Sitecore.Data.Items;
    using Sitecore.Diagnostics;
    using Sitecore.IO;
    using Sitecore.Shell.Framework;
    using Sitecore.Web;
    using Sitecore.Web.UI.HtmlControls;
    using Sitecore.Web.UI.Pages;
    using Sitecore.Web.UI.Sheer;
    
    namespace ColorBox4Sitecore.UI.sitecore_modules.Shell.Controls.Rich_Text_Editor.ColorboxLink
    {
        public class ColorboxLink : DialogForm
        {
            // Fields
            protected Edit ColorboxClass;
            protected Edit ColorboxText;
    
            protected override void OnCancel(object sender, EventArgs args)
            {
                Assert.ArgumentNotNull(sender, "sender");
                Assert.ArgumentNotNull(args, "args");
                if (this.Mode == "webedit")
                {
                    base.OnCancel(sender, args);
                }
                else
                {
                    SheerResponse.Eval("scCancel()");
                }
            }
            
            protected override void OnOK(object sender, EventArgs args)
            {
                Assert.ArgumentNotNull(sender, "sender");
                Assert.ArgumentNotNull(args, "args");
                if (string.IsNullOrWhiteSpace(ColorboxClass.Value))
                {
                    SheerResponse.Alert("Please enter a Colorbox class.", new string[0]);
                }
    
                if (this.Mode == "webedit")
                {
                    SheerResponse.SetDialogValue(StringUtil.EscapeJavascriptString(ColorboxClass.Value));
                    base.OnOK(sender, args);
                }
                else
                {
                    SheerResponse.Eval("scClose('" + ColorboxClass.Value + "')");
                }
            }
    
            // Properties
            protected string Mode
            {
                get
                {
                    string str = StringUtil.GetString(base.ServerProperties["Mode"]);
                    if (!string.IsNullOrEmpty(str))
                    {
                        return str;
                    }
                    str = WebUtil.GetQueryString("mo");
                    if (!string.IsNullOrEmpty(str))
                    {
                        return str;
                    }
                    return "shell";
                }
                set
                {
                    Assert.ArgumentNotNull(value, "value");
                    base.ServerProperties["Mode"] = value;
                }
            }
        }
    }
    Nothing special in this class the important line is:
    SheerResponse.SetDialogValue(StringUtil.EscapeJavascriptString(ColorboxClass.Value));
    where we pass the parameters to send back.

    And the JS code (normally you shouldn't change it)
    function GetDialogArguments() {
        return getRadWindow().ClientParameters;
    }
    
    function getRadWindow() {
      if (window.radWindow) {
            return window.radWindow;
      }
        
        if (window.frameElement && window.frameElement.radWindow) {
            return window.frameElement.radWindow;
        }
        
        return null;
    }
    
    var isRadWindow = true;
    
    var radWindow = getRadWindow();
    
    if (radWindow) { 
      if (window.dialogArguments) { 
        radWindow.Window = window;
      } 
    }
    
    
    function scClose(colorboxClass) {
        var returnValue = {
            colorboxClass: colorboxClass
        };
    
        getRadWindow().close(returnValue);
    
    }
    
    function scCancel() {
      getRadWindow().close();
    }
    
    function scCloseWebEdit(colorboxClass) {
        window.returnValue = colorboxClass;
        window.close();
    }
    
    if (window.focus && Prototype.Browser.Gecko) {
      window.focus();
    }
  5. Handle the response message:

    When the user click on ok, sitecore will pass on this line in the dialog box:
    SheerResponse.SetDialogValue(StringUtil.EscapeJavascriptString(ColorboxClass.Value));
    This line return send arguments to the WebEditCommand and we will have arguments so it will pass into this code:
    if (args.IsPostBack)
    {
     if (args.HasResult)
     {
      //The response to the javascript
      SheerResponse.Eval("window.parent.Sitecore.PageModes.ChromeManager.handleMessage('chrome:field:colorboxlinkinserted',{{colorboxClass:{0}}})", new object[] { args.Result });
     }
    }
    And this code will send the message 'chrome:field:colorboxlinkinserted’ with one argument.
  6. Handle the response message in the JS:

    Now as we did in the beginning in FieldChromeType.js we need to handle the message in the javascript so:
    Add a new case in the handleMessage method, in my example it is this one :
    case "chrome:field:colorboxlinkinserted":
     this.colorboxlinkinserted(params.colorboxClass);
     break;
    And my function is:
    colorboxlinkinserted: function (colorboxClass) {
     var isIE = document.selection && document.selection.createRange;
     if (!this.selection) {
      return;
     }
    
     // TODO: add preserving link contents for FF.
     var data = {
      html: isIE ? this.selection.htmlText : this.selection.toString(),
      colorboxClass: colorboxClass
     };
    
     // If link is selected, replace it with a new one, preserving link contents.
     if (isIE) {
      // OT issue#338106
      data.html = this._processHtml(data.html);
      var htmlSelection = $sc.trim(data.html.toLowerCase()) || "";
      if (htmlSelection.indexOf("<a ") == 0 && htmlSelection.indexOf("</a>") == (htmlSelection.length - "</a>".length)) {
       htmlSelection = data.html.substring(data.html.indexOf(">") + 1);
       htmlSelection = htmlSelection.substring(0, htmlSelection.length - "</a>".length);
       data.html = htmlSelection;
      }
     }
    
     var htmlToInsert = "<a href='#' class='" + data.colorboxClass + "'>" + data.html + "</a>";
     if (isIE) {
      this.selection.pasteHTML(htmlToInsert);
     }
     else {
      node = this.selection.createContextualFragment(htmlToInsert);
      this.selection.deleteContents();
      this.selection.insertNode(node);
     }
    
     this.setModified();
    },

2 comments: