-
[LWC] 실습 3) lightning-datatable에 lookup field 표시하기 - How to display lookup field in datatableLWC 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 컴포넌트 생성
- 기본으로 제공되는 lightning-datatable에서는 조회 필드를 보여줄 수 없어서
- 1. datatable을 extends한 lwc 컴포넌트를 생성하고
- 2. 해당 컴포넌트(=datatable 컴포넌트)에 custom type 추가하여
- 3. 조회 필드 편집화면 처럼 보여주도록 구현
- 기본으로 제공되는 lightning-datatable에서는 조회 필드를 보여줄 수 없어서
1. 추가한 조회 필드(=고객사) 조회해 오도록 apex 컨트롤러에서 sql문 수정- OppItemManagementController.cls
- oppItems 가져오는 부분에서 Account__c 필드 가져오도록 추가
public with sharing class OppItemManagementController {public OppItemManagementController() {}//기회 제품 리스트 반환@AuraEnabledpublic 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__cFROM OpportunityLineItemWHERE OpportunityId = :oppIdORDER 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"?><apiVersion>61.0</apiVersion><isExposed>false</isExposed></LightningComponentBundle>2-4 : lookupColumn.html
- customDatatableTypeLookup.cmp 하위의 lookupColumn.html 파일
- 내가 생성한 custom type의 필드를 선택할 시 화면에 어떻게 표시될지 구현하는 곳
- 또 다른 lwc 컴포넌트인 lookupColumn.cmp 를 호출하여 구현하였다.
<template><c-lookup-columnname={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-fieldclass="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
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"?><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-lookupkey-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. 최종 정리 및 화면 확인
- 기회제품에 조회 필드 추가
- apex controller의 sql문 수정
- extends LightningDatatable 컴포넌트 생성 (=CustomDatatableTypeLookup.cmp)
- 조회 필드 보여줄 컴포넌트 생성 (=lookupColumn.cmp)
- 조회 필드의 값 변경을 관리할 수 있도록 부모 컴포넌트의 html 및 js 파일 수정 ( = oppItemManagementEdit.cmp )
선택한 값으로 제품의 정보가 변경되어 저장되도록하려면
datatable에 draft-values와 onlookupchanged 속성부여가 필요하다.
이번 실습에서는 보여주는 것이 목표
해당 기능은 실습5번에서 포스팅할 예정
반응형