1

My nested string-list template table in LWC is working well, but as a js/LWC noob, I'm struggling with making some enhancements, particularly adding new data points to .

I need to add some information to the top level of data - I currently have a row displaying the map key, but I need to add a couple of links to that row. These links are contained in every row of the list in the map, but I can't figure out how to get that data outside of the iteration template for that map.

Here is the js, with my most recent failure commented out

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

   @wire(getRecords, { TimeFrame: "$TimeFrame" }) 
    getResults(result) {
        
        if(this.TimeFrame == 'Open'){
            this.isreview = true;
        }
        
        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.
                //this.deliverables.acceptLink = deliverableMap[key][0].acceptLink;
                //this.deliverables.declineLink = deliverableMap[key][0].declineLink;
                
            }
            this.displaydata = true;
        } else {
            console.log('Error');
        }
    }
    
}

Here is the relevant subsection of the HTML - I'm not sure you need the rest of it since it all displays fine - I just can't figure out how to get the accept and decline links to appear.

<template for:each={deliverables} for:item="sow">
    <tr class="sowHeaderRow" key ={sow.key}>
        <template lwc:if={isreview}>
            <td data-label="SOW Name" colspan="2">
                <strong>{sow.key}</strong>
            </td>
        </template>  
        <template lwc:else>
            <td data-label="SOW Name" colspan="4">
                <strong>{sow.key}</strong>
            </td>
        </template> 
        
        <td>
            <template lwc:if={isreview}>
                <a href='{sow.acceptLink}' >Accept</a>&nbsp;&nbsp;&nbsp;
                <a href='{sow.declineLink}' >Decline</a>
            </template>
        </td>
    </tr>
    <template for:each={sow.value} for:item="sowli">

Here is the Apex Controller:

