Salesforce LWC學習(十五) Async 以及 Picklist 公用方法的實現

本篇參考:salesforce 零基礎學習(六十二)獲取sObject中類型為Picklist的field values(含record type)

https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.reference_wire_adapters_picklist_values

https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.reference_wire_adapters_picklist_values_record

Salesforce lwc中給我們提供了很多優秀的wire adapter使我們的開發更加便捷,比如getPicklistValues以及getPicklistValuesByRecordType 

可以實現獲取某個欄位或者某個record type所有picklist類型欄位的 picklist values。這個組件在便捷的同時又讓我們心生哀怨,因為他不是所有的對象都支援,針對常用對象 Account / Opportunity / Contact 或者自定義對象等可以直接使用,方便快捷,但是針對一些對象則不支援,比如 Event & Task。 所以當項目中使用到 Event & Task 進行自定義開發需要獲取某個或者某些欄位的 picklist values的值時,如果需求不變,我們進行 hard code,將所有的 label-value鍵值對使用 {label,value}的map進行封裝,此種需求只是針對不經常修改的場景。如果需求不明確,或者需要指定的record type顯示指定的picklist values,而 record type配置的 picklist values又可以實時變動的場景來說簡直是災難的,所以有了這篇的針對 LwC公用的方法的實現思路。程式碼並不完善,思路僅供參考。

思路分析:

1. 後台如何構建,需要滿足哪些場景;

2. 前台如何搭建,如何做成公用組件使大部分的場景都可以簡單引用便可以使用。

伴隨著這兩個問題進行了考慮。

1) 針對後台搭建,暫時滿足兩個場景

  • 針對一個表可以獲取到所有的 Picklist類型欄位的所有的 Picklist值;
  • 針對一個表的某個欄位(可包含 record type)獲取對應的Picklist值。

2)針對前台的搭建,因為需要從後台獲取數據,需要保證數據獲取支援非同步處理,即數據處理完進行picklist 數據賦值。

思路分析以後進行功能的構建,本篇參考以前寫過的一篇文章,這裡 PicklistDescriber程式碼便不在放出,直接引用。

一. 後台搭建

CommonUtilsController:因為Salesforce目前沒有針對 包含 record type對應的 Picklist values的特別好的獲取方法,所以我們根據以前的XML解析模式進行獲取包含record type的場景。 很多人可能會說Salesforce支援了通過user interface方式獲取,只需要一個callout就可以獲取到包含record type對應的picklist欄位對應的values。這種方式其實和wire adapter原理一樣,只是針對一部分object,而不是針對所有的object,考慮到組件的共用性,所以放棄了那種方式。

