Jul 9, 2011

Customizing rendering templates for Document Set

In my previous article I blogged about how to provision custom Document Set with custom Welcome Page.

There are cases in which we want to preserve out-of-the-box functionality for adding and editing Document Set, but to plug in the screens our custom UI controls and logic for persisting the users’ input.

The challenges are:

- Plug your own UI controls in the out-of-the-box Share Point page

- Catch the out-of-the-box save event and plug your own persistence logic there to handle the user’s input

The default form templates for Edit and Display are ListForm. You can see it from the Document Set Elements.xml definition.


So, if we want to customize the Edit form, we have to either modify ListForm template (which will affect tons of other functionality and behavior, so please don’t do that) or create new rendering template which is copy of ListForm template, to modify it according our needs and to use it in the definition of the Document Set.

You can find the ListForm template in 14\TEMPLATE\CONTROLTEMPLATES\DefaultTemplates.ascx


If you open the file and search for “ListForm”, you will see the definition of the rendering template used in Edit form for Document Sets.


In the solution I created new user control under the CONTROLTEMPLATES mapped folder and named it KBDocSetEditTemplate.ascx. That is what I am going to use from now on for editing my custom Document Set.


Below is its content, but in generally you can copy paste the register controls from DefaultTemplates.ascx (on the top of the file) and after that you should paste the OOB ListForm template definition. After that you can customize it according your needs. You can plug custom controls, user controls, etc. in the template’s definition.

<%@ Control Language="C#"   AutoEventWireup="false" %>
<%@Assembly Name="Microsoft.SharePoint, Version=, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@Register TagPrefix="SharePoint" Assembly="Microsoft.SharePoint, Version=, Culture=neutral, PublicKeyToken=71e9bce111e9429c" namespace="Microsoft.SharePoint.WebControls"%>
<%@Register TagPrefix="ApplicationPages" Assembly="Microsoft.SharePoint, Version=, Culture=neutral, PublicKeyToken=71e9bce111e9429c" namespace="Microsoft.SharePoint.ApplicationPages.WebControls"%>
<%@Register TagPrefix="SPHttpUtility" Assembly="Microsoft.SharePoint, Version=, Culture=neutral, PublicKeyToken=71e9bce111e9429c" namespace="Microsoft.SharePoint.Utilities"%>
<%@ Register TagPrefix="wssuc" TagName="ToolBar" src="~/_controltemplates/ToolBar.ascx" %>
<%@ Register TagPrefix="wssuc" TagName="ToolBarButton" src="~/_controltemplates/ToolBarButton.ascx" %>
<%@ Register TagPrefix="KB" TagName="Meeting" Src="~/_controltemplates/KB/MeetingDetails.ascx" %>
<%@ Register Tagprefix="KB" Namespace="KB.Core" Assembly="KB.Core, Version=, Culture=neutral, PublicKeyToken=6f920b1b926475ec" %>
<SharePoint:RenderingTemplate id="KBDocumentSetEdit" runat="server">
    <span id='part1'>
      <SharePoint:InformationBar runat="server"/>
      <div id="listFormToolBarTop">
      <wssuc:ToolBar CssClass="ms-formtoolbar" RightButtonSeparator="&amp;#160;" runat="server">
            <SharePoint:NextPageButton ID="NextPageButton1" runat="server"/>
            <KB:DocSetEditFormSaveButton runat="server"/>
            <SharePoint:GoBackButton ID="GoBackButton1" runat="server"/>
        <SharePoint:FormToolBar  runat="server"/>
      <SharePoint:ItemValidationFailedMessage  runat="server"/>
      <table class="ms-formtable" style="margin-top: 8px;" border="0" cellpadding="0" cellspacing="0" width="100%">
      <SharePoint:ChangeContentType runat="server"/>
      <SharePoint:FolderFormFields  runat="server"/>
      <SharePoint:ListFieldIterator runat="server"/>
      <SharePoint:ApprovalStatus runat="server"/>
      <SharePoint:FormComponent TemplateName="AttachmentRows" runat="server"/>
      <table cellpadding="0" cellspacing="0" width="100%"><tr><td class="ms-formline"><img src="/_layouts/images/blank.gif" width='1' height='1' alt="" /></td></tr></table>
      <table cellpadding="0" cellspacing="0" width="100%" style="padding-top: 7px"><tr><td width="100%">
      <SharePoint:ItemHiddenVersion runat="server"/>
      <SharePoint:ParentInformationField runat="server"/>
            <div class="form-container">
                <KB:Meeting runat="server" />
      <SharePoint:InitContentType runat="server"/>
      <wssuc:ToolBar CssClass="ms-formtoolbar" id="toolBarTbl" RightButtonSeparator="&amp;#160;" runat="server">
            <SharePoint:CreatedModifiedInfo runat="server"/>
            <KB:DocSetEditFormSaveButton runat="server"/>
            <SharePoint:GoBackButton runat="server"/>
    <SharePoint:AttachmentUpload runat="server"/>

