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
-
This comment has been removed by a blog administrator.
Please do not leave comments that advertise products. There are other forums for that purpose.
My apologies. I’m not a bot. I thought the product would be interesting to you since its another way to tackle the problem your post describes.
Sincerely,
John Milan
No problem. I have nothing against highlighting useful products from time to time, but I prefer to keep that content separate from other posts.
How do you change the default workspace template that gets used when creating the List Item Workspace. It seems to default to creating a Team Site every time. Can I control that?
Look for the line that has “STS#0”. That is the pointer to the Team Site site definition. Change that string to whichever site definition you would like to use.
Hi,
This looks very interesting and is close to what I need for something in MOSS 2007.
I’ve enabled the list item workspaces in a task list that I’ve created.
I create new tasks and check the box “use list item workspace” but nothing seems to be happening.
What am I missing?
Cheers,
Chris
Not having access to your system limits my debugging options. However, take a look at a walk-through I’ve posted to see how it is supposed to work:
http://www.blackbladeinc.com/en-us/products/liworkspaces
I have installed the software and it works fine but I can not find where to change it from creating a team site.
I need to create my own template and create that instead, could you tell me where to find the code for this I can not find “STS#0”, Thanks
Elliott, The code that selects the team site site definition is in the EventReceiver.cs file, in the ProcessItemWorkspace method:
http://liwwwv3.codeplex.com/SourceControl/changeset/view/18458#154960Do a search for the text “STS#0”.
Thanks for that but I can not see the EventReceiver.cs file in the wsp file I downloaded.
Where can I get to this file once deployed? or where can I find this before deployement?
Also is it possible to specify a different template per site within a site collection or a different template per list.
Thanks for your help.
The source code is not part of the WSP. The WSP only contains the compiled code. You will need to open the source code in Visual Studio, modify the contents of EventReceiver.cs file to use the site definition you need, recompile the code and WSP, then re-deploy the WSP.
Will this work with MOSS2007 or is it just WSS as I have it working on a virtual server running wss but not on my main MOSS server?
Elliott, we have this running on our MOSS 2007 supporting our intranet and extranet sites. Works like a champ!
I have installed this on WSS 3.0 Site with Forms Authentication. But its not creating workspace either on Add or on edit. Please help me out…
Niren, please provide some details about how your SharePoint site is configured. Here are a few things to check:
* make sure the feature is activated on the site
* make sure the users are selecting the option to create a list item workspace when creating the list items
* make sure the users have permissions to create sub-sites
Take a look at a walk-through I've posted to see how it is supposed to work:
http://www.blackbladeinc.com/en-us/products/liworkspaces
Hi Eugene,
Thnx for quick response, I checkd all options. Now its creating workspace. I have another issue now I write following code for my custom template
string template = "MyWorkspaceForAKS";
SPGlobalAdmin globalAdmin = new SPGlobalAdmin();
SPWebTemplateCollection webTemplates = globalAdmin.VirtualServers[0].GetWebTemplates((uint)System.Threading.Thread.CurrentThread.CurrentCulture.LCID);
SPWebTemplate ObjSPWebTemplate = webTemplates[0] ;
foreach (SPWebTemplate t in webTemplates)
{
if (t.Title.Trim().ToUpper() == template.ToUpper())
//if (t.Title.CompareTo(template) == 0)
{
template = t.Name;
ObjSPWebTemplate = t;
break;
}
}
//the properties.ListItem will be null if called from ItemAdding
// use the AfterProperties instead
if (newWeb == null)
newWeb = currentWeb.Webs.Add(
strSiteRelativeUrl,
(properties.ListItem != null ? properties.ListItem["Title"].ToString() : properties.AfterProperties["Title"].ToString()) + " Site",
string.Empty, (uint)System.Threading.Thread.CurrentThread.CurrentCulture.LCID, ObjSPWebTemplate /*Custom site template*/,
false,
false);
BUT ITS STILL CREATING SITE IN TEAM SITE TEMPLATE
Sounds like the previous assembly is being cached by IIS. Make sure you do an IISRESET after deploying the new version of the assembly. For additional help please post to the SharePoint development forum:
http://www.microsoft.com/communities/newsgroups/en-us/default.aspx?dg=microsoft.public.sharepoint.development_and_programming&cat=en_US_51794C2C-8A9B-8882-507F-15B41E2DF2D6&lang=en&cr=US
OK thanks.