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?

Apexとフローの使い分け(画面あり編)

Posted at

LWC(Lightning Web Components)で画面開発が必要なケースとして、標準UIだけでは実現できない動的な操作やリアルタイム更新が求められる場面を挙げます。
具体的な事例を5つ紹介し、それぞれの要件LWC実装の概要を記載します。


LWCが必要なケース(標準UIでは対応困難なもの)

1. 商談の見積明細を動的に追加・削除できるカスタムUI

要件

  • Opportunityのレコードページに、関連するQuoteLineItemを表示。
  • 行を追加・削除できるインタラクティブなUIを提供。
  • 計算結果(合計金額など)をリアルタイム更新。

LWC実装の概要

  • lightning-datatableを使用して見積明細を表示。
  • ボタン操作で行の追加・削除を可能にする。
  • @trackを使い、合計金額などの計算値を即時更新。
  • Apexでデータの取得・保存を処理。

2. リアルタイム在庫チェックと注文処理

要件

  • Productオブジェクトの在庫数をリアルタイムで取得。
  • OpportunityLineItem追加時に、在庫が不足していれば警告を表示。
  • WebSocketPlatform Events を利用し、他ユーザーの変更を即時反映。

LWC実装の概要

  • @wireを使ってApexから在庫データを取得。
  • lightning-inputで注文数を入力し、在庫と比較。
  • 在庫不足時はlightning-toastでアラート表示。
  • Platform Events を購読し、他ユーザーの更新を反映。

3. 商談の進捗をガントチャートで可視化

要件

  • Opportunityの各フェーズをガントチャートで表示。
  • ドラッグ&ドロップでフェーズを変更可能。
  • 変更内容は即座にSalesforceへ保存。

LWC実装の概要

  • third-party JavaScriptライブラリ(D3.js, Chart.js)を活用。
  • lightning-record-edit-formを利用してドラッグ&ドロップ後にデータ更新。
  • @wireで商談フェーズのデータを取得・更新。

4. ユーザーがファイルをドラッグ&ドロップでアップロードし、プレビュー

要件

  • Case(問い合わせ)にファイルを添付する際、ドラッグ&ドロップでアップロード可能にする。
  • アップロード前に画像プレビューを表示。
  • PDFや画像ファイルの内容をプレビュー可能。

LWC実装の概要

  • lightning-file-uploadを使用。
  • onchangeイベントでファイルを一時保存し、プレビュー表示。
  • ContentDocumentLinkを利用し、ファイルをCaseに関連付け。

5. 顧客検索&即時編集可能なカスタム検索コンポーネント

要件

  • Accountを検索できるコンポーネントを作成。
  • 部分一致検索が可能(標準のlookupでは難しい)。
  • 検索結果をクリックすると、その場で編集&保存。

LWC実装の概要

  • lightning-inputで検索キーワードを入力。
  • @wireを使用し、ApexでLIKE検索を実施。
  • lightning-datatableに結果を表示し、inline editingを有効化。

まずは 「商談の見積明細を動的に追加・削除できるカスタムUI」 の実装例を示します。
このLWCコンポーネントは、Opportunity のレコードページに配置し、QuoteLineItem(見積明細)の追加・削除、合計金額のリアルタイム更新ができるようにします。


1. 商談の見積明細を動的に追加・削除できるカスタムUI

実装概要

  • 見積明細(QuoteLineItem)を lightning-datatable で表示
  • ユーザーが行を追加・削除可能
  • 合計金額をリアルタイムで計算
  • SalesforceデータをApex経由で取得・更新

1.1 Apexクラス

LWC から OpportunityQuoteLineItem データを取得・更新する Apex クラスを作成します。

public with sharing class QuoteLineItemController {
    @AuraEnabled(cacheable=true)
    public static List<QuoteLineItem> getQuoteLineItems(Id opportunityId) {
        return [SELECT Id, QuoteId, Product2Id, Quantity, UnitPrice, TotalPrice 
                FROM QuoteLineItem 
                WHERE Quote.OpportunityId = :opportunityId];
    }

    @AuraEnabled
    public static void saveQuoteLineItems(List<QuoteLineItem> quoteLineItems) {
        upsert quoteLineItems;
    }

    @AuraEnabled
    public static void deleteQuoteLineItem(Id quoteLineItemId) {
        delete [SELECT Id FROM QuoteLineItem WHERE Id = :quoteLineItemId];
    }
}

1.2 LWCコンポーネント

HTML: quoteLineItemTable.html

