Creating a new form property in Activiti

Activiti has many ways to integrate with your environment and technologies. Forms is typically an area where there are many different solutions needed. Activiti offers out of the box two options: using a form key (that is how for example Alfresco forms are correlated with process tasks) or using form properties. The details of forms and form properties are described in the userguide, but basically it boils down to

  • Defining form properties in the BPMN 2.0 xml
  • Using the FormService at runtime to fetch and submit forms and form properties

For example, this is how the form properties can be defined on a user task:


<userTask id="provideNewSalesLead" name="Provide new sales lead" activiti:assignee="${initiator}">
  <extensionElements>
    <activiti:formProperty id="customerName" name="Customer name" type="string" required="true"/>
    <activiti:formProperty id="potentialProfit" name="Potential profit" type="long" />
    <activiti:formProperty id="details" name="Details" type="string"/>
  </extensionElements>
</userTask>

To fetch this information at runtime, you have to use something like:

TaskFormData taskFormData = formService.getTaskFormData(myTaskId);
List<FormProperty> formProperties = taskFormData.getFormProperties();

When using Activiti Explorer, all of the supported form properties have specific renderes that know how to render that form property type. Eg. a date form property will be visualized using a date picker, a number property must be checked on validity, etc.

The xml example of above looks as follows in Explorer:

Screen Shot 2013-03-08 at 11.30.40

Custom form properties

A question we often hear on the Activiti Forum is how to add new form property types and render them in Explorer. And actually doing that is very easy.

Let’s start with a simple example: we want to add a form property type that allows the user to select a month. Visually, we want to use a dropdown menu for that on the UI side of things.

Create a new class that subclasses AbstractFormType and implement the two abstract methods:


public class MonthFormType extends AbstractFormType {

  public static final String TYPE_NAME = "month";

  public String getName() {
    return TYPE_NAME;
  }

  public Object convertFormValueToModelValue(String propertyValue) {
    Integer month = Integer.valueOf(propertyValue);
    return month;
  }

  public String convertModelValueToFormValue(Object modelValue) {
    if (modelValue == null) {
      return null;
    }
    return modelValue.toString();
  }

}

To understand this code, you just have to keep in mind that for the FormService, everyhing is a string. This is done so it is very easy to include in your own UI technology. The FormType class itself, like the one above will do the actual conversion to real objects.

The convertFormValueToModelValue method implements the conversion from the form to the internal object. Here it’s very simple, we assume the value stored in the form is an integer which is the number of the month. The other method, convertModelValueToFormValue is the other way around. Since we just use the month index as model value here, that’s also pretty easy.

The form type must now be plugged into the process engine config:


<bean id="processEngineConfiguration" ... >
  ...
  <property name="customFormTypes">
    <list>
      ...
      <bean class="org.activiti.explorer.form.MonthFormType"/>
    </list>
  </property>
</bean>

For the renderer in Activiti Explorer, we need to write the dropdown of months. We also know from the MonthFormType above that we must send an integer (as a string) back and forth indicating the index of the month:

public class MonthFormPropertyRenderer extends AbstractFormPropertyRenderer {

  private static final long serialVersionUID = 1L;

  public MonthFormPropertyRenderer() {
    super(MonthFormType.class);
  }

  public Field getPropertyField(FormProperty formProperty) {
    ComboBox comboBox = new MonthCombobox(getPropertyLabel(formProperty));
    comboBox.setRequired(formProperty.isRequired());
    comboBox.setRequiredError(getMessage(Messages.FORM_FIELD_REQUIRED, getPropertyLabel(formProperty)));
    comboBox.setEnabled(formProperty.isWritable());

    // Fill combobox
    I18nManager i18nManager = ExplorerApp.get().getI18nManager();
    for (int i=0; i<12; i++) {
      comboBox.addItem(i);
      comboBox.setItemCaption(i, i18nManager.getMessage(Messages.MONTH_PREFIX + i));
    }

    return comboBox;
  }

}

In the code it is easy to see how that is done: we simple set the caption to the month name and store the index as value. In our messages.properties file, we do need to have entries like month.0=January’ to make this work.

