Convert a WebPart Zone in MOSS into Accordion or Tabs Style Interface

This is a web part that turns all the visible web parts with a Title in a Zone into Tab or Accordion interface.  Just drop the webpart into a zone and see it convert all the visible webparts with a title in the zone  into part of Tab or Accordion.  It renders one tab/accordion menu per webpart.

Originally inspired by EasyTabs from PathToSharePoint but instead of embedding some JavaScript with Content editor web part, it’s a custom web part providing a property to control if it renders Tab or Accordion and JavaScript bit is done using jQuery.

Some customary screen shots!

WebPart Zone Converted into Tabs

WebPart Zone Converted into Tabs

WebPart Zone containing the TabAccordion WebPart in Edit mode

WebPart Zone converted into Accordion

WebPart Zone converted into Accordion

WebPart Zone containing the TabAccordion WebPart in Edit mode

WebPart Zone containing the TabAccordion WebPart in Edit mode

Properties of the WebPart

Properties of the WebPart

Download Full Source Code. Hopefully it’s commented just enough to understand what’s going on in the JavaScript.

WSP Solution can be downloaded from here. Once the package is deployed, Activate the Kark SharePoint Web Parts feature at Site Collection level and Tab Accordion web part will be available in the Web Part Gallery.

UPDATE
Have changed the JavaScript to render tabs at the top

Download WSP Here

Here is a screenshot


Please excuse the ugly looking Tabs, but hopefully CSS should be really easy to change

Created Date in SPListItemVersion does not respect locale settings?

A Quick post! On a recent project we had a custom control that was showing document version history information such as “Created By” and “Date Created” in a table format, it was looking all good. After the day light savings time (British Summer Time) one of the tester spotted a bug in document version history control where “Date Created” was still showing the GMT time, it looked like SPListItemVersion.Created was not respecting day light savings time.

TimeZone.xml looked good and every other Dates on the site were showing correct time. It turns out that SPListItemVersion.Created was only showing time in GMT (Don’t know why!) so a simple fix was to convert the time into local time at the render time.


lblDateCreated.Text = version.Created.ToLocalTime().ToString();

Various MOSS Enterprise Search Stuff – Part I

Recently I have been working on a large Content Management solution that required 300+ custom Managed/Metadata Properties in MOSS Search and had requirements to allow user to search on all of the Metadata Properties. MOSS Search Index was also used to run a number of reports such as “What Asset (Image, Video, Document etc) is used on what page?”

While working with MOSS Search I came across a number of things that I think are worth sharing.

Programmatically setting up Shared Scope

APIs to work with MOSS Search related Administration tasks such as creating Scopes, Managed Properties etc are very straight forward and well documented under Getting Started with the Enterprise Search Administration Object Model

But if you want to set up a shared scope, then it’s not very obvious from API how to do it. ScopeCollection.Create method as shown below takes owningSiteUrl as a parameter and to set up a shared scope just pass in null for owningSiteUrl and the scope will be created as a Shared Scope!

public Scope Create (string name,string description, Uri owningSiteUrl,   bool displayInAdminUI,string alternateResultsPage,ScopeCompilationType compilationType)

Programmatically setting up “Include values from a single crawled property based on order specified”

Programmatically setting up mappings to crawled properties is straight forward, if you want to add multiple crawled properties and include value only from a single crawled property based on the order than from the UI, you can simply check the radio button next “Include values from a single crawled property based on order specified”.

Managed Properties Mappings

To do it programmatically add multiple Crawled properties to MappingCollection and then set ManagedProperty.RespectPriority property to true, here is some code (pseudo code really!) to add multiple crawled property mappings


Schema propertiesSchema = new Schema(SearchContext.Current);
ManagedPropertyCollection managedProperties = propertiesSchema.AllManagedProperties;
ManagedProperty managedProperty = managedProperties["Test Property"];
MappingCollection mappingCollection = managedProperty.GetMappings();//Create Crawled Property Mappings
Mapping propMapping = new Mapping(new Guid("00130329-0000-0130-c000-000000131346"),  "ows_Modified_x0020_By", "31",managedProperty.PID);
Mapping propMapping1 = new Mapping(new Guid("b725f130-47ef-101a-a5f1-02608c9eebac"), "11","31",managedProperty.PID);//Add in the order in which you want it to appear in the Admin UI
mappingCollection.Add(propMapping);

