Adding Dynamic Forms Content to Lists using Workflows

Writing the AddToList Activity

The AddToListActivity will have just one setting to configure: the List to add the content item to.

The following listing provides a complete implementation for an activity that:

  1. Enables the user to configure a list to add content to.
  2. When executed, adds the current content item to the configured list.
using System.Collections.Generic;
using Orchard.ContentManagement;
using Orchard.Core.Containers.Models;
using Orchard.Core.Containers.Services;
using Orchard.Localization;
using Orchard.Workflows.Models;
using Orchard.Workflows.Services;

namespace IDeliverable.Demos.AddToListWorkflow.Activities {
    public class AddToListActivity : Task {
        private readonly IContentManager _contentManager;
        private readonly IContainerService _containerService;

        public AddToListActivity(IContentManager contentManager, IContainerService containerService) {
            _contentManager = contentManager;
            _containerService = containerService;
            T = NullLocalizer.Instance;
        }

        public Localizer T { get; set; }
        public override string Name => "AddToList";
        public override LocalizedString Category => T("Content");
        public override LocalizedString Description => T("Add content items to the specified list.");

        // The Form to display when editing this activity.
        public override string Form => "AddToListForm";

        public override bool CanExecute(WorkflowContext workflowContext, ActivityContext activityContext) {
            // If there is no current content item, there is nothing to add to the list, so exit.
            if(workflowContext.Content == null)
                return false;

            // We can only add the content item if it has the ContainablePart attached.
            if(!workflowContext.Content.Is<ContainablePart>())
                return false;

            // We can only add the current content item to the list if a list was specified.
            return GetTargetList(activityContext) != null;
        }

        public override IEnumerable<LocalizedString> GetPossibleOutcomes(WorkflowContext workflowContext, ActivityContext activityContext) {
            yield return T("Done");
        }

        public override IEnumerable<LocalizedString> Execute(WorkflowContext workflowContext, ActivityContext activityContext) {
            // Load the specified list.
            var targetList = GetTargetList(activityContext);

            // Add the current content item to the list.
            _containerService.MoveItem(workflowContext.Content.As<ContainablePart>(), targetList);

            // All set.
            yield return T("Done");
        }

        /// <summary>
        /// Reads the selected list ID from the activity context.
        /// If an ID was provided, the List is loaded via the content manager and returned.
        /// If no ID was specified, or no List was found by the specified ID, null is returned.
        /// </summary>
        private ContainerPart GetTargetList(ActivityContext activityContext) {
            var targetListId = activityContext.GetState<string>("TargetListId");

            if (targetListId == null)
                return null;

            var targetListIdentity = new ContentIdentity(targetListId);
            var targetList = _contentManager.ResolveIdentity(targetListIdentity);

            return targetList?.As<ContainerPart>();
        }
    }
}

The most interesting part of the activity is the code that acually moves the content item to the selected list.

The following listing shows the complete implementation for the associated form:

using System.Linq;
using System.Web.Mvc;
using Orchard;
using Orchard.ContentManagement;
using Orchard.Core.Containers.Services;
using Orchard.Forms.Services;

namespace IDeliverable.Demos.AddToListWorkflow.Forms {
    public class AddToListForm : Component, IFormProvider {
        private readonly IContainerService _containerService;
        private readonly IContentManager _contentManager;

        public AddToListForm(IContainerService containerService, IContentManager contentManager) {
            _containerService = containerService;
            _contentManager = contentManager;
        }

        public void Describe(DescribeContext context) {
            // Describe the "AddtoListForm".
            context.Form("AddToListForm", factory => {
                var shapeFactory = (dynamic) factory;

                // Create a Form shape.
                var form = shapeFactory.Form(
                    Id: "AddToListForm",

                    // Add a dropdown list named "TargetListId" that will provide the available
                    // List content items to choose from.
                    _TargetListId: shapeFactory.SelectList(
                        Id: "TargetListId",
                        Name: "TargetListId",
                        Title: T("Target List"),
                        Description: T("Select the target list to add content items to.")));

                // Get all available list content items.
                var lists = _containerService.GetContainers(VersionOptions.Latest).ToList();

                // Populate the dropdown list with available list content items.
                foreach (var list in lists) {
                    var listMetadata = _contentManager.GetItemMetadata(list);
                    var listTitle = listMetadata.DisplayText;

                    // Use the content identity instead of primary key value, so that export/import will work for this form.
                    var listIdentity = listMetadata.Identity.ToString();

                    form._TargetListId.Add(new SelectListItem {Text = listTitle, Value = listIdentity});
                }

                return form;
            });
        }
    }
}

