October 25, 2012

Add a custom button in the rich text editor

For one of my tasks I had to add a custom button in the sitecore rich text editor (RADEditor) to open a dialog box. But we don’t find a lot of documentation about that on the web. The only post I have found about this is this one: http://www.awareweb.com/AwareBlog/9-21-11-HyperlinkMgr.aspx which is a good start but with this method we need to decompile the code of the RADEditor and override the Rich Text Editor javascript.

Here is another method to create a button without overriding the code for sitecore and RADEditor.

First of all, we will add the new button in the interface. As you may know they are different profiles for the rich text. A profile define which features and button are available for a specific field. By default, the selected profile in the “Rich Text Default” but you may specify which profile you want need to be apply by setting the datasource of the field. (example: /sitecore/system/Settings/Html Editor Profiles/Rich Text Full). In this example, I will add a button in the Toolbar 1 of the Full profile.

So, in the core database, add an item in of type /sitecore/templates/System/Html Editor Profiles/Html Editor Button below /sitecore/system/Settings/Html Editor Profiles/Rich Text Full/Toolbar 1. Assign an icon to you item and in the field “Click” add an id for your action. In this example the field “Click” is set to “TestBtn”.

Now, we need to handle this action in the javascript. By default, those actions are handled in \sitecore\shell\Controls\Rich Text Editor\RichText Commands.js but I don’t want to modify the default file to stay as updatable as possible with the future versions of sitecore. By using my friend Reflector, I have found a work around. You may add some javascript in the Rich Text Editor IFrame by adding it in the config below the node /configuration/sitecore/Clientscript/htmleditor. So for my example, add this config file in \App_Config\Include:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <clientscripts>
      <htmleditor>
        <script src="/sitecore/shell/Controls/Rich Text Editor/TestBtn/TestBtn Commands.js" language="JavaScript"/>
      </htmleditor>
    </clientscripts>
  </sitecore>
</configuration>

And the content of this new javascript file located into /sitecore/shell/Controls/Rich Text Editor/TestBtn/TestBtn Commands.js is:

var scEditor = null;
var scTool = null;

//Set the Id of your button into the RadEditorCommandList[]
RadEditorCommandList["TestBtn"] = function (commandName, editor, args) {
    var d = Telerik.Web.UI.Editor.CommandList._getLinkArgument(editor);
    Telerik.Web.UI.Editor.CommandList._getDialogArguments(d, "A", editor, "DocumentManager");

 //Retrieve the html selected in the editor
    var html = editor.getSelectionHtml();

    scEditor = editor;

 //Call your custom dialog box
    editor.showExternalDialog(
  "/sitecore/shell/default.aspx?xmlcontrol=RichText.TestBtn&la=" + scLanguage,
  null, //argument
  500, //Height
  180, //Width
  scTestBtnCallback, //callback
  null, // callback args
  "TestBtn",
  true, //modal
  Telerik.Web.UI.WindowBehaviors.Close, // behaviors
  false, //showStatusBar
  false //showTitleBar
   );
};

//The function called when the user close the dialog
function scTestBtnCallback(sender, returnValue) {
    if (!returnValue) {
        return;
    }
 
 //You may retreive some code from your returnValue
 
 //For the example I add Hello and my return value in the Rich Text
    scEditor.pasteHtml("Hello " + returnValue.text, "DocumentManager");
}

And the dialog as a XAML form located into /sitecore/shell/Controls/Rich Text Editor/TestBtn/TestBtn.xml is:

<?xml version="1.0" encoding="utf-8" ?>
<control xmlns:def="Definition" xmlns="http://schemas.sitecore.net/Visual-Studio-Intellisense">
  <RichText.TestBtn>
    <FormDialog Icon="Network/32x32/link.png" Header="Insert a html code"
      Text="Insert a html code." OKButton="Link">

      <script Type="text/javascript" Language="javascript" Src="/sitecore/shell/Controls/Rich Text Editor/TestBtn/TestBtn.js">.</script>

      <CodeBeside Type="MyNameSpace.TestBtn, MyDll"/>

      <Border Padding="4px 0px 4px 0px">
        <GridPanel Width="100%" Columns="2">
          <Border Padding="0px 4px 0px 0px">
            <Literal Text="Enter you text:"/>
          </Border>

          <Edit ID="TextEdit" Width="100%" GridPanel.Width="100%"/>
        </GridPanel>
      </Border>
    </FormDialog>
  </RichText.TestBtn>
