Showing posts with label Customization. Show all posts
Showing posts with label Customization. Show all posts

Thursday, October 04, 2012

Dynamically Assign Approver for the Content Approval Workflow in SP2010

This post is a how-to guide for customizing the ootb SharePoint 2010 content approval workflow to automatically pick a user from the current list item such as the 'content resposible' field, and assign that user as the approver of the content, using SharePoint Designer. The customization of the content approval using SPD is quite straightforwards except for some less intuitive and misleading options for editing the workflow task process. It also involves publishing and editing the XML config of the workflow to enable using the "Start this workflow to approve publishing a major version of an item" option for automatically starting the approval workflow when the author check-in (submits) the page or document for approval.

To get started, follow the SharePoint Designer Walkthrough: Copy & Modify Publishing Workflow steps 1-4 to make a copy of the "Approval - SharePoint 2010" workflow in the site-collection root. Edit the workflow name to suit your needs and make sure to pick the content type that contains the user field that you will use to auto-assign as the approver in "Pick a base content type to limit this workflow to". Otherwise you won't be able to add a lookup for that content type field. Click OK and save.


Open the saved custom approval workflow and click "edit workflow". Change the name of the "Start Approval Workflow Task" action as you like. Then click on "Parameter: Approvers" to change the "with [these users]" for the workflow action into using an user from the current item that is pending approval. Now, to dynamically assign an approver, you need to click the "Enter participants manually" button.


Then in "Select task participants" click the address book button to open the "Select users" dialog box. Now select "Workflow lookup for a user", which will trigger the "Lookup for person or group" dialog.


Click OK three times, and the start approval workflow action should now look like this:

Now, as a side-effect the comments for the task has been unbound, so you need to click on properties for the workflow action and bind the comments to the "Parameters: Request" again. This will ensure that the text entered in the request field when starting the approval workflow will not be missing when the approver opens the workflow task to approve or reject the pending content.


Click OK two times, and your customized approval workflow with a dynamic approver is almost ready. There are a couple of workflow parameters that are not needed when automatically assigning the approver, these can be hidden from the workflow initiation form so that the author is not confused when the workflow starting form is shown on check-in. Click "Initiation form parameter" in the ribbon and make sure that the "Approvers" and "Expand groups" parameters are not shown during workflow initiation.


Removing the parameters is also an option, it just feels safer to just hide them rather than deleting them. Save and publish your custom content approval workflow as a globally reusable workflow as shown in step 13-15. You can now follow the Configuring Approval in SharePoint steps to use your workflow on a document library that requires content approval, and it will work for the content types that the custom approval workflow is associated with for the selected list.


However, the "Start this workflow to approve publishing a major version of an item" option will be missing when associating the workflow with the document library, even if it is a bonafide approval workflow and even if the list has "Require content approval for submitted items" turned on. This is caused by the workflow being a content type rather than a list workflow, and only list workflows can be configured to start content approval on check-in. Luckily, it is rather simple to change this by editing the XML config file for the published workflow XOML definition using SPD, as shown in Writing Your Own SharePoint Publishing Approval Workflow. I made these changes to the file:
  • changed the Category attribute to "List;Language:1033;#ContentType;Language:1033" 
  • changed the AllowStartOnMajorCheckin attribute to "true"
  • removed the ContentTypeId attribute completely
After directly editing and saving the workflow config file using SPD, the workflow designer will be somewhat out of sync with the XOML definition, due to the removal of the content type and changing of the association category. Still, the actual workflow logic is still working as expected.

You can now associate and test the custom approval workflow on your document library. These are the typical "Version settings" used for the "Pages" list in publishing sites:


Note that the users that are assigned as the approver must be members of the "Approvers" group in publishing sites, or have the right to edit draft items in the document library, otherwise they cannot open the  workflow task to approve or reject the content, even if the user is the owner of the task.

Wednesday, August 10, 2011

Some Gotchas when Customizing the "My Content" Personal Site

Customizing an existing SharePoint 2010 site definition such as the personal site (SPSPERS) that provides the "My Content" section in the My Site Host web-application, is a bit different than customizing your own site definitions. As the supported way of customizing existing site definitions is to use feature stapling, you need to consider the provisioning order of elements in onet.xml and referenced and stapled 'SPSite' and 'SPWeb' features. Failing to do so might result in strange end results when creating a new site.

