Showing posts with label Workflow. Show all posts
Showing posts with label Workflow. Show all posts

Monday, April 9, 2012

How to show related item on InfoPath task form

Task form is displayed using WrkTaskIP.aspx page from TEMPLATE\LAYOUTS folder.

  1. Create copy of WrkTaskIP.aspx into your project named CustomWrkTaskIP.aspx.
  2. Register forms.css style:
    <asp:Content ContentPlaceHolderID="PlaceHolderAdditionalPageHead" runat="server">
    <SharePoint:CssRegistration runat="server" Name="forms.css" EnableCssTheming="true" />
    </asp:Content>

  3. Add the following code into page:
    <div>
    <h3>Related item</h3>
    <table width="100%" class="ms-formtable" border="0" cellspacing="0" cellpadding="0">
    <SharePoint:ListFieldIterator ID="ItemFieldIterator" runat="server" ControlMode="Display" />
    </table>
    </div>

  4. Add codebehind to your page:
    public class CustomWrkTaskPage : WrkTaskIPPage
    {
    protected ListFieldIterator ItemFieldIterator;

    protected override void OnLoad(EventArgs ea)
    {
    base.OnLoad(ea);

    Guid workflowInstanceId = new Guid((string)m_task[SPBuiltInFieldId.WorkflowInstanceID]);
    SPWorkflow workflow = new SPWorkflow(Web, workflowInstanceId);


    ItemFieldIterator.ListId = workflow.ParentList.ID;
    ItemFieldIterator.ItemId = workflow.ParentItem.ID;
    }
    }

  5. Set DisplayFormUrl and EditFormUrl properties of all task content types to your CustomWrkTaskIP.aspx:
    foreach (SPContentType taskContentType in taskList.ContentTypes)
    {
    if (!taskContentType.Id.IsChildOf(SPBuiltInContentTypeId.WorkflowTask))
    continue;

    taskContentType.DisplayFormUrl = "_layouts/My/CustomWrkTaskIP.aspx";
    taskContentType.EditFormUrl = "_layouts/My/CustomWrkTaskIP.aspx";
    taskContentType.Update();
    }

Thursday, December 29, 2011

Workflow related content field contains old item title

We have the following workflow:

  1. User enters new item without Title and workflow starts
  2. New Title is built and set in workflow
  3. Task is created

image

Related content field in task should contain new Title but it contains the empty one. You can create empty workflow action which has [PersistOnClose] attribute which commits all pending changes but it doesn’t help in this case. So we have created custom event receiver for Tasks list. It solves the problem.

public override void ItemAdding(SPItemEventProperties properties)
{
try
{
string listIdText = properties.AfterProperties["WorkflowListId"] as string;
string itemIdText = properties.AfterProperties["WorkflowItemId"] as string;
string workflowLink = properties.AfterProperties["WorkflowLink"] as string;
if (!string.IsNullOrEmpty(listIdText) &&
!string.IsNullOrEmpty(itemIdText) &&
!string.IsNullOrEmpty(workflowLink))
{
Guid listId = new Guid(listIdText);
int itemId = Convert.ToInt32(itemIdText);

SPList list = properties.Web.Lists[listId];
SPListItem item = list.GetItemById(itemId);

SPFieldUrlValue url = new SPFieldUrlValue(workflowLink);
url.Description = item.Title;

properties.AfterProperties.ChangedProperties.Add("WorkflowLink", url.ToString());
}
}
catch (Exception ex)
{
Trace.WriteLine("WorkflowTaskEventReceiver.ItemAdding: " + ex.Message);
}
base.ItemAdding(properties);
}


Links:
About workflow batches and commit pending changes action

Thursday, December 22, 2011

Custom task form showing related item

When you publish workflow in SharePoint Designer and workflow contains some task activity, it generates custom task form. Generated form shows only task fields and link to related item. It means one extra click for users to see related item. How to show related item right on the task form?

  1. Move custom task form (DataFormWebPart) into new WebPartZone
    <WebPartPages:WebPartZone ID="MainZone" runat="server" Title="Main Zone"><ZoneTemplate>
    ...
    </ZoneTemplate></WebPartPages:WebPartZone>

  2. Add new WebPartZone for related item
    <WebPartPages:WebPartZone ID="ItemZone" runat="server" Title="Item Zone"><ZoneTemplate>
    ...
    </ZoneTemplate></WebPartPages:WebPartZone>

  3. Add column WorkflowItemId to DataSource of task. Find <datafields> element of <DataFormWebPart> and add 
    @WorkflowItemId,Workflow Item ID;

  4. Add WorkflowItemId as a new row to table in task DataFormWebPart (it will be used later for webpart connection)
    <tr>
    <td width="25%" class="ms-formlabel">
    <b>Workflow Item ID:</b>
    </td>
    <td width="75%" class="ms-formbody">
    <xsl:value-of select="@WorkflowItemId" />
    </td>
    </tr>

  5. Add Custom List Form - Display Form of related item
    clip_image001
  6. Create WebPart Connection
    image
    image
    image
    image
    image
  7. Change ParameterBinding of ListItemId in related item DataFormWebPart from QueryString to Connection
    <ParameterBinding Name="ListItemId" Location="Connection" DefaultValue="0"/>

  8. Now you can hide Workflow Item ID table row
    <tr style="display:none"> 

