If you are familiar with Custom Columns or Custom Fields in SharePoint 2007, they will feel very similar in 2010. They follow the same basic steps for creating a column/field. The one big change is that the Rendering template has been deprecated, and replaced with XSL to perform the same task. This provides much more flexibility, and with a little jQuery, you can accomplish things that were not possible before.
Problem:
I want a list to hold configuration values for my web parts and read the values in when a web part is rendered. One of the columns/fields in this list would need to be the web part type. I could have the user type the web part type, but this is too prone for errors. I want to show a drop down of items of available web parts in the site collection, storing the type, but displaying a user friendly value. I am going to use SPFieldText for storage, but when looking at the list in a view, I want to show something meaningful.
Solution:
Create a custom field/column definition, add this type to my list to expose currently available web parts. Create an XSL view to show my field, inserting an ID that I can use with jQuery and the SharePoint JavaScript\ECMA Client Object Model to lookup a friendly name.
Steps:
Create a new Visual Studio SharePoint Project:
Select Farm Solution:
Add a reference to System.Web:
Now we need to add some “Mapped” folders. Visual Studio 2010 with the addition of SharePoint project types also adds options for mapping to SharePoint folders. These map to the 14 hive within the WSP and SharePoint will handle placing the files on the filesystems for you.
For the first folder, select “Template\XML” - This will house the field definition file.
Add another Mapped folder for “Template\Layouts\XSL” – This will contain the XSL files for rendering our field.
Now we need to add a couple of classes.
1. WebPartField.cs and WebPartFieldControl.cs
In the WebPartField class mark the class as public, derive from SPFieldText, and add the following:
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
public class WebPartField : SPFieldText
{ public WebPartField(SPFieldCollection fields, string fieldName)
: base(fields, fieldName)
{ }
public WebPartField(SPFieldCollection fields, string typeName, string displayName)
: base(fields, typeName, displayName)
{ }
public override BaseFieldControl FieldRenderingControl
{ get
{ BaseFieldControl fieldControl = new WebPartFieldControl(this);
fieldControl.FieldName = InternalName; return fieldControl;
}
}
}
In the WebPartFieldControl.cs file, mark the class public, derive from BaseFieldControl, and add the following:
using System.Web.UI.WebControls;
using System.Web.UI;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
private WebPartField field;
private DropDownList ddlWebParts;
public WebPartFieldControl(WebPartField parentField)
{ this.field = parentField;
this.ddlWebParts = new DropDownList();
}
protected override void CreateChildControls()
{ base.CreateChildControls();
if (ControlMode == Microsoft.SharePoint.WebControls.SPControlMode.Display)
return;
ddlWebParts = new DropDownList();
ddlWebParts.CssClass = "ms-long";
ddlWebParts.ClearSelection();
ddlWebParts.Items.Clear();
// we need to fill the drop down list with the values for sites in this site collection.
foreach (SPListItem webpart in SPContext.Current.Site.GetCatalog(SPListTemplateType.WebPartCatalog).Items)
{ ddlWebParts.Items.Add(new ListItem(webpart.Title, webpart.Properties[""].ToString()));
}
this.Controls.Add(ddlWebParts);
}
public override object Value
{ get { EnsureChildControls(); return this.ddlWebParts.SelectedItem.Value;
}
set
{ EnsureChildControls();
ListItem item = ddlWebParts.Items.FindByValue(Convert.ToString(value));
if (item != null)
{ ddlWebParts.SelectedIndex = ddlWebParts.Items.IndexOf(item);
}
}
}
public override void UpdateFieldValueInItem()
{ this.EnsureChildControls();
try
{ string value = this.ddlWebParts.SelectedItem.Value;
this.Value = value;
this.ItemFieldValue = Value;
}
catch (Exception)
{ }
}
protected override void Render(HtmlTextWriter output)
{ try
{ // If we are not in edit mode.
if (ControlMode == Microsoft.SharePoint.WebControls.SPControlMode.Display)
{ if (!String.IsNullOrEmpty(this.ItemFieldValue.ToString()))
{ output.WriteLine(SPContext.Current.Site.GetCatalog(SPListTemplateType.WebPartCatalog).GetItemById(Convert.ToInt32(this.ItemFieldValue.ToString())).Title);
}
}
else
{ ddlWebParts.RenderControl(output);
}
}
catch (Exception)
{ }
}
You can see in the code above, we are using a DropDownList control to render the web parts for us. In the Render method, we are only showing the control if the item is in edit mode, otherwise we are showing the Title.
Now we have the fields ready to go in code, but we need to add the definition for SharePoint to pickup.
Right click on the mapped XML folder and select add new item, here we want a descriptive XML file “fldtypes_Custom.WebPartsList.xml”
Make sure the name is fldtypes_ or SharePoint will not pickup the field.
In the XML file, we are going to define things like what we want to call the column, description, where to show the column, but most importantly the class that defines it. Notice in this case, we have the Namespace + Class name, then the token for the assembly name. This token will gather the Public Key Token, culture, assembly name, etc. so we don’t have to.
<?xml version="1.0" encoding="utf-8" ?>
<FieldTypes>
<FieldType>
<Field Name="TypeName">WebPartList</Field>
<Field Name="ParentType">Text</Field>
<Field Name="TypeDisplayName">WebPart List</Field>
<Field Name="TypeShortDescription">WebPart List</Field>
<Field Name="UserCreatable">TRUE</Field>
<Field Name="FieldTypeClass">CustomColumn.WebPartList.WebPartField, $SharePoint.Project.AssemblyFullName$</Field>
</FieldType>
</FieldTypes>
Save the file.
We are basically done. Notice that RenderingTemplate that was used in previous versions is not included. We will now create the template to render the item in lists.
Right click on the XSL mapped folder and select add. We want to create an XSLT file that matches the name of the xml file, just with the XSL extension. fldtypes_Custom.WebPartsList.xsl”
In the file add the following:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:x="http://www.w3.org/2001/XMLSchema"
xmlns:d="http://schemas.microsoft.com/sharepoint/dsp"
version="1.0"
exclude-result-prefixes="xsl msxsl ddwrt"
xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime"
xmlns:asp="http://schemas.microsoft.com/ASPNET/20"
xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:SharePoint="Microsoft.SharePoint.WebControls"
xmlns:ddwrt2="urn:frontpage:internal">
<xsl:template match="FieldRef[@Name = 'WebPart']" mode="Text_body">
<xsl:param name="thisNode" select="." />
<span class="customWebPartID">
<xsl:value-of select="$thisNode/@*[name()=current()/@Name]" />
</span>
</xsl:template>
</xsl:stylesheet>
You may notice that I have included the class=”customWebPartID”, we will use this in our jQuery to replace the ID that would normally be displayed with the WebPart Title.
Now, let’s go ahead and deploy. Right click on the project and select deploy.
After it has completed, let’s create a list, and assign our field. It is important to note that the Field added to the list must match the FieldRed[@Name = ‘’] in the XSL above to have it render properly.
And let’s add our field.
Notice the name of the Field matches the XSL and the new column is shown, this value displayed comes from the TypeShortDescription in the field definition XML file.
I am going to use the title to store the key, and I am going to create another column for the value of the property.
Now let’s add an item.
Obviously there is one issue here, and that is this will only work for your own web parts, but you get the idea. Now hit save. You can now see the item you created in your list.
If you inspect the HTML, you will see that the value is contained within a span with out css class defined.
So at this point, we have a custom column, where we only store the ID, and in the edit and display forms we show the web part title. The next step is add a little jQuery. Check out my next post to find out how.