ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [LWC] 실습 3) lightning-datatable에 lookup field 표시하기 - How to display lookup field in datatable
    LWC 2024. 9. 12. 13:11

     

     

    실습 계획

    이번시간은 실습 3) custom lightning-datatable에 lookup field 표시하기

     

     

    실습 1) lightning-datatable 생성하기

    실습 2) custom lightning-datatable에 picklist field 표시하기

    실습  3) custom lightning-datatable에 lookup field 표시하기

    실습  4) custom datatable에 lookup 필드와 picklist 필드 함께 표시하기

    실습  5) datatable 설정 Tip 및 issue 공유

     

     

     


     

    0. 기본 Setup 및 개념

    • 기회제품 개체에 계정을 조회하는 필드 추가
    • 추가한 조회 필드 조회해 오도록 apex 컨트롤러에서 sql문 수정
    • 조회 필드를 보여주기 위한 lwc 컴포넌트 생성
      1. 기본으로 제공되는 lightning-datatable에서는 조회 필드를 보여줄 수 없어서
        1. 1. datatable을 extends한 lwc 컴포넌트를 생성하고
        2. 2. 해당 컴포넌트(=datatable 컴포넌트)에 custom type 추가하여
        3. 3. 조회 필드 편집화면 처럼 보여주도록 구현

     

     

     

     


    1. 추가한 조회 필드(=고객사) 조회해 오도록 apex 컨트롤러에서 sql문 수정

    • OppItemManagementController.cls
    • oppItems 가져오는 부분에서 Account__c 필드 가져오도록 추가
    public with sharing class OppItemManagementController {
        public OppItemManagementController() {}
       
        //기회 제품 리스트 반환
        @AuraEnabled
        public static List<OpportunityLineItem> getOppItems(String oppId){

            List<OpportunityLineItem> oppItems = new List<OpportunityLineItem>();
           
            try {
                //Account__c 조회 필드 추가 함
                oppItems = [
                    SELECT Id, OpportunityId, Product2Id, Product2.Name, PricebookEntryId, PricebookEntry.Name,  Quantity, UnitPrice, Description, SortOrder, Account__c
                    FROM OpportunityLineItem
                    WHERE OpportunityId = :oppId
                    ORDER BY SortOrder, Id
                ];

                return oppItems;

            } catch (Exception e) {
                throw new AuraHandledException(e.getMessage());
            }
        }
     
    }

     

     

     

    2. 조회 필드를 보여줄 lwc 컴포넌트 - customDatatableTypeLookup.cmp 생성

    • Custom Datatable 컴포넌트이다.

     

     

    2-1 : customDatatableTypeLookup.html

    • 해당 컴포넌트(customDatatableType.cmp)는 extends LightningDatatable한 데이터테이블 컴포넌트이다.  
    • 따라서 html 파일에 코드를 입력하지 않아도 datatable 형식으로 표시된다.
    • 코드를 입력해도 화면에 나타나지 않는다.
    <template>
       
    </template>

     

     

    2-2 : customDatatableTypeLookup.js

    import { LightningElement } from 'lwc';
    import LightningDatatable from 'lightning/datatable';
    import lookupColumn from './lookupColumn.html';

    export default class CustomDatatableTypeLookup extends LightningDatatable {

        static customTypes = {
            lookupColumn: {
                template: lookupColumn,
                standardCellLayout: true,
                typeAttributes: ['value', 'fieldName', 'object', 'context', 'name', 'fields', 'target' ]
            }
        };

    }

     

    2-3 : customDatatableTypeLookup.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
        <apiVersion>61.0</apiVersion>
        <isExposed>false</isExposed>
    </LightningComponentBundle>

     

    2-4 : lookupColumn.html

    • customDatatableTypeLookup.cmp 하위의 lookupColumn.html 파일
    • 내가 생성한 custom type의 필드를 선택할 시 화면에 어떻게 표시될지 구현하는 곳
    • 또 다른 lwc 컴포넌트인 lookupColumn.cmp 를 호출하여 구현하였다.
    <template>
        <c-lookup-column
            name={typeAttributes.name}
            value={typeAttributes.value}
            field-name={typeAttributes.fieldName}
            object={typeAttributes.object}
            context={typeAttributes.context}
            fields={typeAttributes.fields}
            target={typeAttributes.target}
           >
        </c-lookup-column>
    </template>

     

     

     

     

    3. 조회 필드 편집 화면을 보여줄  lwc 컴포넌트 - lookupColumn.cmp 생성

     

    3-1 : lookupColumn.html

    <template>
        <div class="lookupcontainer" id="lookup">
            <div if:true={showLookup} class="lookup-container">
                <div tabindex="0" class="container" style="position:fixed">
                <lightning-record-edit-form object-api-name={object}>
                    <lightning-input-field
                        class="slds-popover slds-popover_edit slds-popover__body"
                        field-name={fieldName}
                        value={value}
                        variant='label-hidden'
                        onchange={handleChange}
                        data-id="input"
                        >
                    </lightning-input-field>
                </lightning-record-edit-form>
     
                </div>
            </div>
     
            <div if:false={showLookup} class="slds-table_edit_container slds-is-relative">
                <span class="slds-grid slds-grid_align-spread slds-cell-edit slds-align_absolute-center">
                <span class="slds-truncate" title={lookupName}>
                    <lightning-formatted-url value={lookupValue} label={lookupName} target={target}>
                    </lightning-formatted-url>
                </span>
                <button data-id={context} class="slds-button slds-button_icon slds-cell-edit__button slds-m-left_x-small" tabindex="-1"
                    title="Edit" onclick={handleClick}>
                    <svg class="slds-button__icon slds-button__icon_hint slds-button__icon_lock slds-button__icon_small slds-button__icon_edit slds-icon slds-icon-text-default slds-icon_xx-small"
                        aria-hidden="true">
                        <use xlink:href="/_slds/icons/utility-sprite/svg/symbols.svg?cache=9.37.1#edit"></use>
                    </svg>
                    <span class="slds-assistive-text">Edit</span>
                </button>
                </span>
            </div>
        </div>
    </template>

     

    3-2 : lookupColumn.js

    • setup > 정적자원 등록 필요 > 정적자원 이름 = LWCDatatableLookup

    LWCDatatableLookup.css
    0.00MB

    import { LightningElement, api, track, wire } from 'lwc';
    import { loadStyle } from 'lightning/platformResourceLoader';
    import LWCDatatableLookup from '@salesforce/resourceUrl/LWCDatatableLookup';
    import { getRecord } from "lightning/uiRecordApi";
     
    export default class LookupColumn extends LightningElement {
        @api value;
        @api fieldName;
        @api object;
        @api context;
        @api name;
        @api fields;
        @api target;
        @track showLookup = false;
     
        @wire(getRecord, { recordId: '$value', fields: '$fields' })
        record;
     
        getFieldName() {
            let fieldName = this.fields[0];
            fieldName = fieldName.substring(fieldName.lastIndexOf('.') + 1, fieldName.length);
            return fieldName;
        }
     
        get lookupName() {
            return (this.value != undefined && this.value != '' && this.record.data != null) ?  this.record.data.fields[this.getFieldName()].value : '';
        }
     
        get lookupValue() {
            return (this.value != undefined && this.value != '' && this.record.data != null && this.record.data.fields[this.getFieldName()].value) ? '/' + this.value : '';
        }
     
        renderedCallback() {
            Promise.all([
                loadStyle(this, LWCDatatableLookup),
            ]).then(() => { });
     
            let container = this.template.querySelector('div.container');
            container?.focus();
     
            window.addEventListener('click', (evt) => {
               if(container == undefined){
                   this.showLookup = false;
               }
            });
        }
     
        handleChange(event) {
            this.value = event.detail.value[0];
            if(this.value == undefined){
                this.record.data = null;
            }

            this.dispatchEvent(new CustomEvent('lookupchanged', {
                composed: true,
                bubbles: true,
                cancelable: true,
                detail: {
                    data: { context: this.context, value: this.value }
                }
            }));    

        }
     
        handleClick(event) {
            setTimeout(() => {
                this.showLookup = true;
            }, 100);
        }
    }

     

    3-3 : lookupColumn.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
        <apiVersion>61.0</apiVersion>
        <isExposed>false</isExposed>
    </LightningComponentBundle>

     

    3-4 : lookupColumn.css

    .lookup-section{
        margin-top: -0.6rem;
        margin-left: -0.5rem;
        position: absolute!important;
        z-index: 9999999999999999999999;
    }
     
    .lookup-section .slds-dropdown{
        position: fixed !important;
        max-height: 120px;
        max-width: fit-content;
        overflow: auto;
    }

     

     

     

    4. custom datatable을 화면에 보여주도록 부모 컴포넌트의 html 파일 수정

    • 부모 컴포넌트(데이터 테이블 및 main화면 보여줄 컴포넌트) : oppItemManagementEdit.cmp
    • 자식 컴포넌트(커스텀 데이터테이블 컴포넌트) : customDatatableTypeLookup
      • 컴포넌트 호출 시에는 c- 로 시작하며 이름에 대문자가 있는 경우 케밥케이스 형식으로 표시
      • customDatatableTypeLookup.cmp 호출 시 <c-custom-datatable-type-lookup>

     

    4-1 : 부모컴포넌트 - oppItemManagementEdit.html

    • <c-custom-datatable-type-lookup> 컴포넌트 호출
    • draft-values={draftValues} 추가 -> 데이터 테이블의 값 변경 시 변경 값 임시 저장 관리
     <template>

        <section role="dialog" tabindex="-1" aria-modal="true" aria-labelledby="modal-heading-01" class="slds-modal slds-fade-in-open slds-modal_large">
            <div class="slds-modal__container">

            <lightning-button-icon icon-name="utility:close" onclick={fnCloseModal} alternative-text="close" variant="bare-inverse" class="slds-modal__close" ></lightning-button-icon>

            <!-- 모달 헤드 -->
            <div class="slds-modal__header">
                <h2 id="modal-heading-01" class="slds-modal__title slds-hyphenate">기회제품 편집</h2>
            </div>

            <div style="background-color: white; ">
                    <div class="datatable-container" onitemregister={handleRegisterItem}>

                        <c-custom-datatable-type-lookup
                            key-field="Id"
                            data={data}
                            columns={columns}
                            draft-values={draftValues}
                        >
                        </c-custom-datatable-type-lookup>
                       
                    </div>
            </div>

             <!-- 모달 푸터 -->
             <div class="slds-modal__footer">
                <lightning-button name="close" label="닫기" class="slds-float_right" onclick={fnCloseModal}></lightning-button>
            </div>


        </div><!--modal container-->
    </section>


    <div class="slds-backdrop slds-backdrop_open" role="presentation"></div>


    </template>

     

     

    5. custom datatable을 호출하도록 부모 컴포넌트의 js 파일 수정

    • 부모 컴포넌트(데이터 테이블 및 main화면 보여줄 컴포넌트) : oppItemManagementEdit.cmp
    • 자식 컴포넌트(커스텀 데이터테이블 컴포넌트) : customDatatableTypeLookup.cmp
      • 자식자식 컴포넌트(커스텀 데이터테이블의 조회 필드 편집화면 구현한 컴포넌트)  : lookupColumn.cmp

     

    5-1 : 조회 필드를 column에 추가

    • label → 필드 레이블
    • fieldName → 필드 api명
    • type → 내가 생성한 커스텀 타입명
    • editable → false로 지정
      • 커스텀 lwc테이블에서 펜 아이콘(edit 버튼)을 만들어서 해당 버튼으로 동작하도록 부여함
      • true 설정 시 펜 아이콘이 2개 보임
    • typeAttributes → 해당 필드 타입의 추가적인 정보
      • object : 조회 필드가 있는 개체
      • fieldname : 조회필드의 api명
      • type : 커스텀 타입명
      • value : 조회필드 value
      • context : 기회제품 id
      • name : 조회 대상 개체 api 명
      • fields : 테이블에 표시될 라벨명

     

    부모컴포넌트 - oppItemManagementEdit.js

    const columns=[
        { label: '제품 Id',          fieldName : 'Product2Id',         type: 'text', hideDefaultActions : "true",  editable: true, },
        { label: '제품명',          fieldName : 'Product2.Name',         type: 'text', hideDefaultActions : "true",  editable: true, },
        { label: '수량',            fieldName : 'Quantity',             type: 'decimal', hideDefaultActions : "true", editable: true, cellAttributes:{ class: { fieldName: 'quantityCellClass' } }},
        { label: '판매가격',        fieldName : 'UnitPrice',            type: 'currency', hideDefaultActions : "true", editable: true, typeAttributes: { currencyCode: 'KRW', style: 'currency', minimumFractionDigits: 0 } },
        { label: '설명',            fieldName : 'Description',         type: 'text', hideDefaultActions : "true",  editable: true, },
        {
            label: '고객사',
            fieldName: 'Account__c',
            type: 'lookupColumn',
            typeAttributes: {
                object: 'OpportunityLineItem',
                fieldName: 'Account__c',
                value: { fieldName: 'Account__c' },
                context: { fieldName: 'Id' },
                name: 'Account',  
                fields: ['Account.Name'],
                target: '_self'
            },
            editable: false
        }
    ];

     

     

    5-2 : 조회 필드 값 변경 시 부모 컴포넌트에 변경사항 반영되도록 JS 파일 수정

    • 자식 컴포넌트(lookupColumn.cmp)에서 조회필드 값이 변경되면
    • 해당 제품의 id와 변경된 계정 id를 전달하는
    • Custom 이벤트인 lookupchanged 생성하고 있다.
    • 부모 컴포넌트에서는 이 이벤트를 받아 변경사항을 임시저장 data list인 draftValue에 반영
    • 취소 또는 저장 시에 조회필드의 변경사항이 적용되도록 관리

     

     

     

    부모컴포넌트 - oppItemManagementEdit.js

    • lookupChanged() 메서드 추가
      • 자식의 custom event 전달받아 처리하는 메서드로 조회 필드의 변경 값을 임시저장 데이터(draftValues)에 반영
    • updateDraftValues() 메서드 추가
      • datatable의 변경되는 임시저장 값 최신화 
        lookupChanged(event) {
            console.log('lookupChanged ---- ' + event.detail.data);
            event.stopPropagation();
            let dataRecieved = event.detail.data;
            let accountIdVal = dataRecieved.value != undefined ? dataRecieved.value : null;
            let updatedItem = { Id: dataRecieved.context, Account__c: accountIdVal  };
            console.log(updatedItem);
            this.updateDraftValues(updatedItem);
        }
       
        //임시저장 값 최신화
        updateDraftValues(updateItem) {
            console.log('draftValue 확인 ===== ' + JSON.stringify(this.draftValues));
            let draftValueChanged = false;
            let copyDraftValues = [...this.draftValues];
            copyDraftValues.forEach(item => {
                if (item.Id === updateItem.Id) {
                    for (let field in updateItem) {
                        item[field] = updateItem[field];
                    }
                    draftValueChanged = true;
                }
            });
     
            if (draftValueChanged) {
                this.draftValues = [...copyDraftValues];
            } else {
                this.draftValues = [...copyDraftValues, updateItem];
            }

        }

     

     

    5-3: 부모 컴포넌트 전체 JS 파일 확인

    부모컴포넌트 - oppItemManagementEdit.js

    import { LightningElement, track, wire, api } from 'lwc';
    import getOppItems from '@salesforce/apex/OppItemManagementController.getOppItems';
    import { CurrentPageReference, NavigationMixin } from 'lightning/navigation';

    //5-1 : columns에 조회 필드 추가
    const columns=[
        { label: '제품 Id',          fieldName : 'Product2Id',         type: 'text', hideDefaultActions : "true",  editable: true, },
        { label: '제품명',          fieldName : 'Product2.Name',         type: 'text', hideDefaultActions : "true",  editable: true, },
        { label: '수량',            fieldName : 'Quantity',             type: 'decimal', hideDefaultActions : "true", editable: true, cellAttributes:{ class: { fieldName: 'quantityCellClass' } }},
        { label: '판매가격',        fieldName : 'UnitPrice',            type: 'currency', hideDefaultActions : "true", editable: true, typeAttributes: { currencyCode: 'KRW', style: 'currency', minimumFractionDigits: 0 } },
        { label: '설명',            fieldName : 'Description',         type: 'text', hideDefaultActions : "true",  editable: true, },
        {
            label: '고객사',
            fieldName: 'Account__c',
            type: 'lookupColumn',
            typeAttributes: {
                object: 'OpportunityLineItem',
                fieldName: 'Account__c',
                value: { fieldName: 'Account__c' },
                context: { fieldName: 'Id' },
                name: 'Account',  
                fields: ['Account.Name'],
                target: '_self'
            },
            editable: false
        }
    ];


     
    export default class OppItemManagementEdit extends NavigationMixin(LightningElement) {
        columns = columns;
       
        @track data = [];
        @track oppItemList;
        @track draftValues = [];
        @api oppId;
       
       

        //lwc를 호출한 url에서 파라미터 값 가져오기
        @wire(CurrentPageReference)
        setCurrentPageReference(currentPageReference) {
            if (currentPageReference) {
                const state = currentPageReference.state;
                const newOppId = state.c__oppId;
                if (newOppId !== this.oppId) {
                    this.oppId = newOppId;
                    this.fetchOppItems();
                }
            }
        }
     
     
        //5-3 : 기회제품 가져오기 및 제품 별 선택목록 option 값 세팅해주기
        fetchOppItems() {
            console.log('save시 fetchOppItems 동작 == ');
           
            if (this.oppId) {
            getOppItems({ oppId: this.oppId })
                .then(result => {
                    this.data = JSON.parse(JSON.stringify(result));
                })
                .catch(error => {
                    console.error(error);
                    this.data = undefined;
                });
            }
        }


        //기본 실행
        connectedCallback() {
     
        }


        fnCloseModal(){
           
            this[NavigationMixin.Navigate]({
                type: 'standard__recordPage',
                attributes: {
                    recordId: this.oppId,
                    objectApiName: 'Opportunity',
                    actionName: 'view'
                }
            });
        }

      //5-2 : lookupChanged 및 updateDraftValues 메서드 추가
        lookupChanged(event) {
            console.log('lookupChanged ---- ' + event.detail.data);
            event.stopPropagation();
            let dataRecieved = event.detail.data;
            let accountIdVal = dataRecieved.value != undefined ? dataRecieved.value : null;
            let updatedItem = { Id: dataRecieved.context, Account__c: accountIdVal  };
            console.log(updatedItem);
            this.updateDraftValues(updatedItem);
        }
       
        //임시저장 값 최신화
        updateDraftValues(updateItem) {
            console.log('draftValue 확인 ===== ' + JSON.stringify(this.draftValues));
            let draftValueChanged = false;
            let copyDraftValues = [...this.draftValues];
            copyDraftValues.forEach(item => {
                if (item.Id === updateItem.Id) {
                    for (let field in updateItem) {
                        item[field] = updateItem[field];
                    }
                    draftValueChanged = true;
                }
            });
     
            if (draftValueChanged) {
                this.draftValues = [...copyDraftValues];
            } else {
                this.draftValues = [...copyDraftValues, updateItem];
            }

        }


    }

     

     

     

    6. 최종 정리 및 화면 확인

    1. 기회제품에 조회 필드 추가
    2. apex controller의 sql문 수정
    3. extends LightningDatatable 컴포넌트 생성 (=CustomDatatableTypeLookup.cmp)
    4. 조회 필드 보여줄 컴포넌트 생성 (=lookupColumn.cmp) 
    5. 조회 필드의 값 변경을 관리할 수 있도록 부모 컴포넌트의 html 및 js 파일 수정 ( = oppItemManagementEdit.cmp )

     


     

    선택한 값으로 제품의 정보가 변경되어 저장되도록하려면

    datatable에 draft-values와 onlookupchanged 속성부여가 필요하다.

    이번 실습에서는 보여주는 것이 목표

    해당 기능은 실습5번에서 포스팅할 예정

    반응형
Designed by Tistory.