Rendre l'attribut de wrapper réactif dans LWC

Aug 19 2020

Je vais élaborer plus que nécessaire sur le problème auquel je suis confronté, de sorte que s'il existe une approche entièrement meilleure pour mettre en œuvre ce que je fais, quelqu'un puisse le suggérer.

Je crée une page de communauté Lightning pour montrer les cas d'un utilisateur. La page est divisée en 3 colonnes. La colonne la plus à gauche montre tous les cas par l'utilisateur (avec le sujet, le statut et le numéro de cas), la colonne du milieu affiche les commentaires sur le cas sélectionné dans la colonne la plus à gauche, la colonne la plus à droite montre un peu plus de détails sur le cas sélectionné dans la colonne la plus à gauche.

Veuillez voir l'image ci-dessous.

Le nœud du problème est que lorsque quelqu'un clique sur le bouton «Fermer le cas» dans la colonne la plus à droite, il appelle le serveur pour changer son statut en «Fermé». Cela fonctionne bien, mais je dois ensuite mettre à jour l'état affiché dans la colonne la plus à gauche, et celui où je rencontre des problèmes.

Ce sont les détails de la mise en œuvre.

Chacune des colonnes est un composant et toutes les 3 sont dans un composant conteneur.

Code du composant conteneur -

HTML du composant conteneur

<template>
   <div>
    <lightning-layout>
            <lightning-layout-item size="4">
                    <c-ma-my-cases-list-view oncaseselect={handlecaseselect} ></c-ma-my-cases-list-view>
            </lightning-layout-item>
            
            <lightning-layout-item size="4">
                    <c-ma-my-cases-comment selectedcaseid={selectedcaseid}></c-ma-my-cases-comment>
            </lightning-layout-item>

            <lightning-layout-item size="4">
                   <c-ma-my-cases-detail selectedcaseid={selectedcaseid}></c-ma-my-cases-detail>                  
            </lightning-layout-item>

    </lightning-layout>
  </div>
</template>

JS du composant conteneur

import { LightningElement, track } from 'lwc';

export default class MaMyCasesContainer extends LightningElement {
    selectedcaseid;

    handlecaseselect(event){
        const caseId = event.detail;
        this.selectedcaseid = caseId;
    }
}

Détails du composant le plus à gauche - Il récupère les cas du serveur dans une structure de données personnalisée, effectue une boucle sur eux et utilise un composant enfant pour afficher des éléments individuels

HTML du composant le plus à gauche maMyCasesListView

<template>
    <div class="container slds-p-around_large slds-scrollable_y slds-theme_shade">
        <div>
            <!-- Server call successful logic-->
            <template if:true={caseswrapper}>
                <lightning-layout class="slds-grid slds-grid_vertical" horizontal-align="left">
                    <template for:each={caseswrapper} for:item="currentcase">

                        <c-ma-case-list-item
                            data-id={currentcase.caseId}
                            key={currentcase.caseId}
                            macasewrapper={currentcase}
                            onselect={handleSelect}
                        ></c-ma-case-list-item>
                        
                    </template>
                </lightning-layout>
            </template>
        </div>      
    </div>
</template>

JS du composant le plus à gauche maMyCasesListView

import { LightningElement, wire, track, api } from 'lwc';
import getCaseList from '@salesforce/apex/MA_MyCasesController.getCaseWrapperList';

export default class MaMyCasesListView extends LightningElement {

    selectedcasewrapper;
    @track caseswrapper;
    @track error;
    @track isLoading;


    constructor(){
        super();
        this.isLoading = true;
        getCaseList()
            .then(result =>{
                this.caseswrapper = result;
                this.error = undefined;
                this.isLoading = false;
            })
            .catch(error => {
                this.error = error;
                this.caseswrapper = undefined
                this.isLoading = false;
                this.showErrorToast();
            })
    }

    handleSelect(event) {
        const caseId = event.detail;
        this.toggleListItems('selected', caseId);
        this.selectedcasewrapper = this.caseswrapper.find(
            (macase) => macase.caseId === caseId
        );
        const selectEvent = new CustomEvent('caseselect', {
            detail: caseId
        });
        // Fire the custom event
        this.dispatchEvent(selectEvent);
    }
}

Comme visible à partir du code HTML du composant ci-dessus, il existe un composant interne qui affiche un élément de liste individuel.

HTML du composant qui affiche l'élément de liste de cas individuel maCaseListItem

<template>
    <div onclick={handleClick}>
        <lightning-layout horizontal-align="left" class="slds-grid slds-grid_vertical slds-text-align--left">
            <lightning-layout-item horizontal-align="left" class="slds-text-align--left">
                <b>{macasewrapper.Subject}</b>
            </lightning-layout-item>
            <lightning-layout-item class="slds-text-align--left slds-m-top_small">
                <lightning-layout>
                    <lightning-layout-item  horizontal-align="left" size="4">{macasewrapper.Status}</lightning-layout-item>
                    <lightning-layout-item horizontal-align="right">{macasewrapper.CaseNumber}</lightning-layout-item>
                </lightning-layout>
            </lightning-layout-item>
        </lightning-layout>
    </div>   
