Monday, May 7, 2012

Custom Forms for SharePoint Content Types


1. Create a content type and declare the forms used for rendering in <XmlDocuments> element.
<xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<ContentType ID="0x01009f39b33e90e84fd8aa62abdd11ec3313"
Name="ContentTypeCustomForms - MyContentType"
Group="Custom Content Types"
Description="My Content Type"
Inherits="TRUE"
Version="0">
<FieldRefs>
<FieldRef ID="{4672BFD1-40BD-4793-8C35-89EA7C0BEB7D}" Name="MyField" />
</FieldRefs>
<XmlDocuments>
<XmlDocument NamespaceURI="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms">
<FormTemplates xmlns="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms">
<Edit>MyCTForm</Edit>
<New>MyCTForm</New>
<FormTemplates>
</XmlDocument>
</XmlDocuments>
</ContentType>
<Field ID="{4672BFD1-40BD-4793-8C35-89EA7C0BEB7D}" Type="Text" Name="MyField" DisplayName="MyField" />
</Elements>
Note: each form (New, Edit, Display) can use a different rendering control. See http://msdn.microsoft.com/en-us/library/ms468901.aspx for more information.
2. Create an ascx control (CustomCTForm.ascx) under CONTROLTEMPLATES folder (CustomCTForm.ascx in this case). This control cannot have code behind (even if it's declared, SharePoint will ignore it), but it can be used as a container for code-behind user controls. Every child control it contains, must be declared within the SharePoint:RenderingTemplate control:

<SharePoint:RenderingTemplate id="MyCTForm" runat="server">
<Template>
...
</Template>
</SharePoint:RenderingTemplate>
You can look into "CONTROLTEMPLATES\DefaultTemplates.ascx" for some examples (especially "ListForm").
Note: when starting, SharePoint will load all ascx files it finds in the CONTROLTEMPLATES folder (but not its sub-folders!), scan each ascx for all the RenderingTemplate controls and add them to a Hashtable with their ID property as the key and the Template property as the value.
Whenever the form template will be required to render a content type, SharePoint will read the id from the content type definition and get the corresponding rendering template (as a string) from the Hashtable. As a result of this mechanism, the acsx code behind (if any) will be ignored.
(Use Reflector on the SPControlTemplateManager private methods GetTemplateCollection and LoadControlTemplate to see the code)
3. Create the code-behind user control (MyUserControl.ascx). This ascx is not restricted to a specific location, in this case is deployed under "CONTROLTEMPLATES\MyFolder".

4. Declare the code-behind control (MyUserControl.ascx) in the rendering control (CustomCTForm.ascx):
...
<%@ Register TagPrefix="MyProject" TagName="MyUserControl" Src="~/_controltemplates/MyFolder/MyUserControl.ascx" %>
...
<SharePoint:RenderingTemplate id="MyCTForm" runat="server">
    <Template>
...
        <MyProject:MyUserControl ID="MyUserControl1" runat="server" />
...
    </Template>
</SharePoint:RenderingTemplate>

5. Add the necessary controls in the code-behind ascx (MyUserControl.ascx). Those can be either standard SharePoint controls (SharePoint:FieldLabel, SharePoint:FormField, SharePoint:FieldDescription, etc) or ASP.NET controls or a combination of both (as in this example).

Note: if an ASP.NET control is used for rendering SharePoint fields, a SharePoint:FormField control needs to be added on the form (with Visible="false") so SharePoint will add it's corresponding field in the SPContext.Current.FormContext.FieldControlCollection, making it easy to save its value.
Note2: Even if a SharePoint:FormField control is marked as Visible="false", the SharePoint:ListFieldIterator control, if present on the form, will still render it. Easiest way of preventing this is to exclude it from the ListFieldIterator control:
<SharePoint:ListFieldIterator ID="ListFieldIterator1" ExcludeFields="MyField" runat="server" />

6. In case ASP.NET controls have been used to render a SharePoint feild, add code for saving/ retrieving values in/ from SharePoint storage.
One way to save the values into SP is to set on each post back, the properties Value and ItemFieldValue of the required fields (available in the SPContext.Current.FormContext.FieldControlCollection) with the values fom the ASP.NET controls. SharePoint will take care of the rest.
foreach (object field in SPContext.Current.FormContext.FieldControlCollection)
{
    TextField tField = field as TextField;
    if (null != tField && "MyField" == tField.FieldName)
    {
         Field.Value = myTextBox.Text;
         tField.ItemFieldValue = myTextBox.Text;
    }
}

Note: for fields declared as Required="TRUE" in the schema definition, SP will validate their value and if is empty it will fail, without any error message (the form will not be closed). Each SP Field type has is own validation mechanism to check for empty value.

 To load the value from SharePoint, on  you can check if the page is in edit mode () and set the values for the UI controls with the values from SP:
string myFieldValue = (string)SPContext.Current.ListItem["MyField"];
myTextBox.Text = myFieldValue;




Source code on codeplex http://spcustomforms.codeplex.com/
 

No comments: