0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Salesforce】Lightning Web Componentsを使用したレコード検索画面の構築方法

Last updated at Posted at 2025-11-14

はじめに

SalesforceでLightning Web Componentsを使用したレコード検索画面の作成方法を記載します。

テキスト、日付、選択リスト、参照、ロングテキストエリアといった様々なデータ型をどのように検索するかを学ぶことを目的とします。

1. 画面の全体像

スクリーンショット 2025-11-10 14.02.53.png

主な機能

  • 検索結果の表示: 検索結果の表示を行い、レコードへアクセスすることができます
  • 動的な選択リスト: UI APIを使用し、Salesforceのスキーマ(項目定義)から直接選択リストの値を取得・表示します
  • ロングテキストエリアの部分検索: ロングテキストエリアの検索はSOQLで実行することができませんが、SOQLと組み合わせることによって実現します

2. テキスト項目の検索画面作成方法

「テキスト(文字列)」項目の検索方法です。

概要

ファイル 役割 使用コンポーネント / Apex構文
HTML ユーザーインターフェース <lightning-input type="text">
JS 入力値の保持 searchCriteria オブジェクト
Apex サーバー側処理 String 型プロパティ、LIKE 演算子

サンプル解説

HTML (.html)
labelname を指定し、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`` のように動的に文字列を生成します
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?