</template>

JS du composant qui affiche l'élément de liste de cas individuel maCaseListItem

import { LightningElement, api } from 'lwc';

export default class MaCaseListItem extends LightningElement {
    @api macasewrapper;
    
    handleClick(event) {

        const selectEvent = new CustomEvent('select', {
            detail: this.macasewrapper.caseId
        });
        // Fire the custom event
        this.dispatchEvent(selectEvent);
    }
}

Le composant le plus à gauche affiche la liste des cas. Lorsqu'un utilisateur clique sur l'un d'entre eux, un événement "caseselect" est déclenché par le composant maCaseListItem avec l'ID de cas comme détail de l'événement. Le composant conteneur l'attrape dans la fonction handlecaseselect et le stocke dans l'attribut selectedcaseid.

Désormais, le composant conteneur transmet cet identifiant aux deuxième et troisième composants, afin qu'ils puissent récupérer plus d'informations du serveur et afficher les données pertinentes. Je vais sauter le deuxième élément, car il montre les commentaires de cas. Passons au troisième composant, qui a le bouton Fermer la casse. Je me concentre uniquement sur le bouton de fermeture du boîtier et je supprime d'autres codes par souci de concision

HTML du composant de troisième colonne maMyCasesDetail

<template>
    <div if:true={localSelectedCaseId} >
    <div class="container slds-p-around_large slds-scrollable_y">
    <lightning-card>
        <!-- code to show status, created date etc -->
    </lightning-card>
    <lightning-card>
        <!-- code about showing attachments -->
    </lightning-card>
    <div class="slds-box slds-align_absolute-center slds-theme_default">
       <lightning-button variant="brand-outline" label="Close Case" title="Close the case" onclick={handleCloseClick}></lightning-button>
    </div>    
    </div> 
</div>
</template>

JS du composant de troisième colonne maMyCasesDetail

import { LightningElement, api, track, wire } from 'lwc';
import { getRecord } from 'lightning/uiRecordApi';
import closeCase from '@salesforce/apex/MA_MyCasesController.closeCase';

export default class MaMyCasesDetail extends LightningElement {


    @track localSelectedCaseId;

    @api
    get selectedcaseid() {
        return this.localSelectedCaseId;
    }
    set selectedcaseid(value) {
        this.localSelectedCaseId = value;
    }


    @wire(getRecord, { recordId: '$localSelectedCaseId', fields: [CASENUMBER_FIELD, CASESUBJECT_FIELD, STATUS_FIELD, DESCRIPTION_FIELD, 'Case.Owner.Name', 'Case.CreatedDate'] })
    wiredRecord({ error, data }) {
        //code that gets case details from server based on localSelectedCaseId
    }


    handleCloseClick(event) {
        if (this.localSelectedCaseId && this.status != this.label.CaseClosedStatus) {
            this.isLoaded = !this.isLoaded;
            closeCase({ caseId: this.localSelectedCaseId })
                .then(result => {
                    this.caseStatus = result.Status;
                    this.handleSuccess();
                    this.status = result.Status;
                    this.sendCloseCaseDetails();
                })
                .catch(error => {
                    var errorMsg = 'Unknown error';
                    if (error.body) {
                        errorMsg = error.body.message;
                        console.log(errorMsg);
                    }
                })
        } 
    }

    sendCloseCaseDetails() {
        var caseClosedDetails = {status:this.status, id:this.localSelectedCaseId};
        const closeEvent = new CustomEvent('close', {
            detail: caseClosedDetails
        });
        this.dispatchEvent(closeEvent);
    }

}

Ainsi, lorsque l'utilisateur clique sur le bouton «Fermer le cas», la méthode côté serveur met à jour le statut sur «Fermé» et renvoie le cas mis à jour. Ce composant déclenche un événement contenant l'id du cas et le statut mis à jour. Le composant parent peut l'attraper, mais après cela, je n'arrive pas à faire fonctionner les choses. Le parent peut les transmettre comme paramètres au composant le plus à gauche.

@AuraEnabled
public static Case closeCase(String caseId) {
    Case c = [Select Id, Status from Case where Id=:caseId];
    c.Status = "Closed";
    update c;
    return c;   
}

le code côté serveur qui obtient la liste initiale des cas (pour le composant le plus à gauche) est -

@AuraEnabled
public static List<CaseCommentWrapper> getCaseCommentWrapperList(String caseId){
    User currentUser= [select Id,timezonesidkey from user where Id =:UserInfo.getUserId()];
    List<CaseComment> allCaseComments = [Select Id, ParentId, IsPublished, CommentBody, CreatedById, CreatedBy.Name, CreatedDate, SystemModstamp, LastModifiedDate, LastModifiedById, IsDeleted From CaseComment where ParentId=:caseId ORDER BY CreatedDate ASC];
    List<CaseCommentWrapper> allCaseCommentWrapper = new List<CaseCommentWrapper>();
    for(CaseComment c : allCaseComments){
        String createdDateStringFormat = getDateInStringFormat(c.CreatedDate,currentUser.timezonesidkey);
        Boolean isCurrentUserAuthor = (currentUser.Id == c.CreatedById)?True:False;
        String caseCommentId = String.ValueOf(c.Id);
        allCaseCommentWrapper.add(new CaseCommentWrapper(caseCommentId,c.CommentBody, c.CreatedBy.Name, createdDateStringFormat,isCurrentUserAuthor));
    }
    return allCaseCommentWrapper;
}

