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.

RenderingTemplatesOOB

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

DefaultTemplates

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.

RenderingTemplate

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.

SolutionRenderingTemplate

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=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@Register TagPrefix="SharePoint" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" namespace="Microsoft.SharePoint.WebControls"%>
<%@Register TagPrefix="ApplicationPages" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" namespace="Microsoft.SharePoint.ApplicationPages.WebControls"%>
<%@Register TagPrefix="SPHttpUtility" Assembly="Microsoft.SharePoint, Version=14.0.0.0, 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=1.0.0.0, Culture=neutral, PublicKeyToken=6f920b1b926475ec" %>
<SharePoint:RenderingTemplate id="KBDocumentSetEdit" runat="server">
  <Template>
    <span id='part1'>
      <SharePoint:InformationBar runat="server"/>
      <div id="listFormToolBarTop">
      <wssuc:ToolBar CssClass="ms-formtoolbar" RightButtonSeparator="&amp;#160;" runat="server">
          <Template_RightButtons>
            <SharePoint:NextPageButton ID="NextPageButton1" runat="server"/>
            <KB:DocSetEditFormSaveButton runat="server"/>
            <SharePoint:GoBackButton ID="GoBackButton1" runat="server"/>
          </Template_RightButtons>
      </wssuc:ToolBar>
      </div>
        <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>
      <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" />
            </div>
      <SharePoint:InitContentType runat="server"/>
      <wssuc:ToolBar CssClass="ms-formtoolbar" id="toolBarTbl" RightButtonSeparator="&amp;#160;" runat="server">
          <Template_Buttons>
            <SharePoint:CreatedModifiedInfo runat="server"/>
          </Template_Buttons>
          <Template_RightButtons>
            <KB:DocSetEditFormSaveButton runat="server"/>
            <SharePoint:GoBackButton runat="server"/>
          </Template_RightButtons>
      </wssuc:ToolBar>
      </td></tr></table>
    </span>
    <SharePoint:AttachmentUpload runat="server"/>
  </Template>
</SharePoint:RenderingTemplate>

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

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

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.