<template>
    <lightning-card title="見積明細(QuoteLineItem)">
        <div class="slds-m-around_medium">
            <lightning-datatable
                key-field="Id"
                data={quoteLineItems}
                columns={columns}
                draft-values={draftValues}
                onsave={handleSave}
                onrowaction={handleRowAction}>
            </lightning-datatable>

            <lightning-button
                label="行を追加"
                onclick={addRow}
                class="slds-m-top_medium">
            </lightning-button>

            <p class="slds-m-top_medium">
                <strong>合計金額: {totalAmount} 円</strong>
            </p>
        </div>
    </lightning-card>
</template>

JavaScript: quoteLineItemTable.js

import { LightningElement, api, track, wire } from 'lwc';
import getQuoteLineItems from '@salesforce/apex/QuoteLineItemController.getQuoteLineItems';
import saveQuoteLineItems from '@salesforce/apex/QuoteLineItemController.saveQuoteLineItems';
import deleteQuoteLineItem from '@salesforce/apex/QuoteLineItemController.deleteQuoteLineItem';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';

const COLUMNS = [
    { label: '商品ID', fieldName: 'Product2Id', editable: true },
    { label: '数量', fieldName: 'Quantity', type: 'number', editable: true },
    { label: '単価', fieldName: 'UnitPrice', type: 'currency', editable: true },
    { label: '合計', fieldName: 'TotalPrice', type: 'currency' },
    { type: 'action', typeAttributes: { rowActions: [{ label: '削除', name: 'delete' }] } }
];

export default class QuoteLineItemTable extends LightningElement {
    @api recordId;
    @track quoteLineItems = [];
    columns = COLUMNS;
    draftValues = [];

    @wire(getQuoteLineItems, { opportunityId: '$recordId' })
    wiredQuoteLineItems({ error, data }) {
        if (data) {
            this.quoteLineItems = data;
        } else if (error) {
            this.showToast('エラー', '見積明細の取得に失敗しました', 'error');
        }
    }

    get totalAmount() {
        return this.quoteLineItems.reduce((sum, item) => sum + (item.TotalPrice || 0), 0);
    }

    handleSave(event) {
        const updatedFields = event.detail.draftValues;
        saveQuoteLineItems({ quoteLineItems: updatedFields })
            .then(() => {
                this.showToast('成功', '見積明細が更新されました', 'success');
                return refreshApex(this.wiredQuoteLineItems);
            })
            .catch(error => {
                this.showToast('エラー', '保存に失敗しました', 'error');
            });
    }

    handleRowAction(event) {
        const actionName = event.detail.action.name;
        const row = event.detail.row;
        if (actionName === 'delete') {
            deleteQuoteLineItem({ quoteLineItemId: row.Id })
                .then(() => {
                    this.showToast('成功', '行を削除しました', 'success');
                    return refreshApex(this.wiredQuoteLineItems);
                })
                .catch(error => {
                    this.showToast('エラー', '削除に失敗しました', 'error');
                });
        }
    }

    addRow() {
        const newItem = {
            Id: 'new_' + Date.now(),
            Product2Id: '',
            Quantity: 1,
            UnitPrice: 0,
            TotalPrice: 0
        };
        this.quoteLineItems = [...this.quoteLineItems, newItem];
    }

    showToast(title, message, variant) {
        this.dispatchEvent(new ShowToastEvent({ title, message, variant }));
    }
}

メタデータXML: quoteLineItemTable.js-meta.xml

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata"
    fqn="quoteLineItemTable">
    <apiVersion>58.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__RecordPage</target>
    </targets>
    <targetConfigs>
        <targetConfig targets="lightning__RecordPage">
            <property name="recordId" type="String" label="レコードID" />
        </targetConfig>
    </targetConfigs>
</LightningComponentBundle>

2. 実装ポイント

  1. データの取得

    • @wire(getQuoteLineItems, { opportunityId: '$recordId' }) を使用し、Apexからデータ取得。
  2. リアルタイムで合計金額を計算

    • get totalAmount() を利用し、quoteLineItems の合計金額を計算。
  3. 見積明細の編集・保存

    • lightning-datatableonsave イベントを handleSave で処理。
  4. 行の追加・削除

    • addRow() で仮の新規レコードを追加。
    • handleRowAction()deleteQuoteLineItem を呼び出し削除。

3. まとめ

このLWCは、Salesforceの標準画面では実現できない「動的なUI」 を提供し、
リアルタイムでデータ変更
ダイナミックなUI操作
Apexと連携したデータ管理
を可能にします。

このパターンを応用すれば、リアルタイム在庫チェックやガントチャート表示 なども実装できます!🎯

全体のまとめ

LWCが必要なケースは、
リアルタイム更新(在庫チェック・WebSocket・Platform Events)
複雑なUI操作(ドラッグ&ドロップ・カスタム検索)
即時フィードバック(インタラクティブなフォーム・データプレビュー)
が求められる場面です。

Apex + LWCを組み合わせることで、Salesforce標準UIでは難しい高度な画面開発を実現できます!

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?