【LWC】CSVアップロードコンポーネントを作成

さいしょに

こんにちは、タンバリン東京開発チームの岩澤です。

Salesforceが公開しているUI開発フレームワークの中でも比較的新しいLightning Web Componentにふれる機会が多いので、本日はLightning Web ComponentでCSVアップロードコンポーネントを作成してSalesforce標準オブジェクトにデータを追加する方法を書いていこうかと思います。

※ Lightning Web Componentは以降LWCと略します。
※ Salesforceは以降SFと略します。

前提知識としては以下のTrailheadで環境構築、基本的なLWCの扱いは完了しているものとします。

クイックスタート: Lightning Web コンポーネント | Salesforce Trailhead

工程

以下の工程で実装していきます。

  1. CSVファイルの準備
  2. アップロード画面のコンポーネントをつくる
  3. JSでアップロードしたデータを取得
  4. インポート処理を実行

順番にみていきましょう。

CSVファイルの準備

今回はSFの標準オブジェクトである取引先(Account)への登録を行いますので、対象にする数件の項目に対するCSVファイルを用意しました。

以下、CSVファイルを使用します。

account.csv

取引先名,取引先 部門,都道府県 (請求先),電話,種別
user1,部門1,千葉,080-0000-0000,Customor
user2,部門1,東京,080-1111-1111,Customor
user3,部門1,神奈川,080-2222-2222,Customor
user4,部門1,埼玉,080-3333-3333,Customor

DE環境のデフォルト設定で取引先オブジェクトのページに表示されていた項目になります。 都道府県(請求先)は国などその他の項目がマージされて構成された住所(請求先)オブジェクトの一部でオブジェクトマナージャーなどでは見かけからず、API名が見つけづらいので公式ページを参考にします。

https://help.salesforce.com/articleView?id=000323000&language=ja&type=1&mode=1

また、SFに限った話ではないですがCSVファイルを扱う時は最終行に改行がないことを確認してください。 改行をフィールドと認識してしてフィールドが空となりエラーになります。今回のコードでは改行処理はしていないのであしからず。

アップロード画面のコンポーネントをつくる

クイックスタート: Lightning Web コンポーネント | Salesforce Trailhead

上記を参考にcommand ⌘+shift ⇧+pSFDX: Create Lightning Web Componentを選択→csvtest(適当)を入力してEnterEnterでデフォルトのlwcフォルダ配下にコンポーネントの雛形が作成されます。

作成されたコンポーネントのフォルダ配下にcsvtest.htmlファイルがありますのでに以下の修正を加えます。

force-app/main/default/lwc/csvtest/csvtest.html

<template>
  <lightning-card
    title="CSVアップロード"
    icon-name="custom:custom15"
  >
    <div style="padding: 0 20px;">
      <div style="
        padding: 10px 0 0 0;
      ">
        <lightning-input
          type="file"
          name="csv"
          label="CSV"
          onchange={handleCsvUpload}
          accept="text/csv"
        ></lightning-input>
      </div>
      <p style="padding: 10px 0 0 0;">{fileName}</p>
      <div style="
        padding: 20px 0 10px 0;
        margin: 0 0 0 -5px;
      ">
        <div
          if:true={isLoaded}
          class="slds-is-relative"
          style="
            height: 60px;
            margin: 0 0 -10px 0;
          "
        >
          <lightning-spinner alternative-text="Loading..." variant="brand"></lightning-spinner>
        </div>
        <div if:false={isLoaded}>
          <div if:true={isSend}>
            <lightning-button
                variant="brand"
                label="送信"
                title="Primary action"
                onclick={handleUpload}
                class="slds-m-left_x-small">
            </lightning-button>
          </div>
          <div if:false={isSend}>
            <lightning-button
              variant="brand"
              label="送信"
              disabled
              title="Primary action"
              onclick={handleUpload}
              class="slds-m-left_x-small">
            </lightning-button>
          </div>
        </div>
      </div>
    </div>
  </lightning-card>
</template>

<lightning-input type="file>でファイル選択ができるようになります。

ファイルを選択するとif:true={isSend}になり送信ボタンが押せるようになり、送信ボタンを押すとif:true={isLoaded}になりロード画面が表示されアップロードが始まります。

これらの変数はJS側で定義しています。

また、CSSはインラインで直接書いていますが、同じディレクトリ内でcsvtest.cssファイルを作成してCSSを書けばファイルを分けることができます。

続いて、コンポーネントの表示の場所を定義していきます。