The MCS Norway team has done a good job of documenting the SharePoint element provisioning order, as part of their SiteConfigurator available at CodePlex:
"There are several steps in the creation process and SharePoint provisions in the following order:
  1. Global onet.xml This file defines list templates for hidden lists, list base types, a default definition configuration, and modules that apply globally to the deployment.
  2. SPSite scoped features defined in site definitions onet.xml, in the order they are defined in the file. The onet.xml file defined in the site definition can define navigational areas, list templates, document templates, configurations, modules, components, and server e-mail footers used in the site definition to which it corresponds.
  3. SPSite scoped stapled features, in quasi random order
  4. SPWeb scoped features defined in onet.xml, in the order they are defined in the file.
  5. SPWeb scoped stapled features, in quasi random order
  6. List instances defined in onet.xml
  7. Modules defined in onet.xml
This is a fairly complex process and it can often be hard to know the method for customizing a site definition. A solution can be right in one scenario and completely wrong in another, making this somewhat confusing."
Here are some gotchas related to SPSPERS customization:
  • The doc-libs Shared Documents and Personal Documents do not exist yet during feature stapling; list instances in onet.xml are provisioned in step 6.
  • The my content home page default.aspx do not exist yet during feature stapling; files and pages in onet.xml are provisioned in step 7.
  • Do not create your own customized default.aspx file, it will get filled with the standard web-parts when the file's AllUsersWebParts are provisioned by the onet.xml module in step 7.
  • The quick launch heading node titles are not yet localized during site provisioning, look them up by their id rather than their title when adding links
  • The standard BlogView web-part only works when in a site page in the site root, it won't work in pages stored in lists, doc-libs or custom folders.
  • The Wiki Page Home Page feature is not activated by default for personal sites; do not provision your own /SitePages/ custom list, doc-lib or folder, as this will prevent enabling wiki pages later on.
To customize the standard personal site home page, you must provision a new home page with a different name and change the site's home page setting (SPFolder rootFolder.WelcomePage). Remember to restore the standard setting when deactiving your customization feature.

I strongly recommend using or learning from the SiteConfigurator, download the feature and the source code from CodePlex and join the community there.

Tuesday, September 28, 2010

Customizing the Search Action Links in SharePoint 2010

The SharePoint 2010 Search Action Links web-part is really just the Search Core Results web-part with the 'Show search results' settings set to false. Some of the action links have configurable captions such as the 'Show more results' link, while the 'Alert me' and 'RSS feed' link captions are not exposed in the web-part settings. However, it is quite easy to customize the layout, styling, links and content of the Search Action Links web-part - you can even add custom action links.

This example shows a searchable site directory built from customized standard SharePoint 2010 search web-parts:


Customizing the Search Action Links web-part is done exactly like for the Search Core Results web-part: you customize the embedded XSL or provide a custom XSL file using the hidden XslLink setting in the .webpart file. The following example shows how to customize the caption of the alerts and RSS links:


The example XSL shows how to set an <xsl:variable> field based on different conditions using <xsl:choose>. XSL don't have variables like in programming languages; they behave like C# const or read-only fields, they cannot be changed when first set.

The example XSL also shows how to fix an error with the RSS feed link when using a 'Fixed query' in your search page, or when using a specific search 'Scope' and no user query keyword by default, to show all results from that scope. The RSS link do not pick up these settings from the web-part, and the query RSS feed will generate the "Search RSS feed generation failed" error message. This error is caused by there being no keyword, which is fixed by adding a keyword to the URL using the k= querystring key. Just add the fixed query or the scope as the keyword:

/_layouts/srchrss.aspx?k=scope:"Puzzlepart"

Note that the 'Alert me' link don't have the k= RSS feed issue, rather it uses JavaScript and the hidden <input name="P_Query"> field on the page. This field is emitted by the Search Core Result web-part and contains the complete keyword query including the user query and the Scope, FixedQuery and AppendedQuery property settings.

Add custom parameters to the XSL when you need to have extra web-part configuration options beyond the provided standard parameters. For example, to simulate the P_Query mechanism, pass the query keywords as custom parameters. The example XSL shows how to use the custom parameter, along with the standard parameters. Use the 'Parameters Editor' in the web-part tool pane to configure your custom web-part setting:

