Creating a custom View tab in the EditPanel
The EditPanel contains the default View tab to view the page you are editing.
But what if you have a PageData object that has no explicit view? For instance a teaser or menu block which is reusable throughout your website project and is part of another page template?
It would be useful if we could create a custom view in the EditPanel View tab that displays the PageData object ‘in place’ on a specific page.
This post explains how to create such a custom View tab. Bear in mind this is just an example of how to achieve such a custom View. In this specific problem there are other ways of handling the View of such PageData objects. The main objective of this post is to give insight in a possible solution to the problem and should not by default be read as the best solution. All code mentioned within this post is usable, but could well be optimized in many ways.
To accomplish this we need to create a custom View plugin UserControl and ‘Hook’ into the EditPanel LoadComplete event.
Create our GUI Plugin UserControl
We start by creating the correct code which will render the content of the View tab. This code would be very similar to the PreviewControl.ascx UserControl which is used for the default View tab that comes with your standard EPiServer installation.
Take a look at the default PreviewControl.ascx UserControl in your EPiServer install directory. This should be available at the following location, dependent on which version of EPiServer you're running:
C:\Program Files (x86)\EPiServer\CMS\5.2.375.236\Application\UI\Edit\PreviewControl.ascx.
1: <%@ Control Language="C#" AutoEventWireup="true" CodeBehind="CustomViewGUIPlugin.ascx.cs" Inherits="EPiServerDemo.Business.View.CustomViewGUIPlugin" %>
2:
3: <div Runat="server" ID="FirstLanguageDiv">
4: <EPiServerUI:SystemIframe Id="PreviewFrame" Name="PreviewFrame" CssClass="previewframe" runat="server" />
5: <EPiServerScript:ScriptEvent ID="ScriptEvent1" EventTargetId="PreviewFrame" EventType="ReadyStateChange" EventHandler="SkipReadyState" runat="server"/>
6: <EPiServerScript:ScriptEvent ID="ScriptEvent2" EventTargetClientNode="window" EventType="Load" EventHandler="SetupFrameContentLoaded" runat="server"/>
7: <EPiServerScript:ScriptResizeWindowEvent ID="ScriptResizeWindowEvent1" EventTargetClientNode="window" ResizeElementId="PreviewFrame" runat="server" />
8: </div>
9: <div Runat="server" ID="SecondLanguageDiv" />
Our ‘CustomViewGUIPlugin’ UserControl,displayed above, looks pretty much the same as the default PreviewControl.ascx UserControl mentioned. At this point we have the rendering of our View tab in place.
The code behind of our custom GUI Plugin UserControl.
To create a UserControl that will act as a GUI Plugin we need to add the ‘[GuiPlugIn]’ attribute to our class definition as shown in the following code fragment.
1: [GuiPlugIn(
2: Area = PlugInArea.EditPanel,
3: DisplayName = "View",
4: SortIndex = 100,
5: Url = "~/Plugins/CustomViewGUIPlugin.ascx",
6: RequiredAccess = AccessLevel.Publish)]
7: public partial class CustomViewGUIPlugin : UserControlBase, ICustomPlugInLoader
8: {
9: //logic
10: }
Make sure the different GuiPlugIn properties reflect your implementation. The SortIndex property is important here. The default View tab doesn’t have a SortIndex, since this is not being generated through a plugin. The first tab that has a SortIndex is the Edit tab which has a SortIndex of 200. So by setting the SortIndex property of our custom GuiPlugIn to a value lower than 200 (in our case to 100) would automatically mean it will be displayed first in the list of tabs in the EditPanel. (Thanks to the article ‘Setting the Edit Panel Tab Order in CMS 5’ by Jeff Wallace!)
We also need a way to provide the Url of the page we want to use for our custom View. Since our objective is to create a generic solution we need to make sure we can set this custom Url differently for specific pages. A good way to provide such a flexible Url is to add a Dynamic Property to the website project.
So I’ve added a ‘Page’ property as a Dynamic Property to the website project called ‘CustomViewUrl’. The main reason for choosing a Dynamic Property was simply because those are available on any PageData object within our website project.
Within our GUI Plugin we need to check if this Dynamic Property has been set for the current page.
Take a look at the post ‘Access Dynamic Properties when PageData is writable’ by Fredrik Hagland for more details on how to do this. In this example the CustomViewUrl has been implemented as a protected property with only a getter and no setter.
Further we need to check if the value has been set, and if it can be parsed back to the Page provided, since we need the LinkURL of that page.
1: /// <summary>
2: /// Initialize the plugin
3: /// </summary>
4: protected override void OnLoad(EventArgs e)
5: {
6: base.OnLoad(e);
7:
8: if (string.IsNullOrEmpty(CustomLinkUrl))
9: {
10: Visible = false;
11: return;
12: }
13:
14: //set the url to the previewframe
15: PreviewFrame.SourceFile = Url;
16: }
The next step is to implement the ‘PlugInDescriptor[] List()’ method from the ICustomPlugInLoader interface.
1: public PlugInDescriptor[] List()
2: {
3: _pageToEdit = CurrentPage.CreateWritableClone();
4:
5: PlugInDescriptor[] desc = null;
6:
7: // hook LoadComplete-event on EditPanel page
8: EPiServer.UI.Edit.EditPanel editPanel = HttpContext.Current.Handler as EPiServer.UI.Edit.EditPanel;
9:
10: if (null != editPanel)
11: {
12: //Only modify tabs when the page selected is of a specific type - in this case the types name starts with "[Data]".
13: if ((editPanel as PageBase).CurrentPage.PageTypeName.StartsWith("[Data]"))
14: {
15: editPanel.LoadComplete += new EventHandler(EditPanelLoadComplete);
16:
17: //disable View tab if no valid url is present
18: if (string.IsNullOrEmpty(CustomLinkUrl))
19: {
20: return new PlugInDescriptor[0] { };
21: }
22:
23: desc = new PlugInDescriptor[1];
24: // Smart way to load plug-ins
25: desc[0] = PlugInDescriptor.Load(GetType());
26: }
27: }
28: return desc;
29: }
In this implementation we add a ‘LoadComplete’ event for the EditPanel so we can manipulate the rendering of the EditPanel tabs. Thanks to the article ‘Neat Trick: Modifying Edit Mode Tabs’ by Allan Thræn! Further we check if a valid CustomViewUrl is present. If there isn’t a valid CustomViewUrl present, we simply return a new empty PlugInDescriptor so no new tabs will be rendered.
We also need to implement our LoadComplete event of the EditPanel for which we’ve added a new EventHandler. Again taken directly from the article by Allan Thræn mentioned above.
1: protected void EditPanelLoadComplete(object sender, EventArgs e)
2: {
3: // find the TabStrip with id = "actionTab"
4: TabStrip actionTabStrip = this.FindControl<TabStrip>(sender as Control, "actionTab");
5:
6: //THIS IS WHERE YOU CAN DO WHATEVER YOU WANT TO THE TABS!
7:
8: // remove View-tab and set active tab to next first tab.
9: if (actionTabStrip.SelectedTab == 0) actionTabStrip.SetSelectedTab(1);
10: ((Tab)actionTabStrip.Controls[0]).Visible = false;
11: }
We’re only manipulating our tabs by doing two things. First we need to disable the original View tab. Secondly we set the active tab to the first next tab available, which is our custom View tab thanks to the SortIndex we’ve set earlier.
That’s it, just make sure your project compiles and open up your website. Set the Dynamic Property, on the PageData object you want to provide a custom view for, with another page in your website project where the object will be used and has a default View. Edit your PageData object which doesn’t have an explicit View, and open up the View tab (which is our custom View tab). You should now see the provided page in your View instead of an empty View.
Nice post!
if you are using CMS 6 R2, don't forget about container pages:
http://world.episerver.com/Blogs/Linus-Ekstrom/Dates/2011/3/Container-pages/
When setting access rights for different tabs, make sure PageTypeBuilder is set with the EPiServer.Security.AccessLevel and the level you would like your content editors to access the tabs. There was no collection of subtabs to the parent tabs in Control class what I could find.