Salesforce LWC學習(八) Look Up組件實現

  • 2019 年 12 月 25 日
  • 筆記

本篇參考https://www.salesforcelwc.in/2019/10/lookup-in-lwc.html,感謝前人種樹。

我們做lightning的時候經常會遇到Look up 或者MD欄位在頁面搜索展示的需求,在標準的頁面很常見,而且很好看。但是很遺憾的是在自定義組件中還沒有現成的標準組件去搞定。下面介紹兩種方式去實現展示lookup / MD欄位的組件樣式。

一. record-edit-form搭配 lightning-input-field 曲線救國

標準功能也不是100%的不近人情,還是給出一個workaround的方案去實現,實現的方式為在後台聲明一個Lookup / MD的組件,然後使用lightning-input-field去實現。此組件會根據欄位的類型去自動轉換成其相應的樣式進行展示,效果很像classic中的apex:inputField或者lightning aura中的lightning:inputField。使用lightning-record-edit-form來指定某個表的LDS,使用lightning-input-field進行效果展示,然後提交的操作時阻止默認的submit操作並且在event detail中獲取到我們選擇的Look up/MD對應的ID即可。demo中在Account上新建一個欄位Test_User__c,類型為Look up (User).

testLookUpForLwc.html:使用LDS設置object api name為Account,這樣下面就可以通過lightning-input-field針對Account的欄位的類型動態展示相關的樣式

<template>      <lightning-record-edit-form          object-api-name='Account'          onsubmit={handleSubmit}      >          <lightning-input-field field-name="Test_User__c"></lightning-input-field>          <lightning-button type="submit" label="get test user id" variant="brand">          </lightning-button>      </lightning-record-edit-form>  </template>

testLookUpForLwc.js:針對submit事件首先組織提交免得生成不需要的記錄,然後通過event.detail.fields.Test_User__c便可以獲取到所選擇的Test_User__c的ID。

import { LightningElement,track } from 'lwc';    export default class TestLookUpForLwc extends LightningElement {      handleSubmit(event) {          event.preventDefault();          console.log(JSON.stringify(event.detail.fields.Test_User__c));      }  }

結果展示:選擇一個用戶以後,點擊get test user id便可以獲取到當前選擇的user的id。

二.自定義組件實現

上面的方式好是好,但是此種寫法沒法更改相關的label資訊,中國項目可能新創建個欄位進行translation也可以實現,後台進行匹配也可以,但是對日項目可能管理嚴格,所以需要考慮自定義組件實現。自定義組件的實現的原理相對簡單,難得是UI的構建,好在前輩有畫好的功能直接使用,對上面的鏈接中的程式碼進行簡單的修改即可使用。

customLookUpForLwc.html:展示UI,上面是一個lightning-pill / lightning-input,通過isValue來判斷當前是輸入框還是展示pill,下面是列表。當列表選擇以後觸發事件父進行處理。

<template>      <div>          <div class="slds-form-element">              <div class="slds-form-element__control">                  <div class="slds-combobox_container">                      <div id="box" class={boxClass} aria-expanded="true" aria-haspopup="listbox" role="combobox">                          {searchLabel}                          <div class="slds-combobox__form-element slds-input-has-icon slds-input-has-icon_right" role="none">                              <template if:true={isValue}>                                  <div id="lookup-pill" class="slds-pill-container">                                      <lightning-pill class="pillSize" label={valueObj} name={valueObj} onremove={handleRemovePill}>                                          <lightning-icon icon-name={iconName} alternative-text="acc" ></lightning-icon>                                      </lightning-pill>                                  </div>                              </template>                              <template if:false={isValue}>                                  <div class="slds-p-top_none">                                      <lightning-input class={inputClass} type="search" id="input" value={searchTerm}                                          onclick={handleClick}  onchange={onChange}                                          variant="label-hidden" autocomplete="off" placeholder="Search..." label='account search'>                                      </lightning-input>                                  </div>                              </template>                          </div>                          <div id="listbox-id-1" class="slds-dropdown slds-dropdown_length-with-icon-7 slds-dropdown_fluid" role="listbox">                              <ul class="slds-listbox slds-listbox_vertical" role="presentation">                                  <template for:each={options} for:item="item">                                      <li key={item.Id} onclick={onSelect} data-id={item.Id} role="presentation">                                          <span class="slds-lookup__item-action slds-lookup__item-action--label" role="option">                                              <lightning-icon class="slds-icon slds-icon--small slds-icon-text-default" icon-name={iconName} alternative-text={objName} size="small"></lightning-icon>                                              <span class="slds-truncate">{item.Name}</span>                                          </span>                                      </li>                                  </template>                              </ul>                          </div>                      </div>                  </div>              </div>          </div>      </div>    </template>

