Monday, December 17, 2012

Implementing a jQuery Callback Function for SharePoint

Introduction

It is sometimes useful to be able to subscribe to the successful completion of asynchronous jQuery calls. For example, if you make a call to get data, you may want several events to fire upon successful completion of that call and the events may vary based on what page or area of the site you are on. You could just call those events directly from the asynchronous data call and add conditional logic for each page, but a layer of abstraction allows for more flexible and reusable code. This post will show you how to setup your asynchronous jQuery data calls in a generic way (for example, to be included in your master page) and then setup custom subscriber events that vary by page or page layout.

Generic Subscriber and Data Retrieval Functions

The code below is a function that sets up jQuery callbacks and allows subscribers to add or remove themselves from the callbacks. You would include this in a javascript library on your master page (note that this function is not my creation but comes from here: https://gist.github.com/1321768).

 jQuery.Status = function(id) {  
   var callbacks,  
     status = id && statuses[id];  
   if (!status) {  
     callbacks = jQuery.Callbacks();  
     status = {  
       publish: callbacks.fire,  
       subscribe: callbacks.add,  
       unsubscribe: callbacks.remove  
     };  
     if (id) {  
       statuses[id] = status;  
     }  
   }  
   return status;  
 };  

The code below is a sample ajax data call to a web service that returns json data. If, for example, this data is commonly used throughout your SharePoint farm, you could include a jQuery library on your master page that includes this function.

 function GetData() {  
   $.ajax({  
     type: "GET",  
     url: path,  
     data: {},  
     dataType: "json",  
     timeout: 10000,  
     success: function (data) {  
       //Do Stuff  
       //Publish event  
       $.Status("EventToSubscribeTo").publish();  
     },  
     error: function (xhr, status, error) {  
       alert(error);  
     }  
   });  
 }  

Page or Layout Specific Code

Once you have your master page setup, you could include code on a single page or layout to call the GetData function. You could then subscribe to the successful completion of the GetData function using the code below. The code below subscribes to the successful completion of the "EventToSubscribeTo" function. Once the "EventToSubscribeTo" function is successfully complete, it calls the "EventToFire" function. I do not include code for the "EventToFire" function, but that is where you would add page or layout specific code to manipulate the resulting data call.

 $.Status("EventToSubscribeTo").subscribe(EventToFire);  

Conclusion

Abstracting asynchronous data calls by placing the functions to make those calls in a master page javascript library allows for more flexible and reusable code. You could then call the functions to get the data on a page specific basis and manipulate the data in a custom manner by subscribing to the successful completion of the data call.

Thursday, December 6, 2012

Programmatically Creating a Content Type, Site Column and List in SharePoint

I often find the need to create a new site collection and start throwing objects (lists, content types, etc...) onto it. There are obviously a million ways to do this, but I just thought I would share how I do it.

In this code example, I create a Feature Receiver that inherits from SPFeatureReceiver and overrides the FeatureActivated and FeatureDeactivating methods. In the FeatureActivated method, I create a new SharePoint content type that inherits from item. I then create a new list and site column. finally, I connect them all together.

Note that I use a couple of helper classes. to actually create each item. I do it often enough that I have abstracted it out into a Utility class.

This is the code for FeatureReceiver class. Note that it inherits from SPFeatureReceiver

 
public class FeatureReceiver : SPFeatureReceiver
{
}

Within the FeatureReceiver class, I override the FeatureActivated method. So, when my feature is activated on the site, it will create my content type, list, and site column

 
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
}