public without sharing class CommonUtilsController {        private static Map<String,Schema.SObjectType> globalDescribeMap = Schema.getGlobalDescribe();        @AuraEnabled(cacheable=true)      public static Map<String,Map<String,String>> getPicklistMapByObject(String objectName) {          Map<String,Map<String,String>> resultMap = new Map<String, Map<String,String>>();          Schema.DescribeSObjectResult objectResult = getDescribeObjectResult(objectName);          Map<String,SObjectField> fieldsMap = objectResult.fields.getMap();          Map<String,Schema.DescribeFieldResult> picklistName2DescribeFieldMap = new Map<String,Schema.DescribeFieldResult>();          for(String fieldName : fieldsMap.keySet()) {              SObjectField objField = fieldsMap.get(fieldName);              Schema.DescribeFieldResult fieldResult = objField.getDescribe();              if(fieldResult.getType() == Schema.DisplayType.Picklist) {                  picklistName2DescribeFieldMap.put(fieldName,fieldResult);              }          }            if(!picklistName2DescribeFieldMap.isEmpty()) {              for(String fieldName : picklistName2DescribeFieldMap.keySet()) {                  Schema.DescribeFieldResult fieldResult = picklistName2DescribeFieldMap.get(fieldName);                  List<Schema.PicklistEntry> picklistEntries = fieldResult.getPicklistValues();                  Map<String,String> fieldValue2LabelMap = new Map<String,String>();                  for(Schema.PicklistEntry picklistEntry : picklistEntries) {                      if(picklistEntry.isActive()) {                          fieldValue2LabelMap.put(picklistEntry.getValue(),picklistEntry.getLabel());                      }                  }                  resultMap.put(fieldName,fieldValue2LabelMap);              }          }          return resultMap;      }        @AuraEnabled(cacheable=true)      public static Map<String,String> getPicklistMapByObjectAndField(String objectName,String field,String recordTypeDevelopName) {          Map<String,String> resultMap = new Map<String,String>();          Schema.DescribeSObjectResult objectResult = getDescribeObjectResult(objectName);          Map<String,SObjectField> fieldsMap = objectResult.fields.getMap();          if(fieldsMap.containsKey(field)) {              SObjectField objField = fieldsMap.get(field);              Schema.DescribeFieldResult fieldResult = objField.getDescribe();              List<Schema.PicklistEntry> picklistEntries = fieldResult.getPicklistValues();              for(Schema.PicklistEntry picklistEntry : picklistEntries) {                  if(picklistEntry.isActive()) {                      resultMap.put(picklistEntry.getValue(),picklistEntry.getLabel());                  }              }              if(String.isNotBlank(recordTypeDevelopName)) {                  List<String> picklistValueWithRecordTypeList = PicklistDescriber.describe(objectName,recordTypeDevelopName,field);                  Map<String,String> resultForRecordTypeMap = new Map<String,String>();                  for(String picklistValue : picklistValueWithRecordTypeList) {                      if(resultMap.containsKey(picklistValue)) {                          resultForRecordTypeMap.put(picklistValue,resultMap.get(picklistValue));                      }                  }                  return resultForRecordTypeMap;              }            }          return resultMap;      }        private static Schema.DescribeSObjectResult getDescribeObjectResult(String objectName) {          Schema.SObjectType objectType = globalDescribeMap.get(objectName);          Schema.DescribeSObjectResult objectResult = objectType.getDescribe();          return objectResult;      }  }

後台就這樣搭建完成,暴露了兩個方法:getPicklistMapByObject & getPicklistMapByObjectAndField。第一個方法用來獲取一個表的所有 Picklist類型欄位的label api name對,key為api name,value為picklist的label。我們以 Account表為例,返回的結構類似如下圖所示:

 第二個方法用來獲取某個指定object指定欄位的 picklist values的獲取,有record type則傳遞,如果不需要record type則傳遞 null或者不傳遞即可。針對結果集來說則沒有外層的field api name,直接就是 picklist 欄位的 api value -> label,這裡不做截圖。

二. 前台搭建

這裡需要分成兩步, 第一步是做一個公用組件來實現 傳遞相關參數獲取指定的我們想得到的結果集。

picklistUtils.js:封裝了兩個公用函數,getAllPicklist用於獲取object所有的picklist 類型欄位的結果集;getFieldPicklistMap用於通過object & field [record type developer name]來獲取指定欄位的結果集。

