I wanted to create a web part for Sharepoint 2010 that would let an editor add a block of free-form html to a page, but wrapped up in a nicely formatted HTML container of my choosing. I also wanted the web part to include some custom properties to allow the user to select some permutations for the HTML container (colour, position etc.). The key thing was that when the editor is amending the content, I wanted to be able to use the standard ribbon controls instead of having to hook in a 3rd party rich text control like Telerik RadEditor.
So, I assumed I could just take the build-in Content Editor Web Part (CEWP) and extend it. No dice - cos it's sealed!
So I ended up whipping out Reflector and digging into the CEWP code, ripping the guts out of it to hack a new webpart. The following Web Part does what I set out to do, and maybe it will be a good base for you too.
Step 1: Create a new Visual Web Part called DCContentBlock.
Step 2: In the ASCX template, paste this:
<div class="DCContentBlock">
<div class="DCContent">
<asp:Panel ID="plhContentEdit" runat="server" />
<asp:Panel ID="plhContentDisplay" runat="server" />
<asp:Panel ID="plhNoContent" runat="server" />
</div>
</div>
Step 3: In the UserControl class in the ASCX.CS file, paste this:
public Panel BodyContentEdit { get { return plhContentEdit; } }
public Panel BodyContentDisplay { get { return plhContentDisplay; } }
public Panel BodyNoContent { get { return plhNoContent; } }
Step 4: In the web part CS file, here is the main code to make this sucker work:
[ToolboxItemAttribute(false)]
public class DCContentBlock : Microsoft.SharePoint.WebPartPages.WebPart
{
// Visual Studio might automatically update this path when you change the Visual Web Part project item.
private const string _ascxPath = @"~/_CONTROLTEMPLATES/YOUR_PROJECT_NAME_HERE/DCContentBlock/DCContentBlockUserControl.ascx";
private string _content;
private DCContentBlockUserControl control;
private HtmlGenericControl editableRegion = new HtmlGenericControl();
private HtmlGenericControl emptyPanel = new HtmlGenericControl();
private bool IsInEditMode
{
get
{
SPWebPartManager currentWebPartManager = (SPWebPartManager)WebPartManager.GetCurrentWebPartManager(this.Page);
return (((currentWebPartManager != null) && !base.IsStandalone) && currentWebPartManager.GetDisplayMode().AllowPageDesign);
}
}
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
if (this.IsInEditMode)
{
SPRibbon current = SPRibbon.GetCurrent(this.Page);
if (current != null)
{
current.MakeTabAvailable("Ribbon.EditingTools.CPEditTab");
current.MakeTabAvailable("Ribbon.Image.Image");
current.MakeTabAvailable("Ribbon.EditingTools.CPInsert");
current.MakeTabAvailable("Ribbon.Link.Link");
current.MakeTabAvailable("Ribbon.Table.Layout");
current.MakeTabAvailable("Ribbon.Table.Design");
if (!(this.Page is WikiEditPage))
{
current.TrimById("Ribbon.EditingTools.CPEditTab.Layout");
current.TrimById("Ribbon.EditingTools.CPEditTab.EditAndCheckout");
}
}
}
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
// Prevent default display of webpart chrome in standard view mode
this.ChromeType = PartChromeType.None;
control = (DCContentBlockUserControl)Page.LoadControl(_ascxPath);
Controls.Add(control);
control.BodyContentDisplay.Controls.Add(new LiteralControl(this.Content));
control.BodyContentEdit.Controls.Add(this.editableRegion);
control.BodyNoContent.Controls.Add(this.emptyPanel);
string strUpdatedContent = this.Page.Request.Form[this.ClientID + "content"];
if ((strUpdatedContent != null) && (this.Content != strUpdatedContent))
{
this.Content = strUpdatedContent;
try
{
SPWebPartManager currentWebPartManager = (SPWebPartManager)WebPartManager.GetCurrentWebPartManager(this.Page);
Guid storageKey = currentWebPartManager.GetStorageKey(this);
currentWebPartManager.SaveChanges(storageKey);
}
catch (Exception exception)
{
Label child = new Label();
child.Text = exception.Message;
this.Controls.Add(child);
}
}
if (this.IsInEditMode)
{
this.Page.ClientScript.RegisterHiddenField(this.ClientID + "content", this.Content);
control.BodyContentDisplay.Visible = false;
this.emptyPanel.TagName = "DIV";
this.emptyPanel.Style.Add(HtmlTextWriterStyle.Cursor, "hand");
this.emptyPanel.Controls.Add(new LiteralControl("Click here to Edit"));
this.emptyPanel.Style.Add(HtmlTextWriterStyle.TextAlign, "center");
base.Attributes["RteRedirect"] = this.editableRegion.ClientID;
ScriptLink.RegisterScriptAfterUI(this.Page, "SP.UI.Rte.js", false);
ScriptLink.RegisterScriptAfterUI(this.Page, "SP.js", false);
ScriptLink.RegisterScriptAfterUI(this.Page, "SP.Runtime.js", false);
this.editableRegion.TagName = "DIV";
this.editableRegion.InnerHtml = this.Content;
this.editableRegion.Attributes["class"] = "ms-rtestate-write ms-rtestate-field";
this.editableRegion.Attributes["contentEditable"] = "true";
this.editableRegion.Attributes["InputFieldId"] = this.ClientID + "content";
this.editableRegion.Attributes["EmptyPanelId"] = this.emptyPanel.ClientID;
this.editableRegion.Attributes["ContentEditor"] = "True";
this.editableRegion.Attributes["AllowScripts"] = "True";
this.editableRegion.Attributes["AllowWebParts"] = "False";
string script = "RTE.RichTextEditor.transferContentsToInputField('" + SPHttpUtility.EcmaScriptStringLiteralEncode(this.editableRegion.ClientID) + "');";
this.Page.ClientScript.RegisterOnSubmitStatement(base.GetType(), "transfer" + this.editableRegion.ClientID, script);
}
}
// Properties
[WebPartStorage(Storage.Shared)]
public string Content
{
get
{
return this._content;
}
set
{
_content = value;
}
}
}
STEP 5: Your CS file will also need the following using statements at the top:
using System;
using System.ComponentModel;
using System;
using System.Globalization;
using System.Collections.Generic;
using System.ComponentModel;
using System.Xml.Serialization;
using System.Web;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.WebPartPages;
using Microsoft.SharePoint.WebControls;
using Microsoft.Web.CommandUI;
STEP 6: The use of Microsoft.Web.CommandUI means we need to add a project reference to the appropriate dll.
- Right click on the project and Add Reference.
- Click Browse and find: C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\ISAPI\Microsoft.Web.CommandUI.dll
That's all! Build it, edit the page, insert a web part and the part will be in the custom folder. Edit the project's .webpart file and the Elements.xml file if you want to change the name, description and group that the webpart belongs to.
After this, you can extend and style the control to your heart's content. Some ideas:
1. Use CSS to style the DCContentBlock and DCContent classes
2. Add your own HTML to the webpart's ASCX template
3. Add
editor properties to allow the user to select from some preset styling options.
I went to the additional trouble of adding
editor properties by following
this excellent article by Sahil Malik. When the user selects from my custom editor property drop-down, I store the preference value. I then refer to this value to append an appropriate css class to the DCContentBlock div in the WebPart's OnPreRender. Let me know if you'd like me to do a separate post on how to do that.