MOSS: Functional Example – Custom Data Type

Business Scenario:

Enterprise-wide implementation of MOSS for manufacturing company with 30+ sites and a few dozen corporate departments.

Business Objective:

Despite a multitude of business groups (departments, locations, etc), certain data should be maintained at a global level. For example, an authoritative master list of all physical locations of the company (e.g. manufacturing facilities, warehouse locations, sales offices) should be maintained in a central location.

Technical Problem:

The enterprise taxonomy was implemented using multiple site collections. We would have liked to create the authoritative list of physical locations in a custom WSS list. Then, when we needed to have a column in a content type (or a column added to a list or doc library) that contained corporate locations, we would create a column using the "lookup" datatype and point to this master list.

Unfortunately, lookup datatypes must access a source list "locally" meaning that our authoritative list cannot span site collections.

Technical Solution:

Implement a new custom data type implemented based on SPField and represented as a DropDownList in the UI whose ListItems populate from the master WSS list.

We created a new site collection called "http://localhost/EnterpriseData". There, we created a custom list named "Corporate Locations". This list just uses the standard "Title" field to contain the list of actual corporate locations.

One follows several discrete steps to create a custom data type in WSS. They are:

  1. Define a class which inherits from SPField (one may inherit from other fields if required).

Here is the code for that:

    public class XYZZYCorporateLocationField : SPFieldText
    {
        public XYZZYCorporateLocationField
            (SPFieldCollection fields, string typeName, string displayName)
            : base(fields, typeName, displayName) { }

        public XYZZYCorporateLocationField
                    (SPFieldCollection fields, string displayName)
            : base(fields, displayName) { }

        public override BaseFieldControl FieldRenderingControl
        {
            get
            {
                BaseFieldControl control = new XYZZYCorporateLocationFieldControl();
                control.FieldName = this.InternalName;
                return control;
            } //get
        } // fieldrenderingcontrol

        public override string GetValidatedString(object value)
        {
            if (this.Required || value.ToString().Equals(String.Empty))
            {
                throw new SPFieldValidationException ("Department is not assigned.");
            }
            return base.GetValidatedString(value);
        } // getvalidatedstring

      } // XYZZYCorporateLocation

  1. Define another class that inherits from the base field control, as in:

    public class XYZZYCorporateLocationFieldControl : BaseFieldControl
    {
        protected DropDownList XYZZYCorporateLocationSelector;
        
        protected override string DefaultTemplateName
        {
            get
            {
                return "XYZZYCorporateLocationFieldControl";
            }
        } // DefaultTemplateName

        public override object Value
        {
            get
            {
                EnsureChildControls();
                return this.XYZZYCorporateLocationSelector.SelectedValue;
            } // get
            set
            {
                EnsureChildControls();
                this.XYZZYCorporateLocationSelector.SelectedValue = (string)this.ItemFieldValue;
            } // set
        } // override object Value

        protected override void CreateChildControls()
        {

            if (this.Field == null || this.ControlMode == SPControlMode.Display)
                return;

            base.CreateChildControls();

            this.XYZZYCorporateLocationSelector =
                (DropDownList)TemplateContainer.FindControl("XYZZYCorporateLocationSelector");

            if (this.XYZZYCorporateLocationSelector == null)
                throw new Exception("ERROR: Cannot load .ASCX file!");

            if (!this.Page.IsPostBack)
            {

                using (SPSite site = new SPSite("http://localhost/enterprisedata"))
                {
                    using (SPWeb web = site.OpenWeb())
                    {

                        SPList currentList = web.Lists["Corporate Locations"];

                        foreach (SPItem XYZZYCorporateLocation in currentList.Items)
                        {
                            if (XYZZYCorporateLocation["Title"] == nullcontinue;

                            string theTitle;
                            theTitle = XYZZYCorporateLocation["Title"].ToString();

                            this.XYZZYCorporateLocationSelector.Items.Add  
                                (new ListItem(theTitle, theTitle));

                        } // foreach

                    } // using spweb web = site.openweb()
                } // using spsite site = new spsite("http://localhost/enterprisedata")

            } // if not a postback

        } // CreateChildControls

    } // XYZZYCorporateLocationFieldControl

The above code basically implements the logic for populating the DropDownList with values from the WSS custom list located at http://localhost/enterprisedata and named "Corporate Departments".

I defined both classes in a single .cs file, compiled it and put it into the GAC (strong required, of course).

  1. Implement a control template (.ascx) as shown:

     

<%@ Control Language="C#" Inherits="Microsoft.SharePoint.Portal.ServerAdmin.CreateSiteCollectionPanel1,Microsoft.SharePoint.Portal,Version=12.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c"   compilationMode="Always" %>
<%
@ Register Tagprefix="wssawc" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> <%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<SharePoint:RenderingTemplate ID="XYZZYCorporateLocationFieldControl" runat="server">
  <Template>
    <asp:DropDownList ID="XYZZYCorporateLocationSelector" runat="server" />
  </Template>
</
SharePoint:RenderingTemplate>

The above is saved into c:\program files\common files\microsoft shared\web server extensions\12\controltemplates.

  1. Finally, we create an XML file to save into the …..\12\XML directory. This is CAML that defines our custom data type and for my example, looks like this:

<?xml version="1.0" encoding="utf-8" ?>
<
FieldTypes>
  <
FieldType>
    <
Field Name="TypeName">CorporateLocations</Field>
    <
Field Name="ParentType">Text</Field>
    <
Field Name="TypeDisplayName">Corporate Locations</Field>
    <
Field Name="TypeShortDescription">All XYZZY Corporate locations including manufacturing or other facilities.</Field>
    <
Field Name="UserCreatable">TRUE</Field>
    <
Field Name="ShowInListCreate">TRUE</Field>
    <
Field Name="ShowInDocumentLibraryCreate">TRUE</Field>
    <
Field Name="ShowInSurveyCreate">TRUE</Field>
    <
Field Name="ShowInColumnTemplateCreate">TRUE</Field>
    <
Field Name="FieldTypeClass">Conchango.XYZZYCorporateLocationField, XYZZYCorporateLocationField, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b0b19e85410990c4</Field>
    <
RenderPattern Name="DisplayPattern">
      <
Switch>
        <
Expr>
          <
Column />
        </
Expr>

        <Case Value=""/>

        <Default>
          <
HTML>
            <![CDATA[
<span style="color:Red"><b>]]>
          </
HTML>
          
          <
Column SubColumnNumber="0" HTMLEncode="TRUE"/>

          <HTML><![CDATA[</b></span>]]></HTML>
          
        </
Default>
      </
Switch>
      
    </
RenderPattern>
  </
FieldType>
</
FieldTypes>
This XML file adds the custom data type to the WSS "library" and matches it up against the GAC’d assembly.

After moving all these bits into place, iisreset on the server and it should all start working nicely.

3 thoughts on “MOSS: Functional Example – Custom Data Type

  1. Alejandro
    Hi Paul,
    First of all, thanks for the article, because it’s very interesting. Only one question;
    Do you know if it’s posible to render correctly a custom field type in the datasheet view of a list?
    Because every custom field type I create it’s shown as read-only in datasheet view (and the MSDN, for example, doesn’t help me very much :-S).
     
    Thanks
    Reply
  2. Lyndsay

    I am attempting to implement your solution.  However I instead of DropDownList, I only have the option for a DropDownChoiceList.  Do you happen to know how to add items to a DropDownChoiceList? We are using SharePoint 2007 SP1 and Visual Studio 2005 SP1.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *