Understanding the List Dialogs setting in SharePoint 2010

Overview

With the introduction of SharePoint 2010 two modes became available  when working with Libraries/List forms   from user experience perspective. Dialogs option in List Settings indicates whether to navigate the full page (Yes) or that the list form page is launched in a modal dialog (No)

XLV_DialogsOption

Figure 1. List settings 

Pay attention to the last part of the description that says:

Note: Dialogs may not be available on all forms.

Interesting, isn’t it, but what exactly does it mean? In order to find out we need to investigate how List Dialogs settings is handled in SharePoint 2010.  So, Let’s get started.

How it works

Depending on which option is selected link events for List/Library forms (New|Edit|View) are handled differently. When the “Launch forms in a dialog” is selected form pages are opened in modal dialog boxes.

How it is implemented

Through SharePoint Object Model the specified List settings for enabling/disabling modal dialog boxes is accessible via  SPList.NavigateForFormsPages property.   As it turns out, the value of  this property is passed to the XSL transform via  XsltListViewWebPart.ModifyXsltArgumentList method:

/// <summary>
/// Partial implementation of XsltListViewWebPart.ModifyXsltArgumentList method
/// The remaining code is omitted for clarity here
/// </summary>
protected override void ModifyXsltArgumentList(ArgumentClassWrapper argList)
{
argList.AddParameter("NavigateForFormsPages", string.Empty, this.SPList.NavigateForFormsPages ? (object) BaseXsltListWebPart.XslOneString : (object) BaseXsltListWebPart.XslZeroString);
}

The XSLT global parameter NavigateForFormsPages  declared in main.xsl file is utilized in CTXGeneration template:

<!–
Partial implementation of CTXGeneration template
(The remaining code is omitted for clarity here)
–>
<xsl:template name="CTXGeneration" ddwrt:ghost="always">
<script type="text/javascript">
ctx = new ContextInfo();
<!–Save List Dialogs parameter in NavigateForFormsPages property of ContextInfo structure–>
ctx.NavigateForFormsPages = <xsl:choose>
<xsl:when test="$NavigateForFormsPages='1'">true</xsl:when>
<xsl:otherwise>false</xsl:otherwise>
</xsl:choose>;
ctx<xsl:value-of select="$ViewCounter"/> = ctx;
g_ctxDict['ctx<xsl:value-of select="$ViewCounter"/>'] = ctx;
</script>
</xsl:template>

view raw
CTXGeneration.xsl
hosted with ❤ by GitHub

This template is intended for rendering  extra information about List settings on the client side  (ContextInfo structure). It is invoked every time when the List View is rendered on page. Pay attentions that List Dialogs setting that corresponds to NavigateForFormsPages property of ContextInfo structure.

And finally the event handler of the List form links is as follows:

//Handler for List form links in SharePoint 2010
// (from CORE.js)
function _EditLink2(elm, ctxNum)
{ULSrLq:;
var fn=function()
{ULSrLq:;
var url=GetGotoLinkUrl(elm);
if (url==null)
return;
var ctxT=window["ctx"+ctxNum];
if (ctxT !=null && ctxT.clvp !=null)
{
var clvp=ctxT.clvp;
if (FV4UI() && !ctxT.NavigateForFormsPages) //check if Dialogs option (NavigateForFormsPages property of ContextInfo) is set to 'No'
{
PreventDefaultNavigation();
clvp.ShowPopup(url); //open form in modal dialog boxes
return false;
}
}
GoToLink(elm); //navigate using href attribute of link
}
EnsureScript("inplview", typeof(inplview), fn);
}

view raw
_EditLink2.js
hosted with ❤ by GitHub

How the List Form link should be handled is determined by the NavigateForFormsPages parameter in the specified function, i.e. :

  • by navigating to a full page using href attribute of link
  • display form in modal dialog box

List Dialogs setting in ListViewWebPart

Now it is time to return to the question about why this settings is not available on all the forms. For answering on that question let me step back and explain a little bit about rendering a List View. Up until now we were talking only about rendering a List View via XsltListViewWebPart  that  handles view rendering for default lists, such as document libraries and announcements. But what about ListViewWebPart that is used for rendering specific List Views like  Calendar, Gantt or Chart Views.

The point is that this parameter (List Dialogs) does not take into account when a List View is rendered via ListViewWebPart. And the message “Dialogs that may not be available on all forms” concerns exactly this situation.

Then the question arise, how do we deal with the case where we need to open List Forms as a full pages for ListViewWebPart?  Let’s consider a real-world example when we need a Calendar forms to be opened as a full pages.

