LWCでラッパー属性をリアクティブにする

Aug 19 2020

私が直面している問題について必要以上に詳しく説明します。そうすれば、私がしていることを実装するための完全に優れたアプローチがある場合、誰かがそれを提案することができます。

ユーザーのケースを表示するために、Lightningコミュニティページを作成しています。ページは3列に分かれています。左端の列には、ユーザーによるすべてのケースが(件名、ステータス、ケース番号とともに)表示され、中央の列には、左端の列で選択されたケースに関するコメントが表示され、右端の列には、左端の列で選択されたケースの詳細が少し表示されます。

下の画像をご覧ください。

問題の核心は、誰かが右端の列の「ケースを閉じる」ボタンをクリックすると、サーバーを呼び出してステータスを「クローズ」に変更することです。これは正常に機能しますが、左端の列に表示されているステータスと、問題が発生しているステータスを更新する必要があります。

これらは実装の詳細です。

各列はコンポーネントであり、3つすべてがコンテナコンポーネントに含まれています。

コンテナコンポーネントのコード-

コンテナコンポーネントのHTML

<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

import { LightningElement, track } from 'lwc';

export default class MaMyCasesContainer extends LightningElement {
    selectedcaseid;

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

左端のコンポーネントの詳細-カスタムデータ構造でサーバーからケースを取得し、それらをループして、子コンポーネントを使用して個々の要素を表示します

左端のコンポーネントmaMyCasesListViewのHTML

<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>

左端のコンポーネントmaMyCasesListViewのJS

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);
    }
}

上記のコンポーネントのhtmlからわかるように、個々のリストアイテムを表示する内部コンポーネントがあります。

個々のケースリストアイテムを表示するコンポーネントのHTMLmaCaseListItem

<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>

個々のケースリストアイテムを表示するコンポーネントのJSmaCaseListItem

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);
    }
}

したがって、左端のコンポーネントはケースのリストを示しています。ユーザーがそれらの1つをクリックすると、イベント「caseselect」が、イベントの詳細としてケースIDを持つコンポーネントmaCaseListItemによって発生します。コンテナコンポーネントはそれを関数handlecaseselectでキャッチし、属性selectedcaseidに格納します。

これで、コンテナコンポーネントはこのIDを2番目と3番目のコンポーネントに渡すため、サーバーからより多くの情報を取得して、関連するデータを表示できます。ケースコメントが表示されているため、2番目のコンポーネントはスキップします。ケースを閉じるボタンがある3番目のコンポーネントに移動しましょう。簡潔にするために、大文字と小文字を区別するボタンと他のコードの削除のみに焦点を当てています

3列目のコンポーネントmaMyCasesDetailのHTML

<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>

3列目のコンポーネントmaMyCasesDetailのJS

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);
    }

}

そのため、ユーザーが[ケースを閉じる]ボタンをクリックすると、サーバー側のメソッドはステータスを[クローズ]に更新し、更新されたケースを返します。このコンポーネントは、ケースのIDと更新されたステータスを含むイベントを発生させます。親コンポーネントはそれをキャッチできますが、その後は動作させることができないようです。親はそれらをパラメータとして左端のコンポーネントに渡すことができます。

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

(左端のコンポーネントの)ケースの初期リストを取得するサーバー側のコードは-

@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;
    }
}

したがって、3番目(右端の列)は、ステータスが更新されたケースのIDと、更新されたステータスをイベントの詳細として持つイベントを発生させます。

左端のコンポーネントのHTMLでその特定のケースのステータスを更新するための良い方法は何ですか?親コンポーネントがイベントを処理する場所を試し、maMyCasesListViewコンポーネント(左端の列)に2つの属性を設定しました。1つはケースID(localcasestatuschangeid)を表し、もう1つは更新されたステータス(localcasestatuschangeupdatedstatus)を表します。maMyCasesListView内の各リスト項目を繰り返し処理して、どのケースIDが一致するかを確認し、ステータスを更新してみました。ただし、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;
    }
});

しかし、私が言ったように、それはHTMLには表示されません。maMyCasesListViewコンポーネントにlocalcasestatuschangeidとlocalcasestatuschangeupdatedstatusの2つの属性があるという考えさえあまり明るくありません。親によって処理されるイベントはオブジェクトであり、event.detailには多数のパラメーターがあります。event.detailをコンテナコンポーネントからmaMyCasesListViewに渡して、そこからmaMyCasesListViewにIDとステータスを抽出させる方法はありますか?

回答

rahulgawale Aug 20 2020 at 07:33

これを行うには2つの方法がありますが、その前に1つの変更を行う必要がありますgetCaseList。コンストラクターで命令的に呼び出すのではなく、メソッドを有線のapexメソッドに変更する必要があります。

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;
    }
}

また、をインポートする必要がありますrefreshApex

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

有線方式には2つの利点があります。1つはサーバー呼び出しの削減であり、もう1つはを使用して必要なときにいつでもキャッシュを更新できることですrefreshApex

アプローチ1

ステップ1

左端のコンポーネントに1つのパブリックメソッドを記述しますmaMyCasesListView

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

ステップ2

maMyCasesDetailケースが閉じられたときに、handleCloseClickメソッドですでに実行した、右端のコンポーネントからイベントをディスパッチします。

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

これで、データを送信する必要がなくなることに注意してください。

ステップ3

コンテナコンポーネントでそのイベントを処理し、手順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();
}

アプローチ2

pub-subイベント、つまりLightning Messaging Serviceを使用して、refreshApexメソッドを呼び出すことができます。

提案

ケースのステータスを更新するだけなので、カスタムのapexメソッドの代わりにupdateRecordfromuiRecordApiを使用してステータスを更新します。

特に必要がない場合は、ラッパーを削除して、getCaseListメソッドでケースリストを返すことができます。