I recently had some time on my hands, so I decided to explore different ways of customizing SharePoint forms in an effort to find the "best" way. It seemed to me that to truly reap the benefits of using SharePoint as a development platform, the key was to reuse as much out-of-box functionality as possible (i.e. attachments, delete logic that is sensitive to the recycle bin's state, optimistic conflict detection on updates, etc). However, once I started digging into the options for customizing forms, I was left wanting.
First, I tried my hand at writing custom web parts and ended up with a lot of tedious coding (compared with the classic ASP.NET designer experience). While I did wrap up some of the rendering logic into reusable base class methods to maintain the SharePoint look and feel, I was not satisfied with the experience and continued my search.
This led me to find Jan Tielens' excellent work on the SmartPart which is essentially a web part that hosts an ASP.NET user control. It's great for enabling an ASP.NET designer experience, but doesn't really help accomplish the goal of reusing existing SharePoint functionality (additionally it has the drawback of making it a bit clumsy for users to configure your web parts themselves if that is applicable in your scenario).
At this point, my history in using code generation for data layers going back into the 90's came to the surface, and I briefly considered implementing code templates that would inspect the SharePoint list definitions and do all the initial gruntwork of setting up a page. But then again, I'd be in the position of essentially reimplementing or figuring out how to integrate all the out-of-the-box SharePoint goodness described above. Not the business I wanted to be in.
Still not satisfied I found some information on using SharePoint Designer to insert a custom list form, but I (and obviously a few others) found it to be problematic since it broke the functionality for attachments (or "attachements" if you ask the Microsoft developer who wrote the JavaScript). Also, I was well outside of the world I wanted to be in for further customizations (some pretty heinous looking XSLT and JavaScript... yeech).
And so the little bird looked on...
I had recently become aware of the existence of all the templates in the the C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\CONTROLTEMPLATES folder, and I decided that to stand the best chance of reusing SharePoint functionality, I needed to understand how SharePoint renders its own content.
So I rolled up my sleeves, whipped out my trusty .NET Reflector and went to work. If there is interest, I will post a follow up on the details of my exploration through SharePoint's template rendering logic.
For the rest of you who just want to customize a form and retain attachments functionality, here's the skinny.
Solution
I have a list in my site that has some test data on Test Centers which I'll be using for this example.
Below is a screen shot of the out-of-the-box Edit form which I want to customize. For this example, let's assume that I want to make the Test Center Number and address components read-only, and I don't want either the phone or fax number to be displayed at all. When I'm done, I want all the existing out-of-the-box SharePoint functionality to be preserved (Attach File, Delete Item, Spelling, optimistic Updates, created/modified by information, etc.).
When I put this solution together, I used Jan Tielens' Smart Templates for VS 2008 which made the process of building and deploying the web part an absolute breeze. It creates a Setup folder that contains an installer (Setup.exe) which you'll need to run.
Once installed and deployed, activate it in the Site Collection Features page:
Before attempting to use the web part, you'll also need to install the CustomListForms.dll assembly (which should now be located in your web site's bin folder) into the GAC. This can be done by dragging the file into the C:\Windows\assembly folder in Windows Explorer, or from a command prompt using the following command:
gacutil.exe /i "<assemblyFilePath>"
Next, add a new web part page to your site (I called mine "TestCenterEdit") and add an instance of the Custom List Form Web Part, which can be found in the "Custom List Forms" section.
If you scroll all the way to the bottom in the properties pane on the right side of the page, you'll see the "Custom List Properties" section. Expanding that shows the following settings (yours will be blank initially):
The Template ID identifies the ID of the template that SharePoint is going to use to render the web part (I'll show you how to add that template shortly). The Field List allows you define the fields (as a semicolon delimited list) that you are targeting for inclusion/exclusion on the form based on the selection in Field List Type. The Read-only Fields setting allows you to identify any fields that should be rendered for display only. Finally, the Control Mode value tells SharePoint whether the controls should be rendered in New, Edit or Display mode. Basically, this setting should match the function of the form you are customizing. Since I'm customizing my Edit form, I've set mine to "Edit". Once you've set the values the way you want them, click Apply.
The web part will still appear blank because we still need to provide the custom template that SharePoint will use to render our custom Edit form. Create a new file called "CustomForms.ascx" in the C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\CONTROLTEMPLATES folder. SharePoint loads all the user control files in this folder when the application pool starts in IIS, enabling each "RenderingTemplate" to be referenced by ID. Add the following markup to the new file:
<%@ Control Language="C#" AutoEventWireup="false" %>
<%@Assembly Name="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@Register TagPrefix="Custom" Assembly="CustomListForms, Version=1.0.0.0, Culture=neutral, PublicKeyToken=7597e2c2cd876846" namespace="CustomListForms" %>
<%@Register TagPrefix="SharePoint" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" namespace="Microsoft.SharePoint.WebControls"%>
<%@Register TagPrefix="SPHttpUtility" Assembly="Microsoft.SharePoint, Version=12.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" %>
<SharePoint:RenderingTemplate ID="CustomListForm" runat="server">
<Template>
<SPAN id='part1'>
<wssuc:ToolBar CssClass="ms-formtoolbar" id="toolBarTbltop" RightButtonSeparator=" " runat="server">
<Template_RightButtons>
<SharePoint:SaveButton runat="server"/>
<SharePoint:GoBackButton runat="server"/>
</Template_RightButtons>
</wssuc:ToolBar>
<SharePoint:FormToolBar runat="server"/>
<TABLE class="ms-formtable" style="margin-top: 8px;" border=0 cellpadding=0 cellspacing=0 width=100%>
<TR><SharePoint:CompositeField FieldName="Name" ControlMode="Display" runat="server"/></TR>
<Custom:CustomListFormFieldIterator 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"/>
<wssuc:ToolBar CssClass="ms-formtoolbar" id="toolBarTbl" RightButtonSeparator=" " runat="server">
<Template_Buttons>
<SharePoint:CreatedModifiedInfo runat="server"/>
</Template_Buttons>
<Template_RightButtons>
<SharePoint:SaveButton runat="server"/>
<SharePoint:GoBackButton runat="server"/>
</Template_RightButtons>
</wssuc:ToolBar>
</td></tr></TABLE>
</SPAN>
<SharePoint:AttachmentUpload runat="server"/>
</Template>
</SharePoint:RenderingTemplate>
If you're wondering how I came up with this template, open up the DefaultTemplates.ascx file in the CONTROLTEMPLATES folder and search for the "UserListForm" template. It's basically identical, except I switched out the <SharePoint:ListFieldIterator> element for my <Custom:CustomListFormFieldIterator>. As an interesting learning exercise, search the file for all usages of the ListFieldIterator and see where else SharePoint uses this control.
Once the template is in place, restart IIS (or recycle the application pool) so that SharePoint will see the new template. The last thing you need to do is associate your new web part page as the Edit form for your list. The easiest way to do this is with SharePoint Designer (but could also be done through a feature):
- Connect to your site in SharePoint Designer and expand the "Lists" node.
- Right-click on the tree view item for your list and select "Properties".
- Click on the "Supporting Files" tab.
- Change the "Content type specific forms" to "Item".
- On the "Edit item form" setting, click the "Browse..." button and locate your web part page.
- Click OK to apply the changes.
Here is how my dialog looks after I've configured custom forms for the "New" and "Edit" functionality:
Go back to your web browser and navigate to the list and edit an item. Voilà!
Attach File works!
Before saving...
After saving...
Delete works! (And it knows whether or not the Recycle Bin is enabled)
Optimistic updates work!
Spelling works!
(You get the idea)
The template I've given here should work for most needs, so the only work needed to customize additional forms is basically to add a new web part page, add the web part and change the properties appropriately, and configure the SharePoint list to use the new form for new/edit/display functionality.
If there is interest, and time allowing, I'll post a follow up going over the details of the web part implementation and explore other possibilities beyond just rendering the usual templated SharePoint controls (i.e. inserting a map immediately after the address). You are, after all, operating within the ASP.NET infrastructure so a lot more customization is possible.
Get the source and Setup executable here.
UPDATE: Based on a response I got on the TechNet thread on using SharePoint Designer for this, I added another property to the web part: FieldTemplateOverrides. This allows you to specify a different template to use when rendering a particular field. I changed my sample usage from above to exclude the State/Province and the ZIP/Postal Code columns, and then told the web part to use a different template for the "City" column (by setting the FieldTemplatesOverrides property to "City=CityStateZIPField". Here's what the two templates that I added to my "CustomTemplates.ascx" file look like:
<SharePoint:RenderingTemplate ID="CityStateZIPField" runat="server">
<Template>
<TD nowrap="true" valign="top" width="190px" class="ms-formlabel">
<H3 class="ms-standardheader">
<nobr>City/State/ZIP<span class="ms-formvalidation"> *</span></nobr>
</H3>
</TD>
<TD valign="top" class="ms-formbody" width="400px">
<SharePoint:TextField runat="Server" FieldName="City" DisplaySize="15" TemplateName="TextFieldNoBr"/>, <SharePoint:TextField runat="Server" FieldName="State/Province" DisplaySize="2" TemplateName="TextFieldNoBr"/>
<SharePoint:TextField runat="Server" FieldName="ZIP/Postal Code" DisplaySize="10" TemplateName="TextFieldNoBr"/>
</TD>
</Template>
</SharePoint:RenderingTemplate>
<SharePoint:RenderingTemplate ID="TextFieldNoBr" runat="server">
<Template>
<asp:TextBox ID="TextField" MaxLength="255" runat="server"/>
</Template>
</SharePoint:RenderingTemplate>
I had to add a special template for the TextField because the out-of-the-box template appends a <br> tag at the end. Here's how the web part renders now:
I didn't handle the "Display" or "Required" cases, but I could augment the property to take multiple templates for each (kind of like how Excel accepts various formats for positive and negative values), but at that point I should probably look to creating a custom UI so that it doesn't get overly convoluted to use...