In my custom template I plugged one user control - MeetingDetails.

  <div class="form-container">
    <KB:Meeting runat="server" />

In my sample I want to fill data for “Outcome Due Date” and based on its value to fill “Meeting Reference Number” behind the scene. The idea here is to demonstrate how you can invoke javascript code on clicking the “Save” button in Document Set Edit form

I created user control MeetingDetails.ascx which contains one SharePoint Web Control for displaying the current value of “Outcome Due Date”. It is placed within KB folder in CONTROLTEMPLATES.

<SharePoint:FormField ID="fldOutcomeDueDate" runat="server" ControlMode="Edit" FieldName="OutcomeDueDate" />

Next I had to figure out is how to intercept the “Save” button event and to plug there my own logic (which in my case is javascript function for setting the value “Meeting Reference Number” and updating the custom Document Set data in order the users’ input to be saved).

From the ListForm template definition we can see that there is a control called SharePoint:SaveButton.

If we review its definition with ILSpy (or .Net Reflector), on its OnPreRender method we will see that the

ButtonControl property is responsible for instantiating the save button. Unfortunately it is internal, so we cannot access it through our code.


If we review the OnBubbleEvent we will see that there is event called: FormContext.OnSaveHandler.

To achieve what I need I had to define a button which inherits from SaveButton, accesses the instance (as in the screenshot above) and changing its OnClientClick property in its OnPreRender.

public class DocSetEditFormSaveButton : SaveButton
        internal Button customButton
                Button button = (Button)this.TemplateContainer.FindControl("diidIOSaveItem");
                return button;
        protected override void OnPreRender(EventArgs e)
            //overrides the original Save button javascript and replaces with our method
            customButton.OnClientClick = "InitializeMeetingRefNumber()";

In the javascript function we must preserve what is organically called in SaveButton OnClientClick – the invocation of PreSaveItem() function.

The function code doesn’t really make sense, but in generally it takes the value of “Outcome Due Date” and copies it in a hidden field by appending “client side set” string to its value. In the code behind the “Meeting Reference Number” is populated based on the hidden input value.


The custom save button implementation is placed in KB.Core project in solution.

The last thing I had to do is to subscribe for SPContext.Current.FormContext.OnSaveHandler and to update “Meeting Reference Number” field of my custom Document Set.


In Elements.xml of our custom Document Set we must change the rendering template for Edit and to place our custom one.


After deploy and creation of custom Document Set “Meeting Document Set”, View All Properties screen looks like this:


On Edit Properties our custom templates kicks in and we can see our user control at the bottom of the page.

Edit Properties

After hitting the “Save” button, our custom save button logic is executed and “Meeting Reference Number” is updated based on the value of filled “Outcome Due Date”.


You can attach the related code sample here.

No comments:

Post a Comment