<ParameterBinding Name="AppendedFilter" Location="None" DefaultValue="scope:&qout;Puzzlepart&qout;" />

Pass your configuration value using the DefaultValue attribute, using "None" as the Location. You can of course also set the 'alert me' and 'RSS feed' link captions using parameters.

I use code like this in a FeatureActivated event receiver to configure and add the customized web-part to our custom web part pages:

private static void AddWebPartResultsActionLinks(SPLimitedWebPartManager lwpm, string title, string siteTemplate)
{
    string xslParams = "";
    if(!String.IsNullOrEmpty(siteTemplate))
        xslParams = String.Format("<ParameterBinding Name='AppendedFilter' Location='None' DefaultValue='SiteInfoSiteTemplate:\"{0}\"' />", siteTemplate);

    CoreResultsWebPart ralwp = new CoreResultsWebPart()
    {
        Title = title,
        ChromeType = PartChromeType.None,
        QueryNumber = QueryId.Query1, //must be UserQuery because user can search the SiteInfoScope
        ShowActionLinks = true,
        ShowSearchResults = false, //this makes it an "action links" web-part
        ShowWindowsSearch = false,
        DisplayRSSLink = true,
        DisplayAlertMeLink = true,
        XslLink = "/CenterPages/XSL/SearchActionLinks.xsl",
        ParameterBindings = xslParams,
        UseLocationVisualization = false,
        ShowMessages = false
    };
    lwpm.AddWebPart(ralwp, "MiddleRightZone", 1);
}


You can of course export the customized Search Action Links web-part and use <AllUsersWebPart> in a file module to provision the web-part to a page, but the elements.xml file quickly gets huge and unmanageable. In addition, you'll have to encode any CDATA sections embedded in the exported web-part XML, as CDATA sections cannot be nested. That's why I recommend using XslLink rather than embedded XSL, and then code to provision the customized web-parts.

Friday, August 27, 2010

SharePoint 2010 My Tasks Web Part using Search Driven Cross-Site Query with Muenchian Grouping

There always seems to be a requirement for rolling up data from all sites in one or more SharePoint solutions, such as getting a list of my tasks, a list of new documents this week, or creating a searchable news archive for publishing sites; or such as creating a site map or dynamic site directory based on metadata collected in your site provisioning workflow, that are later maintained by site owners.

SharePoint has several web-parts that can do cross-list and cross-subsite queries, such as the Content Query web-part, but all restricted to a single site-collection. In addition, there are the classic Data View web-part and the new XSLT List View web-parts that can be configured using SharePoint Designer. These web-parts can connect to a diverse set of data sources, from internal SharePoint lists to external REST and OData services.

Still, the simplest solution for cross-site/cross-solution rollups is to customize the ootb search web-parts against custom search scopes in the Search Service application. In most cases, no coding will be required, pure configuration of SharePoint will go a long way. This post will show how to configure a search driven "My Tasks" web-part that will show all tasks assigned to the user across all SharePoint sites across all indexed SharePoint solutions. The unstyled cross-site task rollup web-part looks like this, included some debug info:


First you need to configure the results scope behind the search driven web-part in Central Admin. Start by adding a new scope in 'Search Service Application>Scopes' called TaskRollup using the rules as shown here:


If you can't see ContentType when adding a rule, then go to 'Search Service Application>Metadata Properties' and edit the managed property to set Allow this property to be used in scopes.

As the TaskStatus site column is not mapped to any managed property by default, you must map the crawled property ows_Status to one before it can be used. Go to 'Search Service Application>Metadata Properties' and create a managed property called TaskStatus using the mapping as shown here:


Do not go creative with the naming, stay away from spaces and special characters such as ÆØÅ - a SharePoint best practice for any artifact name used as an identifier or an URL fragment. For example, a name like "Contoso Web Ingress" first gets encoded as "Contoso_x0020_Web_x0020_Ingress" when stored, and then once more encoded as "Contoso_x005F_x0020_Web_x005F_x0020_Ingress" in a search result XML.

A full crawl is required after adding or changing crawled or managed properties. Do a full crawl of the content source you used in the TaskRollup scope. Note that there must be some matching content stored in SharePoint for these properties to be added to the property database in the first place. Thus after provisioning new site content types or site columns, you must add some sample content and then do a full recrawl of the applicable content source.