Within the FeatureActivated method I go through the work of creating each item and connecting them. I also set a few properties along the way.

 
SPSite site = (SPSite)properties.Feature.Parent;
using (SPWeb web = site.OpenWeb())
{
	//Create a new content type. Note that it inherits from item
	SPContentTypeId itemContentTypeId = new SPContentTypeId("0x01");
	SPContentType myContentType = Utility.CreateSiteContentType(web, "Content Type Name", itemContentTypeId, "Grouping Name for the Custom Content Type");

	//Write the changes to the database
	web.AllowUnsafeUpdates = true;
	myContentType.Update();
	web.AllowUnsafeUpdates = false;

	//Add a single choice field to the site as a site column
	SPField field = Utility.CreateSiteColumn(web, "My Choice Field", SPFieldType.Choice, "My Custom Fields");
	field.Title = "Type of Data";
	field.Description = "Audience : Choice field used to determine whether the requested video is for internal, external or both internal and external dissemination.";

	//Add the new field to the new content type
	Utility.AddFieldToContentType(web, myContentType.Id, field);

	//Write the changes to the database
	web.AllowUnsafeUpdates = true;
	myContentType.Update();
	web.AllowUnsafeUpdates = false;

	//Add a new list to the site
	Guid guid = web.Lists.Add("List Name", "List Description", SPListTemplateType.GenericList);

	//Write the changes to the database
	web.AllowUnsafeUpdates = true;
	web.Update();
	web.AllowUnsafeUpdates = false;

	//Add the new content type to the new list
	SPList myList = web.Lists.GetList(guid, false);
	myList.ContentTypesEnabled = true;
	myList.ContentTypes.Add(myContentType);

	//Add a couple of event receivers to the list 
	myList.EventReceivers.Add(SPEventReceiverType.ItemAdded, "[DLLName], Version=1.0.0.0, Culture=neutral, PublicKeyToken=[DLL Key Token]", "[Namespace].[MethodName]");
	myList.EventReceivers.Add(SPEventReceiverType.ItemUpdating, "[DLLName], Version=1.0.0.0, Culture=neutral, PublicKeyToken=pDLL Key Token]", "[Namespace].[MethodName]");

	//Change some of the properties on the list
	myList.EnableAttachments = false;
	myList.EnableFolderCreation = false;
	myList.EnableVersioning = true;
	myList.ForceCheckout = false;

	//Write the changes to the database
	web.AllowUnsafeUpdates = true;
	myList.Update();
	web.AllowUnsafeUpdates = false;
}

I also override the FeatureDeactivating method and clean everything up when the feature is deactivated. Depending on your environment and what you are using this for, you may not want to do this. It will delete your data.

 
public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
	try
	{
		SPSite site = (SPSite) properties.Feature.Parent;
		using (SPWeb web = site.OpenWeb())
		{
			//Delete the list.
			//Note that if the list is not there
			//it throws an Argument Exception.
			//We trap it and ignore it.
			try
			{
				web.Lists["List Name"].Delete();
				web.AllowUnsafeUpdates = true;
				web.Update();
				web.AllowUnsafeUpdates = false;
			}
			catch (ArgumentException)
			{				}

			//Delete the content type. 
			//Note that if the content type is not there
			//it throws an Null Reference Exception.
			//We trap it and ignore it.
			try
			{
				web.ContentTypes["Content Type Name"].Delete();
				web.AllowUnsafeUpdates = true;
				web.Update();
				web.AllowUnsafeUpdates = false;
			}
			catch (NullReferenceException)
			{				}

			//Loop through the fields in My Custom Group
			//and delete them
			for (int i = 0; i < web.Fields.Count; i++)
			{
				if (web.Fields[i].Group == "My Custom Fields")
					web.Fields[i].Delete();
			}
		}
	}
	catch (Exception exception)
	{
		//Insert code to write the error to the log
	}
}

This utility class provides a couple of methods for creating a content type, site column and adding the site column to the content type. My actual implementation of this has several overloaded versions of each method for various purposes.

public static class Utility
{
	public static SPContentType CreateSiteContentType(SPWeb web, string contentTypeName,
		SPContentTypeId parentItemCTypeId, string group)
	{
		if (web.AvailableContentTypes[contentTypeName] == null)
		{
			SPContentType itemCType = web.AvailableContentTypes[parentItemCTypeId];
			SPContentType contentType = 
				new SPContentType(itemCType, web.ContentTypes, contentTypeName) {Group = @group};
			web.ContentTypes.Add(contentType);
			contentType.Update();
			return contentType;
		}
		return web.ContentTypes[contentTypeName];
	}

	public static SPField CreateSiteColumn(SPWeb web, string displayName,
		SPFieldType fieldType, string groupDescriptor)
	{
		if (!web.Fields.ContainsField(displayName))
		{
			string fieldName = web.Fields.Add(displayName, fieldType, false);
			SPField field = web.Fields.GetFieldByInternalName(fieldName);
			field.Group = groupDescriptor;
			field.Update();
			return field;
		}
		return web.Fields[displayName];
	}
		
	public static void AddFieldToContentType(SPWeb web,
		SPContentTypeId contentTypeId, SPField field)
	{
		SPContentType contentType = web.ContentTypes[contentTypeId];
		if (contentType == null) return;
		if (contentType.Fields.ContainsField(field.Title)) return;
		SPFieldLink fieldLink = new SPFieldLink(field);
		contentType.FieldLinks.Add(fieldLink);
		contentType.Update();
	}
}

As always comments and feedback are welcome. Enjoy!