public class CaseWrapper{
    @AuraEnabled
    public Id caseId;
    @AuraEnabled
    public String CaseNumber;
    @AuraEnabled
    public String Status;
    @AuraEnabled
    public String Subject;

    public CaseWrapper(Id caseId,String CaseNumber, String Status, String Subject){
        this.caseId = caseId;
        this.CaseNumber = CaseNumber;
        this.Status = Status;
        this.Subject = Subject;
    }
}

Ainsi, la troisième (colonne la plus à droite) déclenche un événement qui a l'identifiant du cas pour lequel le statut a été mis à jour, et le statut mis à jour en tant que détails de l'événement.

Quel est le bon moyen de mettre à jour l'état de ce cas particulier sur le HTML du composant le plus à gauche? J'ai essayé où le composant parent gère l'événement, définissez 2 attributs sur le composant maMyCasesListView (colonne la plus à gauche) - un attribut qui représente l'ID de cas (localcasestatuschangeid), un autre attribut qui décrit l'état mis à jour (localcasestatuschangeupdatedstatus). J'ai essayé d'itérer chaque élément de la liste dans maMyCasesListView pour voir quel identifiant de cas correspond et mettre à jour le statut. Mais cela n'apparaît pas sur HTML.

this.template.querySelectorAll('c-ma-case-list-item').forEach(item => {
    console.log('iterating through list');
    if (item.macasewrapper.caseId === this.localcasestatuschangeid) {
        console.log('case found');
        item.macasewrapper.Status = this.localcasestatuschangeupdatedstatus;
    }
});

Mais comme je l'ai dit, cela n'apparaît pas sur HTML. Je trouve même l'idée d'avoir 2 attributs - localcasestatuschangeid et localcasestatuschangeupdatedstatus - sur le composant maMyCasesListView pas très lumineux. L'événement géré par le parent est un objet, où event.detail avait un tas de paramètres. Existe-t-il un moyen de simplement passer event.detail du composant de conteneur à maMyCasesListView, puis d'avoir l'ID et l'état d'extrait de maMyCasesListView à partir de là?

Réponses

rahulgawale Aug 20 2020 at 07:33

Il existe deux façons de procéder, mais avant cela, vous devez apporter une modification, vous devez changer la getCaseListméthode en méthode apex câblée au lieu de l'appeler impérativement dans le constructeur.

caseListWired; // define new property

@wire(getCaseList)
getCaseListWired(value) {
    this.caseListWired = value;
    let { error, data } = value;
    if (error) {
        this.error = error;
        this.caseswrapper = undefined
        this.isLoading = false;
        this.showErrorToast();
    } else if (data) {
        this.caseswrapper = result;
        this.error = undefined;
        this.isLoading = false;
    }
}

En outre, vous devez importer refreshApex.

import { refreshApex } from '@salesforce/apex';

Avec la méthode câblée, vous avez deux avantages, l'un est la réduction des appels au serveur et l'autre est que vous pouvez actualiser le cache chaque fois que vous en avez besoin refreshApex.

Approche 1

Étape 1

Écrivez une méthode publique dans le composant le plus à gauche, c'est-à-dire maMyCasesListView.

@api refreshCaseList(){
    refreshApex(this.caseListWired); // this is the same attribute assigned in wired method.
}

Étape 2

Distribuez un événement à partir du composant le plus à droite maMyCasesDetail, lorsque le cas est fermé, ce que vous avez déjà fait dans la handleCloseClickméthode.

const closeEvent = new CustomEvent('close');
this.dispatchEvent(closeEvent);

Notez que, maintenant, vous n'avez plus besoin d'envoyer de données.

Étape 3

Vous devez gérer cet événement dans le composant conteneur et appeler la méthode publique que nous avons définie à l'étape 1.

 <c-ma-my-cases-detail selectedcaseid={selectedcaseid} onclose={handleCloseCase}></c-ma-my-cases-detail>

JS

handleCloseCase(){
    this.template.querySelector('c-ma-my-cases-list-view').refreshCaseList();
}

Approche 2

Vous pouvez utiliser les événements pub-sub, c'est-à-dire Lightning Messaging Service, pour appeler la méthode refreshApex.

Suggestions

Comme vous ne faites que mettre à jour le statut du cas, vous utilisez updateRecordde uiRecordApipour mettre à jour le statut au lieu de la méthode apex personnalisée.

Si vous n'avez aucun besoin spécifique, vous pouvez éliminer le wrapper et renvoyer la liste de cas dans la getCaseListméthode.