mappingCollection.Add(propMapping1);

managedProperty.SetMappings(mappingCollection); // You have to do this for Mappings to work

//Tell it to respect priority!

managedProperty.RespectPriority = true;

managedProperty.Update();

It takes a Guid (crawledPropset) which is the category of crawled property (SharePoint, People, etc) and also takes an int (crawledPropertyVariantType) which represents the DataType of crawled property, I find it annoying that list of possible values crawledPropset and crawledPropertyVariantType is not very handy so here are the possible values

Some Possible Values of crawledPropertyVariantType

Data Type crawledPropertyVariantType
string 31
bool 11
integer 20
datetime 64
Something to do with Custom field 4127

*Please note that above values are what I have encountered till now *

Commonly Used crawledPropset

Category crawledPropset
Sharepoint 00130329-0000-0130-c000-000000131346
Basic 0b63e343-9ccc-11d0-bcdb-00805fccce04
Mail aa568eec-e0e5-11cf-8fda-00aa00a14f93
Office f29f85e0-4ff9-1068-ab91-08002b27b3d9
People 00110329-0000-0110-c000-000000111146

Programmatically adding RSSAggregatorWebPart to a Page

I have been struggling to find information on how to add RSSAggregatorWebPart to a page. First I started with doing something like

RSSAggregatorWebPart rssViewer = new RSSAggregatorWebPart();
rssViewer.FeedUrl = "https://ketulpatel.wordpress.com/feed/";
rssViewer.ID = "rssFeed";
rssViewer.DataSourcesString = dataSourceString.ToString();
rssViewer.Title = "My Blog";
//AddWebPart is an helper method to add a webpart to a page
AddWebPart(web,rssViewer,"default.aspx","CenterColTop",0);

That added the web-part to the page but when rendering the Page, web part gave an error asking to check error logs for errors and error log under 12 hive contained following error