The renderer must now be plugged into the FormPropertyRenderManager bean. You can find this bean in the activiti-ui-context.xml file:

<bean id="formPropertyRendererManager" class="org.activiti.explorer.ui.form.FormPropertyRendererManager" lazy-init="true">
  ...
  <property name="propertyRenderers">
    <list>
      ...
      <bean class="org.activiti.explorer.ui.form.MonthFormPropertyRenderer" />
    </list>
  </property>
</bean>

The custom form type and renderer are now known to the system. If you use it in your BPMN 2.0 processes like this

<activiti:formProperty id="month" name="Month" type="month" required="true" />

It will be rendered nicely to this:

Screen Shot 2013-03-08 at 11.47.57

And that’s all there is to it!

Another example

The previous example was of course pretty easy. Let me show you how you can create a more complex form type. In the following example, we want the user to select a process definition from a drop-down.

The FormType implementation is a bit more involved now. Again, the value that is being sent back and forth is a string. So for this case, we use the process definition id and convert it to a ProcessDefinition object when needed:

public class ProcessDefinitionFormType extends AbstractFormType {

  public static final String TYPE_NAME = "processDefinition";

  public String getName() {
    return TYPE_NAME;
  }

  public Object convertFormValueToModelValue(String propertyValue) {
    if(propertyValue != null) {
      ProcessDefinition processDefinition = ProcessEngines.getDefaultProcessEngine()
        .getRepositoryService()
        .createProcessDefinitionQuery()
        .processDefinitionId(propertyValue)
        .singleResult();

      if(processDefinition == null) {
        throw new ActivitiObjectNotFoundException("Process definition with id " + propertyValue + " does not exist", ProcessDefinitionEntity.class);
      }

      return processDefinition;
    }

    return null;
  }

  public String convertModelValueToFormValue(Object modelValue) {
    if (modelValue == null) {
      return null;
    }

    if (!(modelValue instanceof ProcessDefinition)) {
      throw new ActivitiIllegalArgumentException("This form type only support process definitions, but is " + modelValue.getClass());
    }
    return ((ProcessDefinition) modelValue).getId();
  }
}

We must now make the user select a process definition from the dropdown, and send the process definition back and forth. Keep in mind that the renderer only keeps the process definition id’s as values, not the process definition objects. The object is only retrieved by the ProcessDefinitionFormType class above when the user actually submits the form.

public class ProcessDefinitionFormPropertyRenderer extends AbstractFormPropertyRenderer {

  private static final long serialVersionUID = 1L;

  public ProcessDefinitionFormPropertyRenderer() {
    super(ProcessDefinitionFormType.class);
  }

  public Field getPropertyField(FormProperty formProperty) {
    ComboBox comboBox = new ComboBox(getPropertyLabel(formProperty));
    comboBox.setRequired(formProperty.isRequired());
    comboBox.setRequiredError(getMessage(Messages.FORM_FIELD_REQUIRED, getPropertyLabel(formProperty)));
    comboBox.setEnabled(formProperty.isWritable());

    List<ProcessDefinition> processDefinitions = ProcessEngines.getDefaultProcessEngine()
      .getRepositoryService()
      .createProcessDefinitionQuery()
      .orderByProcessDefinitionName().asc()
      .orderByProcessDefinitionVersion().asc()
      .list();

    for (ProcessDefinition processDefinition : processDefinitions) {
      comboBox.addItem(processDefinition.getId());
      String name = processDefinition.getName() + " (v" + processDefinition.getVersion() + ")";
      comboBox.setItemCaption(processDefinition.getId(), name);
    }

    return comboBox;
  }
}

And of course both must be configured in the configuration files as described in the previous example. The result looks as follows:

Screen Shot 2013-03-13 at 10.34.10

That’s all there is to it to create custom form properties. Happy Coding!

