Tuesday, January 18, 2011

Publishing HtmlEditor on a Custom Application Page

As promised, I thought I would give you a quick tutorial on how to add an HtmlEditor to a custom application page in SharePoint 2010.

As a quick overview, we had a need for streamlining our content creation process. We use standard HTML blocks to perform custom XSLT transformation before posting the content to our customer facing website, and needed a structured method for storing and transforming the data. Initially, we used Reusable Content Blocks to store the base HTML, and then used some CSS to highlight the editable regions. Unfortunately, the process became very difficult to manage, and required us to train our content editors in html best practices, which was extremely inefficient.

To streamline the process, we created a custom HTML editor and gave the users simple menus to click which would allow them to enter just the content which we would then wrap with our predefined content blocks. The menu's allow a user to click a specific block type, key in the content, and click the save button to view the preview article before saving. Here are a few screen captures to illustrate:
Content Editor Menus


Content Editor Dialog
















As you can see in the above images, we created a menu to allow for editing and adding of content blocks. When an option is clicked, a Dialog Window opens containing 1 or 2 rich html editors depending on the block type. We chose to go with the standard Publishing HtmlEditor control because our users liked the ribbon options for easily formatting text and inserting links. I have seen other similar implementations that use the Telerik RadEditor or another third-party editor for adding HTML content.

The Nuts and Bolts

The HtmlEditor can be found in the Microsoft.SharePoint.Publishing.WebControls namespace, and is intended to work specifically with publishing pages. The class is sealed, so creating a new editor from the base class is impossible. After a little bit of tweaking though, it really wasn't difficult to get the editor working on our custom application page.

I first created a new SharePoint Empty Project in VS 2010, and added a new Application Page to the /_layouts folder. I like to keep my custom pages organized, so I created a folder under Layouts called "Editor". To use the HtmlEditor, you will need to add references to the following DLL's in your project:

Microsoft.SharePoint.Publishing.dll - located in \14\ISAPI\
Microsoft.Web.CommanUI.dll - located in \14\ISAPI\

I then modified opened my ApplicationPage.aspx file and made the following modifications:



I removed the reference to the aspx.cs page, which is added by default, and changed the MasterPageFile to use v4.master instead of ~/masterurl/default.master. The reason for the different master page is that we have some heavy customization for our default branding, and I didn't want this to carry over to our application page.

I then added the following references:




These references ensure that you can add the HtmlEditor to the page, and that the Ribbon controls will load.

Next, in the PlaceHolderMain section I added a reference to the editor:

<Publishing:HtmlEditor ID="htmlEditor" runat="server" />

By default, the reference to the editor was not added to the designer.cs file, so you will need to manually add it if you want to interact with it in your aspx.cs file. 

In the aspx.cs file, you will need to add these two using statements:

using Microsoft.SharePoint.Publishing;
using Microsoft.SharePoint.Publishing.WebControls;

To fully configure the editor for use, you will need to override the OnInit() function. Here is the code I used:

        protected override void OnInit(EventArgs e)
        {
            SPRibbon ribbon = SPRibbon.GetCurrent(this.Page);
            if (ribbon != null)
            {
                ribbon.TrimById("Ribbon.EditingTools.CPEditTab.Layout");
                ribbon.TrimById("Ribbon.EditingTools.CPEditTab.EditAndCheckout");
            }

            htmlEditor.Field = new RichHtmlField();
            htmlEditor.Field.ControlMode = SPControlMode.Edit;
            htmlEditor.Field.EnableViewState = true;
            htmlEditor.Field.AllowReusableContent = false;
            htmlEditor.Field.MinimumEditHeight = "200px";

            this.Form.Controls.Add(htmlEditor.Field);

            btnSave.Click += new EventHandler(btnSave_Click);

            base.OnInit(e);
        }

As you can see, I also trim the Wiki controls from the Ribbon. I don't have a need for the Layout and Check out functions. I then create a new RichHtmlField() which stores the contents of the HtmlEditor. Notice that I set the field ControlMode to edit, and modify a few additional parameters to hide options from the ribbon. The final step is to add the RichHtmlField control to the page. Adding the field to the page causes the page to render the associated Ribbon Contextual Tabs. Without it, you would only see a text area for entering content, but the Ribbon would never display.