</control>

The associate cs file. This file is located into /sitecore/shell/Controls/Rich Text Editor/TestBtn/TestBtn.cs 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 MyNamespace
{
    public class TestBtn : DialogForm
    {
        // Fields
        protected Edit TextEdit;

        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(TextEdit.Value))
            {
                SheerResponse.Alert("Please enter a html code.", new string[0]);
            }

            if (this.Mode == "webedit")
            {
                //SheerResponse.SetDialogValue(StringUtil.EscapeJavascriptString(mediaUrl));
                base.OnOK(sender, args);
            }
            else
            {
                SheerResponse.Eval("scClose('" + TextEdit.Value + "')");
            }
        }

        // Properties
        protected string Mode
        {
            get
            {
                string str = StringUtil.GetString(base.ServerProperties["Mode"]);
                if (!string.IsNullOrEmpty(str))
                {
                    return str;
                }
                return "shell";
            }
            set
            {
                Assert.ArgumentNotNull(value, "value");
                base.ServerProperties["Mode"] = value;
            }
        }
    }
}

Associate to this XAML form, I also have this javascript to handle the close and cancel (to only interesting method is the scClose and scCloseWebEdit). This file is located into /sitecore/shell/Controls/Rich Text Editor/TestBtn/TestBtn.js

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(text) {
    var returnValue = {
        text: text
    };

    getRadWindow().close(returnValue);

}

function scCancel() {
  getRadWindow().close();
}

function scCloseWebEdit(text) {
    window.returnValue = text;
    window.close();
}

if (window.focus && Prototype.Browser.Gecko) {
  window.focus();
}

UPDATE:
We have found that if the command contains some special characters like ':' the button icon may be not loaded correctly. So just avoid it :-)

October 9, 2012

Create your own pipeline

As you know sitecore use a lot of pipeline but did you already try to create your own pipeline for your custom actions?
To create your own pipeline in sitecore it is easy, you will need to:
  • Add your pipeline and porocessors in the web.config (or externalize it in the \app_config\include folder)
    <configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
      <sitecore>
        <pipelines>
          <myCustomPipeline>        
            <processor type="MyNamespace.MyProcessor, MyDll" />          
          </myCustomPipeline>
        </pipelines>
      </sitecore>
    </configuration>
  • After you may call it with this code and t will wall all the processors in this pipeline:
    PipelineArgs args = new PipelineArgs();
    CorePipeline.Run("myCustomPipeline", args);
Simple doesn’t it? You may also use your own argument if you need to pass some additional properties to the processors. To do that, you just need to create a new class who inherit from PipelineArgs.

October 4, 2012

Open an admin screen in a tab of the content editor

Here is the method to open a tab in the content editor and display an aspx page in it.
Here is an example:

  1. The first you will need is a command. You can add it into your command.config, or in a separate .config in \App_config\Include\. In my example my command is named “commentandrate:opencommenteditoverview” but you can name it as you want of course.
    <configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
      <sitecore>
        <commands>
          <command name="commentandrate:opencommenteditoverview" type="MyNamespace.OpenCommentEditOverView, MyDllName"/>      
        </commands>
      </sitecore>
    </configuration>
  2. After that you will only need to write the corresponding class:
    using Sitecore;
    using Sitecore.Diagnostics;
    using Sitecore.Resources;
    using Sitecore.Shell.Framework.Commands;
    using Sitecore.Text;
    using Sitecore.Web;
    using Sitecore.Web.UI.Framework.Scripts;
    using Sitecore.Web.UI.Sheer;
    using Sitecore.Web.UI.XamlSharp.Continuations;
    
    namespace MyNamespace
    {
        public class OpenCommentEditOverView : Command, ISupportsContinuation
        {
            // Methods
            public override void Execute(CommandContext context)
            {
                Assert.ArgumentNotNull(context, "context");
                if (context.Items.Length == 1)
                {
        //Check if the tab is already open and refresh it is needed
                    if (WebUtil.GetFormValue("scEditorTabs").Contains("commentandrate:opencommenteditoverview"))
                    {
                        SheerResponse.Eval("scContent.onEditorTabClick(null, null, 'OpenCommentEditOverView')");
                    }
                    else //Open a new tab
                    {
         //The Path to the aspx file to dsplay in this tab
                        UrlString urlString = new UrlString("/sitecore/shell/Applications/LBi/CommentEditOverView.aspx");
                        context.Items[0].Uri.AddToUrlString(urlString);
                        UIUtil.AddContentDatabaseParameter(urlString);
                        //urlString["fld"] = "__Tracking";
                        SheerResponse.Eval(new ShowEditorTab
                            {
                                Command = "commentandrate:opencommenteditoverview",
                                Header = "Comments", 
                                Icon = Images.GetThemedImageSource("Network/16x16/lock.png"), 
                                Url = urlString.ToString(), 
                                Id = "OpenCommentEditOverView", 
                                Closeable = true
                            }.ToString());
                    }
                }
            }
    
        }
    }
