Fixing the Lookup fields in List Definition Schema
[Update 24/04/07]
I tried Donal's solution and it didn't work for me. Did it work for anyone else?.
Also, I should mention that this is for WSS3 (2007)
[Update 14/04/07]
A commenter by the name of Donal McWeeney updates me that it is possible to refer to other lists by using the relative path in the "List" attribute in the schema. That will make most of the code below unnessecary - which is good. but will not solve the List="Self" problem (when we want the lookup to look into the list we are creating), because we want the user to be able to create many lists with different relative paths. So my code is still useful, but please read the comment by Donal before starting. If his method solves your problem you should use it!
[End Update 14/04/07]
Here is the first output out of the nested tasks project - a solution for the challange of createing list definitions with lookups.
The Problem
If you want to create a feature that will deploy two (or more) lists, with a lookup field connecting them, you are in trouble.
The schema for defining a lookup field requires you to put the ID (Guid) for the list you are referancing. This is a problem since you don't know the ID - this is randomly generated when the list is created.
The Solution
-
Define the lookup list as you would:
<Field Type="Lookup" DisplayName="Parent Task" Required="FALSE" List="Self" ShowField="Title" UnlimitedLengthInDocumentLibrary="FALSE" ID="{94f89715-e097-4e8b-ba79-ea02aa8b7abc}" SourceID="http://schemas.microsoft.com/sharepoint/v3" StaticName="ParentTask" Name="ParentTask" ColName="int3" RowOrdinal="0" />
-
Install my "LookupFixer" web control. Here is the Source code (I will create this as a download as part of the Nested Tasks project when I can):
using System;
using System.Collections;
using System.Collections.Generic;
using System.Xml;
using System.Text;
using System.Web;
using System.Web.UI.WebControls;
using System.Xml.Serialization;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
namespace SharePointTips.SharePoint.WebControls
{
public class LookupFixer : WebControl
{
private string lookupFieldName = "";
public string LookupFieldName
{
get
{
return lookupFieldName;
}
set
{
lookupFieldName = value;
}
}
private string targetListName = "";
public string TargetListName
{
get
{
return targetListName;
}
set
{
targetListName = value;
}
}
protected override void OnLoad(EventArgs e)
{
SPList currentList = SPContext.Current.List;
FixLookUps(currentList);
base.OnLoad(e);
}
public void FixLookUps(SPList list)
{
string fieldName = this.LookupFieldName;
if (list.Fields.ContainsField(fieldName))
{
list.ParentWeb.AllowUnsafeUpdates=true;
SPFieldLookup field = (SPFieldLookup)list.Fields[fieldName];
SPList targetList = GetTargetList();
//check if the field link to the list is broken.
if (field.SourceId != targetList.ID.ToString())
{
//create the new lookup field from the old field
string newFieldXml = field.SchemaXml;
string title = field.Title;
list.Fields.Delete(field.InternalName);
list.Update();
//put the ID of the current list as the source for the lookup
newFieldXml = newFieldXml.Replace("http://schemas.microsoft.com/sharepoint/v3", targetList.ID.ToString());
newFieldXml = newFieldXml.Replace("Self", targetList.ID.ToString());
//change the ID of the field to create it as a unique new field.
newFieldXml = newFieldXml.Replace(field.Id.ToString(), System.Guid.NewGuid().ToString());
//TODO - check if the old field was in the default view, and set it in the optionns
string newFieldID = list.Fields.AddFieldAsXml(newFieldXml);
list.Update();
}
}
}
private SPList GetTargetList()
{
if (TargetListName.Length == 0)
TargetListName = SPContext.Current.List.Title;
return SPContext.Current.Web.Lists[TargetListName];
}
}
}
-
Modify the "AllItems.aspx" to include this control in the following format:
<%@ Register Tagprefix="SharePointTips" Namespace="SharePointTips.SharePoint.WebControls" Assembly="LookupFixer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=074b83dc3e6008ae" %>
<SharePointTips:LookupFixer ID="LookupFixer1" runat="server" TargetListName="Announcements" LookupFieldName="Parent Task" />
What did we just do?
The web control has two properties that allow you to configure the lookup. TargetListName can remain empty if you want the lookup to be to the same list (self referencing list) - like we want it to work in nested tasks, and LookupFieldName is the name of the lookup field that we are trying to create and is broken.
The control, since it is embedded in the "AllItems.aspx" view, will get triggered when the list is created from the list definition, because the AllItems.aspx is the default view.
(*If you change the default view in the schema, make sure you also embedd the control in that aspx file*)
The code in the control reads the properties, loads the list, loads the field, gets the field settings from the schema, and then - because it is impossible to change the broken field - we delete the field, and create a new one from the schema.
The only thing missing here is changing the code to also add the new field to the views that the old field was part of. I didn't have time to look into that yet.
Note!
This is an ugly workaround that works. If anyone has a better solution - please let me know!