To get the rest of the editor wired up required some javascript to handle the population of the editor control, and a few hidden fields for storing parameters from the parent page such as editor type and control id. We have up to 8 editable sections for a standard article, so the id of the parent content area was necessary to maintain the position of our content blocks. I also perform an initial post back of the page via javascript in order to validate the variables and throw an error message is one of the required values is missing. 

To post the content back to the parent page, I use a button click event, and the following code to write the information back to the parent:


string close = "<script src="\"/_layouts/jquery/jquery-1.4.2.min.js\"" type="\"text/javascript\"">
</script>
<div id="\"content\"">
" + Html + "</div>
<script type="\"text/javascript\"">
var obj = new Object(); obj.content = $('#content').html(); obj.id = "
+ hdControlId.Value + "; window.frameElement.commonModalDialogClose(obj, 1);
</script>";

            Context.Response.Write(close);
            Context.Response.Flush();
            Context.Response.End();


This essentially clears the context of the page and returns a modalDialogClose() command with my returned values.

Conclusion

Hopefully this post will save you some time in trying to get the SharePoint HTML Editor to display on an application page. If you have any questions, feel free to drop me a line.


11 comments:

  1. Hi Thomas, is it possible to share the sample source.

    I'm having trouble getting the result from the popup page.

    Thanks so much!

    ReplyDelete
  2. Jeffrey,

    in your dialog options, you need to define the dialogReturnValueCallback like this

    function showEditorDialog() {

    var options = SP.UI.$create_DialogOptions();
    options.url = [url];
    options.title = "Editor";
    options.width = 600;
    options.height= 400;
    options.args = {
    valuetopass: ctrl.html
    }

    options.dialogReturnValueCallback = Function.createDelegate(null, editorCallback);

    }

    function editorCallback(result, target) {
    if(result) {
    ctrl.html = result.content;
    } else {
    alert("an error occurred.")
    }
    }

    Does that make sense?

    ReplyDelete
  3. Thomas:

    In the picture of the Content Editor Dialog, I noticed that the Spell Checking is enabled. When I tried your sample, I did not get this icon to appear on the ribbon. Any thoughts?

    Also, I noticed that the references that are to appear in the posting just after "I then modified opened my ApplicationPage.aspx file and made the following modifications" do not seem to appear for me (IE8).

    Thanks,
    David

    ReplyDelete
  4. Hi,

    You're missing some code rows in your post which are critical to understand and to implement your solution. For example: the references which should be added and some modifications in the application page. Can you please publish it?

    Thanks in advance!
    Elad

    ReplyDelete
    Replies
    1. Elad,

      What references are you having trouble with specifically?

      Here are the references I have on the page:

      using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Text;
      using System.Web;
      using System.Web.UI;
      using System.Web.UI.WebControls;

      using Microsoft.SharePoint;
      using Microsoft.SharePoint.Publishing;
      using Microsoft.SharePoint.Publishing.WebControls;
      using Microsoft.SharePoint.Utilities;
      using Microsoft.SharePoint.WebControls;

      You will need to include Microsoft.SharePoint.Publishing in your project, which can be found in the 14\ISAPI folder

      You will also need the following references on you .aspx page:

      <%@ Assembly Name="Microsoft.Web.CommandUI, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
      <%@ Register TagPrefix="Publishing" Namespace="Microsoft.SharePoint.Publishing.WebControls" Assembly="Microsoft.SharePoint.Publishing, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

      This will allow you to add the tag to the page and give it the necessary ID and runat=server tag.

      Let me know if you need anything else.

      Delete
  5. Hi Thomas,
    I got following JS error:

    $v_0 is null in RTE.Canvas.checkCurrentFocus function of sp.ui.rte.debug.js

    Can you help?

    ReplyDelete
    Replies
    1. Please make sure that the following assembly is referenced on you application page:


      <%@ Assembly Name="Microsoft.Web.CommandUI, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

      Delete
  6. Hi, i am not seeing any method "TrimById" attached to the ribbon class? I can't compile this code? Is there something I'm missing? I only see a short list of properties. Did you do this on SP2010?

    ReplyDelete
    Replies
    1. Make sure you have added the reference for the Microsoft.SharePoint.Publishing.dll and added the using clause for Microsoft.SharePoint.Publishing and Microsoft.SharePoint.Publishing.WebControls to your class.

      Delete
  7. I liked your article, I will share your article to everyone!!



    ________________________________________________________________________
    WoW gold|Diablo 3 Gold|RS Gold|GW2 Gold

    ReplyDelete