The Code

All source code relating to this article can be downloaded from CodePlex at:

http://www.codeplex.com/LIWWWV3

 
 

Background

A client of mine had asked for a task management solution based on MOSS 2007. Now, notice that they wanted a task management solution, not a project management solution. They did not want to procure, deploy, or train users on a “real” project management system. They just wanted simple tasking.

 
 

Well, almost “simple tasking”. One thing they wanted was to have the concept of parent and child tasks. That is, one logically complex task could have several simpler sub tasks created and each of those sub tasks could be assigned to different people to work on. Ok, sounds simple enough. Create a task list with “Parent Task” as a lookup column to the same list. Well, that would work. I’ve created similar implementations in the past. The problem that that implementation eventually runs into is that as the tasks list grows in size, the tasks become increasingly difficult to manage, especially when users want to start managing the parent / sub tasks as hierarchies rather than a flat list of tasks. For example:

  • Delete all children, grand children, great grand children, etc… of a particular task.
  • Roll up task status based on parent / child relationships.
  • Set permissions on a task and all of its descendants.

 
 

A Conceptual Solution

All is of course doable, but the implementation grows in complexity rather quickly. Intuitively I though that leveraging SharePoint’s capability to create sub-webs in the same fashion as meeting workspaces or document workspaces would be an elegant solution to management of the hierarchies. So, if I had a task in a top-level task list and I needed to create several logical sub-tasks for the task, I would create a sub-site for the top-level task and place the logical sub-tasks in the physical sub-site. If I then needed to create sub-tasks for one of those tasks, I would create another sub site. And so on.

 
 

Conceptually, this seemed to work. Implementing the concept in WSS V2 was another matter. In WSS V2, we could have the users trained to do this manually, but the process would become cumbersome, especially when parent tasks were deleted and orphaned sub-sites were left around. In WSS V2, there was no easy way to automate this process because the event model was only implemented for document libraries, not lists in general. Yes, we could create web parts to handle things, but issues always come up when users alter list items directly without the specialized web part. For this reason, I strayed away from implementing automation that relied on web parts.

 
 

A New Implementation Strategy

Enter WSS V3. The event model has now been extended to all lists, so I decided to revisit the problem. Right away event receivers seemed to hold the key to the solution. I figured that I could create a check box field in a list easily enough and tie an event receiver to the list that would create a sub-web for a list item that had a check in the check box. This is what the document and meeting workspaces do after all. Deploying the event receiver should be easy enough; I would simply use a feature for that.

 
 

The one snag I ran into is that I would want to be able to enable and disable the capability to list item workspaces on a list by list basis. To make that happen, I would need to add an option to the list settings page and create an ASPX page to implement the option selection. Well, I don’t think I missed anything, so on to the solution components.

 
 

The Use List Item Workspace Feature

We need a new web feature, called Use List Item Workspace, that will create a list settings option to enable creating sites associated with the items in the list. We want this to be turned on or off on a list by list basis because we will need to add fields to the list.

 
 

When the Use List Item Workspace feature is activated, a new option appears in on the List Settings menu in the list toolbar to enable or disable list item workspaces for each list. This menu item is implemented as a custom action feature that takes the user to a custom ASP page deployed to the Layouts folder in the 12 hive. The ASPX page receives the list ID and a URL as parameters. The list ID tells the ASP page on which list to act. The URL parameter tells the page where to navigate once the user click the OK or Cancel buttons. The ASPX page offers the user three options: activating List Item Workspaces for the current list and two forms of deactivating List Item Workspaces for the current list. These options are discussed below.

 
 

Turning on list item workspaces for a particular list binds the event receiver to the list and creates or shows two new fields for the list schema:

  • Use List Item Workspace – Boolean – editable
  • List Item Workspace – Url – read-only

Turning off list item workspaces for a particular list prompts the user to choose one of two options:

  • Remove the capability of creating new workspaces, but leave existing workspaces intact. This option simply hides the Use List Item Workspace field but leave the List Item Workspace field and all of the created workspaces alone. Users can not create new workspaces but can still access existing workspace. The event receiver is unbound from the list. This option does not have any data loss.
  • Remove the capability of creating new workspaces and delete existing workspaces. This option deletes the Use List Item Workspace and the List Item Workspace fields. All sub-webs specified by the values of the list items’ List Item Workspace fields are deleted. The event receiver is unbound from the list. This option can result in a great deal of data loss.

 
 

Deactivating the feature is equivalent to turning off the for each list and selecting the first option, removing the capability of creating new workspaces, but leaving existing workspaces intact.

 
 

The Use List Item Workspace Event Receiver

We need a list item event receiver that is attached to lists on which users have enabled list item workspaces. The event receiver handles the ItemAdding, ItemAdded, ItemUpdating , and ItemDeleting events. For ItemAdding and ItemUpdating events, the receiver checks for the current value of the Use List Item Workspace field.

  • If its value is true, the event receiver will provision a sub-web and set the value of the List Item Workspace field to the Url of the sub-web. If the sub-web already exists, nothing will occur.
  • If its value is false, the event receiver will de-provision a sub-web whose Url is specified by the value of the List Item Workspace field. The value of the List Item Workspace field will be reset to empty. If the sub-web does not exist, no error is reported to the user as the goal is the removal of the sub-web.