24 Comments

  1. Rubén April 8, 2013

    Thanks,
    I think I’ll use this feature more than once.

    Regards.

  2. Mathias June 30, 2013

    This works for me. But is there any possibility to use form values (like used for enums) in my form renderer?

    Regards, Mathias

  3. Joram Barrez July 1, 2013

    @Mathias: sure, why not? You can query anything what you want to have it displayed.

  4. Mathias July 1, 2013

    @Joram: I don’t know if I understand you correctly. As far as I can see, queries always refer to instances of process definitions, task definitions or something like that. In my case I want to have a new data type which can be used in potentially every process or task.

    Basically the idea was to implement a new data type “rstring” which should be a string validated against a regex. The regex should be provided with a form property holding the regex.

    My approach was to implement something analogous to EnumFormType but the mechanism how the values map is filled by the BpmnParser/DefaultFormHandler/… is rather complex and deep in the code.

  5. Joram Barrez July 2, 2013

    @Mathias: ok, i sort of understand now.

    The EnumFormType isn’t that hard: extends the AbstractFormType and plug it into your process engine configuration.

    However, the tricky bit will be to have dependencies between the form fields (ie your regex field and the actual field), as I don’t know if the current code is capable of doing something like that.

  6. Sunil August 23, 2013

    Hi Joram,

    Using this we can develop any kind of form element. Can you please share how can we design a radio button. and use them in form.

  7. Tan September 30, 2013

    My Eclipse complains about AbstractFormPropertyRenderer. I then had to google and found the answer that the AbstractFormPropertyRenderer is from activiti-explorer.jar which is not stored in Maven repo. Now I still have the error “Field” (in public Field getProeprtyField) and then ComboBox and MonthCombobox. Theses classes are defined anywhere else? I just try to run the example to see how the whole thing works.

    Thanks,

    Tan

  8. Tan September 30, 2013

    I found it. If you have Maven, this is the jar you will need:

    com.vaadin
    vaadin
    6.8.12

  9. Shiang October 22, 2013

    Thanks for this great tutorial, I’m new to activiti and it is great to understand it
    However, where do I put the code? is it in the bpmn xml file? Or?
    Thanks again

  10. Joram October 22, 2013

    The form property xml goes into the BPMN 2.0 file.

    The java code goes of course as a jar on the classpath or directly in the source of Explorer

  11. Marc December 16, 2013

    Thanks, it’s well explained and worked right away.

    Now I have a suggestion if I may. Both the date and enum properties have extra parameters (datePattern and values). If we could have access to these parameters with custom properties, it would be great !

    (that or some generic parameters)

  12. Joram Barrez December 19, 2013

    @Marc: that is possible

    for example:

    @Override
    public Field getPropertyField(FormProperty formProperty) {
    // Writable string
    PopupDateField dateField = new PopupDateField(getPropertyLabel(formProperty));
    String datePattern = (String) formProperty.getType().getInformation(“datePattern”);
    dateField.setDateFormat(datePattern);

  13. Marc December 20, 2013

    Thanks for the precision Joram.

    But I still have trouble getting the information.

    First of all I should have mentioned that I use the REST API to retrieve the form.

    But even using the normal API, I have a problem. When I import the XML file, datePattern is read by FormPropertyParser.parseChildElement. But when I deploy the file, it looks like the file is parsed again but this time datePattern has disappeared (xtr.getAttributeValue returns null whereas the other attributes are correctly read). So datePattern is null in the BpmnModel.

    I guess during deployment it’s the not the original file that is read but a version that is generated for the purpose?

  14. Joram Barrez January 8, 2014

    Sorry for the delay in responding (christman holidays).

    I checked our test suite, and we do test this:


    which is tested in this way:

    property = startFormData.getFormProperties().get(1);
    assertEquals(“start”, property.getId());
    assertNull(property.getValue());
    assertTrue(property.isReadable());
    assertTrue(property.isWritable());
    assertFalse(property.isRequired());
    assertEquals(“date”, property.getType().getName());
    assertEquals(“dd-MMM-yyyy”, property.getType().getInformation(“datePattern”));

    Do I misunderstand your issue?

  15. TomC January 28, 2014

    Joram, I am having a similar issue in that I am trying to create a radio button formtype(modeled after the EnumFormType). I can create the type no problem.

    public class RadioFormType extends AbstractFormType {
    protected Map values;
    public RadioFormType(Map values) {
    this.values = values;
    }
    public String getName() {
    return “radio”;
    }

    @Override
    public Object getInformation(String key) {
    if (key.equals(“values”)) {
    return values;
    }
    return null;
    }

    @Override
    public Object convertFormValueToModelValue(String propertyValue) {
    validateValue(propertyValue);
    return propertyValue;
    }

    @Override
    public String convertModelValueToFormValue(Object modelValue) {
    if(modelValue != null) {
    if(!(modelValue instanceof String)) {
    throw new ActivitiIllegalArgumentException(“Model value should be a String”);
    }
    validateValue((String) modelValue);
    }
    return (String) modelValue;
    }

    protected void validateValue(String value) {
    if(value != null) {
    if(values != null && !values.containsKey(value)) {
    throw new ActivitiIllegalArgumentException(“Invalid value for radio form property: ” + value);
    }
    }
    }

    }

    I have the new property loading at startup. Also working

    List customFormTypes = new ArrayList();
    customFormTypes.add(new FileFormType());
    customFormTypes.add(new TextAreaFormType());
    customFormTypes.add(new RadioFormType());
    ((ProcessEngineConfigurationImpl) processEngineConfiguration).setCustomFormTypes(customFormTypes);

    I can create my process in the designer, set the type to ‘radio’ then I set the values as I would for an enum type. It saves out fine to the xml.

    We have our own GUI and when I try and get the values

    Map values = (Map) formProperty.getType().getInformation(“values”);

    I get a null pointer error. From what I can see by tracing thru the code only the enumtype has the values loaded. I have other custom types where I get the values dynamically from a database and that works but I really need to access the values stored in the process definition.

    Any thoughts?

    Thanks

  16. Marc March 18, 2014

    @Joram, I realise what I presented was confusing.

    My problem is the following:

    I want to define a new FormType and use it like this in the process XML:

    Then I want to retrieve the floors parameter. I’m open as how to pass it. It could be in a activiti:value tag, in a datePattern parameter or anything else, as long as I can get it.

    So I create a new class :

    public class BuildingType extends AbstractFormType {

    public void setFloors(String floors) {

    }

    It is recognised (I added the entry in the configuration file).

    Now I want to retrieve the floor count when the process arrives to the specific task

    TaskFormData formData = processEngine.getFormService().getTaskFormData(task.getId());
    FormProperty property = formData.getFormProperties().get(0);
    FormType buildingType = property.getType

    Alas I have no information in buildingType.

    Any pointer?

    Thanks,

    Marc

  17. Marc March 18, 2014

    The XML part of my previous post is not displayed.

    Here it is:

    <activiti:formProperty id=”building” name=”Building” type=”buildingType” floors=”10″ required=”true”>

  18. Joram Barrez April 1, 2014

    If you add new element to the schema (which is possible), you will also need to extend the BpmnXmlConverters i’m afraid, otherwise they won’t be picked up.

    Another hacky way could be to misuse the value element eg to store json, that contains both the ‘buildingType’ and the ’10’ value.

  19. […] In particular i want to create a comboBox that fills up with data from a database (PostgreSQL), and i’m following this post: http://www.jorambarrez.be/blog/2013/03/13/creating-a-new-form-property-in-activiti/ […]

  20. Akshay January 29, 2016

    Hey, I followed the same tutorial.
    Instead of using comboBox I rendered Table in my application.
    and given it name as “map” instead of “month” as demonstrated above.
    but while running this it says unknown type ‘map’ map.
    I think i am missing something please can you help??

    thanks,

  21. Joram Barrez February 5, 2016

    @Akshay … have you registered it with the engine? That error seems to point in that direction. or do you mean it’s showing that in the UI?

  22. […] a way to define form’s property renderer and form’s property type ( see this link […]

  23. Santiago May 7, 2017

    I’m facing the unknown type ‘products’ products error in the UI.
    I defined a ProductFormType and the renderer, did every step in this tutorial but still I get that error message in the activiti explorer UI.

  24. nit May 15, 2017

    sir, i have one problem regarding activiti explorer that is how to create a custom form using UI.for example dropdown and other things

Leave a Reply

Your email address will not be published. Required fields are marked *