RssWebPart: Exception handed to HandleRuntimeException.HandleException System.ArgumentNullException: Value cannot be null. Parameter name: key at Microsoft.SharePoint.WebControls.CacheObject.LoadDataFromCache(String key) at Microsoft.SharePoint.WebControls.BaseXmlDataSource.GetXmlDocument() at Microsoft.SharePoint.WebPartPages.DataFormWebPart.GetHierarchicalDocument(IHierarchicalDataSource ds) at Microsoft.SharePoint.WebPartPages.DataFormWebPart.GetHierarchicalXPathNavigator(IHierarchicalDataSource ds) at Microsoft.SharePoint.WebControls.SingleDataSource.GetXPathNavigatorInternal() at Microsoft.SharePoint.WebControls.SingleDataSource.GetXPathNavigator() at Microsoft.SharePoint.WebControls.SingleDataSource.GetXPathNavigator(IDataSource datasource, Boolean origin…

Blha Blha….. Really could not tell what’s going on, but If I add the same web-part from the UI (Using the Web Part Gallery) than it worked fine and my feed showed up in the web-part. After examining the properties of the working web-part in the debug mode, I noticed the web part has DataSourceString and ParameterBindings property set, MSDN has no documentation about it (or at least I could not find it) and setting those properties when adding RSS WebPart programmatically worked

Here is the full code snippet

RSSAggregatorWebPart rssViewer = new RSSAggregatorWebPart();
rssViewer.FeedUrl = "https://ketulpatel.wordpress.com/feed/";
rssViewer.ID = "rssFeed";
StringBuilder dataSourceString = new StringBuilder("<%@ Register TagPrefix=\"WebControls\" Namespace=\"Microsoft.SharePoint.WebControls\" Assembly=\"Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c\" %>");
            dataSourceString.Append("<%@ Register TagPrefix=\"WebPartPages\" Namespace=\"Microsoft.SharePoint.WebPartPages\" Assembly=\"Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c\" %>");
            dataSourceString.Append("<WebControls:XmlUrlDataSource runat=\"server\" AuthType=\"None\" HttpMethod=\"GET\">");
            dataSourceString.Append("<DataFileParameters>");
            dataSourceString.Append("<WebPartPages:DataFormParameter Name=\"RequestUrl\" ParameterKey=\"RequestUrl\" PropertyName=\"ParameterValues\"/>");
            dataSourceString.Append("</DataFileParameters>");
            dataSourceString.Append("</WebControls:XmlUrlDataSource>");

rssViewer.DataSourcesString = dataSourceString.ToString();
rssViewer.ParameterBindings = "<ParameterBinding Name=\"RequestUrl\" Location=\"WPProperty&#91;FeedUrl&#93;\"/>";
rssViewer.Title = "My Blog";
AddWebPart(web,rssViewer,"default.aspx","CenterColTop",0);

Value of DataSourceString is:

<%@ Register TagPrefix="WebControls" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="WebPartPages" Namespace="Microsoft.SharePoint.WebPartPages" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<WebControls:XmlUrlDataSource runat="server" AuthType="None" HttpMethod="GET">
<DataFileParameters>
<WebPartPages:DataFormParameter Name="RequestUrl" ParameterKey="RequestUrl" PropertyName="ParameterValues"/>
</DataFileParameters>
</WebControls:XmlUrlDataSource>

Value of ParameterBindings is:

<ParameterBinding Name="RequestUrl" Location="WPProperty&#91;FeedUrl&#93;"/>

Programmatically setting up Approval workflow in MOSS

Out-of-box Approval works pretty well for simple content approval and that’s what exactly I wanted. If you attach an Approval workflow to a list from the UI you get number of options to set such as Approvers, People who needs to be notified and an important one if you want to update the approval status of a list item to Approved or Rejected is “Update the approval status (use this workflow to control content approval)” in “Post completion Workflow Activities”

As in most the real world situation I want to set this up during deployment process and not from the UI.

Following code snippet will set up your list with Moderation enabled:

SPList list = web.Lists["MyList"];
list.EnableModeration = true;
list.Update();

Following code snippet sets up the Approval work flow on the list

SPWorkflowTemplate baseTemplate = web.WorkflowTemplates.GetTemplateByName("Approval", CultureInfo.InvariantCulture);
SPWorkflowAssociation assoc = SPWorkflowAssociation.CreateListAssociation(baseTemplate, "Property Approval", web.Lists["Workflow Tasks"], web.Lists["Workflow History"]);
assoc.AllowManual = true;
assoc.AutoStartCreate = true;
string data = GetApprovalWorkFlowData();
assoc.AssociationData = data;
list.AddWorkflowAssociation(assoc);

In Above code snippet AssociationData property of SPWorkflowAssociation can be used to set things like Approvers, People who needs to be notified and “Post completion Workflow Activities” for content approval

AssociationData is in XML format and looks like:

<my:myFields xml:lang="en-us" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:my="http://schemas.microsoft.com/office/infopath/2003/myXSD">
  <my:Reviewers>
    <my:Person>
      <my:DisplayName>Ketul Patel</my:DisplayName>
      <my:AccountId>domain\kpatel</my:AccountId>
      <my:AccountType>User</my:AccountType>
    </my:Person>
  </my:Reviewers>
  <my:CC>
    <my:Person>
      <my:DisplayName>Joe Doe</my:DisplayName>
      <my:AccountId>domain\jdoe</my:AccountId>
      <my:AccountType>User</my:AccountType>
    </my:Person>
  </my:CC>
  <my:DueDate xsi:nil="true"></my:DueDate>
  <my:Description>A new Property has been added to the system and requires approval.</my:Description>
  <my:Title></my:Title>
  <my:DefaultTaskType>1</my:DefaultTaskType>
  <my:CreateTasksInSerial>true</my:CreateTasksInSerial>
  <my:AllowDelegation>true</my:AllowDelegation>
  <my:AllowChangeRequests>true</my:AllowChangeRequests>
  <my:StopOnAnyReject xsi:nil="true"></my:StopOnAnyReject>
  <my:WantedTasks xsi:nil="true"></my:WantedTasks>
  <my:SetMetadataOnSuccess>false</my:SetMetadataOnSuccess>
  <my:MetadataSuccessField></my:MetadataSuccessField>
  <my:MetadataSuccessValue></my:MetadataSuccessValue>
  <my:ApproveWhenComplete>true</my:ApproveWhenComplete>
  <my:TimePerTaskVal xsi:nil="true"></my:TimePerTaskVal>
  <my:TimePerTaskType xsi:nil="true"></my:TimePerTaskType>
  <my:Voting>false</my:Voting>
  <my:MetadataTriggerField></my:MetadataTriggerField>
  <my:MetadataTriggerValue></my:MetadataTriggerValue>
  <my:InitLock>false</my:InitLock>
  <my:MetadataStop>false</my:MetadataStop>
  <my:ItemChangeStop>false</my:ItemChangeStop>
  <my:GroupTasks>false</my:GroupTasks>
</my:myFields>

Various elements in the above XML are pretty self explaining and roughly maps to relevant tick box or textbox on the “Customize Workflow” Page (CstWrkflIP.aspx)

<my:ApproveWhenComplete>true</my:ApproveWhenComplete>

Is the element to use to set the “Update the approval status (use this workflow to control content approval)” checkbox

Programmatically Working with ListViewWebPart using Non-Default View (GetUncustomizedViewByBaseViewId)

If you add an Announcement web part to page from the web part gallery you get a nice view of the list which shows 5 items, shows (More Announcements…) link and “Add a new Announcement” link

ListViewWebPart - Announcements

This is exactly what I needed for one of my web part but my requirement was to add the web part to page from a feature programmatically and not using the UI.

After looking around for a while in MSDN and searching most of the examples showed how to set the properties of the ListViewWebPart programmatically but all of the examples used the Default View of the list as shown below (The default view is the view which you get by default on AllItems.aspx).

<br /> SPList list = web.Lists["Announcement"];<br /> ListViewWebPart webPart = new ListViewWebPart();<br /> webPart.Title = "Announcements";<br /> webPart.ViewGuid = list.DefaultView.ID.ToString("B").ToUpper();<br /> 

If you look at the Schema file for the list, the View with BaseViewID of 0 is the one which has the HTML to render the view in the ListViewWebPart

<View BaseViewID=”0″ FreeForm=”TRUE” Type=”HTML”>

So First thing that came to my mind was to iterate though the Views collection of SPList object and look for a view with BaseViewID of zero, but to my surprise it was not there in the collection OOPSSS! But If I add the Announcement web part using the UI and than search the Views collection of SPList object for BaseViewID of Zero than the View is found in the collection. So the moral of the story is SharePoint is doing some thing to customize the view and add it to the Views collection.

Now how do you do the same programmatically? Luckily I did not try very hard and found a method called GetUncustomizedViewByBaseViewId on the SPList object and that was it, here is the code snippet:

<br /> SPList list = web.Lists["Announcement"];<br /> ListViewWebPart webPart = new ListViewWebPart();<br /> webPart.Title = "Announcements";<br /> webPart.ListName = list.ID.ToString("B").ToUpper();<br /> SPView view = list.GetUncustomizedViewByBaseViewId(0);<br /> webPart.ListViewXml = view.HtmlSchemaXml;<br /> 

Few things though:

  • You cannot use ViewGuid property of ListViewWebPart as ID of the View returned by GetUncustomizedViewByBaseViewId will be empty instead use ListViewXml property of ListViewWebPart and assign View’s HtmlSchemaXml property to it
  • If your List is in a different SPWeb than the page on which you are adding ListViewWebPart than use WebId property of ListViewWebPart to point it to the ID of SPWeb which contains the List

Happy SharePointing!