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/
 

Friday, March 13, 2009

Error when accessing the "Site Columns" from Site Admin (../_layouts/mngfield.aspx):

Error:

ErrorObject reference not set to an instance of an object. at Microsoft.SharePoint.ApplicationPages.FieldListRenderer.Render(HtmlTextWriter output) at System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) at System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) at System.Web.UI.Control.RenderControl(HtmlTextWriter writer) at System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) at System.Web.UI.Control.RenderChildren(HtmlTextWriter writer) at System.Web.UI.Control.Render(HtmlTextWriter writer) at System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) at System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) at System.Web.UI.Control.RenderControl(HtmlTextWriter writer) at System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) at System.Web.UI.Control.RenderChildren(HtmlTextWriter writer) at System.Web.UI.HtmlControls.HtmlForm.RenderChildren(HtmlTextWriter writer) at System.Web.UI.HtmlControls.HtmlForm.Render(HtmlTextWriter output) at System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) at System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) at System.Web.UI.HtmlControls.HtmlForm.RenderControl(HtmlTextWriter writer) at System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) at System.Web.UI.Control.RenderChildren(HtmlTextWriter writer) at System.Web.UI.HtmlControls.HtmlContainerControl.Render(HtmlTextWriter writer) at System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) at System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) at System.Web.UI.Control.RenderControl(HtmlTextWriter writer) at System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) at System.Web.UI.Control.RenderChildren(HtmlTextWriter writer) at System.Web.UI.Control.Render(HtmlTextWriter writer) at System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) at System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) at System.Web.UI.Control.RenderControl(HtmlTextWriter writer) at System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) at System.Web.UI.Control.RenderChildren(HtmlTextWriter writer) at Microsoft.SharePoint.WebControls.UnsecuredLayoutsPageBase.RenderChildren(HtmlTextWriter writer) at System.Web.UI.Page.Render(HtmlTextWriter writer) at Microsoft.SharePoint.WebControls.UnsecuredLayoutsPageBase.Render(HtmlTextWriter writer) at System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) at System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) at System.Web.UI.Control.RenderControl(HtmlTextWriter writer) at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)

Caused By:

Found it while deploying a solution for custom sites columns, which was generated by Visual Studio extension project.
(I haven't test it yet, but I guess that running either:
stsadm -o deploysolution -name "%PackageName%" -local -allowGacDeployment -url %TargetWebUrl%
or
stsadm -o activatefeature -id 772a7b6b-30ee-4041-83f7-7620dc74c142 -url %TargetSiteUrl%
will cause this problem)

It only appear when localhost is used for the TargetWebUrl and/or TargetSiteUrl.

Solution:

Uninstall the solution (setup -u in case you are using the solution files generated by the VS extension templates)
Use the fully qualified name of the web application (case sensitive?).

Thursday, March 12, 2009

The operation could not be completed because the Web Part is not on this page

Error:


The operation could not be completed because the Web Part is not on this page.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.


Exception Details: Microsoft.SharePoint.WebPartPages.WebPartPageUserException: The operation could not be completed because the Web Part is not on this page.


Source Error:


An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.


Stack Trace:

[WebPartPageUserException: The operation could not be completed because the Web Part is not on this page.]
Microsoft.SharePoint.WebPartPages.SPWebPartManager.ThrowIfNotOnPage(WebPart webPart) +128
Microsoft.SharePoint.WebPartPages.SPWebPartManager.GetStorageKey(WebPart webPart) +36
Microsoft.SharePoint.WebControls.ViewIcon.get_Src() +172
Microsoft.SharePoint.WebControls.AlphaImage.Render(HtmlTextWriter output) +57
System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) +25
System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) +121
System.Web.UI.Control.RenderControl(HtmlTextWriter writer) +22
System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) +199
System.Web.UI.Control.RenderChildren(HtmlTextWriter writer) +20
System.Web.UI.Control.Render(HtmlTextWriter writer) +7
System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) +25
System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) +121
System.Web.UI.Control.RenderControl(HtmlTextWriter writer) +22
System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) +199
System.Web.UI.Control.RenderChildren(HtmlTextWriter writer) +20
System.Web.UI.HtmlControls.HtmlForm.RenderChildren(HtmlTextWriter writer) +59
System.Web.UI.HtmlControls.HtmlForm.Render(HtmlTextWriter output) +68
System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) +25
System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) +121
System.Web.UI.HtmlControls.HtmlForm.RenderControl(HtmlTextWriter writer) +37
System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) +199
System.Web.UI.Control.RenderChildren(HtmlTextWriter writer) +20
System.Web.UI.HtmlControls.HtmlContainerControl.Render(HtmlTextWriter writer) +29
System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) +25
System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) +121
System.Web.UI.Control.RenderControl(HtmlTextWriter writer) +22
System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) +199
System.Web.UI.Control.RenderChildren(HtmlTextWriter writer) +20
System.Web.UI.Control.Render(HtmlTextWriter writer) +7
System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) +25
System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) +121
System.Web.UI.Control.RenderControl(HtmlTextWriter writer) +22
System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) +199
System.Web.UI.Control.RenderChildren(HtmlTextWriter writer) +20
System.Web.UI.Page.Render(HtmlTextWriter writer) +26
System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) +25
System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) +121
System.Web.UI.Control.RenderControl(HtmlTextWriter writer) +22
System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +2558


Cause by:

Web parts added declaratively (in the asp code) in a list's view (AllItems.aspx, Flat.apx, Threaded.aspx) or form (NewForm.aspx, DispForm.aspx or EditForm.aspx) which contains a SharePoint:ViewIcon control (responsible of drawing the list's image in the upper left corner of the page):




Example:




Solution:
1) Remove the SharePoint:ViewIcon from the page
or
2) Use C# code to inject the web parts in the page:
Example:
SPWeb web = SPContext.Current.Web;
SPLimitedWebPartManager wpManager = web.GetLimitedWebPartManager(web.Lists[listId].DefaultViewUrl, PersonalizationScope.Shared);
MyWebPart myWP = new MyWebPart();
myWP.ChromeType = PartChromeType.TitleAndBorder;
myWP.Title = "My Web Part";
wpManager.AddWebPart(myWP, "WebPartZone1", 0);