global without sharing class CoachPortalHelperClassesEYD {
    @AuraEnabled(cacheable = true)
    global 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);
        
        //Get SOW fulfillments and Engagement Reports to display status
        set<string> SOWLI_Ids = new set<string>();
        for(Statement_of_Work__c SOW: SOWs){
            for(sow_line_item__c SOWLI: SOW.sow_line_items__r){
                SOWLI_Ids.add(SOWLI.Id);
            }
        }
        Map<ID, sow_line_item__c> sowli_fulfillment_map = new Map<ID, sow_line_item__c>([SELECT Id, 
                                                                                         (select createddate from sow_line_item_fulfillments__r order by createddate desc limit 1),
                                                                                         (select lastmodifieddate from Coach_Engagement_Reports__r order by lastmodifieddate desc limit 1)
                                                                                         from sow_line_item__c
                                                                                         where id in :SOWLI_Ids]);
        
        
        String currentFiscalYear = [SELECT FiscalYearSettings.Name FROM Period WHERE Type = 'Year' AND StartDate <= TODAY AND EndDate >= TODAY].FiscalYearSettings.Name;

        List<String> args = new String[]{'0','number','###,###,##0'};
        
        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;
                sow_line_item__c related = sowli_fulfillment_map.get(sowli.id);
                if(SOWLI.Number_of_Work_Units_Remaining__c == 0){
                    d.Status =  'Fulfilled';
                    if(related.sow_line_item_fulfillments__r.size()>0){
                        d.Status += ' '+ related.sow_line_item_fulfillments__r[0].createddate.Format('MM/dd/yyyy');
                    }
                } else if (related.coach_engagement_reports__r.size()>0){
                    d.Status = 'Resume report saved on '+ related.coach_engagement_reports__r[0].createddate.Format('MM/dd/yyyy');
                } else {
                    d.Status = 'Click to fulfill';}
                d.isOpen = SOWLI.Number_of_Work_Units_Remaining__c == 0 ? False : True;
                d.ExpectedDate = SOWLI.Estimated_Completion_Date__c;
                d.noDate = d.expectedDate == null ? true : false;
                d.SOWLIId = SOWLI.id;
                d.FulfillmentLink = 'coach-fulfillment?SOWLI='+SOWLI.id;
                d.changeDateLink = 'update-estimated-completion-date?SOWLI='+SOWLI.id;
                d.Description = SOWLI.description__c; 
                d.showECDate = SOWLI.Fulfillment_Type__c == 'Engagement Report' && d.isOpen ? True: False;
                d.payRate = '$'+ String.format(SOWLI.Pay_Rate_Unit__c.format(),args);
                string MapKey = SOW.College__c != null ? SOW.College__c : 'Training, Institutes & Retreats';
                if(TimeFrame == 'Open'){
                    MapKey = SOW.Name + ' ' + MapKey;
                    d.acceptLink = 'review-assignment?SOW='+SOW.id+'&accept=Accepted';
                    d.declineLink = 'review-assignment?SOW='+SOW.id+'&accept=Rejected';
                    d.isReview = true;
                } else {
                    MapKey += ' - ' + SOWLI.Year__c;
                    d.isReview = false;
                }
                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;
    }
    global 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 = '0035500001P8zBGAAZ'; 
        string CoachContactId = u.contactId;
        system.debug(CoachContactId);
        if(TimeFrame == 'Current'){
            SOWLIConditions = ' Year__c = \'' + currentFiscalYear + '\' or Number_of_Work_Units_Remaining__c > 0 ';
        } else if (TimeFrame == 'Open'){
            SOWLIConditions = ' statement_of_work__r.status__c = \'Pending Execution\' '; 
        } else {
            SOWLIConditions = ' Year__c < \'' + currentFiscalYear + '\'';
        }
        string SOWQuery = 'Select Id, Name, College__c, (Select Id, Name, Type__c, Estimated_Completion_Date__c, description__c, Number_of_Work_Units_Remaining__c, Pay_Rate_Unit__c, title__c, coach_deliverables__r.name, Year__c, Fulfillment_Type__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 display_on_portal__c = TRUE and status__c = ';
        if(TimeFrame != 'Open'){
            SOWQuery += '\'Accepted\' ';
        } else {
            SOWQuery += '\'Pending Execution\' ';
        }
        SOWQuery +='order by Institution__r.name asc';
        list<Statement_of_Work__c> SOWS =  Database.query(SOWQuery);
        
        system.debug(SOWQuery);
        system.debug(SOWs);
        return SOWS;
    }
    global 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 changeDateLink;
        @AuraEnabled
        public string description; 
        @AuraEnabled
        public boolean isOpen;
        @AuraEnabled
        public boolean noDate; 
        @AuraEnabled
        public string payRate; 
        @AuraEnabled
        public string acceptLink; 
        @AuraEnabled
        public string declineLink; 
        @AuraEnabled
        public boolean isReview;
        @AuraEnabled
        public boolean showECDate;
    }
}
2
  • Could you please edit the question adding the Apex controller? Did you update both Deliverable class and getDeliverables method to include those link?
    – RubenDG
    Commented Aug 15, 2023 at 21:50
  • I did. I will add the controller. Commented Aug 15, 2023 at 22:48

1 Answer 1

1

In the template sow is the object with just two properties: key (a string) and value (a list of Deliverable). So the items in sow.value are the ones with acceptLink and declineLink properties. Therefore the links should be shown in the for:each on sow.value:

<template for:each={deliverables} for:item="sow">
    <tr class="sowHeaderRow" key ={sow.key}>
        <template lwc:if={isreview}>
            <td data-label="SOW Name" colspan="2">
                <strong>{sow.key}</strong>
            </td>
        </template>  
        <template lwc:else>
            <td data-label="SOW Name" colspan="4">
                <strong>{sow.key}</strong>
            </td>
        </template> 
        
        <td lwc:if={isreview}>
            Links <!-- this is just an header -->
        </td>
    </tr>
    <template for:each={sow.value} for:item="sowli">
        <tr class="sowliHeaderRow" key={sowli.SOWLIId}>   
            <!-- current columns: status, description, etc... -->
            <template lwc:if={isreview}>
                <a href={sowli.acceptLink}>Accept</a>&nbsp;&nbsp;&nbsp;
                <a href={sowli.declineLink}>Decline</a>
            </template> 
        </tr>
    </template>