That is it for the command and the code itself but of course you will need a way to call the command.
You can for example add a button in a toolbar.
If you don’t know what is a chunk, a strip, … please refer to this post: http://sitecoreblog.blogspot.be/2010/11/use-contextual-button.html

To create this new button:
  1. Switch to the core database
  2. In /sitecore/content/Applications/Content Editor/Ribbons/Chunks, add a new “Chunk” (template: /sitecore/templates/System/Ribbon/Chunk)
  3. Fill in the fields:
    1. Header: The name who will appear below the block of button
    2. ID: an ID
  4. Below this chunk, add a new item of type /sitecore/templates/System/Ribbon/Large Button
  5. Fill in the fields:
    1. Header: The name who will appear below the block of button
    2. Icon: The icon of this button
    3. Click: you command name (as in the .config)
    4. Tooltip: the text when you let the mouse on this button
  6. To include this chunk in the “Home” toolbar, go in /sitecore/content/Applications/Content Editor/Ribbons/Strips/Home
  7. Add a new subitem (template: /sitecore/templates/System/Reference)
  8. And fill in the Reference field (example of value: sitecore/content/Applications/Content Editor/Ribbons/Chunks/Comment Rate Admin)

The most useful shortcut for VS 2010

UPDATE: For visual studio 2012, you may install the great extension "AttachTo" via the "Extension and Update" of visaul studio. Here is maybe the most useful keyboard shortcut you can create in visual studio for the web development.
Don’t you have dream to push on one shortcut only to attach to the w3wp process without accept all the dialog boxes?
This is the equivalent of pushing on ctrl+alt+p (open attach to process window), w (go to the letter w), enter (to accept to attach).

Here is the method to do that with only one key combination:
  1. Click on “View” – “Other windows” – “Macro Explorer” (or alt+F8)
  2. Right click on “MyMacros” – “New Module”
  3. Name it “MyCustomMacros” for example and click on “Add”
  4. Replace the content of the file by:
    Imports System
    Imports EnvDTE
    Imports EnvDTE80
    Imports EnvDTE90
    Imports EnvDTE90a
    Imports EnvDTE100
    Imports System.Diagnostics
    
    Public Module MyCustomMacros
    
        ' This subroutine attaches to calc.exe if it is running.
        Sub AttachToW3WP()
            Dim attached As Boolean = False
            Dim proc As EnvDTE.Process
    
            For Each proc In DTE.Debugger.LocalProcesses
                If (Right(proc.Name, 8) = "w3wp.exe") Then
                    proc.Attach()
                    attached = True
                    'Exit For
                End If
            Next
    
            If attached = False Then
                MsgBox("Could not find w3wp")
            End If
    
        End Sub
    End Module
    
  5. Right click on the toolbar zone (on the left of “help” for example) and select the latest option: “Customize…”
  6. Click on the “Keyboard…” button
  7. In the “Show commands containing” search for “w3w” for example

  8. Select the new macro and in the “Press shortcut keys” box type a shortcut. Don’t forget to push on “Assign”