Code Example: Mobile View for a Duet Workflow Form
Learn how to create a mobile view for a Duet Enterprise for Microsoft SharePoint and SAP Workflow form.
Applies to: Duet Enterprise for Microsoft SharePoint and SAP Server 2.0 | Office 2010 | SharePoint Server 2010
Microsoft SharePoint 2010 does not currently support a mobile document approval workflow feature. Because Duet Enterprise workflow is based on SharePoint 2010 workflow, mobile views for Duet Enterprise Workflow forms are not supported by default. In SharePoint 2010 you can navigate to the mobile view of a workflow page by adding /m or /mobile=1 to the URL of the page, but this view does not support fully functional approval forms for workflows.
This topic describes how to customize SharePoint 2010 to create a mobile view for a Duet Enterprise Workflow form.
Note
You will need to test this solution on your own environment to ensure that it conforms to your internal performance requirements.
Creating and Deploying a Mobile Custom Workflow Form in Duet Enterprise
The procedures in the following sections describe how to create and deploy a mobile view for a custom Duet Enterprise Workflow form.
Setting up the Custom Project and Creating a Custom Text Field control
The steps in the following procedure describe how to set up the project that contains the custom workflow and create a custom Text Field control. This control will replace the title in the existing mobile view of the dispform.aspx page. This title will link to the new custom mobile form. For another example of how to customize titles on mobile forms, see Walkthrough: Customizing Item Titles on Mobile Forms (https://msdn.microsoft.com/en-us/library/ms454716.aspx).
To set up the custom project and create a custom Text Field control
In Microsoft Visual Studio 2010, create a new class library project named DuetMobileCustomization. Add references to the System.Web, System.Web.Mobile and Microsoft.SharePoint.dll assemblies.
Add a namespace named MyCompany.SharePoint.MobileControls. (Replace MyCompany with your company's name.) Add a class to this namespace and name it WorkflowControl. Add using statements for System.Web.UI.MobileControls, Microsoft.SharePoint, System.Web.UI and Microsoft.SharePoint.MobileControls. This class needs to inherit from the SPMobileBaseTextField class and override the CreateControlForDisplay method so that it displays the new control instead of the default Text Field control.
Copy the following code to create the new WorkflowControl class.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Web.UI.MobileControls; using Microsoft.SharePoint; using Microsoft.SharePoint.MobileControls; using System.Web.UI; namespace MyCompany.SharePoint.MobileControls { public class WorkflowControl : SPMobileBaseTextField { protected override MobileControl CreateControlForDisplay() { string title = Convert.ToString(this.ItemFieldValue); string MyTaskSite = "MyTaskSite"; // Differentiate MyTaskSite from other sites so that you add this control only to MyTaskSite. Do not display this control for completed tasks. if (!String.IsNullOrEmpty(title) && this.Web.Properties.ContainsKey(MyTaskSite) && !ListItem[SPBuiltInFieldId.TaskStatus].Equals("Completed")) { this.LinkControl.BreakAfter = false; this.LinkControl.Text = title; this.LinkControl.href = this.Web.Url + "/_layouts/mobile/TaskDispForm.aspx" + this.Page.Request.Url.Query; Panel panel = new Panel(); panel.BreakAfter = false; panel.Controls.Add(this.LinkControl); return panel; } return base.CreateControlForDisplay(); }
Creating the Rendering Template for the Mobile Form
The steps in the following procedure describe how to create a template for rendering the custom mobile form.
To create the rendering template for the mobile form
In Solution Explorer, right-click the project name, DuetMobileCustomization. Click Add, and then click New Item.
In the Categories window, click Visual C# Project Items. In the Templates window, click Text File.
Type DuetMobileControlTemplate.ascx in the Name box, and then click Add. Do not put this file in a subfolder of the project folder. If you do, the post-build commands will not find it.
Add the following markup to the DuetMobileControlTemplate.ascx file.
<%@ Register TagPrefix="GroupBoardMobile" Namespace="Microsoft.SharePoint.Applications.GroupBoard.MobileControls" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> <%@ Control Language="C#" %> <%@ Assembly Name="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> <%@ Register TagPrefix="mobile" Namespace="System.Web.UI.MobileControls" Assembly="System.Web.Mobile, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" %> <%@ Register TagPrefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> <%@ Register TagPrefix="SPMobile" Namespace="Microsoft.SharePoint.MobileControls" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> <%@ Register TagPrefix="WPMobile" Namespace="Microsoft.SharePoint.WebPartPages" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> <%@ Register TagPrefix="OBAMobile" Namespace="MyCompany.SharePoint.MobileControls" Assembly="OBA.Server.MobileCustomization, Version=1.0.0.0, Culture=neutral, PublicKeyToken=Token" %> <%@ Import Namespace="Microsoft.SharePoint" %> <SharePoint:RenderingTemplate RunAt="Server" ID="MobileCustomListField_Tasks_Text_Title" > <Template> <OBAMobile:WorkflowControl RunAt="Server" /> </Template> </SharePoint:RenderingTemplate>Replace the value of PublicKeyToken with your own public key token. You can obtain this token by selecting Get Assembly Public Key from the Tools menu. The public key token will appear in the last line of the Output window. Use only the key token value, not the entire key.
Creating the Custom Mobile Approval Form Page
The steps in the following procedure describe how to create the page containing the mobile approval form.
To create the custom mobile approval form page
In Solution Explorer, right-click the project name, DuetMobileCustomization. Click Add, and then click New Item.
In the Categories window, click Visual C# Project Items. Add a text file named TaskDispForm.aspx.
Add the following code to the TaskDispForm.aspx file. This loads the approval form class that you will define in the next steps.
<%@ Register TagPrefix="GroupBoardMobile" Namespace="Microsoft.SharePoint.Applications.GroupBoard.MobileControls" Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=public key token value" %> <%@ Page Language="C#" EnableViewState="false" inherits="Microsoft.Office.Web.Mobile.TaskDispFormPage, Microsoft.Office.Web.Mobile, Version=1.0.0.0, Culture=neutral, PublicKeyToken=Token"%> <%@ Register TagPrefix="mobile" Namespace="System.Web.UI.MobileControls" Assembly="System.Web.Mobile, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" %> <mobile:Form BackColor="white" RunAt="server"> </mobile:Form>Note
Replace the value of the PublicKeyToken property with your own public key token. You can obtain this token by selecting Get Assembly Public Key from the Tools menu. The public key token will appear in the last line of the Output window. Use only the key token value, not the entire key.
In the Categories window, click Visual C# Project Items. Add a class file named TaskDispFormPage.cs. Repeat this step and add another class file named TaskDispForm.cs.
Add the following code to the TaskDispFormPage.cs file. This code adds a control that displays the New form and that retrieves and displays the task data. It also adds a Button control for approving and rejecting the task.
namespace Microsoft.Office.Web.Mobile { using System; using System.Collections; using System.Globalization; using System.Web; using System.Web.UI; using System.Web.UI.HtmlControls; using System.Web.UI.MobileControls; using Microsoft.Office.Web.Mobile; using Microsoft.SharePoint; using Microsoft.SharePoint.MobileControls; using Microsoft.SharePoint.Workflow; using Microsoft.SharePoint.Utilities; using Microsoft.SharePoint.Administration; internal class ApprovalForm : Form { // ========================================================================================= // Member Variables // ========================================================================================= internal const string ListId = "List"; internal const string ItemId = "ID"; internal const string Source = "src"; protected Label m_taskTitleLabel; protected Label m_approvalRequestedLabel; protected Label m_fromLabel; protected Label m_requestorLabel; protected Label m_dueByLabel; protected Label m_dueDateLabel; protected Label m_commentInstructionLabel; protected SPMobileMultiTextBox m_commentTextBox; protected Command m_approveButton; protected Command m_rejectButton; protected Command m_cancelButton; protected Link m_backListlink; protected Label m_initialCommentLabel; protected Label m_initialCommentValue; private SPListItem m_task; private string m_backUrl; private string m_submissionFailureTitle; private string m_submissionFailureMessage; private string m_errorImageUrl; // ========================================================================================= // Constructors // ========================================================================================= /// <summary>Initializing constructor</summary> /// <param name="listGuid">The GUID of the task list.</param> /// <param name="itemId">The item ID of the task.</param> /// <param name="approvalRequestedLabelText">The text for the "Approval Requested" field</param> /// <param name="fromLabelText">The text for the "From :" field </param> /// <param name="dueByLabelText">The text for the "Due By :" field</param> /// <param name="commentInstructionLabelText">The text for m_commentInstructionLabel.Text.</param> /// <param name="approveButtonText">The text for the Approve button.</param> /// <param name="rejectButtonText">The text for the Reject button.</param> /// <param name="cancelButtonText">The text for the Cancel button.</param> /// <param name="backUrl">The URL for redirecting after a button click.</param> /// <param name="submissionFailureTitle">The caption to show when the task submission fails.</param> /// <param name="submissionFailureMessage">The message to show when the task submission fails.</param> /// <param name="errorImageUrl">The icon URL for the error sign.</param> public ApprovalForm( string listGuid, string itemId, string approvalRequestedLabelText, string fromLabelText, string dueByLabelText, string commentInstructionLabelText, string approveButtonText, string rejectButtonText, string cancelButtonText, string backUrl, string submissionFailureTitle, string submissionFailureMessage, string errorImageUrl) { try { //: // Because the approval form control does not have its own strings and ULS category, exceptions are // passed to the caller for handling. Guid guid = new Guid(listGuid); SPList list = SPContext.Current.Web.Lists.GetList(guid, false); m_task = list.GetItemById(Convert.ToInt32(itemId, CultureInfo.InvariantCulture)); } catch (Exception ex) { throw new ArgumentException("Invalid argument is passed and causes the error: " + ex.Message); } // Check for required backUrl value. if (String.IsNullOrEmpty(backUrl)) { throw new ArgumentException(); } m_taskTitleLabel = new Label(); m_approvalRequestedLabel = new Label(); m_fromLabel = new Label(); m_requestorLabel = new Label(); m_dueByLabel = new Label(); m_dueDateLabel = new Label(); m_commentInstructionLabel = new Label(); m_commentTextBox = new SPMobileMultiTextBox(); m_approveButton = new Command(); m_rejectButton = new Command(); m_cancelButton = new Command(); m_backListlink = new Link(); m_initialCommentLabel = new Label(); m_initialCommentValue = new Label(); m_approvalRequestedLabel.Text = approvalRequestedLabelText; m_approvalRequestedLabel.Font.Bold = BooleanOption.True; m_approvalRequestedLabel.Font.Size = FontSize.Small; m_approvalRequestedLabel.Font.Name = "Tahoma"; m_fromLabel.Text = fromLabelText; m_dueByLabel.Text = dueByLabelText; m_commentInstructionLabel.Text = commentInstructionLabelText; m_commentInstructionLabel.Font.Bold = BooleanOption.True; m_approveButton.Text = approveButtonText; m_rejectButton.Text = rejectButtonText; m_cancelButton.Text = cancelButtonText; m_backUrl = backUrl; m_submissionFailureTitle = submissionFailureTitle; m_submissionFailureMessage = submissionFailureMessage; m_errorImageUrl = errorImageUrl; this.BackColor = System.Drawing.Color.White; } // ========================================================================================= // Enums // ========================================================================================= private enum TaskStatus { Approved, Rejected } // ========================================================================================= // Event Handlers // ========================================================================================= /// <summary> /// Override of OnLoad method. /// </summary> /// <param name="e">The event data</param> protected override void OnLoad(EventArgs e) { m_taskTitleLabel.Text = m_task.Name; m_taskTitleLabel.Font.Bold = BooleanOption.True; m_taskTitleLabel.Font.Size = FontSize.Normal; m_taskTitleLabel.Font.Name = "Tahoma"; m_taskTitleLabel.ForeColor = System.Drawing.Color.Blue; //m_taskTitleLabel.Font.Size = FontSize.Large; //m_taskTitleLabel.Font.Bold = BooleanOption.True; this.Title = m_task.Name; m_fromLabel.BreakAfter = false; // Format of this string: n;#user-name. if (m_task["ParentInitiator"] != null) { m_requestorLabel.Text = (string)m_task["ParentInitiator"]; } m_dueByLabel.BreakAfter = false; if (m_task[SPBuiltInFieldId.TaskDueDate] != null) { DateTime dueDate = (DateTime)m_task[SPBuiltInFieldId.TaskDueDate]; m_dueDateLabel.Text = dueDate.ToString("d"); } string description = (string)m_task["EmailNotificationMessage"]; string comments = (string)m_task[SPBuiltInFieldId.Body]; TextView descriptionTextView = new TextView(); if (!String.IsNullOrEmpty(description)) { descriptionTextView.Text = description.Replace(Environment.NewLine, Constants.BrElement); } // descriptionTextView.ForeColor = System.Drawing.Color.Black; descriptionTextView.Font.Bold = BooleanOption.False; descriptionTextView.BreakAfter = false; // descriptionTextView.ForeColor = "#2B1B17"; // Constants.DescriptionFontColor; // Set up the border to make the description text block more readable. HtmlTable descriptionTable = new HtmlTable(); HtmlTableRow row = new HtmlTableRow(); HtmlTableCell cell = new HtmlTableCell(); // Set up the table. Use CellSpacing so that we can have a colored border. //descriptionTable.Width = "100"; descriptionTable.Border = 0; descriptionTable.CellSpacing = 1; descriptionTable.BgColor = Constants.DescriptionBorderColor; cell.BgColor = Constants.DescriptionBgColor; // Glue the controls together. cell.Controls.Add(descriptionTextView); row.Cells.Add(cell); descriptionTable.Rows.Add(row); m_initialCommentLabel.BreakAfter = true; m_initialCommentLabel.Text = Constants.InitialComments; m_initialCommentValue.Text = comments; m_initialCommentValue.BreakAfter = true; m_commentInstructionLabel.BreakAfter = false; m_commentTextBox.Rows = 4; m_approveButton.BreakAfter = false; m_approveButton.Click += this.OnApproveClick; m_rejectButton.BreakAfter = false; m_rejectButton.Click += this.OnRejectClick; m_cancelButton.Click += this.OnCancelClick; m_backListlink.href = m_backUrl; m_backListlink.Text = "Go back to List Item"; this.Controls.Add(m_taskTitleLabel); // this.Controls.Add(new LiteralControl("<div style='width:100%' class='UserDottedLine' ></div>")); // Utility.AddSeparator(this.Controls, ((MobilePage)this.Page).Device); this.Controls.Add(new LiteralControl("<hr />")); this.Controls.Add(m_approvalRequestedLabel); this.Controls.Add(m_fromLabel); this.Controls.Add(m_requestorLabel); this.Controls.Add(m_dueByLabel); this.Controls.Add(m_dueDateLabel); this.Controls.Add(m_initialCommentLabel); this.Controls.Add(m_initialCommentValue); this.Controls.Add(new LiteralControl("<br/>")); this.Controls.Add(descriptionTable); this.Controls.Add(new LiteralControl("<br/>")); this.Controls.Add(m_commentInstructionLabel); this.Controls.Add(new LiteralControl("<br/>")); this.Controls.Add(m_commentTextBox); this.Controls.Add(new LiteralControl("<br/>")); this.Controls.Add(m_approveButton); this.Controls.Add(m_rejectButton); this.Controls.Add(m_cancelButton); this.Controls.Add(new LiteralControl("<br/>")); this.Controls.Add(m_backListlink); this.Controls.Add(new SPMobileFormDigest()); // Required for security validation of SPWorkflowTask.AlterTask(). } /// <summary> /// When user clicks the Approve button, do the "approve" action. /// </summary> /// <param name="sender">Sender of the event</param> /// <param name="e">The event data</param> protected void OnApproveClick(object sender, EventArgs e) { Hashtable formData = new Hashtable(); PrepareFormDataForSubmission(formData, TaskStatus.Approved); if (SPWorkflowTask.AlterTask(m_task, formData, true) == false) { ShowErrorMessage(m_submissionFailureTitle, m_submissionFailureMessage); } else { // If Task returns true, the submission is successful. Redirect to entry page. SPMobileUtility.Redirect(m_backUrl, SPRedirectFlags.Default, HttpContext.Current); } } /// <summary> /// When user clicks the Reject button, do the "reject" action. /// </summary> /// <param name="sender">Sender of the event</param> /// <param name="e">The event data</param> protected void OnRejectClick(object sender, EventArgs e) { Hashtable formData = new Hashtable(); PrepareFormDataForSubmission(formData, TaskStatus.Rejected); if (SPWorkflowTask.AlterTask(m_task, formData, true) == false) { ShowErrorMessage(m_submissionFailureTitle, m_submissionFailureMessage); } else { // If Task returns true, the submission is successful. Redirect to entry page. SPMobileUtility.Redirect(m_backUrl, SPRedirectFlags.Default, HttpContext.Current); } } /// <summary> /// Event handler for the Cancel button. /// </summary> /// <param name="sender">Sender of the event</param> /// <param name="e">The event data</param> protected void OnCancelClick(object sender, EventArgs e) { SPMobileUtility.Redirect(m_backUrl, SPRedirectFlags.Default, HttpContext.Current); } // ========================================================================================= // Methods // ========================================================================================= /// <summary> /// Fill up the formData for approval form submission. /// </summary> /// <param name="formData">The formData hashtable to fill</param> /// <param name="taskStatus">Approve or Rejected</param> private void PrepareFormDataForSubmission(Hashtable formData, TaskStatus taskStatus) { if (taskStatus == TaskStatus.Approved) { formData[Constants.TaskStatus] = Constants.Approved; } else { formData[Constants.TaskStatus] = Constants.Rejected; } formData[Constants.DelegateTo] = String.Empty; formData[Constants.Comments] = m_commentTextBox.Text; formData[Constants.NewDescription] = String.Empty; formData[Constants.NewDueDate] = String.Empty; formData[Constants.RequestTo] = String.Empty; formData[Constants.Status] = Constants.Completed; formData[Constants.Decline] = Constants.DefaultDecline; formData[Constants.Dcr] = Constants.DefaultDcr; } /// <summary> /// Fill up the formData for approval form submission. /// </summary> /// <param name="formData">The formData hashtable to fill</param> /// <param name="taskStatus">Approve or Rejected</param> private void PrepareFormDataForSubmission(Hashtable formData, string taskStatus) { formData[Constants.TaskStatus] = taskStatus; formData[Constants.DelegateTo] = String.Empty; formData[Constants.Comments] = m_commentTextBox.Text; formData[Constants.NewDescription] = String.Empty; formData[Constants.NewDueDate] = String.Empty; formData[Constants.RequestTo] = String.Empty; formData[Constants.Status] = Constants.Completed; formData[Constants.Decline] = Constants.DefaultDecline; formData[Constants.Dcr] = Constants.DefaultDcr; } /// <summary> /// Dynamically insert an error message form. /// </summary> /// <param name="errorTitle">The title of this error message</param> /// <param name="errorMessage">The content of the error message</param> private void ShowErrorMessage(string errorTitle, string errorMessage) { this.Controls.Clear(); Label errorTitleLabel = new Label(); Image errorImage = new Image(); TextView errorTextView = new TextView(); HtmlTable table = new HtmlTable(); HtmlTableRow row = new HtmlTableRow(); HtmlTableCell cell = new HtmlTableCell(); // Set up the table. Use CellSpacing so that we can have a colored border. // table.Width = Utility.Constants.HundredPercent; table.Border = 0; table.CellSpacing = 1; //table.BgColor = Utility.Constants.DialogBorderColor; cell.Controls.Add(errorTitleLabel); // cell.BgColor = Utility.Constants.DialogTitleColor; // Glue the controls together. table.Rows.Add(row); row.Cells.Add(cell); errorTitleLabel.Text = errorTitle; // errorTitleLabel.ForeColor = System.Drawing.Color.FromArgb(Utility.Constants.DialogTextColor); errorImage.ImageUrl = m_errorImageUrl; errorImage.BreakAfter = true; errorTextView.Text = errorMessage; this.Controls.Add(table); this.Controls.Add(errorImage); this.Controls.Add(errorTextView); } /// <summary> /// An inner class to hold all the constants. /// </summary> private static class Constants { // Form data fields. internal const string TaskStatus = "TaskStatus"; internal const string DelegateTo = "DelegateTo"; internal const string Comments = "Comments"; internal const string NewDescription = "NewDescription"; internal const string NewDueDate = "NewDueDate"; internal const string RequestTo = "RequestTo"; internal const string Status = "Status"; internal const string Decline = "Decline"; internal const string Dcr = "dcr"; internal const string InitialComments = "Initial Comments: "; // Task status. internal const string Approved = "Approve"; internal const string Rejected = "Reject"; // Workflow status. internal const string Completed = "Completed"; // Miscellaneous. internal const string DefaultDcr = "0"; internal const string DefaultDecline = "0"; internal const string BrElement = "<br/>"; // Approval Form specific color. internal const string DescriptionBorderColor = "#c3cbd0"; internal const string DescriptionBgColor = "#ffffff"; internal const string DescriptionFontColor = "#444444"; } } }Add the following code to the TaskDispForm.cs file. This code adds the approval form to the new page.
using Microsoft.SharePoint.MobileControls; using Microsoft.Office.Server.Diagnostics; using Microsoft.Office.Web.Mobile; [CLSCompliant(false)] public class TaskDispFormPage : SPMobilePage { internal virtual string BackUrl { get { return Request.UrlReferrer.AbsoluteUri; } } /// <summary> /// The override of OnLoad method. /// </summary> /// <param name="e">The event data</param> protected override void OnLoad(EventArgs e) { try { ApprovalForm approvalForm = new ApprovalForm( Request.QueryString[ApprovalForm.ListId], Request.QueryString[ApprovalForm.ItemId].Split(',')[0].ToString(), "Approval Requested", "From: ", "DueBy: ", "Type Comments:", "Approve", "Reject", "Cancel", BackUrl, "SubmissionFailureCaption", "SubmissionFailure", "/_layouts/images/error.gif"); this.Controls.Add(approvalForm); this.ActiveForm = approvalForm; } catch (ArgumentException ex) { return; } } } }On the Application tab in the Properties dialog box, type DuetMobileCustomization in the Assembly name field and MyCompany.SharePoint.MobileControls in the Default namespace field. (Replace MyCompany with your company's name.) Click the Signing tab, and then click Sign the assembly. In the Choose a strong name key file list box, click <New...>.
Compile the assembly, give it a strong name, and then deploy it either to the global assembly cache or to the \BIN folder of the web application on every front-end web server in the farm. To deploy it to the global assembly cache, ensure that GlobalAssemblyCache is selected in the Assembly Deployment Target of the Properties pane of your class library project in Visual Studio 2010. This topic assumes that you are deploying to the global assembly cache. Ensure that the DuetMobileControlTemplate.ascx file is placed in the controlTemplate folder after the build. Also ensure that the TaskDispForm.aspx file is placed in the LAYOUTS\MOBILE folder. These folders are typically found in the C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE folder of your SharePoint 2010 installation.
Copy the public key of the assembly and replace the public key of the OBAMobile registrytag in DuetMobileControlTemplate.ascx file. When you open the task by using a browser on a mobile device, the task will open the new task display form page that contains the task action buttons. You can act on the task by providing the comments in the form, and the task will be updated in both SAP and SharePoint 2010.
See Also
Concepts
Code Example: Mobile Adapter Class for Business Data Action Web Parts