はじめに
Salesforceの管理者やデベロッパーにとって、オブジェクトの項目構成を把握することは日常的な作業です。特に多くのカスタム項目が追加されたオブジェクトでは、項目の全体像を素早く確認できると便利です。
この記事では、Lightning Web Components (LWC) を使って、任意のオブジェクトの項目一覧を表示するコンポーネントの作成方法を解説します。このコンポーネントは以下の機能を持ちます:
- 任意のSalesforceオブジェクトの項目一覧を表示
- 項目のラベル、API参照名、データ型、必須かどうかを表示
- レコードページで使用すると、そのレコードの項目値も表示
- 各列でのソート機能
- 項目をクリックすると詳細情報をモーダルで表示
- CSVエクスポート機能
完成イメージ
出力したCSVです
前提条件
- Salesforce Developer Edition または Sandbox 環境
- Visual Studio Code と Salesforce Extension Pack
- SFDX CLI のインストール
実装手順
1. プロジェクトの準備
まず、VS Code で新しい LWC コンポーネントを作成します。
sfdx force:lightning:component:create --type lwc --componentName objectFieldsList --outputDir force-app/main/default/lwc
2. メタデータファイルの作成
objectFieldsList.js-meta.xml
ファイルを以下のように編集します:
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>58.0</apiVersion>
<isExposed>true</isExposed>
<masterLabel>オブジェクト項目一覧</masterLabel>
<description>任意のオブジェクトの項目一覧を表示します</description>
<targets>
<target>lightning__AppPage</target>
<target>lightning__RecordPage</target>
<target>lightning__HomePage</target>
</targets>
<targetConfigs>
<targetConfig targets="lightning__RecordPage,lightning__AppPage,lightning__HomePage">
<property name="objectApiName" type="String" label="オブジェクトAPI名"
description="表示するオブジェクトのAPI名(例:Account, Contact, Opportunity)"
default="Account" required="true" />
</targetConfig>
</targetConfigs>
</LightningComponentBundle>
このメタデータファイルでは、コンポーネントをアプリページ、レコードページ、ホームページで使用できるように設定し、表示するオブジェクトのAPI名を設定できるようにしています。
3. JavaScript ファイルの作成
objectFieldsList.js
ファイルを以下のように作成します:
import { LightningElement, wire, api } from 'lwc';
import { getObjectInfo } from 'lightning/uiObjectInfoApi';
import { getRecord } from 'lightning/uiRecordApi';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
export default class ObjectFieldsList extends LightningElement {
@api objectApiName = 'Account'; // デフォルトはAccount、設定で変更可能
@api recordId; // レコードページで使用する場合のレコードID
objectFields = [];
recordData = {};
error;
fieldsToRetrieve = [];
defaultSortDirection = 'asc';
sortDirection = 'asc';
sortedBy;
isExporting = false;
// データテーブルの列定義
columns = [
{
label: '項目ラベル',
fieldName: 'label',
type: 'button',
typeAttributes: {
label: { fieldName: 'label' },
name: 'navigate_to_field',
variant: 'base'
},
sortable: true
},
{ label: 'API参照名', fieldName: 'apiName', type: 'text', sortable: true },
{ label: 'データ型', fieldName: 'dataType', type: 'text', sortable: true },
{ label: '必須', fieldName: 'isRequired', type: 'boolean', sortable: true },
{ label: '値', fieldName: 'value', type: 'text', sortable: true }
];
// オブジェクト情報の取得
@wire(getObjectInfo, { objectApiName: '$objectApiName' })
wiredObjectInfo({ error, data }) {
if (data) {
// オブジェクト情報からフィールド情報を取得
const fields = data.fields;
this.fieldsToRetrieve = Object.keys(fields);
this.objectFields = this.fieldsToRetrieve.map(fieldApi => {
return {
apiName: fieldApi,
label: fields[fieldApi].label,
dataType: fields[fieldApi].dataType,
isRequired: fields[fieldApi].required,
value: ''
};
});
// デフォルトでラベルでソート
this.sortData('label', 'asc');
this.error = undefined;
// オブジェクト名を保存(表示用)
this.objectLabel = data.label;
} else if (error) {
this.error = error;
this.objectFields = [];
console.error(`Error loading ${this.objectApiName} fields`, error);
}
}
// レコードデータの取得(レコードページで使用時)
@wire(getRecord, { recordId: '$recordId', optionalFields: '$fieldsForWire' })
wiredRecord({ error, data }) {
if (data) {
// レコードデータを取得したら、項目値を更新
this.recordData = data;
this.updateFieldValues();
} else if (error) {
console.error(`Error loading ${this.objectApiName} data`, error);
}
}
// 取得する項目のリストを生成
get fieldsForWire() {
if (this.recordId && this.objectApiName && this.fieldsToRetrieve && this.fieldsToRetrieve.length > 0) {
// 全ての項目を取得するように指定
return this.fieldsToRetrieve.map(field => `${this.objectApiName}.${field}`);
}
return [];
}
// カードのタイトルを生成
get cardTitle() {
return `${this.objectLabel || this.objectApiName} 項目一覧`;
}
// オブジェクトのアイコンを取得
get objectIcon() {
// 一般的なオブジェクトのアイコン名をマッピング
const iconMap = {
'Account': 'standard:account',
'Contact': 'standard:contact',
'Opportunity': 'standard:opportunity',
'Lead': 'standard:lead',
'Case': 'standard:case',
'Campaign': 'standard:campaign',
'Contract': 'standard:contract',
'Product2': 'standard:product',
'Order': 'standard:orders',
'Task': 'standard:task',
'Event': 'standard:event',
'User': 'standard:user'
};
return iconMap[this.objectApiName] || 'standard:custom_object';
}
// レコードデータから項目値を更新
updateFieldValues() {
if (this.recordData && this.recordData.fields) {
this.objectFields = this.objectFields.map(field => {
const fieldData = this.recordData.fields[field.apiName];
return {
...field,
value: fieldData ? this.formatFieldValue(fieldData) : ''
};
});
// 値を更新した後、現在のソート順を維持
if (this.sortedBy) {
this.sortData(this.sortedBy, this.sortDirection);
}
}
}
// 項目値のフォーマット
formatFieldValue(fieldData) {
if (fieldData.value === null || fieldData.value === undefined) {
return '';
}
// 日付や日時の場合はフォーマットする
if (fieldData.displayValue) {
return fieldData.displayValue;
}
// 参照項目の場合
if (typeof fieldData.value === 'object' && fieldData.value !== null) {
return fieldData.value.displayValue || '';
}
return String(fieldData.value);
}
// ソート処理
sortData(fieldName, direction) {
const cloneData = [...this.objectFields];
cloneData.sort((a, b) => {
let valueA = a[fieldName];
let valueB = b[fieldName];
// 文字列の場合は小文字に変換
if (typeof valueA === 'string') {
valueA = valueA.toLowerCase();
}
if (typeof valueB === 'string') {
valueB = valueB.toLowerCase();
}
// null/undefined値の処理
if (valueA === null || valueA === undefined) valueA = '';
if (valueB === null || valueB === undefined) valueB = '';
return direction === 'asc' ? this.sortBy(valueA, valueB) : this.sortBy(valueB, valueA);
});
this.objectFields = cloneData;
this.sortDirection = direction;
this.sortedBy = fieldName;
}
// ソート比較関数
sortBy(a, b) {
// null値は常に最後に
if (a === '' && b !== '') return 1;
if (a !== '' && b === '') return -1;
// 通常のソート
if (a > b) return 1;
if (b > a) return -1;
return 0;
}
// ソート方向の切り替え
handleSort(event) {
const { fieldName, sortDirection } = event.detail;
this.sortData(fieldName, sortDirection);
}
// 行アクション処理
handleRowAction(event) {
const actionName = event.detail.action.name;
const row = event.detail.row;
if (actionName === 'navigate_to_field') {
this.showFieldDetails(row.apiName);
}
}
// 項目詳細表示
showFieldDetails(fieldApiName) {
// 項目の詳細情報を取得
const selectedField = this.objectFields.find(field => field.apiName === fieldApiName);
if (selectedField) {
// モーダルで表示するための詳細情報を設定
this.selectedFieldDetails = {
label: selectedField.label,
apiName: selectedField.apiName,
dataType: selectedField.dataType,
isRequired: selectedField.isRequired ? 'はい' : 'いいえ',
value: selectedField.value || '(空白)',
isOpen: true
};
}
}
// モーダル関連
selectedFieldDetails = {
isOpen: false
};
closeModal() {
this.selectedFieldDetails = {
isOpen: false
};
}
// CSVエクスポート
exportToCSV() {
this.isExporting = true;
try {
// CSVヘッダー行
let csvContent = '項目ラベル,API参照名,データ型,必須,値\r\n';
// データ行を追加
this.objectFields.forEach(field => {
const row = [
field.label || '',
field.apiName || '',
field.dataType || '',
field.isRequired ? 'はい' : 'いいえ',
field.value || ''
];
// エスケープ処理
const escapedRow = row.map(cell => {
if (cell.includes(',') || cell.includes('"') || cell.includes('\n')) {
return `"${cell.replace(/"/g, '""')}"`;
}
return cell;
});
csvContent += escapedRow.join(',') + '\r\n';
});
// BOMを追加してUTF-8として認識されるようにする
const BOM = '\uFEFF';
// データURIを使用してダウンロード
const encodedUri = 'data:text/csv;charset=utf-8,' + encodeURIComponent(BOM + csvContent);
const link = document.createElement('a');
link.setAttribute('href', encodedUri);
link.setAttribute('download', `${this.objectApiName}_Fields.csv`);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
this.showToast('成功', 'CSVファイルのエクスポートが完了しました', 'success');
} catch (error) {
console.error('CSV export error:', error);
this.showToast('エラー', `CSVエクスポート中にエラーが発生しました: ${error.message || error}`, 'error');
} finally {
this.isExporting = false;
}
}
// 現在の日時を取得(ファイル名用)
getFormattedDate() {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
return `${year}${month}${day}_${hours}${minutes}`;
}
// トースト通知を表示
showToast(title, message, variant) {
const event = new ShowToastEvent({
title: title,
message: message,
variant: variant
});
this.dispatchEvent(event);
}
}
4. HTML ファイルの作成
objectFieldsList.html
ファイルを以下のように作成します:
<!--
@description : 任意のオブジェクトの項目一覧を表示するコンポーネント
@author : Your Name
@group :
@last modified on : 2023-05-25
@last modified by : Your Name
-->
<template>
<lightning-card title={cardTitle} icon-name={objectIcon}>
<div slot="actions">
<lightning-button
label="CSVエクスポート"
icon-name="utility:download"
onclick={exportToCSV}
disabled={isExporting}
variant="brand">
</lightning-button>
</div>
<div class="slds-m-around_medium">
<template if:false={recordId}>
<div class="slds-text-color_weak slds-m-bottom_small">
レコードページで使用すると、項目値も表示されます。
</div>
</template>
<template if:true={objectFields.length}>
<lightning-datatable
key-field="apiName"
data={objectFields}
columns={columns}
hide-checkbox-column
sorted-by={sortedBy}
sorted-direction={sortDirection}
onsort={handleSort}
onrowaction={handleRowAction}>
</lightning-datatable>
</template>
<template if:true={error}>
<div class="slds-text-color_error">
エラーが発生しました: {error.body.message}
</div>
</template>
</div>
</lightning-card>
<!-- 項目詳細モーダル -->
<template if:true={selectedFieldDetails.isOpen}>
<section role="dialog" tabindex="-1" aria-labelledby="modal-heading-01" aria-modal="true" aria-describedby="modal-content-id-1" class="slds-modal slds-fade-in-open">
<div class="slds-modal__container">
<header class="slds-modal__header">
<button class="slds-button slds-button_icon slds-modal__close slds-button_icon-inverse" title="閉じる" onclick={closeModal}>
<lightning-icon icon-name="utility:close" alternative-text="閉じる" variant="inverse" size="small"></lightning-icon>
<span class="slds-assistive-text">閉じる</span>
</button>
<h2 id="modal-heading-01" class="slds-modal__title slds-hyphenate">項目詳細: {selectedFieldDetails.label}</h2>
</header>
<div class="slds-modal__content slds-p-around_medium" id="modal-content-id-1">
<dl class="slds-dl_horizontal">
<dt class="slds-dl_horizontal__label slds-truncate">項目ラベル:</dt>
<dd class="slds-dl_horizontal__detail">{selectedFieldDetails.label}</dd>
<dt class="slds-dl_horizontal__label slds-truncate">API参照名:</dt>
<dd class="slds-dl_horizontal__detail">{selectedFieldDetails.apiName}</dd>
<dt class="slds-dl_horizontal__label slds-truncate">データ型:</dt>
<dd class="slds-dl_horizontal__detail">{selectedFieldDetails.dataType}</dd>
<dt class="slds-dl_horizontal__label slds-truncate">必須:</dt>
<dd class="slds-dl_horizontal__detail">{selectedFieldDetails.isRequired}</dd>
<dt class="slds-dl_horizontal__label slds-truncate">値:</dt>
<dd class="slds-dl_horizontal__detail">{selectedFieldDetails.value}</dd>
</dl>
</div>
<footer class="slds-modal__footer">
<button class="slds-button slds-button_neutral" onclick={closeModal}>閉じる</button>
</footer>
</div>
</section>
<div class="slds-backdrop slds-backdrop_open"></div>
</template>
</template>
5. コードの解説
主要な機能
-
オブジェクト情報の取得
-
getObjectInfo
ワイヤアダプターを使用して、指定されたオブジェクトの項目情報を取得します。 - 取得した情報から、項目のラベル、API名、データ型、必須かどうかを抽出します。
-
-
レコードデータの取得
- レコードページで使用される場合、
getRecord
ワイヤアダプターを使用してレコードデータを取得します。 - 取得したデータを項目リストに反映させます。
- レコードページで使用される場合、
-
ソート機能
-
lightning-datatable
のonsort
イベントを処理して、指定された列でデータをソートします。 - デフォルトでは項目ラベルで昇順ソートされます。
-
-
項目詳細表示
- 項目ラベルをクリックすると、その項目の詳細情報をモーダルで表示します。
- モーダルには項目のラベル、API名、データ型、必須かどうか、現在の値が表示されます。
-
CSVエクスポート
- 「CSVエクスポート」ボタンをクリックすると、表示されている項目情報をCSVファイルとしてダウンロードします。
- CSVファイルには、項目ラベル、API名、データ型、必須かどうか、値の列が含まれます。
重要なポイント
-
リアクティブプロパティ
-
@api
デコレータを使用して、外部から設定可能なプロパティを定義しています。 -
$recordId
や$objectApiName
のように、リアクティブな変数を使用してワイヤアダプターのパラメータを動的に変更できます。
-
-
エラーハンドリング
- データ取得時のエラーを適切に処理し、ユーザーに表示します。
- CSVエクスポート時のエラーも捕捉し、トースト通知で表示します。
-
パフォーマンス考慮
- 大量の項目がある場合でも効率的に処理できるよう、データのクローンを作成してからソートしています。
使用方法
コンポーネントのデプロイ
作成したコンポーネントをSalesforce組織にデプロイします:
sfdx force:source:deploy -p force-app/main/default/lwc/objectFieldsList
コンポーネントの配置
- Salesforce組織にログインします。
- 設定 > Lightning アプリケーションビルダー から、任意のページを編集します。
- コンポーネントパレットから「オブジェクト項目一覧」を探し、ページにドラッグ&ドロップします。
- コンポーネントの設定で、表示したいオブジェクトのAPI名を指定します(例:Account, Contact, Opportunity)。
- 「保存」をクリックしてページを保存します。
使用例
ホームページでの使用
ホームページに配置すると、指定したオブジェクトの項目構造を素早く確認できます。
レコードページでの使用
レコードページに配置すると、そのレコードの実際の値も含めて項目情報を確認できます。これは、特定のレコードのデータを詳細に分析する際に役立ちます。
アプリページでの使用
カスタムアプリページに配置すると、管理者向けのダッシュボードとして使用できます。
カスタマイズのヒント
表示する列のカスタマイズ
columns
配列を編集することで、表示する列をカスタマイズできます。例えば、特定の列を非表示にしたり、新しい列を追加したりできます。
フィルタリング機能の追加
検索ボックスを追加して、項目名や値でフィルタリングする機能を実装できます:
// JavaScript に追加
searchTerm = '';
handleSearch(event) {
this.searchTerm = event.target.value.toLowerCase();
this.filterFields();
}
filterFields() {
if (!this.searchTerm) {
// 検索語がない場合は全ての項目を表示
this.objectFields = [...this.allObjectFields];
} else {
// 検索語でフィルタリング
this.objectFields = this.allObjectFields.filter(field =>
field.label.toLowerCase().includes(this.searchTerm) ||
field.apiName.toLowerCase().includes(this.searchTerm) ||
field.dataType.toLowerCase().includes(this.searchTerm) ||
(field.value && field.value.toLowerCase().includes(this.searchTerm))
);
}
// 現在のソート順を維持
if (this.sortedBy) {
this.sortData(this.sortedBy, this.sortDirection);
}
}
<!-- HTML に追加 -->
<div class="slds-m-bottom_small">
<lightning-input
type="search"
label="項目を検索"
onchange={handleSearch}
value={searchTerm}>
</lightning-input>
</div>
項目グループの表示
標準項目とカスタム項目を分けて表示するなど、項目をグループ化して表示することもできます:
// JavaScript に追加
get standardFields() {
return this.objectFields.filter(field => !field.apiName.endsWith('__c'));
}
get customFields() {
return this.objectFields.filter(field => field.apiName.endsWith('__c'));
}
<!-- HTML を修正 -->
<lightning-tabset>
<lightning-tab label="すべての項目">
<lightning-datatable
key-field="apiName"
data={objectFields}
columns={columns}
hide-checkbox-column
sorted-by={sortedBy}
sorted-direction={sortDirection}
onsort={handleSort}
onrowaction={handleRowAction}>
</lightning-datatable>
</lightning-tab>
<lightning-tab label="標準項目">
<lightning-datatable
key-field="apiName"
data={standardFields}
columns={columns}
hide-checkbox-column
sorted-by={sortedBy}
sorted-direction={sortDirection}
onsort={handleSort}
onrowaction={handleRowAction}>
</lightning-datatable>
</lightning-tab>
<lightning-tab label="カスタム項目">
<lightning-datatable
key-field="apiName"
data={customFields}
columns={columns}
hide-checkbox-column
sorted-by={sortedBy}
sorted-direction={sortDirection}
onsort={handleSort}
onrowaction={handleRowAction}>
</lightning-datatable>
</lightning-tab>
</lightning-tabset>
まとめ
この記事では、Lightning Web Components を使用して、任意のSalesforceオブジェクトの項目一覧を表示するコンポーネントを作成する方法を解説しました。このコンポーネントは、オブジェクトの構造を理解したり、特定のレコードのデータを確認したりするのに役立ちます。
また、ソート機能、項目詳細表示、CSVエクスポートなどの機能を追加することで、より使いやすいコンポーネントになりました。
このコンポーネントをベースに、さらに機能を追加したり、組織の要件に合わせてカスタマイズしたりすることができます。例えば、検索フィルター、項目グループ化、項目編集機能などを追加することで、より強力なツールになるでしょう。
Salesforceの管理者やデベロッパーにとって、このようなツールは日常業務の効率化に大いに役立つはずです。ぜひ、自分の組織に合わせてカスタマイズして活用してみてください。