Enhanced Lookup Field – Part 1

The SharePoint lookup field is far from perfect. Once configured, it’s not possible to change the list it gets it’s data from. This is a good thing while the list actually exists.

Now, if a user deleted the source list, the lookup field becomes next to useless and will need to be deleted and re-created.

Would it not be good to be able to re-configure it when this happens?

Or, how about when the source list or display field is deleted, items that have values are not deleted but instead display (in views and the item display form) the original value when they were created?

So, if you had a lookup field that retrieved its values from a list called Cities and in another list this column was added and had existing items. With the out of the box lookup field, if you delete the list Cities any fields that depends on this will stop showing the linked data. Wouldn’t it be good if the custom field detected this and showed the lookup values that were set when the item was last updated? When the list exists, the field would then show the current live item. So, if you changed the name of a city, the field would reflect this, delete the item and the original value would be shown.

Read on to find out how…

Note: I’m not going to show all of the code required within this post, only the important parts. I have, however, provided a link to a working solution, with all the source code – click here to download it.

Parts one, two and three of this post will cover the creation of a custom field, based on the SPFieldLookup field type. Using this field type as the base will allow filtering and sorting to function with minimal additional code. Filtering will be based on the values currently stored in the lookup column, so delete a lookup column value and it will stop showing in the filter, even though this solution will allow list items to show values from items that have been deleted – one small caveat to this solution. In saying that, it will work in a similar way to the out of the box Choice column, where only the values in the choice field definition are shown as filter options, even though some list items could have different values.

Part four will cover the creation of another custom field, this time based on the SPFieldMultiLineText field type, which will act as the related field for the main lookup field.

Other pages for this post are:

Custom Lookup Field
The custom lookup field will require the creation of a number of components:

  • A field type class where you manage the type of data the field stores, how it interacts with the creation of a new fields and displaying within views, for example.
  • An acsx and code behind class that will be used to facilitate the creation of a new field.
  • A class for implementing code for a list item’s new, edit and display modes.
  • XML file for defining and registering the field type in the farm.
  • An XSLT file to control the rendering of the custom lookup field.

We’ll start with the creation of the field type class that derives from SPFieldLookup:

public class ExtendedLookupFieldType: SPFieldLookup
{
  public ExtendedLookupFieldType(SPFieldCollection fields, string fieldName)
      : base(fields, fieldName)
  {
  }
 
  public ExtendedLookupFieldType(SPFieldCollection fields, string typeName, string displayName)
      : base(fields, typeName, displayName)
  {
  }
}

Next, we override the FieldRenderingControl method of the parent class:

public override BaseFieldControl FieldRenderingControl
{
    get
    {
        BaseFieldControl fieldControl = new ExtendedLookupFieldControl(this);
        fieldControl.FieldName = InternalName;
 
        return fieldControl;
    }
}

The FieldRenderingControl method returns a field control object that will be used for rendering. A new class called ExtendedLookupFieldControl will be created for this.

Next, the Update method is overridden:

public override void Update()
{
    XmlDocument document = new XmlDocument();
    document.LoadXml(SchemaXml);
 
    if (AllowMultipleValues)
    {
        UpdateProperty(document, "Mult", "TRUE");
    }
 
    UpdateProperty(document, "WebId", WebSourceId);
    UpdateProperty(document, "List", LookupListId);
    UpdateProperty(document, "ShowField", DisplayColumnId);
 
    SchemaXml = document.OuterXml;
 
    base.Update();
    FreeThreadData();
}

Here, we load the XML schema for the field into a XmlDocument object and update\define the property values. I’ve defined three custom properties (WebSourceId, LookupListId and DisplayColumnId) which are used to update the base LookupWebId, LookupList and LookupField properties via the XmlSchema.

The reason these properties are not directly updated when adding\editing the field definition is because the base class does not allow the values for these properties to be updated. So, by using three custom properties and updating them from the ExtendedLookupFieldControl class when creating or editing the field we can get around this and allow users to update the source list\column in the event the list or column no longer exists.

By updating the SchemaXml instead, it’s possible to update the base properties with new values.

