{"id":1235,"date":"2012-12-10T21:08:37","date_gmt":"2012-12-10T21:08:37","guid":{"rendered":"http:\/\/www.stuartroberts.net\/?p=1235"},"modified":"2013-03-17T15:46:35","modified_gmt":"2013-03-17T15:46:35","slug":"enhanced-lookup-field-part-4","status":"publish","type":"post","link":"http:\/\/www.stuartroberts.net\/index.php\/2012\/12\/10\/enhanced-lookup-field-part-4\/","title":{"rendered":"Enhanced Lookup Field &#8211; Part 4"},"content":{"rendered":"<p>This is the fourth and final part of the post describing how to create a custom lookup field for SharePoint.<\/p>\n<p>Other pages in this post are:<\/p>\n<ul>\n<li>Part 1 &#8211; <a href=\"http:\/\/www.stuartroberts.net\/index.php\/2012\/10\/03\/enhanced-lookup-field-part-1\/\" title=\"Enhanced Lookup Field - Part 1\">Creating a custom lookup field type<\/a><\/li>\n<li>Part 2 &#8211; <a href=\"http:\/\/www.stuartroberts.net\/index.php\/2012\/10\/20\/enhanced-lookup-field-part-2\/\" title=\"Enhanced Lookup Field - Part 2\">Add field editor class<\/a><\/li>\n<li>Part 3 &#8211; <a href=\"http:\/\/www.stuartroberts.net\/index.php\/2012\/11\/13\/enhanced-lookup-field-part-3\/\" title=\"Enhanced Lookup Field - Part 3\">Add field control class<\/a><\/li>\n<\/ul>\n<p>This post describes how to implement the hidden related field for controlling the display within views.<\/p>\n<p><!--more--><\/p>\n<p>To begin with, create a new class that inherits from the <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/microsoft.sharepoint.spfieldmultilinetext(v=office.12).aspx\" title=\"SPFieldMultiLineText Class\" target=\"_blank\">SPFieldMultiLineText<\/a> class.<\/p>\n<pre lang=\"csharp\">\r\npublic class RelatedLookupDataFieldType : SPFieldMultiLineText\r\n{\r\n    public RelatedLookupDataFieldType(SPFieldCollection fields, string fieldName)\r\n        : base(fields, fieldName)\r\n    {\r\n    }\r\n\r\n    public RelatedLookupDataFieldType(SPFieldCollection fields, string typeName, string displayName)\r\n        : base(fields, typeName, displayName)\r\n    {\r\n    }\r\n}\r\n<\/pre>\n<p>This class will be used to control the display within views, which we implement by creating an XSLT to control which field to use for rendering.<\/p>\n<pre lang=\"xml\">\r\n<xsl:stylesheet xmlns:x=\"http:\/\/www.w3.org\/2001\/XMLSchema\"\r\n                xmlns:d=\"http:\/\/schemas.microsoft.com\/sharepoint\/dsp\"\r\n                version=\"1.0\"\r\n                exclude-result-prefixes=\"xsl msxsl ddwrt\"\r\n                xmlns:ddwrt=\"http:\/\/schemas.microsoft.com\/WebParts\/v2\/DataView\/runtime\"\r\n                xmlns:asp=\"http:\/\/schemas.microsoft.com\/ASPNET\/20\"\r\n                xmlns:__designer=\"http:\/\/schemas.microsoft.com\/WebParts\/v2\/DataView\/designer\"\r\n                xmlns:xsl=\"http:\/\/www.w3.org\/1999\/XSL\/Transform\"\r\n                xmlns:msxsl=\"urn:schemas-microsoft-com:xslt\"\r\n                xmlns:SharePoint=\"Microsoft.SharePoint.WebControls\"\r\n                xmlns:ddwrt2=\"urn:frontpage:internal\">\r\n  \r\n  <xsl:template match=\"FieldRef[@FieldType='ExtendedLookupFieldType']\" mode=\"Lookup_body\" ddwrt:dvt_mode=\"body\" priority=\"10\">\r\n    <xsl:param name=\"thisNode\" select=\".\"\/>\r\n    <xsl:param name=\"fieldValue\" select=\"$thisNode\/@*[name()=current()\/@Name]\"\/>\r\n    \r\n    <xsl:variable name=\"fieldDefinition\" select=\"$XmlDefinition\/ViewFields\/FieldRef[@Name=current()\/@Name]\"\/>\r\n    <xsl:variable name=\"fieldId\" select=\"$fieldDefinition\/@ID\"\/>\r\n    \r\n    <xsl:variable name=\"lookupDataValue\">\r\n      <xsl:call-template name=\"string-replace-with\">\r\n        <xsl:with-param name=\"candidate\" select=\"$fieldId\" \/>\r\n        <xsl:with-param name=\"replace\" select=\"'-'\" \/>\r\n        <xsl:with-param name=\"with\" select=\"''\" \/>\r\n      <\/xsl:call-template>\r\n    <\/xsl:variable>\r\n\r\n    <xsl:value-of select=\"$thisNode\/@*[name()=concat('r_', substring($lookupDataValue,1,30))]\" disable-output-escaping =\"yes\"\/>\r\n  <\/xsl:template>\r\n  \r\n  <xsl:template name=\"string-replace-with\">\r\n    <xsl:param name=\"candidate\" \/>\r\n    <xsl:param name=\"replace\" \/>\r\n    <xsl:param name=\"with\" \/>\r\n    <xsl:choose>\r\n      <xsl:when test=\"contains($candidate, $replace)\">\r\n        <xsl:value-of select=\"substring-before($candidate,$replace)\" \/>\r\n        <xsl:value-of select=\"$with\" \/>\r\n        <xsl:call-template name=\"string-replace-with\">\r\n          <xsl:with-param name=\"candidate\" select=\"substring-after($candidate,$replace)\" \/>\r\n          <xsl:with-param name=\"replace\" select=\"$replace\" \/>\r\n          <xsl:with-param name=\"with\" select=\"$with\" \/>\r\n        <\/xsl:call-template>\r\n      <\/xsl:when>\r\n      <xsl:otherwise>\r\n        <xsl:value-of select=\"$candidate\" \/>\r\n      <\/xsl:otherwise>\r\n    <\/xsl:choose>\r\n  <\/xsl:template>\r\n<\/xsl:stylesheet>\r\n<\/pre>\n<p>This file (fldtypes_SPS_LookupFieldType.xsl) should be created in the ~hive\\Template\\Layouts\\XSL folder.<\/p>\n<p>The first template in this file matches the ExtendedLookupFieldType field type, which is the master field used to store the lookup data.<\/p>\n<pre lang=\"xml\">\r\n<xsl:template match=\"FieldRef[@FieldType='ExtendedLookupFieldType']\" mode=\"Lookup_body\" ddwrt:dvt_mode=\"body\" priority=\"10\">\r\n<\/pre>\n<p>We start here by retrieving the field definition for the field and then get the identifier for the field.<\/p>\n<pre lang=\"xml\">\r\n<xsl:variable name=\"fieldDefinition\" select=\"$XmlDefinition\/ViewFields\/FieldRef[@Name=current()\/@Name]\"\/>\r\n<xsl:variable name=\"fieldId\" select=\"$fieldDefinition\/@ID\"\/>\r\n<\/pre>\n<p>This is then passed into the string-replace-with template which performs some basic string replacement to remove the dashes (-) from the Guid, so we are left with a string 32 characters in length.<\/p>\n<p>Using this, we are able to retrieve the associated hidden field which is named using the identifier for the master field, which is then used to render the content for the list item.<\/p>\n<pre lang=\"xml\">\r\n<xsl:value-of select=\"$thisNode\/@*[name()=concat('r_', substring($lookupDataValue,1,30))]\" disable-output-escaping =\"yes\"\/>\r\n<\/pre>\n<p>We&#8217;ll come to the creation of the hidden associated field next.  For this we need to edit the <em>RelatedLookupDataFieldType<\/em> class we created back in <a href=\"http:\/\/www.stuartroberts.net\/index.php\/2012\/10\/03\/enhanced-lookup-field-part-1\/\" title=\"Enhanced Lookup Field \u2013 Part 1\">post 1<\/a>.<\/p>\n<pre lang=\"csharp\">\r\n\r\npublic ProcessLookupFieldType(SPFieldCollection fields, string fieldName)\r\n    : base(fields, fieldName)\r\n{\r\n    EnsureAssociatedField();\r\n}\r\n\r\npublic ProcessLookupFieldType(SPFieldCollection fields, string typeName, string displayName)\r\n    : base(fields, typeName, displayName)\r\n{\r\n    EnsureAssociatedField();\r\n}\r\n\r\nprivate void EnsureAssociatedField()\r\n{\r\n    if (Id == Guid.Empty || ParentList == null || !ParentList.Fields.ContainsFieldWithStaticName(RelatedFieldName))\r\n        return;\r\n    \r\n    XmlDocument doc = new XmlDocument();\r\n    doc.LoadXml(SchemaXml);\r\n    if (doc.DocumentElement == null)\r\n        return;\r\n\r\n    XmlNodeList nodes = doc.DocumentElement.SelectNodes(string.Format(\"\/\/FieldRefs\/FieldRef[@Name='{0}']\", RelatedFieldName));\r\n    if (nodes != null && nodes.Count > 0)\r\n        return;\r\n\r\n    bool allowUnsafeUpdates = ParentList.ParentWeb.AllowUnsafeUpdates;\r\n    try\r\n    {\r\n        ParentList.ParentWeb.AllowUnsafeUpdates = true;\r\n        AssociateInternalName(RelatedFieldName);\r\n        base.Update();\r\n    }\r\n    finally\r\n    {\r\n        ParentList.ParentWeb.AllowUnsafeUpdates = allowUnsafeUpdates;\r\n    }\r\n}\r\n\r\npublic override void OnAdded(SPAddFieldOptions op)\r\n{\r\n    AddRelatedField();\r\n    base.OnAdded(op);\r\n    Update();\r\n}\r\n\r\npublic string RelatedFieldName { get { return string.Concat(\"r_\", Id.ToString(\"N\")).Substring(0, 32); } }\r\n\r\nprivate void AddRelatedField()\r\n{\r\n    SPList list = SPContext.Current.List;\r\n    SPFieldCollection fields = list.Fields;\r\n\r\n    SPFieldMultiLineText field = new SPFieldMultiLineText(fields, \"RelatedLookupDataFieldType\", RelatedFieldName);\r\n    field.ReadOnlyField = true;\r\n    field.RichText = false;\r\n    field.UnlimitedLengthInDocumentLibrary = true;\r\n    field.Hidden = true;\r\n\r\n    string internalName = fields.Add(field);\r\n    list.Update();\r\n\r\n    AssociateInternalName(internalName);\r\n}\r\n\r\nprivate void AssociateInternalName(string fieldInternalName)\r\n{\r\n    XmlDocument doc = new XmlDocument();\r\n\r\n    doc.LoadXml(SchemaXml);\r\n    XmlElement fieldRefs = doc.CreateElement(\"FieldRefs\");\r\n    fieldRefs.InnerXml = string.Format(\"<FieldRef Name=\\\"{0}\\\" Explicit=\\\"TRUE\\\" \/>\", fieldInternalName);\r\n    doc.ChildNodes[0].AppendChild(fieldRefs);\r\n\r\n    SchemaXml = doc.InnerXml;\r\n}\r\n<\/pre>\n<p>What we&#8217;ve done here is override the <em>OnAdded<\/em> method of the base lookup field class and call a custom <em>AddRelatedField<\/em> method.<\/p>\n<p>This method creates a new field based on the one created during this part of the blog post &#8211; <em>RelatedLookupDataFieldType<\/em>.  As we don&#8217;t want users to be aware of this field it&#8217;s created as a hidden field and set to read only.  We set <em>UnlimitedLengthInDocumentLibrary<\/em> to true to ensure there we&#8217;re able to store the serialised object string containing the selected lookup field data.  More on this later.<\/p>\n<p>Next, we associate this new field with the master lookup field by adding it to the list of field references for it.  This ensures that whenever the master field is added to a view, this hidden field will also be added, allowing the XSLT to find it.  If we didn&#8217;t do this, the rendering would fail as there would be no way for a general user to manually add a hidden field to a view.  Also, there&#8217;s no way through the object model to achieve this, hence the injection of XML into the SchemaXml property.<\/p>\n<p>Lastly, we update the constructors to call the EnsureAssociatedField method.  This helps to ensure the field reference has been correctly applied to the field definition as the field is instantiated.<\/p>\n<p>To ensure that whenever a custom lookup field is removed from a list, the following code is implemented to ensure the hidden related field is also removed.<\/p>\n<pre lang=\"csharp\">\r\npublic override void OnDeleting()\r\n{\r\n    RemoveRelatedFields();\r\n    base.OnDeleting();\r\n}\r\n\r\nprivate void RemoveRelatedFields()\r\n{\r\n    if (SPContext.Current.List.Fields.ContainsField(RelatedFieldName))\r\n    {\r\n        SPField field = SPContext.Current.List.Fields.GetFieldByInternalName(RelatedFieldName);\r\n        field.ReadOnlyField = false;\r\n        field.Hidden = false;\r\n        field.Update();\r\n        SPContext.Current.List.Fields.Delete(field.InternalName);\r\n    }\r\n}\r\n<\/pre>\n<p>This time, we&#8217;re overriding the <em>OnDeleting<\/em> method from the base class and calling <em>RemoveRelatedFields<\/em> to delete the field from the list.  Before calling delete it&#8217;s necessary to update the field to not be read only or hidden, otherwise the delete call will fail.<\/p>\n<p>Remember, the data is being stored as a serialised <em>ExtendedLookupData<\/em> object in the hidden associated field by the master field when it is saved.  This is what will be passed into the <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/microsoft.sharepoint.spfield.getfieldvalueashtml(v=office.12).aspx\" title=\"SPField.GetFieldValueAsHtml Method\" target=\"_blank\">GetFieldValueAsHtml<\/a> method implemented next.<\/p>\n<pre lang=\"csharp\">\r\npublic override string GetFieldValueAsHtml(object value)\r\n{\r\n    if (value != null)\r\n    {\r\n        string data = SPHttpUtility.HtmlDecode(value.ToString());\r\n        ExtendedLookupData lookupData = Serialization<ExtendedLookupData>.DeserializeObject(data);\r\n\r\n        if (lookupData != null && lookupData.SavedDisplayValues != null && lookupData.SavedDisplayValues.Count > 0)\r\n        {       \r\n            string display = GetFieldValuesAsLinks(lookupData);\r\n\r\n            if (string.IsNullOrEmpty(display))\r\n            {\r\n                display = GetFieldValueAsText(data);\r\n            }\r\n            return display;\r\n        }\r\n    }\r\n\r\n    return null;\r\n}\r\n<\/pre>\n<p>Here, the object passed into the method is deserialised into an <em>ExtendedLookupData<\/em> object, which as long as there are stored values, the object is processed to obtain the HTML to display in the view.<\/p>\n<pre lang=\"csharp\">\r\nprivate string GetFieldValuesAsLinks(ExtendedLookupData itemdata)\r\n{\r\n    Dictionary<Guid, string> listFormUrls = CacheManager.Instance.GetValue(\"extendedLookupListFormUrls\") as Dictionary<Guid, string>;\r\n\r\n    bool retrieveList = listFormUrls == null || !listFormUrls.ContainsKey(itemdata.ListId);\r\n    if (listFormUrls == null)\r\n        listFormUrls = new Dictionary<Guid, string>();\r\n\r\n    if (retrieveList)\r\n    {\r\n        using (SPSecurity.SuppressAccessDeniedRedirectInScope scope = new SPSecurity.SuppressAccessDeniedRedirectInScope())\r\n        {\r\n            try\r\n            {\r\n                using (SPWeb web = SPContext.Current.Site.OpenWeb(itemdata.WebId))\r\n                {\r\n                    SPList lookupList = (from list in web.Lists.Cast<SPList>()\r\n                                            where list.ID.Equals(itemdata.ListId)\r\n                                            select list).SingleOrDefault();\r\n\r\n                    string formUrl = lookupList != null ? string.Concat(lookupList.ParentWeb.Url, \"\/_layouts\/listform.aspx?PageType=4&ListId=\", itemdata.ListId.ToString(\"B\")) : string.Empty;\r\n                    listFormUrls.Add(itemdata.ListId, formUrl);\r\n\r\n                    CacheManager.Instance.Add(\"extendedLookupListFormUrls\", listFormUrls, DateTime.Now.AddMinutes(10), CacheItemPriority.Normal);\r\n                }\r\n            }\r\n            catch (UnauthorizedAccessException)\r\n            {\r\n                return null;\r\n            }\r\n        }\r\n    }\r\n\r\n    if (!listFormUrls.ContainsKey(itemdata.ListId))\r\n        return null;\r\n\r\n    string displayFormUrl = listFormUrls[itemdata.ListId];\r\n\r\n    return GetItemLinks(displayFormUrl, itemdata);\r\n}\r\n<\/pre>\n<p>This method is using a custom caching class (part of the download) to retrieve previously processed lists, where the display form url is stored.<\/p>\n<p>As long as there is a valid list, the <em>GetItemLinks<\/em> method is subsequently called.<\/p>\n<pre lang=\"csharp\">\r\nprivate string GetItemLinks(string displayFormUrl, ExtendedLookupData lookupData)\r\n{\r\n    StringBuilder sb = new StringBuilder();\r\n\r\n    int position = 0;\r\n    foreach (KeyValuePair<int, string> itemValue in lookupData.SavedDisplayValues)\r\n    {\r\n        position++;\r\n        if (position > 1)\r\n        {\r\n            sb.Append(\"; \");\r\n        }\r\n\r\n        bool exists;\r\n        string displayValue = GetCurrentFieldValue(lookupData.WebId, lookupData.ListId, lookupData.ValueId, lookupData.DisplayId, itemValue.Key, itemValue.Value, out exists);\r\n        if (!string.IsNullOrEmpty(displayValue) && exists)\r\n        {\r\n            StringBuilder innerBuilder = new StringBuilder();\r\n            innerBuilder.Append(displayFormUrl);\r\n            innerBuilder.Append(\"&ID=\");\r\n            innerBuilder.Append(itemValue.Key);\r\n            innerBuilder.Append(\"&RootFolder=*\");\r\n            sb.Append(\"<a \");\r\n            if (!SPContext.Current.IsPopUI)\r\n            {\r\n                sb.Append(\"onclick=\\\"OpenPopUpPage('\");\r\n                sb.Append(innerBuilder);\r\n                sb.Append(\"', RefreshPage); return false;\\\" \");\r\n            }\r\n            sb.Append(\"href=\\\"\");\r\n            sb.Append(innerBuilder);\r\n            sb.Append(\"\\\">\");\r\n            sb.Append(SPHttpUtility.HtmlEncode(displayValue));\r\n            sb.Append(\"<\/a>\");\r\n        }\r\n        else\r\n        {\r\n            sb.Append(SPHttpUtility.HtmlEncode(string.IsNullOrEmpty(displayValue) ? itemValue.Value : displayValue));\r\n        }\r\n    }\r\n\r\n    return sb.ToString();\r\n}\r\n<\/pre>\n<p>This method iterates through the stored values and checks to see if there is a valid and current lookup item for it.  If there is, a link is created to allow a user viewing the result within a view to navigate to the source lookup item. Otherwise, static text is displayed.<\/p>\n<p>The method <em>GetCurrentFieldValue<\/em> returns the current display value for an existing item.  This may differ to what was originally selected when creating the master field value but the point of this field is to either display the active value and when this does not exist, to display the user&#8217;s original selection.<\/p>\n<p><em>GetCurrentFieldValue<\/em> is shown below:<\/p>\n<pre lang=\"csharp\">\r\nprivate string GetCurrentFieldValue(Guid webId, Guid listId, Guid valueFieldId, Guid displayFieldId, int storedValueId, string storedValue, out bool exists)\r\n{\r\n    string cacheKey = string.Format(\"extendedLookupFieldValue_{0}_{1}_{2}\", webId, listId, storedValueId);\r\n    string key = displayFieldId.ToString(\"B\");\r\n\r\n    Dictionary<string, FieldValueInfo> fields;\r\n    fields = CacheManager.Instance.GetValue(cacheKey) as Dictionary<string, FieldValueInfo>;\r\n\r\n    if (fields == null || !fields.ContainsKey(key))\r\n    {\r\n        fields = new Dictionary<string, FieldValueInfo>();\r\n\r\n        using (SPWeb web = SPContext.Current.Site.OpenWeb(webId))\r\n        {\r\n            FieldValueInfo fieldInfo = new FieldValueInfo();\r\n\r\n            SPList lookupList = (from list in web.Lists.Cast<SPList>()\r\n                                    where list.ID.Equals(listId)\r\n                                    select list).SingleOrDefault();\r\n\r\n            if (lookupList != null && lookupList.Fields.Contains(valueFieldId) && lookupList.Fields.Contains(displayFieldId))\r\n            {\r\n                SPListItem listItem = (from item in lookupList.Items.Cast<SPListItem>()\r\n                                        where item.ID == storedValueId\r\n                                        select item).SingleOrDefault();\r\n\r\n                if (listItem != null)\r\n                {\r\n                    fieldInfo.Value = listItem[displayFieldId].ToString();\r\n                    fieldInfo.ItemExists = true;\r\n                }\r\n            }\r\n\r\n            if (string.IsNullOrEmpty(fieldInfo.Value))\r\n            {\r\n                fieldInfo.Value = storedValue;\r\n                fieldInfo.ItemExists = false;\r\n            }\r\n            fields.Add(key, fieldInfo);\r\n\r\n            CacheManager.Instance.Add(cacheKey, fields, DateTime.Now.AddMinutes(5), CacheItemPriority.Normal);\r\n        }\r\n    }\r\n\r\n    if (!fields.ContainsKey(key))\r\n    {\r\n        exists = false;\r\n        return storedValue;\r\n    }\r\n\r\n    exists = fields[key].ItemExists;\r\n    string value = fields[key].Value;\r\n    return value;\r\n}\r\n<\/pre>\n<p>This method also utilises caching but this time to store and retrieve the field information.  If a field exists for the lookup item, its current display value is stored.  If the field no longer exists, the value the user selected when creating the field is stored against the field id in the cache.<\/p>\n<p>In the GetFieldValueAsHtml method, when the result is empty, caused by the source list no longer existing for example, the following method is called to produce a list of the stored values as static text, taken from the <em>ExtendedLookupFieldControl<\/em> object<\/p>\n<pre lang=\"csharp\">\r\npublic override string GetFieldValueAsText(object value)\r\n{\r\n    if (value != null)\r\n    {\r\n        ExtendedLookupData lookupData;\r\n        if (value is ExtendedLookupData)\r\n            lookupData = value as ExtendedLookupData;\r\n        else\r\n            lookupData = Serialization<ExtendedLookupData>.DeserializeObject(value.ToString());\r\n\r\n        if (lookupData != null && lookupData.SavedDisplayValues != null && lookupData.SavedDisplayValues.Count > 0)\r\n        {\r\n            string display = string.Empty;\r\n\r\n            foreach (KeyValuePair<int, string> savedValue in lookupData.SavedDisplayValues)\r\n            {\r\n                if (savedValue.Key > 0)\r\n                {\r\n                    bool exists;\r\n\r\n                    string currentValue = GetCurrentFieldValue(lookupData.WebId,\r\n                                                                lookupData.ListId,\r\n                                                                lookupData.ValueId,\r\n                                                                lookupData.DisplayId,\r\n                                                                savedValue.Key, savedValue.Value, out exists);\r\n\r\n                    display += string.IsNullOrEmpty(currentValue) ? savedValue.Value : currentValue;\r\n                    display += ExtendedLookupFieldControl.DisplayDelimiter;\r\n                }\r\n            }\r\n\r\n            display = display.Substring(0, display.Length - 2);\r\n            return display;\r\n        }\r\n    }\r\n\r\n    return null;\r\n}\r\n<\/pre>\n<p>The last part of the post is to create the XML definition file for the hidden field, which as with the main field, should be created in the ~hive\\Template\\XML folder.<\/p>\n<pre lang=\"xml\">\r\n<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n<FieldTypes>\r\n  <FieldType>\r\n    <Field Name=\"TypeName\">RelatedLookupDataFieldType<\/Field>\r\n    <Field Name=\"ParentType\">Note<\/Field>\r\n    <Field Name=\"SQLType\">ntext<\/Field>\r\n    <Field Name=\"TypeDisplayName\">Extended Lookup Data<\/Field>\r\n    <Field Name=\"TypeShortDescription\">Extended Lookup Data<\/Field>\r\n    <Field Name=\"UserCreatable\">FALSE<\/Field>\r\n    <Field Name=\"Sortable\">FALSE<\/Field>\r\n    <Field Name=\"Filterable\">FALSE<\/Field>\r\n    <Field Name=\"ShowInSurveyCreate\">FALSE<\/Field>\r\n    <Field Name=\"ShowInDocumentLibraryCreate\">FALSE<\/Field>\r\n    <Field Name=\"ShowInColumnTemplateCreate\">FALSE<\/Field>\r\n    <Field Name=\"UnlimitedLengthInDocumentLibrary\">TRUE<\/Field>\r\n    <Field Name=\"FieldTypeClass\">SharePointStu.LookupField.Fields.RelatedLookupDataFieldType, SharePointStu.LookupField, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d5173afaa03f7f2d<\/Field>\r\n    <Field Name=\"AllowBaseTypeRendering\">FALSE<\/Field>\r\n    <Field Name=\"CAMLRendering\">FALSE<\/Field>\r\n  <\/FieldType>\r\n<\/FieldTypes>\r\n<\/pre>\n<p>Click <a href=\"http:\/\/www.stuartroberts.net\/downloads\/LookupField.zip\" title=\"Download\">here<\/a> to download the code for this project.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This is the fourth and final part of the post describing how to create a custom lookup field for SharePoint. Other pages in this post are: Part 1 &#8211; Creating a custom lookup field type Part 2 &#8211; Add field &hellip; <a href=\"http:\/\/www.stuartroberts.net\/index.php\/2012\/12\/10\/enhanced-lookup-field-part-4\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_mi_skip_tracking":false,"jetpack_post_was_ever_published":false,"jetpack_publicize_message":"","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":[]},"categories":[3],"tags":[54,81],"jetpack_publicize_connections":[],"aioseo_notices":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/plx2I-jV","_links":{"self":[{"href":"http:\/\/www.stuartroberts.net\/index.php\/wp-json\/wp\/v2\/posts\/1235"}],"collection":[{"href":"http:\/\/www.stuartroberts.net\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.stuartroberts.net\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.stuartroberts.net\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/www.stuartroberts.net\/index.php\/wp-json\/wp\/v2\/comments?post=1235"}],"version-history":[{"count":26,"href":"http:\/\/www.stuartroberts.net\/index.php\/wp-json\/wp\/v2\/posts\/1235\/revisions"}],"predecessor-version":[{"id":1294,"href":"http:\/\/www.stuartroberts.net\/index.php\/wp-json\/wp\/v2\/posts\/1235\/revisions\/1294"}],"wp:attachment":[{"href":"http:\/\/www.stuartroberts.net\/index.php\/wp-json\/wp\/v2\/media?parent=1235"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.stuartroberts.net\/index.php\/wp-json\/wp\/v2\/categories?post=1235"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.stuartroberts.net\/index.php\/wp-json\/wp\/v2\/tags?post=1235"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}