</template>

Moreover as sfdcfox said, merge fields on templates must not be quoted.

Please take a look at these examples of Relative URLs, if review-assignment is an app page you may want to change those URLs to:

d.acceptLink = '/lightning/n/review-assignment?SOW='+SOW.id+'&accept=Accepted';
d.declineLink = '/lightning/n/review-assignment?SOW='+SOW.id+'&accept=Rejected';

Edit: if you want the link at top level, just add two properties to the main object in JS, the one with key and value:

this.isreview = this.TimeFrame === 'Open'; // An if block is not needed here

if (result.data) {
    const deliverableMap = result.data;
    console.log(JSON.stringify(deliverableMap));
    const deliverables = [];
    for (const key in deliverableMap) {
        const sowLineItem = deliverableMap[key][0];
        //Here we are creating the top level array to show on UI.
        deliverables.push({
            value: deliverableMap[key],
            key,
            acceptLink: sowLineItem.acceptLink,
            declineLink: sowLineItem.declineLink;
        });
    }
    this.deliverables = deliverables; // this way the rerender is triggered only once
    this.displaydata = true;
} else if (result.error) {
    console.log('Error', result.error);
}

Then in the HTML you could have:

<template for:each={deliverables} for:item="sow">
    <tr class="sowHeaderRow" key={sow.key}>
        <template lwc:if={isreview}>
            <td data-label="SOW Name" colspan="2">
                <strong>{sow.key}</strong>
            </td>
        </template>  
        <template lwc:else>
            <td data-label="SOW Name" colspan="4">
                <strong>{sow.key}</strong>
            </td>
        </template> 
        
        <td lwc:if={isreview}>
            <a href={sow.acceptLink}>Accept</a>&nbsp;&nbsp;&nbsp;
            <a href={sow.declineLink}>Decline</a>
        </td>
    </tr>
    <template for:each={sow.value} for:item="sowli">
        <tr class="sowliHeaderRow" key={sowli.SOWLIId}>   
            <!-- current columns: status, description, etc... -->
        </tr>
    </template>
</template>

Please note that you never need an if-else or a ternary operator to set a Boolean, you could just use the expression you're evaluating.
That's why you can change

d.isOpen = SOWLI.Number_of_Work_Units_Remaining__c == 0 ? False : True;
d.noDate = d.expectedDate == null ? true : false;

To:

d.isOpen = SOWLI.Number_of_Work_Units_Remaining__c != 0;
d.noDate = d.expectedDate == null;

Moreover there is no need to evaluate TimeFrame == 'Open' in each iteration since it doesn't depend on the current iteration item. You could evaluate it just once before the outer for, i.e. defining Boolean isReview = TimeFrame == 'Open';, then use that value as condition for the if block and set d.isReview = isReview; outside the if-else block.

4
  • but I don't want the links at the detail level. I want them at the top level. Surely there's a way to do this. Commented Aug 16, 2023 at 0:25
  • @ScottWilliams I edited the answer to show the link at the top level.
    – RubenDG
    Commented Aug 16, 2023 at 6:35
  • 1
    Thanks, this is just what I was looking for. I had figured out last night that I could have added a flag to the first sowli in any given deliverable and another iteration template of the sowlis in the <td> where the accept/decline links are so it would just display the result of one sub-record, but this is much cleaner. I appreciate you knowledge and time. Commented Aug 16, 2023 at 12:16
  • @ScottWilliams You're welcome, glad I could help.
    – RubenDG
    Commented Aug 16, 2023 at 12:21

You must log in to answer this question.

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