You’ll have noticed a method called FreeThreadData at the end of the Update method. This is a custom method used to free some thread data slots that are used by the three custom properties mentioned above. The reason for using thread data slots is to persist values set against them in the field editor (ExtendedLookupFieldEditor.cs). If you don’t do this, values set against these properties will be lost when the Update method is called. The FreeThreadData method, custom properties and other supporting methods are shown here:

public Guid WebSourceId
{
    get { return GetThreadDataValue("Thread_WebSourceId") != Guid.Empty ? GetThreadDataValue("Thread_WebSourceId") : LookupWebId; }
    set { SetThreadDataValue("Thread_WebSourceId", value); }
}
 
public Guid LookupListId
{
    get { return GetThreadDataValue("Thread_LookupListId") != Guid.Empty ? GetThreadDataValue("Thread_LookupListId") : new Guid(LookupList); }
    set { SetThreadDataValue("Thread_LookupListId", value); }
}
 
public Guid DisplayColumnId
{
    get { return GetThreadDataValue("Thread_DisplayColumnId") != Guid.Empty ? GetThreadDataValue("Thread_DisplayColumnId") : new Guid(LookupField); }
    set { SetThreadDataValue("Thread_DisplayColumnId", value); }
}
 
private Guid GetThreadDataValue(string propertyName)
{
    LocalDataStoreSlot slot = Thread.GetNamedDataSlot(propertyName);
    object dataSlot = Thread.GetData(slot);
 
    return dataSlot != null ? (Guid)dataSlot : Guid.Empty;
}
 
private void SetThreadDataValue(string propertyName, object value)
{
    Thread.SetData(Thread.GetNamedDataSlot(propertyName), value);
}
 
private void FreeThreadData()
{
    Thread.FreeNamedDataSlot("Thread_WebSourceId");
    Thread.FreeNamedDataSlot("Thread_LookupListId");
    Thread.FreeNamedDataSlot("Thread_DisplayColumnId");
}
 
private void UpdateProperty(XmlDocument document, string name, object value)
{
    if (document == null || document.DocumentElement == null)
        return;
 
    XmlAttribute attribute = document.DocumentElement.Attributes[name] ?? document.CreateAttribute(name);
    attribute.Value = value.ToString();
 
    document.DocumentElement.Attributes.Append(attribute);
}

The final part of this post is to add the field type definition XML to the ~hive\Template\XML folder:

<?xml version="1.0" encoding="utf-8" ?>
<FieldTypes>
  <FieldType>
    <Field Name="TypeName">ExtendedLookupFieldType</Field>
    <Field Name="ParentType">Lookup</Field>
    <Field Name="TypeDisplayName">Lookup (Extended)</Field>
    <Field Name="TypeShortDescription">Lookup (Extended)</Field>
    <Field Name="UserCreatable">TRUE</Field>
    <Field Name="Sortable">TRUE</Field>
    <Field Name="Filterable">TRUE</Field>
    <Field Name="ShowInSurveyCreate">TRUE</Field>
    <Field Name="ShowInDocumentLibraryCreate">TRUE</Field>
    <Field Name="ShowInColumnTemplateCreate">TRUE</Field>
    <Field Name="FieldTypeClass">SharePointStu.LookupField.Fields.ExtendedLookupFieldType, SharePointStu.LookupField, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d5173afaa03f7f2d</Field>
    <Field Name="FieldEditorUserControl">/_controltemplates/SharePointStu_LookupField/LookupFieldEditor.ascx</Field>
    <Field Name="AllowBaseTypeRendering">FALSE</Field>
    <Field Name="CAMLRendering">FALSE</Field>
  </FieldType>
</FieldTypes>

Important attributes here are TypeName and ParentType. TypeName must match the class name of the field type class and the value of ParentType should be the name of the base field type the custom field inherits from, which for SPFieldLookup is Lookup.

Part two of this post will continue with the creation of the custom lookup field. Click here to view it.

This entry was posted in SharePoint and tagged , . Bookmark the permalink.

Leave a Reply

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

Solve the maths problem shown below before posting: *