Como recordaran en el post anterior, mostré un ejemplo de cómo crear columnas de sitio y content types programáticamente, en este post voy a dar un ejemplo de cómo crear un formulario personalizado para el tipo de contenido que creamos en ese ejemplo anterior.
Es común que creemos tipos de contenido personalizados (custom content types) para asociarlos a la lista de tareas de workflows (Workflow Tasks), esto con el fin de poder recolectar más información del usuario cuando se le asigna una tarea dentro de un flujo de trabajo, y también es muy común que necesitemos personalizar el formulario de captura que el usuario utiliza para ingresar esa información adicional que necesitamos recolectar dentro del flujo de trabajo. Por lo anterior y utilizando el tipo de contenido que creamos en el post anterior, y que de manera premeditada hice que heredara del tipo de contenido WorkflowTask (recuerdan?).
También es importante mencionar que el custom approval workflow task form que crearemos será utilizando un formulario ASP.NET, creo que la mayoría sabe que no simpatizo mucho con InfoPath, además que ejemplos de esos hay bastantes en la red.
El escenario de ejemplo es el siguiente, cree una lista llamada “Solicitud Vacaciones”, en la que los usuarios podrán capturar las peticiones para tomar días de vacaciones, una vez que registren sus solicitud se enviará un correo de aviso al usuario responsable y se le asignará una tarea para que apruebe o rechace dicha solicitud, en caso de aprobación o rechazo se le notificará al usuario solicitante.
Como primer paso, creamos un flujo de trabajo sencillo, en este caso construiré un flujo de aprobación de solicitud de vacaciones, como se aprecia en la siguiente imagen:
Como se aprecia en la siguiente imagen, se agregó una actividad de tipo CreateTaskWithContentType la cual recibe el Id del Content Type a partir del cual se creara la tarea que será asignada al usuario responsable de autorizar la solicitud de vacaciones.
Como se aprecia en la imagen, el ID del tipo de contenido se asocia a una propiedad declarada en código, la que manipularemos para buscar y asignar el ID de tipo de contenido correcto cuando el Workflow se inicialice, en el evento WorkflowActivated.
private void onWorkflowActivated(object sender, ExternalDataEventArgs e) { WorkflowContext.Initialize(workflowProperties); workflowID = workflowProperties.WorkflowId; //si el tipo de contenido no ha sido asociado a la lista de tareas try { if (workflowProperties.TaskList.ContentTypes[contentTypeName] == null) { SPContentType ctype = WorkflowContext.Web.AvailableContentTypes[contentTypeName]; if (ctype != null) { workflowProperties.TaskList.ContentTypes.Add(ctype); workflowProperties.TaskList.Update(); } } SPContentType customContentType = WorkflowContext.Web.AvailableContentTypes[contentTypeName]; this.CustomContentTypeId = customContentType.Id.ToString(); } catch (Exception ex) { //registramos la excepcion } }
Estableciendo el formulario de edición al tipo de Contenido
Regresando a la definición del tipo de contenido que creamos en el post anterior, que cómo podrán recordar fue provisionado programáticamente. El siguiente paso es establecer por lo menos el formulario de edición del tipo de contenido, es decir, el formulario que el usuario visualizará cuando vaya a aprobar o rechazar la tarea asignada. Para que el Content Type sepa que formulario debe invocar, utilizaremos la propiedad EditFormUrl la que estableceremos con el valor del formulario ASP.NET que vamos a crear en el siguiente paso.
Noten la línea 131:
using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Security.Permissions; using Microsoft.SharePoint; using Microsoft.SharePoint.Security; namespace WF1.Features.Feature1 { ////// This class handles events raised during feature activation, deactivation, installation, uninstallation, and upgrade. /// ////// The GUID attached to this class may be used during packaging and should not be modified. /// [Guid("dd6b6ec4-3391-46de-a659-55244237f346")] public class Feature1EventReceiver : SPFeatureReceiver { public override void FeatureActivated(SPFeatureReceiverProperties properties) { try { SPWeb web = null; if (properties.Feature.Parent is SPSite) { SPSite sites = (SPSite)properties.Feature.Parent; web = sites.RootWeb; } else { web = (SPWeb)properties.Feature.Parent; } if (web == null) return; //Nombre del grupo de Content Types que crearemos string contentTypeGroupName = "Blog - Content Types"; //Nombre del tipo de contenido que crearemos string contentTypeName = "Blog - Response Workflow Content Type"; //Nombre del grupo de columnas de sitio que crearemos string columnGroupName = "Blog - Site Columns"; //nombre de la columna de sitio que permitira al usuario seleccionar su respuesta (Approve | Reject) string choiceFieldName = "ChoiceWFResponse"; //nombre de la columna de sitio que permitira al usuario capturar comentarios a cerca de su respuesta string commentsFieldName = "CommentsWFResponse"; SPFieldMultiLineText approveRejectCommentsField = null; SPFieldChoice approveRejectChoiceField = null; /*Columna de sitio para seleccionar Aprobar o Rechazar*/ //mucho ojo! Validamos si la columa de sitio ya existe if (!web.Fields.ContainsField(choiceFieldName)) { string approveReject = web.Fields.Add(choiceFieldName, SPFieldType.Choice, true); approveRejectChoiceField = (SPFieldChoice)web.Fields.GetFieldByInternalName(approveReject); approveRejectChoiceField.Group = columnGroupName; approveRejectChoiceField.EditFormat = SPChoiceFormatType.RadioButtons; approveRejectChoiceField.Choices.Add("Approve"); approveRejectChoiceField.Choices.Add("Reject"); approveRejectChoiceField.DefaultValue = "Approve"; approveRejectChoiceField.Update(); } //Si ya existe, solo obtenemos una referencia a la columna else { approveRejectChoiceField = web.Fields[choiceFieldName] as SPFieldChoice; } /*Columna de sitio para permitir al usuario ingresar un comentario*/ if (!web.Fields.ContainsField(commentsFieldName)) { string comments = web.Fields.Add(commentsFieldName, SPFieldType.Note, true); approveRejectCommentsField = (SPFieldMultiLineText)web.Fields.GetFieldByInternalName(comments); approveRejectCommentsField.Group = columnGroupName; approveRejectCommentsField.Update(); } else { approveRejectCommentsField = web.Fields[commentsFieldName] as SPFieldMultiLineText; } // //Ahora creamos el tipo de contenido // //heredamos del WorkflowTask Content Type SPContentType workflowTaskCType = web.AvailableContentTypes[SPBuiltInContentTypeId.WorkflowTask]; SPContentType approvalRejectCType = null; bool isUpdating = false; //validamos si el tipo de contenido ya existe if (web.AvailableContentTypes[contentTypeName] == null) { approvalRejectCType = new SPContentType(workflowTaskCType, web.ContentTypes, contentTypeName); approvalRejectCType = web.ContentTypes.Add(approvalRejectCType); approvalRejectCType.Group = contentTypeGroupName; } else { approvalRejectCType = web.ContentTypes[contentTypeName]; isUpdating = true; } if (!isUpdating) { //agregamo la columna de Seleccion SPFieldLink approveRejectFieldRef = new SPFieldLink(approveRejectChoiceField); approveRejectFieldRef.Required = true; approveRejectFieldRef.DisplayName = "Approve or Reject the task"; approvalRejectCType.FieldLinks.Add(approveRejectFieldRef); //agregamo la columna de Comentarios SPFieldLink commentsFieldRef = new SPFieldLink(approveRejectCommentsField); commentsFieldRef.DisplayName = "Comments"; approvalRejectCType.FieldLinks.Add(commentsFieldRef); } else { //Actualizamos la columna de Seleccion SPFieldLink approveRejectFieldRef = approvalRejectCType.FieldLinks[approveRejectChoiceField.Id]; approveRejectFieldRef.Required = true; approveRejectFieldRef.DisplayName = "Approve or Reject the task"; //Actualizamos la columna de Comentarios SPFieldLink commentsFieldRef = approvalRejectCType.FieldLinks[approveRejectCommentsField.Id]; commentsFieldRef.DisplayName = "Comments"; } approvalRejectCType.EditFormUrl = "_layouts/WF1/CustomApprovalTask.aspx"; //actualizamos el tipo de contenido approvalRejectCType.Update(true); } catch (Exception ex) { //log Exception } } } }
Existen algunas otras propiedades con las cuales podemos indicar que formulario debe mostrarse dependiendo de la vista que sea solicitada, tales como: DisplayFormUrl, MobileDisplayFormUrl, etc, etc
Creación del formulario
El siguiente paso es la creación del formulario que será invocado cuando el usuario haga clic en editar tarea. Para esto agregamos un elemento Application Page y la nombramos como CustomApprovalTask.aspx, el cual usaremos para crear la forma de edición para el tipo de contenido.
A continuación el código del formulario, en realidad es bastante sencillo solo presenta un par de controles, una caja de texto para capturar los comentarios y un dropdownlist para elegir una opción (Aprobar, Rechazar).
using System; using System.Collections; using System.Web; using System.Web.UI.WebControls; using Microsoft.SharePoint; using Microsoft.SharePoint.Utilities; using Microsoft.SharePoint.WebControls; using Microsoft.SharePoint.Workflow; namespace WF1.Layouts { public partial class CustomApprovalTask : LayoutsPageBase { protected SPList targetTasksList; protected SPListItem targetTask; protected SPWorkflow workflowInstance; protected SPWorkflowModification workflowModification; string choiceFieldName = "ChoiceWFResponse"; //nombre de la columna de sitio que permitira al usuario capturar comentarios a cerca de su respuesta string commentsFieldName = "CommentsWFResponse"; protected override void OnLoad(EventArgs e) { base.OnLoad(e); /**/ // Retrieve the current task list and task item targetTasksList = Web.Lists[new Guid(Request.Params["List"])]; targetTask = targetTasksList.GetItemById(int.Parse(Request.Params["ID"])); //Obtiene el autorizador de la tarea string Aprobador = Convert.ToString(targetTask[SPBuiltInFieldId.AssignedTo]); int IdAporbador = Convert.ToInt32(Aprobador.Remove(Aprobador.IndexOf(';'), Convert.ToInt32(Aprobador.Length - Aprobador.IndexOf(";")))); if ((Web.CurrentUser.ID != IdAporbador) ) { ClosePopup(); } /**/ if (!this.Page.IsPostBack) { // Populate fields on the form lblTitle.Text = targetTask["Title"] != null ? targetTask["Title"].ToString() : string.Empty; txtCommnents.Text = targetTask[commentsFieldName] != null ? targetTask[commentsFieldName].ToString() : string.Empty; ListItem selectedItem = ddlResponse.Items.FindByValue(targetTask[choiceFieldName].ToString()); if (selectedItem != null) { this.ddlResponse.ClearSelection(); selectedItem.Selected = true; } } } protected void UpdateTaskFromControls(SPListItem targetTask, System.Collections.Hashtable taskProperties) { // Update task item fields taskProperties[SPBuiltInFieldId.TaskStatus] = SPWorkflowStatus.Completed; taskProperties[commentsFieldName] = txtCommnents.Text; taskProperties[choiceFieldName] = ddlResponse.SelectedValue; } public void btnSaveTask_Click(object sender, EventArgs e) { try { Hashtable taskProperties = new Hashtable(); UpdateTaskFromControls(targetTask, taskProperties); SPWorkflowTask.AlterTask(targetTask, taskProperties, false); } catch (Exception exception) { SPUtility.Redirect("Error.aspx", SPRedirectFlags.RelativeToLayoutsPage, HttpContext.Current, "ErrorText=" + SPHttpUtility.UrlKeyValueEncode(exception.Message)); } ClosePopup(); } public void Cancel_Click(object sender, EventArgs e) { ClosePopup(); } protected void ClosePopup() { this.Response.Clear(); this.Response.Write("Closing ..."); this.Response.Flush(); this.Response.End(); } } }
Pueden descargar el código de aquí
Happy Coding!
0 comentarios:
Publicar un comentario