1

I have a LWC which outputs a list of Line Items. Here is createInvoiceLineItems.html:

<template>
    <lightning-card title="Invoice Line Items">
        <template for:each={lineItems} for:item="item" for:index="index">
            <div key={item.key} class="slds-grid slds-wrap">
                <div class="slds-col">
                    <lightning-input type="number" label="Quantity" value={item.quantity} onchange={handleQuantityChange} data-index={index}></lightning-input>
                </div>
                <div class="slds-col">
                    <lightning-input type="text" label="Description" value={item.description} onchange={handleDescriptionChange} data-index={index}></lightning-input>
                </div>
                <div class="slds-col">
                    <lightning-input type="number" formatter="currency" label="Amount" value={item.amount} onchange={handleAmountChange} data-index={index}></lightning-input>
                </div>
            </div>
        </template>
        <div class="slds-m-top_medium">
            <lightning-button variant="success" label="Add Line Item" title="Add Line Item" onclick={addLineItem}></lightning-button>
            <lightning-button variant="brand" label="Proceed" title="Proceed" onclick={handleProceed}></lightning-button>
        </div>
    </lightning-card>
</template> 

Here is createInvoiceLineItems.js:

import { LightningElement, api, track } from 'lwc';
import { FlowAttributeChangeEvent, FlowNavigationNextEvent } from 'lightning/flowSupport';

export default class CreateInvoiceLineItems extends LightningElement {
    @api availableActions = [];
    @track _lineItems = [];

    @api
    get lineItems() {
        return this._lineItems;
    }

    set lineItems(value) {
        this._lineItems = value;
    }

    addLineItem() {
        const newIndex = this._lineItems.length;
        const newItem = { key: newIndex, quantity: '', description: '', amount: '' };
        this._lineItems = [...this._lineItems, newItem];
    }

    handleQuantityChange(event) {
        const index = event.target.dataset.index;
        this._lineItems[index].quantity = event.target.value;
    }

    handleDescriptionChange(event) {
        const index = event.target.dataset.index;
        this._lineItems[index].description = event.target.value;
    }

    handleAmountChange(event) {
        const index = event.target.dataset.index;
        this._lineItems[index].amount = event.target.value;
    }

    handleProceed() {
        const lineItems = this._lineItems.map((item) => {
            return { key: item.key, quantity: item.quantity, description: item.description, amount: item.amount };
        });
    
        const jsonData = JSON.stringify({ lineItems });
    
        // check if NEXT is allowed on this screen
        if (this.availableActions.find((action) => action === 'NEXT')) {
            // navigate to the next screen
            const attributeChangeEvent = new FlowAttributeChangeEvent('lineItems', jsonData);
            this.dispatchEvent(attributeChangeEvent);
    
            const navigateNextEvent = new FlowNavigationNextEvent();
            this.dispatchEvent(navigateNextEvent);
        }
    }
    
}

Here is createInvoiceLineItems.js-meta.xml:

<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>57.0</apiVersion>
    <isExposed>true</isExposed>
    <masterLabel>Create invoice line items</masterLabel>
    <targets>
        <target>lightning__FlowScreen</target>
    </targets>

    <targetConfigs>
        <targetConfig targets="lightning__FlowScreen">
            <property name="lineItems" type="String[]" label="Line Items"/>
        </targetConfig>
    </targetConfigs>
</LightningComponentBundle>

When using this component in my flow I can see that it outputs the following:

[{"lineItems":[{"key":0,"quantity":"1","description":"desc 1","amount":"1"},{"key":1,"quantity":"2","description":"desc 2","amount":"2"}]}]

I created an Apex class to parse this to Line Items. Here is LineItemJSONParser:

public class LineItemJSONParser {
    @InvocableMethod(label='Parse Line Items' description='Converts JSON object into LineItem objects')
    public static List<LineItem> parseLineItems(List<String> jsonInputList) {
        List<LineItem> lineItems = new List<LineItem>();
        
        for (String jsonInput : jsonInputList) {
            Map<String, Object> jsonMap = (Map<String, Object>) JSON.deserializeUntyped(jsonInput);
            List<Object> items = (List<Object>) jsonMap.get('lineItems');
            
            for (Object item : items) {
                Map<String, Object> itemMap = (Map<String, Object>) item;
                
                LineItem lineItem = new LineItem();
                lineItem.key = (Integer) itemMap.get('key');
                lineItem.quantity = (String) itemMap.get('quantity');
                lineItem.description = (String) itemMap.get('description');
                lineItem.amount = (String) itemMap.get('amount');
                
                lineItems.add(lineItem);
            }
        }
        
        return lineItems;
    }
    
    public class LineItem {
        @InvocableVariable(label='Key' description='Line Item key')
        public Integer key;
        
        @InvocableVariable(label='Quantity' description='Line Item quantity')
        public String quantity;
        
        @InvocableVariable(label='Description' description='Line Item description')
        public String description;
        
