2

I have an Apex Invocable action that takes a list of values in each request. Can I write an LWC Custom Property Editor to configure that list?

Here's what I've tried:

Apex Invocable Action (obviously, it doesn't nothing yet, I'm just scaffolding out the parameters right now):

@InvocableMethod(Label='Map a value' ConfigurationEditor='c-flow-map-editor')
public static List<String> get(List<Request> requests) {
    List<String> results = new List<String>();

    return  results;
}


public class Request {

    @InvocableVariable
    public String thisKey;

    @InvocableVariable
    public List<KeyValue> keyValuePairs;
}

public class KeyValue  {

    @InvocableVariable
    public String key;

    @InvocableVariable
    public String value;
}

Then, in the Javascript of the c-flow-map-editor, I have code based on the example from https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.use_flow_custom_property_editor_action_example :

handleThisKeyChange(event) {
    this.handleChange(event, 'thisKey', 'String');
}

handleKeyValuePairsChange(event) {
    this.handleChange(event, 'keyValuePairs', 'List');
}

handleChange(event, name, newValueDataType) {
    if (event && event.detail) {
        const newValue = event.detail.value;
        const valueChangedEvent = new CustomEvent(
            'configuration_editor_input_value_changed',
            {
                bubbles: true,
                cancelable: false,
                composed: true,
                detail: {
                    name,
                    newValue,
                    newValueDataType
                },
            }
        );
        this.dispatchEvent(valueChangedEvent);
    }
}

This works fine for the thisKey property - it's just a String. But, the keyValuePairs property causes an error at runtime when it's trying to figure out what to do with the type I passed in the configuration_editor_input_value_changed. By stopping it in the browser debugger, I could see the list of types that appear acceptable (as an aside, I couldn't find this list in the documentation):

  • Apex
  • Boolean
  • Date
  • DateTime
  • Number
  • SObject
  • String
  • reference

This makes it look like a list is unsupported unless it can be done via the Apex type somehow.

I could just stuff the list into a Sting, and deserialise it later in Apex. But that's ugly and I'd rather avoid it.

8
  • I assume use of a Map<String, String> also isn't acceptable here? (This would be the obvious way to provide key/value pairs for me.)
    – Phil W
    Commented Dec 9, 2021 at 11:38
  • Good call, but maps are not supported as parameters to Invocable Methods. It's just primitives, SObjects, classes made up of those, or Lists of those. See developer.salesforce.com/docs/atlas.en-us.234.0.apexcode.meta/…
    – Aidan
    Commented Dec 9, 2021 at 12:05
  • I was thinking more within the Apex class, but here you need to fit with the InvocableVariable Considerations (scroll to end). According to that documentation what you've done should work, data type wise. Note, that the datatype can be "A list or a list of lists of primitives, sObjects, objects created from Apex classes, or collections" (emphasis mine) so you should also be able to use a Map since this is a collection.
    – Phil W
    Commented Dec 9, 2021 at 12:21
  • 1
    Loose language in the documentation, I think. I tried changing my Request class to using a Map<String, String> instead of a List<KeyValue> to confirm and the compiler says "InvocableVariable fields do not support type of Map<String,String> (21:36)"
    – Aidan
    Commented Dec 9, 2021 at 12:31
  • One option is to flatten your data so you have a single List<Param> where Param has three invocable variable properties primaryKey, secondaryKey and value. The primaryKey would be repeated versions of your existing thisKey, with the secondaryKey and value being equivalent to your existing key and value sub-properties...
    – Phil W
    Commented Dec 9, 2021 at 12:35

1 Answer 1

1

Well, here's the solution I ended up with:

I changed the list parameter into a String serialised into JSON. Then, I used the getter/setter pattern from the example code to hide that fact from the rest of the LWC, serialising and deserialising in those getters/setters.

From the user's perspective, they set the list of values via a datatable. And from most of the code's point-of-view, it looks the parameter is a list.

So the LWC controller is like this:

get keyValuePairs() {
    const param = this.inputVariables.find(({ name }) => name === 'keyValuePairsString');
    const result = param != null ? JSON.parse(param.value) : [];

    // Provide an id for the datatable
    result.forEach((thisKeyValuePair, index) => thisKeyValuePair.id = index.toString());
    return result;
}

handleKeyValuePairsChange(newKeyValuePairs) {
    this.handleChange({detail : { value: JSON.stringify(newKeyValuePairs)} }, 'keyValuePairsString');
}

Obviously, we need to deserialise in Apex like this:

    List<KeyValue> keyValues = (List<KeyValue>)JSON.deserialize(firstRequest.keyValuePairsString, List<KeyValue>.class);

And the types in Apex are like this:

public class Request {

    @InvocableVariable
    public String thisKey;

    @InvocableVariable
    public String keyValuePairsString; // Serialised List<KeyValue>

    @SuppressWarnings('PMD.EmptyStatementBlock') // Default constructor is required to be used in Flow
    public Request() {
    }

    public Request(String thisKey, String keyValuePairsString) {
        this.thisKey = thisKey;
        this.keyValuePairsString = keyValuePairsString;
    }
}

public class KeyValue {

    @InvocableVariable
    public String key;

    @InvocableVariable
    public String value;

    @SuppressWarnings('PMD.EmptyStatementBlock') // Default constructor is required to be used in Flow
    public KeyValue() {
    }

    public KeyValue(String key, String value) {
        this.key = key;
        this.value = value;
    }
}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .