LWC を作成してモバイルでバーコードリーダー作ってみる

※ これから記載する事項は、私が所属する会社とは一切関係のない事柄です。


今回は[モバイルでのバーコードスキャン](https://developer.salesforce.com/docs/atlas.en-us.mobile_offline.meta/mobile_offline/use_barcodescanner.htm)を利用したコンポーネントを作成してみたいと思います。


## LWC のバーコードスキャンについて

* バーコードスキャナーは下記のアプリケーションで動作します
    * Salesforce Mobile app
    * Mobile Publisher for Salesforce App
    * Mobile Publisher for Experience Cloud
* サポートしているバーコードの種類の一覧は[こちら](https://developer.salesforce.com/docs/atlas.en-us.mobile_offline.meta/mobile_offline/use_barcodescanner_compatibility.htm)
* 使用に関する制限は[こちら](https://developer.salesforce.com/docs/atlas.en-us.mobile_offline.meta/mobile_offline/use_barcodescanner_considerations.htm)



## 作成する LWC の動き

入力ボックスとボタンがセットになったコンポーネントです。
<img width="300" alt="IMG_0030.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2110894/1f8d5229-e636-f575-0c0c-66aae1a270e0.png">

ボタンをクリックすると QR 読み込み画面が表示され、 QR コードをスキャンすることができます。
<img width="300" alt="IMG_0031.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2110894/decc31df-f10f-96b3-ecb6-75f94cbbc87c.png">


スキャンが完了するとスキャンした値が入力ボックスに表示されます。
<img width="300" alt="IMG_0032.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2110894/e479a3ea-d137-6329-d9e6-830e5919bcfa.png">



## 実装のポイント

* [サンプルコード](https://developer.salesforce.com/docs/atlas.en-us.mobile_offline.meta/mobile_offline/use_barcodescanner_example_modern.htm)をもとに作成しました。
* スキャンする際の画面に「QR コードを読み込みましょう！」のような表示をするため[ユーザフェースをカスタマイズ](https://developer.salesforce.com/docs/atlas.en-us.mobile_offline.meta/mobile_offline/use_barcodescanner_ui.htm)しています。
* 「[Single Scanning](https://developer.salesforce.com/docs/atlas.en-us.mobile_offline.meta/mobile_offline/use_barcodescanner_scanning_lifecycle.htm)」モードを想定しています。
* 今回はモバイルのレコードページで利用するので `supportedFormFactor` を設定しています。（これを設定しないと[エラー](https://salesforce.stackexchange.com/questions/277691/expose-lwc-in-mobile-app)が発生しました）
* `scanningOptions` で利用できる値は[こちら](https://developer.salesforce.com/docs/platform/lwc/guide/reference-lightning-barcodescanner-data-types.html)を参照しました。



## 実装したコード

```html:barcodeTest.html
<template>
  <lightning-layout class="x-large" horizontal-align="end">
    <lightning-layout-item flexibility="grow">
      <lightning-input
        type="text"
        label="input"
        variant="label-hidden"
        value={value}
      ></lightning-input>
    </lightning-layout-item>
    <lightning-layout-item>
      <lightning-button-icon
        icon-name="utility:scan"
        variant="brand"
        label="Scan"
        alternative-text="Scan"
        onclick={beginScanning}
      ></lightning-button-icon>
    </lightning-layout-item>
  </lightning-layout>
</template>
```

```js:barcodeTest.js
import { LightningElement, track } from "lwc";
import { getBarcodeScanner } from "lightning/mobileCapabilities";

export default class BarcodeTest extends LightningElement {
    barcodeScanner;
    value;

    connectedCallback() {
        this.barcodeScanner = getBarcodeScanner();
    }

    beginScanning() {
        const backgroundViewHTML = `<header>
        <meta
          name="viewport"
          content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
        />
      </header>
      <html>
        <head>
          <style>
            a:link {
              text-decoration: none;
            }
            a:hover {
              text-decoration: none;
            }
          </style>
        </head>
        <body>
          <h1 align="right"><a href="nimbusbarcodescanner://dismiss">✖</a></h1>
          <h2 align="center">QR コードを読み込みましょう！</h2>
          <h3 align="center">カメラで QR コードを写してください。</h3>
        </body>
      </html>`
        const scanningOptions = {
            barcodeTypes: [this.barcodeScanner.barcodeTypes.QR],
            scannerSize: "XLARGE",
            cameraFacing: "BACK",
            showSuccessCheckMark: true,
            enableBulkScan: false,
            enableMultiScan: false,
            backgroundViewHTML
        };
        if (this.barcodeScanner != null && this.barcodeScanner.isAvailable()) {
            this.value = "";
            this.barcodeScanner
                .scan(scanningOptions)
                .then(this.processScannedBarcodes.bind(this))
                .catch(this.processError.bind(this))
                .finally(() => {
                    this.barcodeScanner.dismiss();
                });
        } else {
            console.log("BarcodeScanner unavailable. Non-mobile device?");
        }
    }

    processScannedBarcodes(barcodes) {
        this.value = barcodes[0].value;
    }

    processError(error) {
        if (error.code == "USER_DISMISSED") {
            console.log("User terminated scanning session.");
        } else {
            console.error(error);
        }
    }

}
```

```xml:barcodeTest.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>*Barcode Test</masterLabel>
    <description>Barcode Test</description>
    <targets>
        <target>lightning__RecordPage</target>
     </targets>
     <targetConfigs>
        <targetConfig targets="lightning__RecordPage">
            <supportedFormFactors>
                <supportedFormFactor type="Small" />
            </supportedFormFactors>
        </targetConfig>
    </targetConfigs>
</LightningComponentBundle>
```