Aura:iteration item with wrapper class not being received from controller

lightning-aura-components

I have a highly dynamic aura controller working with some custom wrappers. The business case is we have a custom object that indicates a "case path" and each of those paths can have "required questions" that we push to a form for users to fill out as they escalate a case. These questions can be varying types, text, number, picklist, multi-select, etc. The output of the form is that it will create a record with some of those responses mapped directly to the record, but all responses must be mapped to a separate junction object on that new record and the required question object.

In my controller, there is a wrapper class defined for the required questions that contains list of another wrapper class to store the mutli-select picklist options.

public class RequiredInformation {
    @AuraEnabled public String reqInfoId{get;set;}
    @AuraEnabled public String comboId{get;set;}
    @AuraEnabled public String reqInfoResponse{get;set;}
    @AuraEnabled public List<String> options{get;set;}
    @AuraEnabled public List<MultiOptionsWrapper> multiOptions{get;set;}

    public RequiredInformation() {
        this.options = new List<String>();
        this.multiOptions = new List<MultiOptionsWrapper>();    
    }

    public RequiredInformation(ComboRequired_Junction__c crj) { 
        this.options = new List<String>();
        this.multiOptions = new List<MultiOptionsWrapper>();  
        this.reqInfoResponse = '';

if(crj != null) {
if(String.isNotBlank(crj.Required_Information__r.Picklist_Values__c)) {
                    this.options = crj.Required_Information__r.Picklist_Values__c.split(';');

                for(String picklistVal : this.options)
                    this.multiOptions.add(new MultiOptionsWrapper(picklistVal));
            }     
    }
}

public class MultiOptionsWrapper {
    @AuraEnabled public String value{get;set;}
    @AuraEnabled public String label{get;set;}

    public MultiOptionsWrapper() {
    }

    public MultiOptionsWrapper(String picklistVal) {
        system.debug('multi options wrapper picklist constructor');         
        this.value = picklistVal;
        this.label = picklistVal;
    }
}

The problem I'm running into, is I want to store the responses from an aura:iteration on the form directly onto this list to keep my responses coupled with my already queried information and Ids that I need to create the junction records. I've abbreviated the code for focus:

<aura:iteration items="{!v.requiredInfo}" var="ri" indexVar="i">
            <aura:if isTrue="{!ri.type == 'Multiselect Picklist'}">
                <lightning:dualListbox value="{!ri.reqInfoResponse}" sourceLabel="Available" selectedLabel="Chosen" options="{!ri.multiOptions}" onchange="{!c.handleInputChanges}"/>
            </aura:if>

The onchange event is just spitting out some output for me to view in the log, it's not doing anything to the data captured in this instance.

The problem I'm having is that I can see when I submit the form, the console is reporting a value, but the controller is getting stuck on the multi-select picklist response while loading the parameters.

Here's the action that calls the controller:

    //When the submit button is created, send all of the collected info to the controller and create the help ticket
submitHelpTicket : function(component) {    
    //set the parameters of the CasePathHelpTicketController.saveHelpTicket() function
var action = component.get('c.saveHelpTicket');         
    action.setParam('helpTicket', component.get('v.helpTicket'));
    action.setParam('accounts', component.get('v.accounts'));
    action.setParam('affectedTaxPeriods', component.get('v.affectedTaxPeriods'));
    action.setParam('reqInfos', component.get('v.requiredInfo'));

action.setCallback(this, function(response) { ...stuff here... }        

$A.enqueueAction(action);

},

Here's the controller constructor:

    @AuraEnabled
    public static Response saveHelpTicket(Help_Ticket__c helpTicket, List<AccountWrapper> accounts, List<String> affectedTaxPeriods, List<RequiredInformation> reqInfos) { 
Savepoint sp = Database.setSavepoint();
        Response response = new Response(); //KS this might be it??
        List<CS_Case_Account__c> csCaseAccountsForInsert = new List<CS_Case_Account__c>();
        List<Affected_Tax_Period__c> affectedTaxPeriodsForInsert = new List<Affected_Tax_Period__c>();
        List<Case_Path__c> reqCaseInfoForInsert = new List<Case_Path__c>();

        try { ... inserting records }

Here's what I see in the console before it sends the parameters to the controller. ReqInfo[3] is the multi-select value coming off of {!ri.reqInfoResponse} prior to going to the apex controller.
console values

Here's what I see in the debug log. The execution stops at the multi-select response just after loading the MultiSelectWrapper. There's no error in a full detail log, just an abrupt 'code finished.'
debug log

I'm going to attempt to create another wrapper class to store the response and "copy" the relevant parts of the requiredInfo wrapper to get away from the need for the MultiSelectWrapper, but I feel like that approach is technically heavy and duplicative. Can someone explain why the approach I outlined above isn't working for multi-select picklists and recommend a more elegant way to store and pass the response over to the apex controller?

Here are the two additional methods I mentioned in comments for clarity. They do not perform actions on the dualList box outside of console logging.

handleInputChanges : function(component, event, helper) {
    //fieldName1 represents the scenario where the field type in the required info matched the field that matches on the HT's type
    var fieldName1 = event.getSource().get("v.fieldName");

    //fieldName2 represents the scenario where the field type in the required info differs from the field that matches on the HT's type
    var fieldName2 = event.getSource().get("v.name");       
    var fieldValue = event.getSource().get("v.value");

    if(fieldName1 != null) {
        console.log('*** handleInputChanges *** field updated(fieldName): ' + fieldName1 + ' *** field value: ' + fieldValue);      
    }
    else if(fieldName2 != null) {
        console.log('*** handleInputChanges *** field updated(name): ' + fieldName2 + ' *** field value: ' + fieldValue);       
        component.set("v.helpTicket." + fieldName2, fieldValue);
    }
    else {
        console.log('*** handleInputChanges *** value only updated: ' + fieldValue);
    }
},

//Required Info Section Support
//additional support for multiselect picklists that are mapped to a help ticket field.
handleMultiPicklistChanges : function(component, event, helper) {       
    var fieldName = event.getSource().get("v.name");
    var multiPicklistVal = event.getSource().get("v.value");

    if(multiPicklistVal.includes(',')) {
        var multiResult = multiPicklistVal.replace(",", ";");
        component.set("v.helpTicket." + fieldName, multiResult);
        //console.log('*** handleMultiPicklistChanges *** field updated(multiResult): ' + fieldName2 + ' *** field value: ' + multiResult);
    } 
    else {
        component.set("v.helpTicket." + fieldName, multiPicklistVal);
        //console.log('*** handleMultiPicklistChanges *** field updated(multiPicklistVal): ' + fieldName2 + ' *** field value: ' + multiPicklistVal);
    }
}

Best Answer

Answering my question - it was an issue with the dualListbox storing the response as an array instead of a concatenated string. The server side couldn't interpret it and it didn't throw an error.

I modified my dualListbox definition to use name instead of aura:id, removed my value param, and changed the onchange function. I removed the value param as I will be using the onchange function to "massage" the actual value to the string my wrapper class needs.

<lightning:dualListbox name="{!'noHT_multi' + i}" sourceLabel="Available" selectedLabel="Chosen" options="{!ri.multiOptions}" onchange="{!c.handleMultiPicklistChanges}"/>

Here's the updated onchange function. I had to iterate over the selected values for the multi-select list to build a new string, then fetch the current iteration, update it, and push it back.

handleMultiPicklistChanges : function(component, event, helper) {       
    var currReqInfoName = event.getSource().get("v.name");              
    var multiPicklistVal = event.getSource().get("v.value"); //this is a list because of the multi-select wrapper

    if(currReqInfoName.substr(0,5) == 'noHT_') {
        var index = currReqInfoName.substr(-1);
        var multiString = '';

        for(var i = 0; i < multiPicklistVal.length; i++) {
            if(multiString == '')
                multiString = multiPicklistVal[i];
            else
                multiString = multiString + ';' + multiPicklistVal[i];
        }   

        var ri = component.get("v.requiredInfo")[index];
        ri.reqInfoResponse = multiString;
        component.set("v.requiredInfo["+index+"]", ri);
    }
    //if currReqInfoName is a HT field, add the results to the field
    else {
        if(multiPicklistVal.includes(',')) {
            var multiResult = multiPicklistVal.replace(",", ";");
            component.set("v.helpTicket." + currReqInfoName, multiResult);
        } 
        else {
            component.set("v.helpTicket." + currReqInfoName, multiPicklistVal);             
        }
    }
},

Several helpful sources:

Related Topic