0

I am trying to create a nested list of deliverables due to colleges for display via a lightning web component. I am doing some pre-processing in my controller, so the output data is a map with key of College Name, and then a list of a public custom class containing the data I'd like displayed in the detail lines.

Here is the Apex Controller:

public without sharing class CoachPortalHelperClassesEYD {
    @AuraEnabled(cacheable = true)
    public static map<string,list<Deliverable>> getDeliverables(string TimeFrame){
        map<string,list<Deliverable>> Deliverables = new map<string,list<Deliverable>>();
        list<Statement_of_Work__c> SOWS = getRecords(TimeFrame);
        
        String currentFiscalYear = [SELECT FiscalYearSettings.Name FROM Period WHERE Type = 'Year' AND StartDate <= TODAY AND EndDate >= TODAY].FiscalYearSettings.Name;
                
        for(Statement_of_Work__c SOW: SOWS){
            for(sow_line_item__c SOWLI: SOW.sow_line_items__r){
                deliverable d = new deliverable();
                d.name = SOWLI.Title__c != null ? SOWLI.Title__c : SOWLI.Coach_Deliverables__r.Name;
                d.Status = SOWLI.Number_of_Work_Units_Remaining__c == 0 ? 'Fulfilled' : 'Open';
                d.ExpectedDate = SOWLI.Estimated_Completion_Date__c;
                d.SOWLIId = SOWLI.id;
                d.FulfillmentLink = SOWLI.fulfillment_link__c;
                d.Description = SOWLI.description__c; 
                
                string MapKey = SOW.College__c != null ? SOW.College__c : 'Internal';
                if(SOWLI.Year__c != currentFiscalYear){
                    MapKey += ' - ' + SOWLI.Year__c;
                }
                if(deliverables.containskey(MapKey)){
                    deliverables.get(MapKey).add(d);
                } else {
                    list<deliverable> Dlist = new list<deliverable>();
                    Dlist.add(d);
                    deliverables.put(MapKey,Dlist);
                }
                
            }
        }
        return Deliverables;
    }
    public static list<Statement_of_Work__c> getRecords(string TimeFrame){
        String currentFiscalYear = [SELECT FiscalYearSettings.Name FROM Period WHERE Type = 'Year' AND StartDate <= TODAY AND EndDate >= TODAY].FiscalYearSettings.Name;
        String SOWLIConditions = '';
        User u = [SELECT Id, ContactId FROM User WHERE Id =: UserInfo.getUserId()];
        string CoachContactId = '0035500001OE30eAAD'; 
        //string CoachContactId = u.contactId;
        system.debug(CoachContactId);
        if(TimeFrame == 'Current'){
            SOWLIConditions = ' Year__c = \'' + currentFiscalYear + '\' or Number_of_Work_Units_Remaining__c > 0 ';
        } else {
            SOWLIConditions = ' Year__c < \'' + currentFiscalYear + '\'';
        }
        string SOWQuery = 'Select Id, Name, College__c, (Select Id, Name, Type__c, Fulfillment_Link__c, Estimated_Completion_Date__c, description__c, Number_of_Work_Units_Remaining__c, title__c, coach_deliverables__r.name, Year__c From SOW_Line_Items__r where '+ SOWLIConditions + ') from Statement_of_Work__c ';
        SOWQuery += 'where Contractor_Contact__c = \'' + CoachContactId + '\' and ';
        SOWQuery += 'id in (select statement_of_work__c from SOW_Line_Item__c where '+ SOWLIConditions + ')';
        SOWQuery += ' and status__c = \'Accepted\' ';
        SOWQuery +='order by Institution__r.name asc';
        list<Statement_of_Work__c> SOWS =  Database.query(SOWQuery);
        
        system.debug(SOWQuery);
        return SOWS;
    }
    public class Deliverable{
        public string name;
        public string status;
        public date expectedDate;
        public string SOWLIId;
        public string fulfillmentLink;
        public string description; 
    }
}

When I test this with an execute anonymous, it looks as intended:

{Test Coach Organization In Network=(Deliverable:[SOWLIId=a1d55000002TZc5AAG, description=During a Virtual Visit, Consultant will conduct virtual meetings in person for a minimum of 1.0 hours., expectedDate=null, fulfillmentLink=<a href="linkformula" target="_blank">Click to Submit</a>, name=Custom Virtual Visit w/o SVR - 1.0 hours, status=Open], Deliverable:[SOWLIId=a1d55000002VK8QAAW, description=Additional hour/s provided to conduct pre-visit preparationand the c

I have tried all manner of online advice in my lwc js file so it is a hot mess at this point, representing my last failure more than anything else. I'll add the html file below as well, though I think it's fine as far as it goes.

When I console log the stringified json (line 16), it has the correct number of list items , but they all appear to be blank, so I'm not sure the data is even reaching the LWC intact.

{"Test Coach Organization In Network":[{},{},{}]}

The html output has a line for the map key "Test Coach Organization in Network", then three blank lines representing the three sub items. Thanks for any hints you can give me - I've spent a lot of time at this dead end.

.js file

import { LightningElement, api, wire } from 'lwc';
import getRecords from '@salesforce/apex/CoachPortalHelperClassesEYD.getDeliverables';
export default class CPAssignmentList extends LightningElement {
    
    @api TimeFrame;
    displaydata = false;
    deliverables=[];

    @wire(getRecords, {       
        TimeFrame: "$TimeFrame"
    }) 
    getResults(result){
        console.log(result.data);
        if(result.data){
            var conts = result.data;
            var str = JSON.stringify(conts);
            console.log(str);
            for(var key in conts){
                this.subarraylist = [];
                for( var item in conts[key]){
                    this.deliverable = conts[key][item];
                                       
                    this.subarray = [];
                    
                    for(var subkey in this.deliverable){
                        subarray.push({value:deliverable[subkey], key:subkey});
                        console.log(subkey);
                    }
                    this.subarraylist.push(this.subarray);
                }
                
                this.deliverables.push({value:this.subarraylist, key:key}); //Here we are creating the top level array to show on UI.
                console.log(key);
                this.displaydata = true;
            }
        }else{
            console.log('Error');
        }

    }
}            

.html file

<template>
    <template if:false={displaydata}>
        <strong>You have no current assignments to fulfill</strong>
    </template>
    <template if:true={displaydata}>
        <table class="slds-table slds-table_cell-buffer slds-table_bordered">
            <thead>
                <tr class="slds-line-height_reset">
                    <th>Institution</th>
                    <th>Fulfillment</th>
                    <th>Item</th>
                    <th>Expected Completion</th>
                </tr>
            </thead>         
            <tbody>
                <template for:each={deliverables} for:item="sow">
                    <tr class="sowHeaderRow" key ={sow.key}>
                        <td data-label="SOW Name" colspan="4">
                           {sow.key}
                        </td>
                    </tr>
                    <template for:each={sow.value} for:item="sowli"> 
                        <tr class="sowliHeaderRow" key ={sowli.SOWLIId}>   
                            <td class="spacer">
                                &nbsp;
                            </td>
                            <td>
                               {sowli.status}
                            </td>
                            <td>
                                <span title='{sowli.description}'>{sowli.Name}</span> 
                            </td>
                            <td>
                                {sowli.ExpectedDate}
                            </td>
                        </tr>
                    </template>

                </template>
            </tbody>
        </table>
        
    </template>
</template>

1 Answer 1

1

In order to expose a property of a custom Apex class to LWC, it must be annotated with @AuraEnabled, so you have to change your Deliverable class to:

public class Deliverable{
    @AuraEnabled
    public String name;

    @AuraEnabled
    public String status;

    @AuraEnabled
    public Date expectedDate;

    @AuraEnabled
    public String SOWLIId;

    @AuraEnabled
    public String fulfillmentLink;

    @AuraEnabled
    public String description; 
}

In getResults method in JS, you mixed instance and local variable: you defined this.subarray (instance variable) but then you used subarray.push without this, so a local one, which was never defined, thus it throws an exception since you cannot call push on undefined. Moreover, looking at the HTML code, you don't need (you don't use) subarray.
As a thumb rule, use local variable when possible and define an instance one only if strictly needed.
I would also suggest to prefer let or const over var. let and const are block scoped and non-hoisted.

The getResults method should look like:

@wire(getRecords, { TimeFrame: "$TimeFrame" }) 
getResults(result) {
    console.log(result.data);
    if (result.data) {
        const deliverableMap = result.data;
        console.log(JSON.stringify(deliverableMap));
        for (const key in deliverableMap) {
            this.deliverables.push({value: deliverableMap[key], key}); //Here we are creating the top level array to show on UI.
            console.log(key);
        }
        this.displaydata = true;
    } else {
        console.log('Error');
    }
}

Keep in mind that Javascript is case-sensitive, so since in the Deliverable class there is a property name, if in the template you refer to it using Name, it won't work. The same goes for expectedDate vs ExpectedDate. That's why the HTML should be changed to:

<template for:each={sow.value} for:item="sowli"> 
    <tr class="sowliHeaderRow" key ={sowli.SOWLIId}>   
        <td class="spacer">
            &nbsp;
        </td>
        <td>
           {sowli.status}
        </td>
        <td>
            <span title="{sowli.description}">{sowli.name}</span> 
        </td>
        <td>
            {sowli.expectedDate}
        </td>
    </tr>
</template>

Please note that if:true and if:false have been deprecated. You should use lwc:if|elseif|else instead.

<template lwc:if={displaydata}>
<!-- the table -->
</template>
<template lwc:else>
    <strong>You have no current assignments to fulfill</strong>
</template>

Please be consistent with your naming convention. I would suggest following Java one, as stated in Apex Developer Guide, so classes (like String, List, Date, etc...) should start with a capital letter, while methods and variable names should start with a lowercase one.

1
  • Thank you - you got me past this hurdle and the next couple as well. I very much appreciate the assistance. Commented Aug 10, 2023 at 18:25

You must log in to answer this question.

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