customLookUpForLwc.js

/* eslint-disable no-console */  /* eslint-disable @lwc/lwc/no-async-operation */    import lookUp from '@salesforce/apex/CustomLookUpForLwcController.lookUp';  import { getObjectInfo } from 'lightning/uiObjectInfoApi';  import { getRecord } from 'lightning/uiRecordApi';  import { api, LightningElement, track, wire } from 'lwc';    export default class CustomLookUpForLwc extends LightningElement {      //store object record id      @api valueId;      //record API name      @api objName;      //record icon name,see Lightning Design System to choose      @api iconName;        @api filter = '';      //unique key used to mark the unique component. several component use this component need to mapping      @api uniqueKey;      //used to set the field to fetch.eg: ['Account.Name'] means we need to search account name field as filter      @api fields;        //search label show in lookup component      @api searchLabel;        @track searchTerm;      //record name value      @track valueObj;      //record href      @track href;      //fetch result      @track options;      //is available value to show in lightning-pill      @track isValue = false;        @track blurTimeout;        //css      @track boxClass = 'slds-combobox slds-dropdown-trigger slds-dropdown-trigger_click slds-has-focus';      @track inputClass = '';        @wire(lookUp, {searchTerm : '$searchTerm', myObject : '$objName', filter : '$filter'})      wiredRecords({ error, data }) {          if (data) {              this.record = data;              this.error = undefined;              this.options = this.record;              console.log("common this.options", JSON.stringify(this.options));          } else if (error) {              this.error = error;              this.record = undefined;              console.log("wire.error",this.error);          }      }        //To get preselected or selected record      @wire(getRecord, { recordId: '$valueId', fields: '$fields' })      wiredOptions({ error, data }) {          if (data) {              console.log('execute1');              this.record = data;              this.error = undefined;              this.valueObj = this.record.fields.Name.value;              this.href = '/'+this.record.id;              this.isValue = true;              console.log("this.href", this.href);              console.log("this.record", JSON.stringify(this.record));          } else if (error) {              console.log('execute2');              this.error = error;              this.record = undefined;              console.log("this.error", this.error);          }      }        handleClick() {          console.log("In handleClick");            this.searchTerm = '';          this.inputClass = 'slds-has-focus';          this.boxClass = 'slds-combobox slds-dropdown-trigger slds-dropdown-trigger_click slds-has-focus slds-is-open';      }        onSelect(event) {          console.log("In onSelect");          let ele = event.currentTarget;          let selectedId = ele.dataset.id;          console.log("selectedId", selectedId);          //As a best practise sending selected value to parent and inreturn parent sends the value to @api valueId          let key = this.uniqueKey;          const valueSelectedEvent = new CustomEvent('valueselect', {              detail: { selectedId, key },          });          this.dispatchEvent(valueSelectedEvent);            // if(this.blurTimeout) {          //     clearTimeout(this.blurTimeout);          // }          console.log(this.isValue);          this.boxClass = 'slds-combobox slds-dropdown-trigger slds-dropdown-trigger_click slds-has-focus';      }        onChange(event) {          console.log("In onChange");          this.searchTerm = event.target.value;          console.log("searchTerm",this.searchTerm);      }        handleRemovePill() {          console.log("In handleRemovePill");          this.isValue = false;          let selectedId = '';          let key = this.uniqueKey;          const valueSelectedEvent = new CustomEvent('valueselect', {              detail: { selectedId, key },          });          this.dispatchEvent(valueSelectedEvent);      }    }

testLookUpForLwc.html:引入組件,設置幾個必填的參數,其中icon-name賦值可以選擇鏈接中的以下內容進行查找https://lightningdesignsystem.com/icons/。使用accountId用來獲取前台組件傳遞過來的ID。

<template>      <c-custom-look-up-for-lwc          unique-key={item.Id}          value-id={accountId}          obj-name="Account"          icon-name="standard:account"          search-label="Search Account"          onvalueselect={handleSelection}          search-label          fields={item.fields}>      </c-custom-look-up-for-lwc>  </template>

testLookUpForLwc.js:handleSelection用來獲取accountId

import { LightningElement,track } from 'lwc';    export default class TestLookUpForLwc extends LightningElement {      @track item = {          id:'xxx',          fields:['Account.Name']      };        @track accountId;        handleSelection(event) {          console.log(event.detail.selectedId);          this.accountId = event.detail.selectedId;      }    }

效果展示:

總結:篇中通過兩種方式實現lookup功能及樣式的實現,如果第一種能搞定強烈推薦使用第一種,因為標準的功能穩定性以及效率會好很多,如果第一種搞定不了可以考慮自定義。lwc不易,且開發且珍惜。