SaveButton

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
        {
            get
            {
                Button button = (Button)this.TemplateContainer.FindControl("diidIOSaveItem");
                return button;
            }
        }
        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(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.

JavaScript

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.

userControlCodeBehind

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



changeRenderingTemplate

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


ViewProperties

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”.

viewPropertiesAfter

You can attach the related code sample here.
Read full article!

Jul 8, 2011

Provision custom Document Sets with CAML

In this article I am going to review how to provision a custom Document Set with CAML. For the demo I will create a custom Document Set called “Meeting Document Set” with one associated custom content type “Meeting Document”. I will provision a custom document library list (named “Meeting Documents”) and our custom content types will be associated with the list, so after the deployment the user can start adding Document Sets and documents within the list. The article also explains how to provision custom Welcome Page for the Document Set and discusses how to customize this page. Below is a brief description how to implement and provision the solution step by step:

1. Activate feature Document Set is a site collection level feature that must be first activated.

  • Go to Site Settings > Site Collection Features page
  • Activate Document Sets feature

DocSetSiteCollectionFeature

2. Create the metadata

Metadata

For the sake of the code sample let’s assume that our custom Document Set will have next columns metadata:

· Meeting Notes – brief description of the meeting

· Conducted At Date – date on which the meeting has held

· Witness – User who is responsible for the goals of the meeting

· Position – his position

· Outcome due Date – end date for achieving goals raised during the meeting

· Internal meeting reference number – internal number. This won’t be visible and will be filled behind the scene (for dummy data for the sake of the demo). The population of this field will be discussed in a separate article for the Document Sets.

The other content type (“Meeting Document”) inherits from Document and which will be associated with the document set will have next metadata:

· Document Internal number – department internal number attached to each document attached to the meeting.

3. Create content types

Creating the “Meeting Document” is out of the scope of this article, so I will skip this.

Below is the CAML definition of “Meeting Document Set” content type:

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <ContentType ID="0x0120D5200064ab7524b97440fcba3080cd5edb05ac"
             Name="Meeting Document Set"
             Group="KB Content Types"
             Description="A custom document set."
             ProgId="SharePoint.DocumentSet" >
    <Folder TargetName="_cts/Meeting Document Set" />
    <FieldRefs>
      <FieldRef ID="{CBB92DA4-FD46-4C7D-AF6C-3128C2A5576E}" ShowInNewForm="TRUE" ShowInEditForm="TRUE"  Name="DocumentSetDescription" />
      <FieldRef ID="{a3a36b22-c551-462c-9ccb-205208770332}" Name="MeetingNotes" Required="TRUE" />
      <FieldRef ID="{4d55b920-f135-4a41-b7cb-4dd94151046d}" Name="ConductedAt"  Required="TRUE" />
      <FieldRef ID="{a646fbed-e3cd-4551-b510-cc92d9eb3c43}" Name="WitnessPosition" Required="TRUE" />
      <FieldRef ID="{165017b9-08c9-4e9e-8560-69488cf67abf}" Name="Witness" Required="TRUE" />
      <FieldRef ID="{36dfd900-a47a-11e0-8264-0800200c9a66}" Name="OutcomeDueDate" />
      <FieldRef ID="{2340e4d0-a55a-11e0-8264-0800200c9a66}" Name="MeetingInternalRefNumber" />
    </FieldRefs>
    <XmlDocuments>
      <XmlDocument NamespaceURI="http://schemas.microsoft.com/office/documentsets/sharedfields">
        <sf:SharedFields xmlns:sf="http://schemas.microsoft.com/office/documentsets/sharedfields" LastModified="1/1/2010 08:00:00 AM">
        </sf:SharedFields>
      </XmlDocument>
      <XmlDocument NamespaceURI="http://schemas.microsoft.com/office/documentsets/allowedcontenttypes">
        <act:AllowedContentTypes xmlns:act="http://schemas.microsoft.com/office/documentsets/allowedcontenttypes" LastModified="1/1/2010 08:00:00 AM">
          <AllowedContentType id="0x01010051a7427b4c1d4fe3aa6a9c0055f60067" />
        </act:AllowedContentTypes>
      </XmlDocument>
      <XmlDocument NamespaceURI="http://schemas.microsoft.com/office/documentsets/welcomepagefields">
        <wpFields:WelcomePageFields xmlns:wpFields="http://schemas.microsoft.com/office/documentsets/welcomepagefields" LastModified="1/1/2010 08:00:00 AM">
          <WelcomePageField id="CBB92DA4-FD46-4C7D-AF6C-3128C2A5576E" />
          <WelcomePageField id="4d55b920-f135-4a41-b7cb-4dd94151046d" />
          <WelcomePageField id="165017b9-08c9-4e9e-8560-69488cf67abf" />
          <WelcomePageField id="a646fbed-e3cd-4551-b510-cc92d9eb3c43" />
        </wpFields:WelcomePageFields>
      </XmlDocument>
      <XmlDocument NamespaceURI="http://schemas.microsoft.com/office/documentsets/defaultdocuments">
        <dd:DefaultDocuments xmlns:dd="http://schemas.microsoft.com/office/documentsets/defaultdocuments" AddSetName="TRUE" LastModified="1/1/2010 08:00:00 AM">
        </dd:DefaultDocuments>
      </XmlDocument>
      <XmlDocument NamespaceURI="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms/url">
        <FormUrls xmlns="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms/url">
          <New>_layouts/NewDocSet.aspx</New>
        </FormUrls>
      </XmlDocument>
      <XmlDocument NamespaceURI="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms">
        <FormTemplates  xmlns="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms">
          <Display>ListForm</Display>
          <Edit>KBDocumentSetEdit</Edit>
          <New>DocSetDisplayForm</New>
        </FormTemplates>
      </XmlDocument>
    </XmlDocuments>
  </ContentType>
</Elements>

As you see the outcome due date and internal reference number fields are not required.

On the Welcome Page we show the out-of-the-box Description field, conducted at, witness and witness position fields.
To get the out-of-the-box description field:
  • Go to C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\FEATURES\DocumentSet

  • Open Elements.XML
Tip: Don’t put comments in your content type definitions. You most likely will face unexpected results, as missing the columns in the content type definition.

4. Create custom Welcome Page
  • Go to C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\FEATURES\DocumentSet

  • Create new module element in your solution and name it MeetingDocSetWelcomePage

  • Copy the DocSetHomePage.aspx from the content of the folder with path 1) and place in the newly created module
Modify the Element.xml in the module to look like this:

CustomWelcomePage


At this point your newly created Welcome Page will be completely empty, and you will see an empty page after creating new meeting document set.

Customization it is up to you and may include custom web parts, user controls, custom controls, etc.

WebPartZones

The out-of-the-box Welcome Page comes with 4 WebPartZones, but you can define as many as you need for your customization purposes.

5. Define web parts for your custom Welcome Page

In this code sample I will define the same content for my custom page as it is in the Document Set OOB.

Create new Document Set (based on OOB content type), select “Edit Page” and export next web parts:
  • Image

  • Document Set Properties

  • Document Set Contents
Below is screenshot of how to export the image web part.

exportImageCustomizingHomePage

Go to the Elements.xml in module MeetingDocSetWelcomePage, open the content of each exported *.dwp file and place them under AllowWebPart elements with appropriate WebPartZoneID within a CDATA.

pageWebParts

At this point you may build and deploy your solution.
If you want to test it:
  • Create new document library

  • Go to Library Settings

  • Go to Advanced settings and select “yes” for Allow management of content types.

  • Under the Content Types list click “Add from existing content types” link, find and add Meeting Document Set.

newDocumentSet

DocSetHome

6. Create List

Now we can create a custom document library, which has the Meeting Document Set associated and it is ready and operational.

The image below is a screen shot from the list definition Schema.xml. Outlined in red are the changes which allow management of list’s content types and associate the custom document set content type and the “Meeting Documents” content type.

In <Fields> sections we enumerate the existing column definitions.

Schema

Our final solution deploys the column definitions, our custom content types “Meeting Document Set” and “Meeting Document”, and custom document library called “Meeting Documents”.

Final

You can attach the related code sample here.

The code sample contains code related to my next article about the rendering templates of Document Set and their customization.
Read full article!