        @InvocableVariable(label='Amount' description='Line Item amount')
        public String amount;
    }
}

When using this in my flow, I do not get the option to select the output of createInvoiceLineItems as an input for the parser. createInvoiceLineItems appears to me to output an array of strings so I don't under stand why LineItemJSONParser.cls cannot accept it in the flow.

EDIT: I have updated my parser so that it looks like the below:

public class LineItemJSONParser {
    @InvocableMethod(label='Parse Line Items' description='Converts JSON input into LineItem objects')
    public static List<List<LineItem>> parseLineItems(List<List<String>> jsonInputList) {
        List<List<LineItem>> lineItemsList = new List<List<LineItem>>();

        for (List<String> jsonInput : jsonInputList) {
            List<LineItem> lineItems = new List<LineItem>();

            for (String jsonString : jsonInput) {
                LineItem lineItem = (LineItem) JSON.deserialize(jsonString, LineItem.class);
                lineItems.add(lineItem);
            }

            lineItemsList.add(lineItems);
        }

        return lineItemsList;
    }

    public class LineItem {
        @InvocableVariable(label='Key' description='Line Item key' required=true)
        public Integer key;

        @InvocableVariable(label='Quantity' description='Line Item quantity' required=true)
        public String quantity;

        @InvocableVariable(label='Description' description='Line Item description' required=true)
        public String description;

        @InvocableVariable(label='Amount' description='Line Item amount' required=true)
        public String amount;
    }
}

According to the flow, the output is as below:

[LineItem : { "quantity" : "1", "key" : 0, "description" : "1", "amount" : "1" },LineItem : { "quantity" : "2", "key" : 1, "description" : "2", "amount" : "2" }]

However I am unable to access this output in my flow.

7
  • 1
    I've linked an answer that I think should solve your problem. Basically, flows are bulkified, so if you want to return a list of parsed items, the return type needs to be a multidimensional array ( List<List<LineItem>> ), so you'll need to adjust your code to handle this return type.
    – sfdcfox
    Commented Jun 26, 2023 at 11:20
  • I've adjusted my code and can input fine. However I can't access the output of the apex. According to my flow the output is [LineItem : { "quantity" : "1", "key" : 0, "description" : "1", "amount" : "1" },LineItem : { "quantity" : "2", "key" : 1, "description" : "2", "amount" : "2" }]. Output type of my parser is List<List<LineItem>> @sfdcfox
    – Luke Bray
    Commented Jun 26, 2023 at 12:40
  • 1
    Please edit your question to include the new code, and where you're stuck. The output looks fine to me.
    – sfdcfox
    Commented Jun 26, 2023 at 12:42
  • thanks, have done so
    – Luke Bray
    Commented Jun 26, 2023 at 12:47
  • 1
    Question: Why didn't you just use an Apex-Defined Data Type instead? I'll reopen this so we can have someone answer the question as written, but you really should look into use the mechanism I've linked to.
    – sfdcfox
    Commented Jun 26, 2023 at 13:00

1 Answer 1

0

As per the comments from @sfdcfox, I ended up going with an Apex Defined data type instead.

To do this I created a new apex class, LineItem, using json2apex:

//
// Generated by JSON2Apex http://json2apex.herokuapp.com/
//

public class LineItem {

    public Integer key;
    @AuraEnabled
    public Integer quantity;
    @AuraEnabled
    public String description;
    @AuraEnabled
    public Double amount;
    
    public static List<LineItem> parse(String json) {
        return (List<LineItem>) System.JSON.deserialize(json, List<LineItem>.class);
    }
}

I changed createInvoiceLineItems.js-meta.xml to the below:

<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>57.0</apiVersion>
    <isExposed>true</isExposed>
    <masterLabel>Create invoice line items</masterLabel>
    <targets>
        <target>lightning__FlowScreen</target>
    </targets>

    <targetConfigs>
        <targetConfig targets="lightning__FlowScreen">
            <property name="lineItems" type="apex://LineItem[]" label="Line Items"/>
        </targetConfig>
    </targetConfigs>
</LightningComponentBundle>

And finally in createInvoiceLineItems.js, I changed handleProceed() to be the following:

handleProceed() {
        const lineItemsList = this.lineItems.map((item) => {
            return {
                key: item.key,
                quantity: parseInt(item.quantity),
                description: item.description,
                amount: parseFloat(item.amount)
            };
        });

        // check if NEXT is allowed on this screen
        if (this.availableActions.find((action) => action === 'NEXT')) {
            // navigate to the next screen
            const attributeChangeEvent = new FlowAttributeChangeEvent('lineItems', lineItemsList);
            this.dispatchEvent(attributeChangeEvent);

            const navigateNextEvent = new FlowNavigationNextEvent();
            this.dispatchEvent(navigateNextEvent);
        }
    }

Now my values are available in my flow and a separate parser is not needed.

You must log in to answer this question.

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