There are several ways how to achieve it, but we will consider only one, one of the simplest. The idea is to specify explicitly NavigateForFormsPages parameter for ContextInfo structure.

Steps:

1. Add CEWP into Calendar View page (Calendar.aspx)

2.  Place the following JavaScript code into CEWP

//Additional properties for ContextInfo () to be utilized in Calendar List View
//Specify actions navigate to the full page
var ContextInfo = (function() {
var ContextInfo_Orig = ContextInfo;
return function() {
ContextInfo_Orig();
this.NavigateForFormsPages = true; // Set navigate to the full page for list forms links
}
})();

That’s all, after that the Calendar forms will be opened as a full pages.

Provisioning Content Types in SharePoint 2010

Overview

Provisioning in SharePoint has always been not the simplest part, especially if it concerns the Content Type provision.

It is known that in SharePoint 2010, you can declare content types in a feature or you can create them programmatically. Programmatically created content types are more flexible and provide more granular control over how and when updates to the content type are made during upgrades. In contrast, you cannot change the declarative definition of a content type that was created by using Collaborative Application Markup Language (CAML) after the content type is activated—instead, you must update the content type programmatically.

Given the Pros and Cons, it is up to you to decide which way is better suits your needs.

How to create a field based on the specified schema

There is one method that allows to create a field based on the specified schema, see AddFieldAsXml method for details . It means that you could declare a field once (CAML), but to provision it either via feature activation or programmatically. As for me, I find this technique very useful for curtain cases. But what about content types?

For content type there is no such capability that allows to create a content type based on the specified schema. And the goal of this post is to demonstrate how it could be achieved.

How to create a content type based on the specified schema

Listing 1.  AddContentTypeAsXml method implementation for creating a content type based on the specified schema

namespace Deployment.ContentType
{
/// <summary>
/// Content Type
/// </summary>
public static class SPContentTypeExtensions
{
/// <summary>
/// Creates a content type based on the specified schema.
/// </summary>
/// <returns>
/// An instance of the new content type.
/// </returns>
/// <param name="contentTypes">Content Type collection</param>
/// <param name="schemaXml">A Collaborative Application Markup Language (CAML) string that contains the schema.</param>
public static SPContentType AddContentTypeAsXml(this SPContentTypeCollection contentTypes, string schemaXml)
{
SPContentType contentType;
using (var xrdr = new XmlTextReader(new StringReader(schemaXml)))
{
xrdr.ProhibitDtd = true;
contentType = contentTypes.CreateContentType();
LoadXmlInternal(contentType, xrdr);
contentTypes.Add(contentType);
}
return contentType;
}
/// <summary>
/// Create content type
/// </summary>
/// <param name="contentTypes"></param>
/// <returns></returns>
private static SPContentType CreateContentType(this SPContentTypeCollection contentTypes)
{
var constructor = (typeof(SPContentType)).GetConstructor(
BindingFlags.NonPublic | BindingFlags.Instance,
null,
Type.EmptyTypes,
null);
var contentType = (SPContentType)constructor.Invoke(new object[0]);
contentType.SetWeb(contentTypes.GetWeb());
return contentType;
}
/// <summary>
/// Load schema for content type
/// </summary>
/// <param name="contentType"></param>
/// <param name="xmlReader"></param>
private static void LoadXmlInternal(SPContentType contentType, XmlReader xmlReader)
{
var loadMethod = contentType.GetType().GetMethod("Load",
BindingFlags.NonPublic | BindingFlags.Instance,
null,
new[] { typeof(XmlReader) },
null);
loadMethod.Invoke(contentType, new object[] { xmlReader });
}
private static SPWeb GetWeb(this SPContentTypeCollection contentTypes)
{
var webProp = typeof(SPContentTypeCollection).GetProperty("Web", BindingFlags.NonPublic | BindingFlags.Instance);
return (SPWeb)webProp.GetValue(contentTypes, null);
}
private static void SetWeb(this SPContentType contentType,SPWeb web)
{
var webProp = typeof(SPContentType).GetProperty("Web", BindingFlags.NonPublic | BindingFlags.Instance);
webProp.SetValue(contentType, web, null);
}
}
}

Listing 2.  AddContentTypeAsXml method implementation for creating a content type based on the specified schema via Managed Client Object Model