import getPicklistMapByObject from '@salesforce/apex/CommonUtilsController.getPicklistMapByObject';  import getPicklistMapByObjectAndField from '@salesforce/apex/CommonUtilsController.getPicklistMapByObjectAndField';    const getAllPicklist = (objectAPIName) => {      //let resultMap = new Map();      return getPicklistMapByObject({objectName:objectAPIName})              .then(result => {                  return result;              })              .catch(error =>{                 console.log(error);              });  };    const getFieldPicklistMap = (objectAPIName, fieldAPIName, recordTypeDevelopName) => {      return getPicklistMapByObjectAndField({objectName:objectAPIName,field:fieldAPIName,recordTypeDevelopName:recordTypeDevelopName})      .then(result => {          return result;      })      .catch(error =>{         console.log(error);      });  }    export {getAllPicklist,getFieldPicklistMap};

當我們將程式碼賦值粘貼到vs code裡面,我們會發現他有一個提示: This may be converted to an async function.為什麼會有這樣的提示呢?是因為我們這個從後台進行結果集獲取,此步驟不是實時的,而是一個非同步的操作,所以他提示了將會將這個轉換成了一個非同步的函數。

 這樣的解釋可能過於乾燥,什麼是非同步的?非同步怎麼處理呢?這裡放一個鏈接用來更好的理解:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/async_function。當我們聲明了非同步函數,調用源調用它時需要使用await去共同使用,從而實現結果集返回時可以正常的接收以及處理。

AccountPicklistComponent.js:公共組件搞定以後我們寫一個組件進行測試,下面的組件只試驗了獲取所有picklist類型欄位的測試,其他的方法感興趣的自行測試。
這裡的程式碼有幾個關鍵點需要注意:
  • 頭部需要引入我們需要用到的函數,這裡引用的是:import {getAllPicklist} from ‘c/picklistUtils’;
  • 我們將生命周期函數connectedCallback使用async聲明成了一個非同步函數,因為這裡我們需要有調用非同步的函數使用await,所以方法聲明async;
  • 針對非同步的函數接受結果集需要使用await,否則獲取的結果集變成了同步操作獲取的便是null,只有通過await進行標識才可以正常返回;
  • 結果集接受操作需要使用臨時變數,最後將臨時變數賦值給我們需要展示前台的變數,不用臨時變數賦值不會進行渲染,因為是非同步的操作,沒法reactive。
import { LightningElement,track } from 'lwc';  import {getAllPicklist} from 'c/picklistUtils';  export default class AccountPicklistComponent extends LightningElement {      @track industryList = [];        @track typeList = [];        @track accountSourceList = [];        @track ratingList = [];        async connectedCallback() {          const result = await getAllPicklist('Account');          console.log('total result : ' + JSON.stringify(result));          let typeTempList = [];          let industryTempList = [];          let accountSourceTempList = [];          let ratingTempList = [];          for(let key in result) {                if (result.hasOwnProperty(key)) { // Filtering the data in the loop                    if(key === 'type') {                      let typeResult = result[key];                      console.log('type result : ' + JSON.stringify(typeResult));                      for(let typeValue in typeResult) {                          typeTempList.push({label:typeResult[typeValue],value:typeValue});                      }                  } else if(key === 'industry') {                      let industryResult = result[key];                      for(let industryValue in industryResult) {                          industryTempList.push({label:industryResult[industryValue],value:industryValue});                      }                  } else if(key === 'accountsource') {                      let accountSourceResult = result[key];                      for(let accountSourceValue in accountSourceResult) {                          accountSourceTempList.push({label:accountSourceResult[accountSourceValue],value:accountSourceValue});                      }                  } else if(key === 'rating') {                      let ratingResult = result[key];                      for(let ratingValue in ratingResult) {                          ratingTempList.push({label:ratingResult[ratingValue],value:ratingValue});                      }                  }              }          }          this.typeList = typeTempList;          this.industryList = industryTempList;          this.accountSourceList = accountSourceTempList;          this.ratingList = ratingTempList;      }  }

accountPicklistComponent.html:用來展示相關欄位的select option
<template>      <lightning-card>          <lightning-layout multiple-rows="true">              <lightning-layout-item size="6">                  <lightning-combobox                  name="industry"                  label="industry"                  options={industryList}>                  </lightning-combobox>              </lightning-layout-item>                <lightning-layout-item size="6">                  <lightning-combobox                  name="type"                  label="type"                  options={typeList}>                  </lightning-combobox>              </lightning-layout-item>                <lightning-layout-item size="6">                  <lightning-combobox                  name="accountSource"                  label="Account Source"                  options={accountSourceList}>                  </lightning-combobox>              </lightning-layout-item>                <lightning-layout-item size="6">                  <lightning-combobox                  name="rating"                  label="rating"                  options={ratingList}>                  </lightning-combobox>              </lightning-layout-item>          </lightning-layout>      </lightning-card>  </template>

結果展示:這個表的相關欄位的picklist值便可以動態取出

總結:篇中只是根據某種需求去分析思考並進行程式碼的編寫。程式碼並沒有進行優化以及異常處理。篇中有錯誤地方還請指出,有不懂歡迎留言。有更好的方式歡迎溝通。