今回はホームタブの画面にコンポーネントを表示しますのでcsvtest.js-meta.xmlに以下の修正を加えます。

force-app/main/default/lwc/csvtest/csvtest.js-meta.xml

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>48.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__HomePage</target>
    </targets>
</LightningComponentBundle>

<targets>要素でSalesforceのどこに表示するか決めている程度の理解でいったん大丈夫な気がします。

ここまで出来ましたら、いったんデプロイしてみましょう。

VSCodeの左側のディレクトリツリーのforce-app/main/defaultの部分を右クリックしてSFDX: Deploy Source to Orgをクリックします。

f:id:AkihitoIwasawaTambourine:20201022182813p:plain

無事にデプロイが完了しましたら、以下の順序で画面に表示します。

command ⌘+shift ⇧+pSFDX: Open Default Orgをクリックしてブラウザ表示

設定から編集ページを開く

f:id:AkihitoIwasawaTambourine:20201022183037p:plain

・カスタムコンポーネントをキャンバスにドラッグして保存

f:id:AkihitoIwasawaTambourine:20201022183104p:plain

以上で設定が反映されホームにアップロード画面が表示されます。

補足:カスタムコンポーネントが表示されなかった場合

編集ページを開いた時にメニューのカスタムにコンポーネントが表示されない場合があります。

原因は私のドメインが設定されていないことが原因です。

f:id:AkihitoIwasawaTambourine:20201022183410p:plain

画面の左下[私のドメイン]をリリースをクリックしてください。

そうしますと以下のようにドメイン名を設定する画面に遷移しますので、お好みのドメインを入力して使用可能か調べるボタンをクリックしまして、問題ないようでしたらドメインの登録をクリックします。

f:id:AkihitoIwasawaTambourine:20201022183448p:plain

ちょっとした注意なのですが、この設定したドメインは一度設定すると自分では変更出来ず、変更する場合は問合せする必要があります。

自分の開発環境ならなんでもいいですが、僕の場合は案件のサンドボックス環境のドメインに自分の名前を入れてしまうというミスをしました。。

こちらも開発環境ではあるから問題ないとは言っていただいたのですが、本番環境だとしたら結構しんどいことになってたかと思いますので、実務の場合は一度ドメインは相談して決めるのがいいかと思います。

登録処理をしますと以下のような画面が表示されます。

f:id:AkihitoIwasawaTambourine:20201023112233p:plain

反映まで少し時間がかかるようです。数分で反映されますのでちょこっと休憩しましょう。

メールでお知らせするとのことですが、メールを確認しなくても少ししますと以下のような画面が表示されますので、ログインボタンをクリックします。

設定したドメインに変更されたトップ画面に遷移されますので、ここまで完了したらカスタムコンポーネントが認識されています。

f:id:AkihitoIwasawaTambourine:20201023112303p:plain

JSでアップロードしたデータを取得

画面のコーディングは完了しましたので、実際の処理を実装していきます。

SalesForce(以降、SF)側にアップロードする処理自体はApexで行いますので、JS側ではApex側に渡すための選択したアップロードのファイルを取得したり、フロント側のバリデーションの処理などを実装してます。

まずは全体のコードを確認しましょう。以下のフォルダ配下にありますcsvtest.jsに以下の変更を加えてください。

force-app/main/default/lwc/csvtest/csvtest.js

// Lightning Web Componentから @wire, @api をインポート
import { LightningElement, api } from 'lwc';
// ShowToastEvent(ポップアップメッセージ)をインポート
import {ShowToastEvent} from 'lightning/platformShowToastEvent';
// Apex Classの定義
import insertCsvData from "@salesforce/apex/csvUploder.insertCsvData";


export default class CsvUploader extends LightningElement {

  // アップロードファイル
  @api file = null;
  // ファイル名(画面表示用)
  @api fileName;
  // 送信フラグ
  @api isSend = false;
  @api isLoaded = false;
  // ファイルの中身
  data;

  // ファイルを選択すると発火
  handleCsvUpload(event) {
    // 選択したアップロードファイルを取得
    this.file = event.detail.files;

    // ファイル名を取得
    this.fileName = this.file[0].name;
    // ファイルが選択されたらボタンをアクティブ化
    this.isSend = this.file;

    // FileReaderオブジェクトの生成
    const fileReader = new FileReader();

    // ファイルの読み込みが完了したら実行
    fileReader.onloadend = () => {
      // 読み込み結果を設定
      this.data = fileReader.result;
    }

    // ファイルを読み込み
    fileReader.readAsText(this.file[0]);
  }