namespace Deployment.Client
{
/// <summary>
/// Creates a content type based on the specified schema (CSOM)
/// </summary>
public static class ContentTypeExtensions
{
/// <summary>
/// Creates a content type based on the specified schema.
/// </summary>
/// <returns>
/// A newly created client object of a content type.
/// </returns>
/// <param name="clientContext">Client Context </param>
/// <param name="schemaXml">A Collaborative Application Markup Language (CAML) string that contains the schema.</param>
public static ContentType AddContentTypeAsXml(ClientContext clientContext, string schemaXml)
{
ContentType contentType;
using (var xmlTextReader = new XmlTextReader(new StringReader(schemaXml)))
{
xmlTextReader.ProhibitDtd = true;
contentType = Load(clientContext, clientContext.Web.ContentTypes, xmlTextReader);
}
return contentType;
}
private static ContentType Load(ClientContext clientContext, ContentTypeCollection cts, XmlReader xrdr)
{
var contentTypeCreation = new ContentTypeCreationInformation { };
xrdr.MoveToContent();
if (xrdr.MoveToAttribute("Name"))
contentTypeCreation.Name = xrdr.Value;
if (xrdr.MoveToAttribute("Group"))
contentTypeCreation.Group = xrdr.Value;
if (xrdr.MoveToAttribute("Description"))
contentTypeCreation.Description = xrdr.Value;
contentTypeCreation.ParentContentType = GetParentContentType(clientContext, xrdr.GetAttribute("ID"));
var contentType = cts.Add(contentTypeCreation);
//Additional properties
if (xrdr.MoveToAttribute("Hidden"))
contentType.Hidden = xrdr.Value == "TRUE";
bool flagReadOnly = xrdr.MoveToAttribute("ReadOnly");
contentType.ReadOnly = flagReadOnly && xrdr.Value == "TRUE";
xrdr.Read();
while (xrdr.LocalName != "ContentType")
{
switch (xrdr.LocalName)
{
case "FieldRefs":
LoadFieldLinks(clientContext, contentType, xrdr);
break;
}
if (!xrdr.Read())
break;
}
return contentType;
}
private static void LoadFieldLinks(ClientContext clientContext, ContentType contentType, XmlReader xrdr)
{
xrdr.Read();
while (xrdr.LocalName == "FieldRef" || xrdr.LocalName == "RemoveFieldRef")
{
if (xrdr.LocalName == "RemoveFieldRef")
{
xrdr.Read();
}
else
{
LoadFieldLink(clientContext, contentType.FieldLinks, xrdr);
contentType.Update(false);
clientContext.ExecuteQuery();
}
}
}
internal static FieldLink LoadFieldLink(ClientContext clientContext, FieldLinkCollection fieldLinks, XmlReader xrdr)
{
var linkCreationInfo = new FieldLinkCreationInformation();
var hostField = GetField(clientContext, xrdr);
linkCreationInfo.Field = hostField;
var fieldLink = fieldLinks.Add(linkCreationInfo);
xrdr.MoveToContent();
if (xrdr.MoveToAttribute("Required"))
{
fieldLink.Required = xrdr.Value == "TRUE" || xrdr.Value == "-1";
}
if (xrdr.MoveToAttribute("Hidden"))
{
fieldLink.Hidden = xrdr.Value == "TRUE" || xrdr.Value == "-1";
}
xrdr.Read();
if (xrdr.LocalName == "Default")
{
xrdr.Read();
}
return fieldLink;
}
/// <summary>
///
/// </summary>
/// <param name="clientContext"></param>
/// <param name="xrdr"></param>
/// <returns></returns>
private static Field GetField(ClientContext clientContext, XmlReader xrdr)
{
var field = clientContext.Web.AvailableFields.GetById(new Guid(xrdr["ID"]));
clientContext.Load(field);
clientContext.ExecuteQuery();
return field;
}
private static ContentType GetParentContentType(ClientContext clientContext, string id)
{
var ctId = new SPContentTypeId(id);
var parentCtId = ctId.Parent.ToString();
return GetContentTypeById(clientContext, parentCtId);
}
public static ContentType GetContentTypeById(ClientContext clientContext, string ctId)
{
var ct = clientContext.Web.AvailableContentTypes.GetById(ctId);
clientContext.Load(ct);
clientContext.ExecuteQuery();
return ct;
}
public static ContentType GetContentTypeByName(ClientContext clientContext, string name)
{
var cts = clientContext.Web.ContentTypes;
clientContext.Load(cts);
clientContext.ExecuteQuery();
foreach (var ct in cts)
{
if (ct.Name == name)
return ct;
}
return null;
}
}
}

The following list summarizes the limitations for Client OM approach:

  • Not the all content type attributes could be specified via Client OM
  • In particular it is not supported to specify Content Type Id, only parent Content Type could be specified

References