For the ItemDeleting event, the receiver checks for the current value of the Use List Item Workspace field. If its value is true, the event receiver will de-provision a sub-web whose Url is specified by the value of the List Item Workspace field. The value of the List Item Workspace field will be reset to empty. If the sub-web does not exist, no error is reported to the user as the goal is the removal of the sub-web.

The ItemAdded event is used to add information to the workspace site about the parent list item that were not available in the ItemAdding event. This information includes the new item ID and the item Url. Adding the a link to the workspace links list that points back to the newly created parent list item allows us to easily navigate directly back to the parent list item from its item workspace.

 
 

Adding an Event Receiver to a List

We can add an event receiver to a list using either a feature or code. The code looks like:

SPList list = web.Lists[“ListName”];

List.EventReceivers.Add(SPEventReceiver.ItemAdding,

“[Fully-qualified assembly name]”,

“[Namespace-qualified receiver type name]”);

 
 

Why would we want to use code rather than a feature? After all, we can activate or deactivate a feature in the Site Settings user interface. A feature would add the event receiver to all lists in the site. This is not what I wanted. I went with the code option because it allows me to activate the event receiver on a per-list basis, right from the Item Workspace Settings page discussed earlier. This is very similar to the way that activating the Audience Targeting capability for list items works in MOSS.

 
 

So, the only thing the feature does is add an item to the List Settings menu to navigate to the Item Workspace Settings page. The Item Workspace Settings page in turn allows the users to enable or disable item workspaces on a list by list basis. Enabling item workspaces add the fields needed to track the item workspace information and also add the event receiver to the list to process the information in the fields. Disabling item workspaces removes the event receiver from the list and may also remove the added fields.

 
 

Working with Url Fields

During the course of this implementation, I created code that adds a text field to lists on which users activated List Item Workspaces. This text field stores the Url of the list items’ List Item Workspaces. The issues I found with this approach was that while SharePoint normally renders the text as an anchor tag <A>, SharePoint does not include the full Url in the tag if there are white spaces in the Url. These white spaces normally come from the web’s Url. So, I changed the field to be a Url field. Working with Url fields in not exactly intuitive when you start. You need to know about how the Url field stores its data in order to be able to work with the field in code.

 
 

The Url field stores both the Url and text associated with an anchor tag. The data is stored in the following format:

<URL><comma><space><Description>

“http://www.microsoft.com,, 1234, Microsoft, Inc”

 
 

This data storage is fairly straight forward. To get the Url portion, you simply look for the first <coma><space> in the string and take everything before. To get the description, take everything after the first <coma><space>. If the actual URL has a <comma><space> character combination set in it, that <comma> is escaped via another <comma>. So the <URL> will have a <comma><comma><space> character set in it. Only the first non-escaped <comma><space> character combination is treated as the field delimiter. Therefore, a <comma><space> combination in the <Description> portion is not escaped.

 
 

Possible Enhancements

As with all projects, there is never enough time to do everything you would like to do. Here are a couple of enhancements to List Item Workspaces capability I feel would be “nice to haves”:

  • Make the breadcrumb trail in the item workspace to look like:

    Path to parent site -> Item List -> Item -> Item Workspace

  • Add a dataview web part to the item workspace that displays all parent item properties where the ShowInDisplayForm attribute is not false

     
     

    Surprises

    And finally, here are the things that did not behave quite as I had expected:

    • Bug in the SPField.AddFieldAsXml Method

      There is a bug in the AddFieldAsXml method on the SPField class that does not set the InternalName property of the new field to the value of the Name attribute in the Field element. Instead the method sets the InternalName to a variation of the DisplayName attribute. The workaround is to set the DisplayName attribute in the XML to be the valid InternalName you originally wanted the field to have. Then, to get a reference to the new field and change its Title property to what you wanted the DisplayName to be. This is documented on Bil Simser’s blog at:

      http://weblogs.asp.net/bsimser/archive/2005/07/21/420147.aspx

       
       

    • SharePoint does not allow the creation of subwebs in the Lists folder. SharePoint generates an error saying the address is already in use.

       
       

    • SharePoint does not allow the creation of subwebs more than two folders under the web’s root folder. SharePoint generates an error saying the address is already in use.

       
       

    • The BeforeProperties and AfterProperties attributes of the properties parameter are not populated for ItemDeleting event override. Use the field values on the properties.ListItem instead.

       
       

    • The ListItem attribute of the properties parameter is null on the ItemAdding event.

       
       

    • We can’t edit the properties.ListItem properties directly when inside the ItemUpdating event. This causes an error message about conflicting updates. The exact error is: “Your changes conflict with those made concurrently by another user.” Instead, set the key / value pairs in the AfterProperties collection with the new desired values. This is documented on Ishai Sagi’s blog at:

      http://www.sharepoint-tips.com/2006/11/synchronous-list-event-handlers-ruin.html