  // csvアップロード処理
  handleUpload() {

    // アップロードファイルを選択していないと送信させない
    if (!this.file) {
      return
    }
    // ローディング表示をtrue
    this.isLoaded = true;

    Promise.resolve().then(() => {
      return new Promise((resolve, reject) => {

        // ファイルを改行で分割しフィールドごとに配列で取得
        const rows = this.data.split(/\r\n|\n/);
        console.log(rows);

        // データをjson形式で取得
        const outputJson = [];
        for(let i=1 ; i<rows.length ; i++) {
          const cols = rows[i].split(',');
          outputJson.push({
            Name: cols[0],
            Site: cols[1],
            BillingState: cols[2],
            Phone: cols[3],
            Type: cols[4],
          });
        }

        // Apexでインポート処理
        insertCsvData({
          jsonData: JSON.stringify(outputJson)
        })
        .then(result => {

          this.data = result;
          resolve(this.data);
          // 成功ポップアップメッセージ表示
          this.dispatchEvent(
            new ShowToastEvent({
              title: 'Success!!',
              message: 'Success!!',
              variant: 'success',
            }),
          );
        })
        // 失敗ポップアップメッセージ表示
        .catch(error => {
          this.dispatchEvent(
            new ShowToastEvent({
              title: 'Error!!',
              message: error,
              variant: 'error',
            }),
          );
        })
      });
    })
    .catch(error => {
      console.log(error)
    })
    // 最終処理
    .finally(() => {
      // input fileを初期化
      this.template.querySelectorAll('lightning-input').forEach(each => {
        each.value = "";
      });
      // アップロードファイルを初期化
      this.file = null;

      // 送信ボタンを非アクティブ化
      this.isLoaded = false;
    });
  }
}

大体はコメントに処理内容を書いてますので、簡単に説明します。

・ファイル選択をしますとhandleCsvUploadメソッドが発火します。csvtest.html<lightning-input>にイベントで定義してます。次に以下の処理が実行されます。

  1. アップロードファイルを取得
  2. 取得ファイルでfileReaderオブジェクトを生成
  3. readAsTextでファイルの中身を文字列で取得

・送信ボタンクリックしますとhandleUploadメソッドが発火します。次に以下の処理が実行されます

  1. ファイルのデータ内容を改行(レコードごと)で分割し、ループを回す
  2. カンマ(フィールドごと)で分割、SFのフィールド名: フィールドの値でJson形式の値を取得
  3. JsonデータをApexへ渡す

インポート処理を実行

インポート処理の前にひとつ確認しておくことがあります。

JSのコードの冒頭にありますimport readCSV from '@salesforce/apex/csvUploder.insertCsvData';です。

これはApexのcsvUploderクラスのinsertCsvDataメソッドをreadCSVで宣言しますという意味です。

ApexとはSalesforceが開発したJavaライクなプログラミング言語になります。

ですので、まだApexクラスもメソッドも作ってないのでファイルを作る必要があります。

command ⌘+shift ⇧+pSFDX: Create Apex Classを選択→csvUploder(適当)を入力してEnterEnterでデフォルトのclassesフォルダ配下にファイルが生成されます。

ファイルが作成されましたらcsvUploder.clsを以下のように編集してください。

force-app/main/default/classes/csvUploder.cls

public with sharing class csvUploder {
    @AuraEnabled
    public static void insertCsvData(String jsonData) {

        // importデータ格納用変数
        List<Account> importData = new List<Account>();
        // 引数のデータをリストで取得
        List<Object> readData = (List<Object>)JSON.deserializeUntyped(jsonData);
        System.debug(readData);
        // リストをループ
        for(Object o : readData) {
            Map<String, Object> m = (Map<String, Object>)o;

            // 項目の値を取得
            Account a = new Account();
            a.Name = (String)m.get('Name');
            a.Site = (String)m.get('Site');
            a.BillingState= (String)m.get('BillingState');
            a.Phone= (String)m.get('Phone');
            a.Type = (String)m.get('Type');

            importData.add(a);
        }
        try {
            insert importData;
        }
        catch(DmlException e) {
            throw e;
        }
    }
}

簡単に説明しますと以下の処理を行っています。

  1. 引数のJsonデータをリストオブジェクトにしてループ
  2. 対象のフィールド名に対してデータをセット
  3. インポート処理

以上でコーディングは完了です。

デプロイして実際にCSVファイルをアップロードしてみるとAccount(取引先)にCSVのデータが挿入されていることが確認出来ます。

以上です。