Thursday, December 15, 2011

Workflow – commit all pending changes

Some workflow actions don’t run immediately. They are queued in workflow batch and executed when the workflow commits. Typical batched actions are “Set Field in Current Item” and “Update List Item”. If you need commit pending changes, you can use the following action.

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Workflow.Activities;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Compiler;
using Microsoft.SharePoint.Workflow;
using Microsoft.SharePoint.WorkflowActions;

namespace Sample.SharePoint.WorkflowActions
{
[PersistOnClose]
public partial class CommitActivity : Activity
{
public CommitActivity()
{
InitializeComponent();
}

protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
return ActivityExecutionStatus.Closed;
}

protected override ActivityExecutionStatus HandleFault(ActivityExecutionContext executionContext, Exception exception)
{
try
{
ISharePointService service = (ISharePointService)executionContext.GetService(typeof(ISharePointService));
if (service == null)
{
Trace.WriteLine("CommitActivity.HandleFault: ISharePointService is null");
}
else
{
service.LogToHistoryList(base.WorkflowInstanceId, SPWorkflowHistoryEventType.WorkflowComment, 0, TimeSpan.MinValue, "Error", "Commit pending changes error: " + exception.Message, string.Empty);
}
}
catch (Exception ex)
{
Trace.WriteLine("CommitActivity.HandleFault: " + ex.Message);
}
return base.HandleFault(executionContext, exception);
}

public static DependencyProperty __ContextProperty = DependencyProperty.Register("__Context", typeof(WorkflowContext), typeof(CommitActivity));
public static DependencyProperty __ListIdProperty = DependencyProperty.Register("__ListId", typeof(string), typeof(CommitActivity));
public static DependencyProperty __ListItemProperty = DependencyProperty.Register("__ListItem", typeof(SPItemKey), typeof(CommitActivity));

[ValidationOption(ValidationOption.Required), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible), Browsable(true)]
public WorkflowContext __Context
{
get
{
return (WorkflowContext)base.GetValue(__ContextProperty);
}
set
{
base.SetValue(__ContextProperty, value);
}
}

[ValidationOption(ValidationOption.Required), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible), Browsable(true)]
public string __ListId
{
get
{
return (string)base.GetValue(__ListIdProperty);
}
set
{
base.SetValue(__ListIdProperty, value);
}
}

[ValidationOption(ValidationOption.Required), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible), Browsable(true)]
public SPItemKey __ListItem
{
get
{
return (SPItemKey)base.GetValue(__ListItemProperty);
}
set
{
base.SetValue(__ListItemProperty, value);
}
}
}
}

Thursday, December 1, 2011

Start workflow from timer job

We cannot start workflow automatically when the item is created. We have to select the right workflow dynamically by data in the item. Workflow starts when user clicks the button (SPWorkflowManager.StartWorkflow method is called). Unfortunately, starting the workflow is very slow, so we decided to start it from the timer job. Instead of starting the workflow immediately we only set field "Start Workflow". All items with flag "Start Workflow" are periodically checked every 5 minutes in timer job and workflow is started using the author's account (SPUserToken).

using (SPSite site = new SPSite(webUrl))
{
using (SPWeb web = site.OpenWeb())
{
// Find all items where field StartWorkflow = TRUE
SPListItemCollection items = web.Lists["ListName"].GetItems(...);
foreach (SPListItem item in items)
{
SPUser author = web.AllUsers.GetByID(Utils.GetLookupId(item[SPBuiltInFieldId.Author]));
// Run workflow as item author (CreatedBy)
SPUserToken token = web.AllUsers[author.LoginName].UserToken;
using (SPSite userSite = new SPSite(web.ID, token))
{
SPWorkflowManager manager = site.WorkflowManager;
using (SPWeb userWeb = userSite.OpenWeb())
{
try
{
SPList listAsUser = userWeb.Lists[item.ParentList.ID];
SPListItem itemAsUser = listAsUser.GetItemById(item.ID);

// Find correct workflow association
SPWorkflowAssociation association = listAsUser.WorkflowAssociations[...];
manager.StartWorkflow(itemAsUser, association, association.AssociationData);
}
catch (Exception ex)
{
// Log error...
}
}
}
}
}
}