Use Array Functions for Multi-Select Widgets and Looping

Here’s how to use an array function to create a configuration wizard widget that lets the user select multiple values.

The following code

"Account_Fields": {
        "label": "Pick the fields for your dataset",
        "description": "Multiselect sobjectfield test",
        "defaultValue": [
          {
            "sobjectName": "Account",
            "fieldName": "Name"
          }
        ],
        "required": false,
        "variableType": {
            "type": "ArrayType",
            "itemsType": {
                "type": "SobjectFieldType"
            }
        },
        "excludeSelected": true
    }

Results in the following wizard widget:

This diagram depicts the resulting wizard question.

The ArrayType variable contains an itemsType attribute, which accepts the following:

  • SObjectType
  • SObjectFieldType
  • DatasetType
  • DatasetDateType
  • DatasetDimensionType
  • DatasetMeasureType
  • StringType, or
  • NumberType

The widget places values in an array. The following show how the widget displays values for the user to select:

These two images depict how selections in the widget are placed into an array.
These two images depict how selections in the widget are placed into an array.

Items in the array can be referenced by index. Arrays of StringType and NumberType are easy to work with, but arrays of SObjectType and SObjectFieldType contain multiple attributes for each item in the array:

Array of SobjectFieldType:

  • ${Variables.Account_Fields[0]} returns (sobjectName=Account, fieldName=Name, sobjectLabel=Account, fieldLabel=Account Name)
  • ${Variables.Account_Fields[0].fieldName} returns Name
  • ${Variables.Account_Fields[0].fieldLabel} returns Account Name
  • fieldName is required for dataflow

The selection values from the widget are stored in the variable and there are two ways to access them in the template files (rules, dashboards, workflow):

  • Use array values from SobjectFieldType without looping, accessing by index
    "fields":[
            {"name":"${Variables.Account_Fields[0].fieldName}"},
            {"name":"${Variables.Account_Fields[1].fieldName}"},
            {"name":"${Variables.Account_Fields[2].fieldName}"},
            {"name":"${Variables.Account_Fields[3].fieldName}"},
            {"name":"${Variables.Account_Fields[4].fieldName}"}
        ]

    This example, without looping, would be tricky to use, because there would always need to be at least five items in the array. Any fewer would fail, and anything over five would not be used.

  • Use array values from SobjectFieldType with looping via the array:forEach function. In this example, a “set” action is going to loop through the SObjectFieldTypes in the array and add them to the “fields” attribute in the Extract_Account step of the workflow. Each entry in “fields” will need “name” and “var.fieldName”.
    • rules.json
      "name": "AddAccountFilesToDataflow",
        "appliesTo": [
          {
            "type": "workflow",
            "name": "*"
          }
        ],
        "actions": [
          {
            "action": "set",
            "description": "use selected values for sfdcDigest in dataflow",
            "path": "$.workflowDefinition.Extract_Account.parameters.fields",
            "value": "${array:forEach(Variables.Account_Fields, '{\"name\": \"${var.fieldName}\"}')}"
          }
        ]
    • dataflow.json
      "Extract_Account":{
         "action":"sfdcDigest",
         "parameters":{
             "fields":[],
             "object":"Account"
         }
      }
    • Results after processing if Account Name, Account Source, and Industry are selected
      "Extract_Account":{
          "action":"sfdcDigest",
          "parameters":{
              "fields":[
                  {"name":"Name"},
                  {"name":"AccountSource"},
                  {"name":"Industry"}
              ]
          }
      ]

Here are more ways to work with arrays that extend the power of the array:forEach function:

  • array:union (only uses unique values, so no duplicated values)
    "value": "${array:union(array:forEach(Variables.Account_Fields,
     '{\"name\": \"${var.fieldName}\"}'),array:forEach(Variables.Account_Fields2,
     '{\"name\": \"${var.fieldName}\"}'))}"
  • array:concat (2 new arrays)
    "value": "${array:concat(array:forEach(Variables.Account_Fields, 
    '{\"name\": \"${var.fieldName}\"}'),array:forEach(Variables.Account_Fields2,
     '{\"name\": \"${var.fieldName}\"}'))}"
  • array:concat (1 new array with existing array)
    "value":"${array:concat(Rules.CurrentNode,
     array:forEach(Variables.Account_Fields,
     '{\"name\":\"${var.fieldName}\"}'))}"
  • You can use array functions in the “set” and “put” actions, but not in the “add” or “delete”.
    • In “add”, the value is being added to an existing array, so the result is an array inside an array, which is not well-formed json. To add more array values to an existing array, use “set” with the array:concat function
    • For “delete”, value is not used
  • Use array values from StringType (same for NumberType)
    • variables.json
      "Account_Fields_String": {
              "label": "Pick string fields for your dataset",
              "description": "Multiselect string test",
              "defaultValue": ["Name"],
              "required": true,
              "variableType": {
                  "type": "ArrayType",
                  "itemsType": {
                      "type": "StringType",
                      "enums": ["Name", "AccountSource", "Industry", "Type"]
                  }
              }
           }
    • rules.json
      "value": "${array:forEach(Variables.Account_Fields_String,
       '{\"name\":
              \"${var}\"}')}"
  • You can also use the multiselect widget to replace the current use of multiple StringType widgets with “Yes” and “No” selections. The following is an example using three StringType widgets on one wizard page:This image depicts an example of three stringtypes in one wizard page.

    You can replace this with a single ArrayType widget:

    This image depicts an example of replacing three stringtypes with a single arrayType widget.

    The JSON for this variable is:

    "SelectedFeatures": {
        "label": "Please select the features you would like to enable?",
        "description": "The selected features for this app",
        "defaultValue": [
        	"Apex Execution"
        ],
        "variableType": {
          "type": "ArrayType",
          "itemsType" : {
            "type" : "StringType",
            "enums" : [
            	"Apex Execution",
            	"API",
            	"Content Transfer",
            	"Dashboard",
            	"Login",
            	"Report Export",
            	"Rest API",
            	"Setup Audit Trail",
            	"UI Tracking",
            	"URI",
            	"Visualforce Request",
            	"Wave Change",
            	"Wave Interaction",
            	"Wave Performance"
            ]
      	  }
        },
        "required": true
      }

    You can use the selected values to set constants in rules.json, which you can then reference in conditionals for actions or in template-info.json.

    "constants" : [
    	{"name":"hasApexExecution", "value": "${array:contains(Variables.SelectedFeatures, 'Apex Execution')}"},
    	{"name":"hasAPI", "value": "${array:contains(Variables.SelectedFeatures, 'API')}"},
    	{"name":"hasContentTransfer", "value": "${array:contains(Variables.SelectedFeatures, 'Content Transfer')}"},
    	{"name":"hasDashboard", "value": "${array:contains(Variables.SelectedFeatures, 'Dashboard')}"},
    	{"name":"hasLogin", "value": "${array:contains(Variables.SelectedFeatures, 'Login')}"},
    	{"name":"hasReport", "value": "${array:contains(Variables.SelectedFeatures, 'Report Export')}"},
    	{"name":"hasRestApi", "value": "${array:contains(Variables.SelectedFeatures, 'Rest API')}"}
    ]