はじめに
SalesforceでLightning Web Componentsを使用したレコード検索画面の作成方法を記載します。
テキスト、日付、選択リスト、参照、ロングテキストエリアといった様々なデータ型をどのように検索するかを学ぶことを目的とします。
1. 画面の全体像
主な機能
- 検索結果の表示: 検索結果の表示を行い、レコードへアクセスすることができます
- 動的な選択リスト: UI APIを使用し、Salesforceのスキーマ(項目定義)から直接選択リストの値を取得・表示します
- ロングテキストエリアの部分検索: ロングテキストエリアの検索はSOQLで実行することができませんが、SOQLと組み合わせることによって実現します
2. テキスト項目の検索画面作成方法
「テキスト(文字列)」項目の検索方法です。
概要
| ファイル | 役割 | 使用コンポーネント / Apex構文 |
|---|---|---|
| HTML | ユーザーインターフェース | <lightning-input type="text"> |
| JS | 入力値の保持 |
searchCriteria オブジェクト |
| Apex | サーバー側処理 |
String 型プロパティ、LIKE 演算子 |
サンプル解説
HTML (.html)
label と name を指定し、onchange でJSのハンドラを呼び出します。
name 属性は、JS側でどの項目からの入力かを識別するために必要です。
<lightning-input
type="text"
label="取引先名"
name="accountName"
onchange={handleCriteriaChange}>
</lightning-input>
JavaScript (.js)
handleCriteriaChange でイベントを受け取ります。
event.target.name(この場合accountName)をキーとして、searchCriteria オブジェクトに入力値 event.target.value を格納します。
@track searchCriteria = {
accountName: null,
// ... 他の項目
};
handleCriteriaChange(event) {
const name = event.target.name;
const value = event.target.value;
this.searchCriteria[name] = value;
}
Apex (.cls)
JSから渡された searchCriteria を SearchCriteria 内部クラスで受け取ります。
// 内部クラスでパラメータを受け取る
public class SearchCriteria {
@AuraEnabled public String accountName { get; set; }
}
// メソッド本体
@AuraEnabled(cacheable=true)
public static List<Account> searchAccounts(SearchCriteria criteria) {
// ...
List<String> conditions = new List<String>();
Map<String, Object> bindParams = new Map<String, Object>();
// String.isNotBlank で null や空文字でないことを確認
if (String.isNotBlank(criteria.accountName)) {
// LIKE演算子とワイルドカード(%)で部分一致検索
conditions.add('Name LIKE :accountName');
// バインド変数に値を設定 (SQLインジェクション対策)
bindParams.put('accountName', '%' + criteria.accountName + '%');
}
// ...
List<Account> queryResults = Database.queryWithBinds(query, bindParams, ...);
}
ポイント
- SOQLインジェクションを防ぐため、必ずバインド変数(:variable)を使用します
- String.isNotBlank を使って、入力値がある場合のみ WHERE 句に追加します
3. 選択リストの検索画面作成方法
| ファイル | 役割 | 使用コンポーネント / Apex構文 |
|---|---|---|
| HTML | ユーザーインターフェース | <lightning-combobox> |
| JS | 選択肢の動的取得 | @wire, getPicklistValues (UI API) |
| Apex | サーバー側処理 | String 型プロパティ、= 演算子 |
サンプル解説
HTML (.html)
lightning-combobox を使用します。options にはJSで取得した選択肢リストをバインドします。
<lightning-combobox
name="industry"
label="業種"
placeholder="--なし--"
options={industryOptions}
onchange={handleCriteriaChange}>
</lightning-combobox>
JavaScript (.js)
UI API を使って、Salesforceから直接選択リストの値を取得するのがベストプラクティスです。
// スキーマのインポート
import { getObjectInfo } from 'lightning/uiObjectInfoApi';
import { getPicklistValues } from 'lightning/uiObjectInfoApi';
import ACCOUNT_OBJECT from '@salesforce/schema/Account';
import INDUSTRY_FIELD from '@salesforce/schema/Account.Industry';
export default class SampleSearch extends LightningElement {
@track industryOptions = []; // 選択肢を格納する配列
// 1. AccountオブジェクトのデフォルトレコードタイプIDを取得
@wire(getObjectInfo, { objectApiName: ACCOUNT_OBJECT })
accountInfo;
// 2. レコードタイプIDと項目API名を使って選択リスト値を取得
@wire(getPicklistValues, {
// '$' を付けることで、accountInfo が取得できたら動的に実行される
recordTypeId: '$accountInfo.data.defaultRecordTypeId',
fieldApiName: INDUSTRY_FIELD
})
wiredIndustryValues({ error, data }) {
if (data) {
// 取得したデータに「--なし--」の選択肢を追加して配列にセット
this.industryOptions = [
{ label: '--なし--', value: ''},
...data.values
];
}
}
}
Apex (.cls)
テキスト検索とほぼ同じですが、SOQLでは LIKE ではなく =(完全一致)を使います。
// 内部クラス
public class SearchCriteria {
@AuraEnabled public String industry { get; set; }
}
@AuraEnabled(cacheable=true)
public static List<Account> searchAccounts(SearchCriteria criteria) {
List<String> conditions = new List<String>();
Map<String, Object> bindParams = new Map<String, Object>();
// メソッド本体
if (String.isNotBlank(criteria.industry)) {
conditions.add('Industry = :industry');
bindParams.put('industry', criteria.industry);
}
// ...
List<Account> queryResults = Database.queryWithBinds(query, bindParams, ...);
}
ポイント
- getPicklistValues を使うことで、管理者がSalesforceの設定で選択リストの項目を変更しても、LWCコードを修正する必要がなくなります(=保守性の向上)
4. 日付項目の検索画面作成方法
| ファイル | 役割 | 使用コンポーネント / Apex構文 |
|---|---|---|
| HTML | ユーザーインターフェース | <lightning-input type="date"> |
| JS | デフォルト値の設定 | searchCriteria オブジェクト |
| Apex | サーバー側処理 | Date 型プロパティ、>=, <= 演算子 |
サンプル解説
HTML (.html)
type="date" を指定するだけで、カレンダーピッカー付きの入力欄が表示されます。
<lightning-input
type="date"
label="作成日 (FROM)"
name="fromDate"
onchange={handleCriteriaChange}>
</lightning-input>
<lightning-input
type="date"
label="作成日 (TO)"
name="toDate"
onchange={handleCriteriaChange}>
</lightning-input>
JavaScript (.js)
@track searchCriteria = {
fromDate: null,
toDate: null,
// ... 他の項目
};
handleCriteriaChange(event) {
const name = event.target.name;
const value = event.target.value;
this.searchCriteria[name] = value;
}
Apex (.cls)
JSから YYYY-MM-DD 形式の文字列で渡された値は、Apex側で Date 型として自動的に受け取ることができます。
public class SearchCriteria {
@AuraEnabled public Date fromDate { get; set; }
@AuraEnabled public Date toDate { get; set; }
}
@AuraEnabled(cacheable=true)
public static List<Account> searchAccounts(SearchCriteria criteria) {
/ ...
List<String> conditions = new List<String>();
Map<String, Object> bindParams = new Map<String, Object>();
// null でないことを確認
if (criteria.fromDate != null) {
conditions.add('CreatedDate >= :fromDate');
bindParams.put('fromDate', criteria.fromDate);
}
if (criteria.toDate != null) {
conditions.add('CreatedDate <= :toDate');
bindParams.put('toDate', criteria.toDate);
}
// ...
List<Account> queryResults = Database.queryWithBinds(query, bindParams, ...);
}
ポイント
- type="date" を使うだけで、カレンダーUIを利用できます
- connectedCallback は、画面表示時の初期値(デフォルト値)を設定するのに最適です
5. 参照関係項目の検索画面作成方法
他のオブジェクトを参照している項目を検索する方法です。
| ファイル | 役割 | 使用コンポーネント / Apex構文 |
|---|---|---|
| HTML | ユーザーインターフェース | <lightning-record-picker> |
| JS | 選択されたIDの取得 | event.detail.recordId |
| Apex | サーバー側処理 | Id 型プロパティ、= 演算子 |
HTML (.html)
lightning-record-picker を使用することで、ルックアップ(参照)検索のUIを作成できます。
<lightning-record-picker
label="親取引先"
name="parentId"
placeholder="取引先を検索..."
object-api-name="Account"
onchange={handleCriteriaChange}
></lightning-record-picker>
JavaScript (.js)
lightning-record-picker の値の取得方法は、他のコンポーネントと少し異なります。event.detail.recordId に選択されたレコードのIDが入っています。
handleCriteriaChange(event) {
const name = event.target.name;
let value = event.target.value;
// record-picker (参照項目) の場合の分岐
if (name === 'parentId') {
this.searchCriteria[name] = event.detail.recordId || null;
} else {
// ... 他の項目の処理
this.searchCriteria[name] = value || null;
}
}
Apex (.cls)
Apex側では、Id 型として受け取ります。
public class SearchCriteria {
@AuraEnabled public Id parentId { get; set; }
}
@AuraEnabled(cacheable=true)
public static List<Account> searchAccounts(SearchCriteria criteria) {
/ ...
List<String> conditions = new List<String>();
Map<String, Object> bindParams = new Map<String, Object>();
if (criteria.parentId != null) {
conditions.add('ParentId = :parentId');
bindParams.put('parentId', criteria.parentId);
}
// ...
List<Account> queryResults = Database.queryWithBinds(query, bindParams, ...);
}
ポイント
- HTMLの object-api-name と、ApexのSOQLで比較する項目のデータ型が一致している必要があります
6. ロングテキストエリアの検索画面作成方法
課題
標準のロングテキストエリア項目は、パフォーマンス上の理由から、SOQLの WHERE 句を使った検索ができません。
解決方法: 2段階フィルタリング
この課題を解決するため、サンプルコードでは「①SOSLで大まかに絞り込み」→「②Apex内で厳密に絞り込み」という2段階の方法を取っています。
| ファイル | 役割 | 使用コンポーネント / Apex構文 |
|---|---|---|
| HTML | ユーザーインターフェース | <lightning-textarea> |
| JS | 入力値の保持 | searchCriteria オブジェクト |
| Apex | サーバー側処理 | SOSL、Id IN :idSet、Apex内 String.contains() |
HTML (.html)
<lightning-textarea
label="説明 (キーワードを含む)"
name="description"
onchange={handleCriteriaChange}>
</lightning-textarea>
JavaScript (.js)
@track searchCriteria = {
consultationContentDocument: null
// ... 他の項目
};
handleCriteriaChange(event) {
const name = event.target.name;
const value = event.target.value;
this.searchCriteria[name] = value || null;
}
Apex (.cls)
public class SearchCriteria {
@AuraEnabled public String description { get; set; }
}
@AuraEnabled(cacheable=true)
public static List<Account> searchAccounts(SearchCriteria criteria) {
List<String> conditions = new List<String>();
Map<String, Object> bindParams = new Map<String, Object>();
// ... [セクション2〜5のSOQL条件構築] ...
// --- 6. ロングテキストエリア (SOSL処理) ---
// criteria.description に値がある場合、SOSLを実行する
if (String.isNotBlank(criteria.description)) {
// SearchUtilityのロジックをここに展開
// キーワードは criteria.description 1つ
String escapedKeyword = String.escapeSingleQuotes(criteria.description);
String soslFindClause = escapedKeyword + '*'; // ワイルドカードを追加
// SOSLクエリを構築
String soslQuery = 'FIND \'' + soslFindClause + '\' IN ALL FIELDS RETURNING Account(Id)';
// SOSLを実行
List<List<SObject>> searchResults = Search.query(soslQuery);
Set<Id> soslMatchingIds;
if (searchResults.isEmpty() || searchResults[0].isEmpty()) {
// SOSLで1件もヒットしなければ、SOQLを実行しても結果は0件
return new List<Account>();
} else {
// ヒットしたレコードのIDセットを取得
soslMatchingIds = (new Map<Id, SObject>(searchResults[0])).keySet();
}
// SOSLの結果をSOQLの条件に追加
conditions.add('Id IN :soslMatchingIds');
bindParams.put('soslMatchingIds', soslMatchingIds);
}
// SOQLクエリを組み立て
String query = 'SELECT Id, Name, Industry, CreatedDate, Parent.Name, Description FROM Account';
if (!conditions.isEmpty()) {
query += ' WHERE ' + String.join(conditions, ' AND ');
}
query += ' ORDER BY CreatedDate DESC LIMIT 100';
// SOQLを実行 (SOSLの結果と他の条件をすべて満たすレコードを取得)
List<Account> soqlResults = Database.queryWithBinds(query, bindParams, AccessLevel.USER_MODE);
// --- 6. Apex内フィルタリング ---
// description の入力があった場合のみ、
// SOSLでヒットしたレコードがDescription にキーワードを含んでいるか確認
if (String.isNotBlank(criteria.description)) {
List<Account> finalResults = new List<Account>();
for (Account acc : soqlResults) {
// Description が null でなく、かつキーワード (criteria.description) を含んでいるか
if (acc.Description != null && acc.Description.contains(criteria.description)) {
finalResults.add(acc);
}
}
// 絞り込んだ結果を返す
return finalResults;
}
// Description の指定がなければ、SOQLの結果をそのまま返す
return soqlResults;
}
ポイント
- ロングテキストエリアの検索は、SOQLの WHERE 句ではできません
- SOSLは全オブジェクトの全項目に対して検索を行い、「キーワードを含んでいる全てのレコード」を返します
- 「SOSLで大まかな候補IDを取得」→「SOQLで他の条件と組み合わせて絞り込み」→「Apexの String.contains() で最終確認」という3ステップが、正確な検索を実現する方法です
7. 検索結果の表示方法 (lightning-datatable)
Apexから取得した検索結果を、LWC画面上にテーブル(表)形式で表示する方法です。
| ファイル | 役割 | 使用コンポーネント / Apex構文 |
|---|---|---|
| HTML | ユーザーインターフェース | <lightning-datatable> |
| JS | カラム定義・データ処理 | const COLUMNS, Apex呼び出し, map()によるデータ加工 |
| Apex | サーバー側処理 | List を返却 |
サンプル解説
HTML (.html)
- lightning-datatable コンポーネントを使用します
- key-field="Id": 各行を一意に識別する項目を指定します(通常は Id)
- data={cases}: JSで処理した検索結果のリストを渡します
- columns={columns}: JSで定義したカラム(列)定義のリストを渡します
- hide-checkbox-column: 行選択のチェックボックスを非表示にします
- isLoading などの制御変数を使い、読み込み中や結果なしの表示を切り替えます
<div class="slds-m-around_medium">
<template if:true={isLoading}>
<lightning-spinner alternative-text="読み込み中..." size="medium"></lightning-spinner>
</template>
<template if:true={results}>
<lightning-datatable
key-field="Id"
data={results}
columns={columns}
hide-checkbox-column>
</lightning-datatable>
</template>
<template if:true={noResultsFound}>
<p class="slds-align_absolute-center">該当する取引先は見つかりませんでした。</p>
</template>
<template if:true={error}>
<p class="slds-text-color_error">{error}</p>
</template>
</div>
JavaScript (.js)
データ表示において、JSは「Apexの呼び出し」「カラム定義」「データ加工」という3つの役割を持ちます。
// Apexメソッドをインポート
import searchAccounts from '@salesforce/apex/SampleSearchController.searchAccounts';
// [セクション7] 結果テーブルのカラム
const COLUMNS = [
{ label: '取引先名',
fieldName: 'accountUrl',
type: 'url',
typeAttributes: {
label: { fieldName: 'Name' },
target: '_blank'
}
},
{ label: '業種', fieldName: 'Industry', type: 'text' },
{ label: '作成日', fieldName: 'CreatedDate', type: 'date' },
{ label: '親取引先',
fieldName: 'ParentName',
type: 'text'
},
];
export default class SampleSearch extends LightningElement {
@track results;
@track error;
columns = COLUMNS;
isLoading = false;
// ... handleCriteriaChange ...
// [セクション7] 検索ボタン押下
handleSearch() {
this.isLoading = true;
this.error = undefined;
this.results = undefined;
// Apexメソッドを呼び出し
searchAccounts({ criteria: this.searchCriteria })
.then(data => {
// [セクション7] 結果を加工 (URLと親取引先名)
// data.map() を使って配列をループ処理
this.results = data.map(acc => {
return {
...acc, // 元のデータをコピー
// 'accountUrl' プロパティを生成
accountUrl: `/lightning/r/Account/${acc.Id}/view`,
// 'ParentName' プロパティを生成 (Parentがnullでないかチェック)
ParentName: acc.Parent ? acc.Parent.Name : ''
};
});
})
.catch(error => {
this.error = error;
})
.finally(() => {
this.isLoading = false;
});
}
// 結果なしの判定
get noResultsFound() {
return !this.isLoading && this.results && this.results.length === 0;
}
}
Apex
検索条件に基づいてSOQLクエリを実行し、List を返します。
lightning-datatable で表示したいリレーション先の項目(例: Contact__r.Name)を、SELECT 句に含めておく必要があります。
// SOQLクエリを組み立て
// [セクション7] リレーション項目 (Parent.Name) も SELECT に含める
String query = 'SELECT Id, Name, Industry, CreatedDate, Parent.Name, Description FROM Account';
// ...
List<Account> soqlResults = Database.queryWithBinds(query, bindParams, AccessLevel.USER_MODE);
// ...
return finalResults;
ポイント
- データはJSで加工する: lightning-datatable は Contact__r.Name のようなリレーション項目を直接表示できません。Apexから返されたデータをJSの map() 関数でループ処理し、ContactName: item.Contact__r.Name のように変換する必要があります
- URLの生成: レコード詳細ページへのリンクも、fieldName:
caseUrlと type:urlを使い、JS側で caseUrl: /lightning/r/Case/${item.Id}/view`` のように動的に文字列を生成します