Notice the usage of the IContainerService to fetch all list content items.

In order for this code to compile, make sure to add a project reference to both Orchard.Workflows and Orchard.Forms. Also, add these features to the list of dependencies of your module's manifest:

Module.txt

Name: IDeliverable.Demos.AddToListWorkflow
AntiForgery: enabled
Author: The Orchard Team
Website: http://orchardproject.net
Version: 1.0
OrchardVersion: 1.0
Description: Description for the module
Features:
   IDeliverable.Demos.AddToListWorkflow:
      Description: Description for feature IDeliverable.Demos.AddToListWorkflow.
      Dependencies: Orchard.Workflows, Orchard.Forms

Trying it out

With above activity in place, we can now use it to for example assign dynamicaly created content items using Dynamic Forms to a list, without cluttering the content list view. Let's see an example.

  1. Enable the feature we just created (IDeliverable.Demos.AddToListWorkflow) as well as the Dynamic Forms and Lists feature.
  2. Create a new content type called Contact Form Submission with the following parts and fields:
    1. Title Part
    2. Body Part
    3. Identity Part
    4. Containable Part
    5. Email (Text Field)
    6. Name (Text Field)
  3. Uncheck all of the following content type settings: Creatable, Listable, Draftable and Securable. We don't want any of these content items to appear in the content list. Instead, we want to add them to a List content item that we'll create next.
  4. Create a new List content item called Contact Form Submissions. configure it such that it will only contain items of type Contact Form Submission. Also configure it to appear as a menu item on the admin menu. Use "Contact Form" as the menu item name, and position 6 for example.
  5. Create a new Form content item and add fields for Name, Email, Subject and Message. Configure the form to create content items of type Contact Form Submission and configure the field bindings. These items should be saved as a draft in order to prevent them from being publicly accessible.
  6. Create a new Workflow with the following activities:
    1. Content Created -> Configure this to be the start activity and to trigger when items of type Contact Form Submission are created. Remember, the dynamic form is configured to create content items of this type.
    2. Add To List -> Configure this to use the Contact Form Submissions list.

With that in place, you now have a contact form that will generate content items of type Contact Form Submission, which will be added to the Contact Form Submissions list.

The following screenshots demonstrate what I just described in a bit more detail:

Figure 1 - Enable the Dynamic Forms and Lists module, and of course the custom feature we're building.

Figure 2 - Create the Contact Form Submission content type. Items of this type will be created by the contact form.

Figure 3 - Create a new List content item that will contain the dynamicly created Contact Form Submission content items.

Figure 4 - Create a new Form content item with a Form element that contain a bunch of fields to be bound against dynamicaly created Contact Form Submission content items.

Figure 5 - The Form element is configured to create content items of type Contact Form Submission. They are saved as a draft to prevent them from being publicly accessible.

Figure 6 - Each field on the Form is bound to a part or field property of the Contact Form Submission type. The above screenshot shows the binding for the Subject text field, which is bound to the Name field on the content type.

Figure 7 - The workflow handling the Content Created event by adding the created content item to a list.

Figure 8 - Trying out the contact form from the front-end.

 

Figure 9 - After submitting the contact form, a new Contact Form Submission was created and added to the Contact Form Submissions list. 

Conclusion

The combination of Dynamic Forms and Workflows is a powerful one. Although we had to write a custom activity, it is easy to imagine that we can implement pretty advanced scenarios without the need for a single line of code once we have a rich set of workflow activities.

The source code can be downloaded here.

The module contains a Setup recipe that you can execute that will enable all of the features mentioned in this post, as well as the Contact Form Submission type, Contact Form Submissions List, Contact Form item and the workflow shown above.

3 comments

  • ... Posted by Marty Posted 08/06/2016 10:28 PM

    Hi, I am looking for help developing a small internal app, we are 2 people reselling/cycling metal-recently we found that we could not race the materials etc. So, we need a safe way for people to drop off, track and recycle or trace where the stuff went.

    I have a network guy, who referred me to you, he will be the primary POC.

    I would like to know who much would it cost to get working application up.

    thanks Marty

  • ... Posted by Sipke Schoorstra Posted 08/09/2016 02:41 PM (Author)

    Hi Marty,

    Please feel free to email me at sipke@ideliverable.com so we can discuss the details.

    Thanks, Sipke

  • ... Posted by Mandeep Posted 02/06/2017 09:37 PM

    Hello Sir/Madam,

    Do you have documentation of 2014 06 10 Orchard Harvest Conference 3 Orchard Workflows + Jobs API . If you have please email me link .

    Regards, Mandeep

Leave a comment