Verifying that the full crawl of the SharePoint sites content source finished without errors completes the Central Admin configuration. Now it's time to configure the ootb Search Core Results web-part to become the customized My Tasks web-part.

Open a team-site and add the Search Core Results web-part to a page. Switch to page edit mode and select 'Edit Web Part' to open the Search Core Results settings panel. Rename the web-part 'Title' to Task Rollup (cross-site) and set the 'Cross Web-Part Query ID' to User query and 'Fixed Keyword Query' to scope: "TaskRollup" as shown here:


The Search Core Results web-part requires a user query, or a configured fixed or appended query, to actually perform a search. No configured or no user query will just show a message asking for query input. The cross-page query ID setting User query is chosen here for reasons explained later.

If you want to further limit what tasks are shown in the My Tasks web-part, just add more query keywords to the 'Append Text to Query' setting as shown here:


The My Tasks web-part will show the two task fields 'Status' and 'Assigned to' in the task list. Any managed crawled property can be added to the search results by configuring the 'Fetched Properties' setting. Add the following XML <Column Name="AssignedTo"/> <Column Name="TaskStatus"/> as shown here:


You need to uncheck the 'Use Location Visualization' setting to enable the controls for customizing the result set and XSL formatting. See A quick guide to CoreResultsWebPart configuration changes in SharePoint 2010 by Corey Roth to learn more about the new search location concept in SharePoint 2010. Read all his Enterprise Search posts for an excellent introduction to the improved SharePoint 2010 search services and web-parts.

After adding 'TaskStatus' and 'AssignedTo' to the fetched properties, you will also need to customize the XSL used to format and show the search results to also include your extra task fields. Click the 'XSL Editor' button in the 'Display Properties' section of the web-part settings panel, and add the fields to the match="Result" xsl:template according to your design. Note that the property names must be entered in lower case in the XSL.

The astute reader will have noticed the nice grouping of the search results. This is done using the Muenchian method as SharePoint 2010 still uses XLST 1.0, thus no simple XSLT 2.0 xsl:for-each-group. The customized "My Tasks" results XSL creates a key called 'tasks-by-status' that selects 'Result' elements and groups them on the 'taskstatus' field as shown here:


Again, note the requirement for lower case names for the fetched properties when used in the XSL. Use the <xmp> trick to see the actual result XML.

The final part of the puzzle is how to turn the cross-site task list into a personal task list. Unfortunately, the [Me] and [Today] filter tokens cannot be used in the enterprise search query syntax, so some coding is required to add such dynamic filter tokens. Export the customized Search Core Results web-part to disk to start packaging into a WSP solution.

Create a new TaskRollupWebPart web-part SPI in your web-parts feature in Visual Studio 2010. Make the new web-part class inherit from CoreResultsWebPart in the Microsoft.Office.Server.Search assembly. Override the methods shown here to add dynamic filtering of the query through the SharedQueryManager for the web-part page:

namespace PuzzlepartTaskRollup.WebParts
{
[ToolboxItemAttribute(false)]
public class TaskRollupWebPart : 
Microsoft.Office.Server.Search.WebControls.CoreResultsWebPart
{
QueryManager _queryManager;
protected override void OnInit(EventArgs e) {
  base.OnInit(e);
  _queryManager = SharedQueryManager.GetInstance(this.Page).QueryManager;
}
 
protected override System.Xml.XPath.XPathNavigator GetXPathNavigator(string viewPath)
{
  SPUser user = SPContext.Current.Web.CurrentUser;
  _queryManager.UserQuery = string.Format("scope:\"TaskRollup\" AssignedTo:\"{0}\""
user.Name);
  return base.GetXPathNavigator(viewPath);
}
 
protected override void CreateChildControls()
{
  base.CreateChildControls();
  //debug info
  //Controls.Add(new Label { Text = string.Format("FixedQuery: {0}<br/>
AppendedQuery: {1}<br/>UserQuery: {2}", 
FixedQuery, AppendedQuery, _queryManager.UserQuery) });
}
}
}

The code in GetXPathNavigator is what adds the current user to the QueryManager.UserQuery to filter tasks based on the assigned user by [me]. There are five query objects available on a search web-part page, where QueryId.Query1 is the default. This is also what is exposed in the web-part settings as the 'User Query' option. Use the GetInstance(Page, QueryId) overload in SharedQueryManager to get at a specific cross-page query object.

