ddraper

Aikau 1.0.34 - Form Control Rule Evaluation Updates

Blog Post created by ddraper on Sep 9, 2015

Introduction

In my last blog post I said that I would try and provide more frequent updates on Aikau. Just over a week has passed since then and we've released version 1.0.34 at the end of Sprint 34. We didn't deliver a huge amount as we only had 35% availability due to team members’ vacation but there was one addition that I thought was worth mentioning.

 

 

Form Control Rule Evaluation

We had a requirement from one the feature teams using Aikau to update the form controls to support OR based evaluation of the dynamic visibility, requirement and enablement behaviour. Previously we had only supported AND based evaluation such that all the configured rules needed to be true but the team wanted to be able to make a form field required if either one of two fields was not empty.

 

As an aside there was already a workaround to implement this using the “autoSetConfig” capabilities provided in forms to set the value of a hidden field and then use the value of that hidden field as the evaluated target for the rules. However, the updates we've since made are a much cleaner way of implementing the solution

 

The original dynamic rule capabilities are described in the Aikau tutorial on the GitHub repository so it is worth reviewing that chapter first if you’re not familiar with the concepts before you read on.

 

So for example you have three fields “accidents”, “convictions” and “details” and you want to ensure that if either “accidents” or “convictions” are indicated that the “details” must be provided. You can set up your basic form controls like this:

{
  id: 'ACCIDENTS',
  name: 'alfresco/forms/controls/RadioButtons',
  config: {
    fieldId: 'ACCIDENTS',
    name: 'accidents',
    value: 'NO',
    label: 'Previous accidents',
    description: 'Have you been involved in any previous accidents?',
    optionsConfig: {
      fixed: [
        { label: 'Yes', value: 'YES'},
        { label: 'No', value: 'NO'}
      ]
    }
  }
},
{
  id: 'CONVICTIONS',
  name: 'alfresco/forms/controls/RadioButtons',
  config: {
    fieldId: 'CONVICTIONS',
    name: 'convictions',
    value: 'NO',
    label: 'Previous Convictions',
    description: 'Do you have any previous convictions?',
    optionsConfig: {
      fixed: [
        { label: 'Yes', value: 'YES'},
        { label: 'No', value: 'NO'}
      ]
    }
  }
},
{
  id: 'DETAILS',
  name: 'alfresco/forms/controls/TextArea',
  config: {
    fieldId: 'DETAILS',
    name: 'details',
    label: 'Details',
    description: 'Please provide the details of your previous accidents and/or convictions'
  }
}

 

Previously if you’d configured the “requirementConfig” attribute so that the “details” field became required based on the values of the “accidents” and “convictions” fields you would have created the following:

requirementConfig: {
  initialValue: true,
  rules: [
    {
      targetId: 'ACCIDENTS',
      isNot: ['NO']
    },
    {
      targetId: 'CONVICTIONS',
      isNot: ['NO']
    }
  ]
}

 

However, this would mean that the “details” field would only be required when both the 'accidents' field and the 'convictions' fields were both set to 'YES'. To resolve this you can now update the configuration with a new “rulesMethod” attribute.

requirementConfig: {
  initialValue: true,
  rulesMethod: 'ANY', // NOTE: This the key attribute!
  rules: [
    {
      targetId: 'ACCIDENTS',
      isNot: ['NO']
    },
    {
      targetId: 'CONVICTIONS',
      isNot: ['NO']
    }
  ]
}

 

Summary

This demonstrates a few of the things we like about the Aikau framework. First of all we were able to make this change in a single module (alfresco/forms/controls/utilities/RulesEngineMixin) and this new capability was immediately available for all of the form controls.

 

It is also a completely backwards compatible change. If you didn't specific a “rulesMethod” attribute before then your form models will continue to work exactly as they did before. Our comprehensive unit test suites ensured that we didn't regress any of the existing functionality.

 

Finally it shows that we’re able to respond to requests as they arrive and to continue to iterate on existing code without needing to constantly re-invent the wheel.

 

So a full page example of this would be:

model.jsonModel = {
  services: [],
  widgets: [
    {
      id: 'FORM',
      name: 'alfresco/forms/Form',
      config: {
        okButtonPublishTopic: 'SAVE_FORM',
        okButtonLabel: 'Save',
        widgets: [
          {
            id: 'ACCIDENTS',
            name: 'alfresco/forms/controls/RadioButtons',
            config: {
              fieldId: 'ACCIDENTS',
              name: 'accidents',
              value: 'NO',
              label: 'Previous accidents',
              description: 'Have you been involved in any previous accidents?',
              optionsConfig: {
                fixed: [
                  { label: 'Yes', value: 'YES'},
                  { label: 'No', value: 'NO'}
                ]
              }
            }
          },
          {
            id: 'CONVICTIONS',
            name: 'alfresco/forms/controls/RadioButtons',
            config: {
              fieldId: 'CONVICTIONS',
              name: 'convictions',
              value: 'NO',
              label: 'Previous Convictions',
              description: 'Do you have any previous convictions?',
              optionsConfig: {
                fixed: [
                  { label: 'Yes', value: 'YES'},
                  { label: 'No', value: 'NO'}
                ]
              }
            }
          },
          {
            id: 'DETAILS',
            name: 'alfresco/forms/controls/TextArea',
            config: {
              fieldId: 'DETAILS',
              name: 'details',
              label: 'Details',
              description: 'Please provide the details of your previous accidents and/or convictions',
              requirementConfig: {
                initialValue: true,
                rulesMethod: 'ANY',
                rules: [
                  {
                    targetId: 'ACCIDENTS',
                    isNot: ['NO']
                  },
                  {
                    targetId: 'CONVICTIONS',
                    isNot: ['NO']
                  }
                ]
              }
            }
          }
        ]
      }
    }
  ]
};

Outcomes