Replace the content of the TaskRollupWebPart.webpart file with the exported Search Core Results configuration. This will ensure that all the configuration done to customize the ootb web-part into the My Tasks web-part is applied to the new TaskRollupWebPart. A small change is needed in the metadata type element to load the new TaskRollupWebPart code rather than the CoreResultsWebPart code:

<webParts>
<webPart xmlns="http://schemas.microsoft.com/WebPart/v3">
<metaData>
<type name="PuzzlepartTaskRollup.WebParts.TaskRollupWebPart, 
$SharePoint.Project.AssemblyFullName$" />
<importErrorMessage>$Resources:core,ImportErrorMessage;</importErrorMessage>
</metaData>

Build the feature and deploy the package to your test site from Visual Studio 2010. Add the web-part to a page and verify that you get only your tasks as expected.

I know that this seems like a lot of work, but a search-driven web-part is easily created and tested before lunch. The inevitable styling & layout using XSL and CSS is what will burn hours, as usual.
 
A drawback of search driven web-parts or code is the delay before new/updated content is shown due to the periodical crawling schedule, typically five or ten minutes. On the positive side, the results will be automatically security trimmed for you based on the logged on user - no authentication hassles or stored username password required as with the XSL List View.

Note that most enterprise search classes are still sealed in SharePoint 2010 as in SharePoint 2007, except the CoreResultsWebPart and some new classes, so you're limited to what customizations can be achieved with configuration or the SharedQueryManager. Search driven web-parts works equally well in SharePoint 2007, except that there is no SharedQueryManager, but rather the infamous search results hidden object (SRHO) which is unsupported.

Recommended: SharePoint Search XSL Samples and the Search Community Toolkit at CodePlex.

Wednesday, July 28, 2010

Using a Page Specific Master Page in SharePoint 2010

It is a common misunderstanding that you cannot have page specific masterpages in SharePoint. The standard configuration of the MasterPageFile setting in the Page directive is to use the well known ~masterurl/default.master and ~masterurl/custom.master dynamic tokens that at run-time are replaced with the site-wide 'Site Master Page' and 'System Master Page' settings. So using these tokens don't allow for having page specific masterpages in SharePoint.

There are, however, two additional tokens, the site-collection and site relative ~sitecollection/<central master page> and ~site/<local master page> static tokens from Windows SharePoint Services 3.

You can still use these static tokens in SharePoint 2010 as shown in this example:

<%@ Page language="C#" MasterPageFile="~site/_catalogs/masterpage/JBV/SiteCenterMasterPage.master" Inherits="Microsoft.SharePoint.WebPartPages.WebPartPage, Microsoft.SharePoint,Version=14.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c" %>

Note how you must specify the site-collection or site relative path of the master page file location in addition to the name of the master page file.

So using the static tokens works like a breeze for web part pages in SharePoint 2010 also. I haven't tested if the Publishing feature's page layouts still are limited to the dynamic tokens. For application pages the ability to use a customized master page is new in SharePoint 2010.

Wednesday, June 09, 2010

Customizing SharePoint 2010 My Sites

A nice improvement on My Sites in SharePoint 2010 is that all of the My Site content and pages except "My Content" is now provided by the My Site Host site-collection, and not as in SharePoint 2007 where all except the public profile page UserDisp.aspx was provided by each user's personal site-collection.


This makes it much simpler to customize the content, links, web-parts, and look and feel of My Sites in 2010. Just customize the shared pages in the My Site Host such as "My Profile" and "My Newsfeed", save/deploy and the changes will immediately affect all My Sites. No more hassle of pushing the profile customizations to thousands of personal site-collections. Note that the "Tags and Notes", "Colleagues" and "Memberships" are stored in the /_layouts/ folder and are application pages, not web-part pages, and as such are not easily customized.

Any customization needed for "My Content" will still depend on e.g. feature stapling or master page delegate controls to push the changes to each personal site.

See Customizing My Sites in Microsoft SharePoint 2010 on the SharePoint Social Computing team blog for an overview of My Site customization options. See also the 1h42m web-cast Customizing My Sites and Social Features in SharePoint 2010.