vue.jsをlightning containerで連携する(実装編)

## 記事構成
記事を二部投稿しています。以下の順番で記録しています。
準備編は[こちら](https://qiita.com/withthenature/items/3e9a9c59e8a79d7511d5)になります。
実際のコードは[こちら]
(https://github.com/EjiOsa/Container_Connect)になります。


#### 1)準備編記事のvue.js編
- vue.jsの生成 → 前処理

#### 2)準備編記事のsalesforce編　前半部
- salesforceの準備 → テスト接続

#### 3)準備編記事のsalesforce編　後半部
- Apexの実装

#### 4)当記事の前半部
- vue.jsの準備 → 実装

1. vuexの準備
2. Vue Router(省略可能)
3. 擬似データの表示
4. salesforceとの接続準備
5. その他のファイルの追記
6. manifest.jsonの追加

#### 5)当記事の後半部
- 仕上げ
- 感想
- 今後の課題

## vue.jsの前処理→実装

### 1.vuexの準備
vuex使用開始のために最低限の実装をします。

- vuex-module-decorators

vue.jsでTypeScriptを使用するためvuex用のパッケージとしてこちらを選択しました。

```zsh
yarn add -D vuex-module-decorators
```


src配下のvuex関連の配置は以下のようになっていると思います。これを

```
src
└── store
    └── index.ts
```
データやオブジェクトごとで1ファイルにしたかったので、こんな感じにしました。

```
src
└── store
    ├── modules
    │   └── Cases.ts // データやオブジェクトごとに作成するファイル
    ├── store.ts // vuex生成
    └── types.ts // interface宣言
```

では、各々の中身です。
まずtypes.tsはinterfaceの宣言用です。
この宣言時にsalesforceから呼び出すオブジェクトの項目名(FIELD NAME)の**大文字小文字**と合わせておきます。

```ts:types.ts
export interface ICaseList {
    CASE_LIST: Array<{ Id: string; Subject: string; Type: string; }>;
}
```
増やしたい場合は下のような要領で増やしていきます。

```ts:types.ts改
export interface ICaseList {
    CASE_LIST: Array<{ Id: string; Subject: string; Type: string; }>;
}

export interface ICounterState {
    COUNT: number;
}
```

次にstore.tsではvuexとして生成します。

```ts:store.ts
import Vue from 'vue';
import Vuex from 'vuex';
import {ICaseList } from './types';

Vue.use(Vuex);

export default new Vuex.Store<ICaseList>({});
```
複数になる場合は以下のように一度まとめてから吐き出します。

```ts:store.ts改
import Vue from 'vue';
import Vuex from 'vuex';
import {ICaseList, ICounterState } from './types';

Vue.use(Vuex);

export interface IRootState { 
    caseList: ICaseList;
    counterName: ICounterState;
}

export default new Vuex.Store<IRootState>({});
```

各オブジェクトをごにょごにょするファイル(ここではCases.ts)となります。
擬似データを突っ込んであります。


```ts:Cases.ts
import {Module, VuexModule, getModule} from 'vuex-module-decorators';
import {ICaseList } from '../types';
import store from '@/store/store';

@Module({store, dynamic: true, namespaced: true, name: 'caseList' })

class CASE extends VuexModule implements ICaseList {
  public CASE_LIST = [ // 擬似データ
    {Id: '1', Subject: 'subject test1', Type: 'new'},
    {Id: '2', Subject: 'subject test2', Type: 'close'},
    {Id: '3', Subject: 'subject test3', Type: 'test'},
  ];

export default getModule(CASE);
```
最後にmain.tsのimportも修正しておきます。

```ts:main.ts
- // import store from './store';
+ import store from './store/store';
```
### 2.Vue Router(省略可能)

ルーティング関連以外は削除していますが、以下のような状態を

```
src
├── App.vue
├── components
│   └── HelloWorld.vue
├── main.ts
├── plugins
│   └── vuetify.ts
├── router
│   └── index.ts
└── views
    ├── About.vue
    └── Home.vue
```
以下のように変更しました。
ルーティングに関しては他の方々の記事が超絶懇切丁寧に説明されているので、ディレクトリ構成のみでその他は割愛します。
今回の記事的には不要なんですが、最終的にはSPAにしたのでなんとなく載せています。

```
src
├── App.vue
├── components
│   ├── pages
│   │   ├── OperationPage.vue //　本投稿では未使用
│   │   └── ReferencePage.vue
│   └── parts
│       ├── CaseDetail.vue // データ表示用のカード
│       ├── DeleteButton.vue // 本投稿では未使用
│       ├── NavigationBar.vue // 本投稿では未使用
│       └── UpdateButton.vue // 本投稿では未使用
├── main.ts
├── plugins
│   └── vuetify.ts
└── router
    └── index.ts
```


### 3.擬似データの表示
先ほど、vuexで作成した擬似データを表示させたいと思います。
データ表示用のCaseDetail.vueに書き込みました。

```vue:CaseDetail.vue
<template>
  <v-container>
    <v-layout>
      <v-flex
      v-for="c in cases" :key="c.Id"
      >
        <v-card
        min-width="380"
        class="ma-2 mp-2"
        outlined
        >
        <v-card-text>
        <div>Case</div>
        <h1 class="display-1 text--primary">
          Name : {{ c.Subject }}
        </h1>
        <p>detail</p>
        <h2 class="text--primary">
          ID : {{ c.Id }}<br>
          Type : {{ c.Type }}
        </h2>
        </v-card-text>
        </v-card>
      </v-flex>
    </v-layout>
  </v-container>
</template>

<script lang="ts">
import {Component, Vue } from 'vue-property-decorator';
import CASE from '@/store/modules/Cases';

@Component
export default class CaseDetail extends Vue {
  public cases = CASE.CASE_LIST;
}
</script>
```
とりあえずデータを受け取って、v-forで配列分の表示をしています。
salesforceのCaseを使用するために、オブジェクト名をCaseにしていますが単数の『case』は予約語になるので『c』にしています。

```vue:ReferencePage.vue
<template>
  <v-container>
    <v-layout
    text-center
    wrap
    >
      <v-flex mb-4>
        <h2>
          データを参照するページ。
        </h2>
        <CaseDetail/>
      </v-flex>
    </v-layout>
  </v-container>
</template>

<script lang="ts">
import {Component, Vue } from 'vue-property-decorator';
import CaseDetail from '../parts/CaseDetail.vue';

@Component({
  components: {
    CaseDetail,
  },
})
export default class ReferencePage extends Vue {
}
</script>
```
ReferencePage.vueで、CaseDetail.vueコンポーネントを表示しています。
ここら辺で `yarn serve ` でどんな表示になっているか確認してみてください。

### 4.salesforceとの接続準備

- lightning-container

salesforceのLightning Containerに接続するにはこのパッケージを導入して、callApexメソッドを使用します。

```
yarn add lightning-container
```
Cases.tsに以下のように追記をします。

```ts:Cases.ts
- // import {Module, VuexModule, getModule} from 'vuex-module-decorators';
+ import {Module, VuexModule, Mutation, Action, getModule} from 'vuex-module-decorators';
import {ICaseList } from '../types';
import store from '@/store/store';
+ import LCC from 'lightning-container';

@Module({store, dynamic: true, namespaced: true, name: 'caseList' })

class CASE extends VuexModule implements ICaseList {
  public CASE_LIST = [
    {Id: '1', Subject: 'subject test1', Type: 'new'},
    {Id: '2', Subject: 'subject test2', Type: 'close'},
    {Id: '3', Subject: 'subject test3', Type: 'test'},
  ];
 // ここから、追記
    @Mutation
    public salesforceResponse(result: any, event: any ): void {
        this.CASE_LIST = result;
    }
    @Action({})
    public salesforce_Case(): object {
        LCC.callApex(
            'CaseController.getCases',
            3,
            this.salesforceResponse,
            {
                buffer: true, // default true
                escape: true, // default true
                timeout: 30000, // default 30000 max 120000
            },
        );
    }
  // ここまで
}
export default getModule(CASE);
```
import部分と、MutationとActionについて追加しています。

callApexの内容は[このドキュメント](https://developer.salesforce.com/docs/atlas.ja-jp.220.0.lightning.meta/lightning/container_npm_callApex.htm)も参照してください。

##### LCC.callApex()
引数は4つです。
1）Apexで宣言したメソッドを文字列で指定します。準備編で宣言したclass名とメソッド名です。
2）1）で書いたメソッドへの引数です。JSON配列を指定されているので、何も渡さない時は{}になると思います。(すいません、試してません)
3）Apexから返ってきたデータの処理です。@Mutationで宣言しています。eventも返ってくるはずですが、undefinedになって操作できませんでした。今後の調査課題です。
ここで、Apexで返ってきたデータをvuexに代入しています。
4）Apexコールの設定とあります。デフォルトのままにしています。[ここらへん](https://developer.salesforce.com/docs/atlas.ja-jp.pages.meta/pages/pages_js_remoting_invoking.htm)にも書いてありますが、深く調査していません。


#### 5.その他のファイルの追記

Salesforceに接続できていることを判別しやすいように、ボタンクリックでデータを変更してみます。

```vue:ReferencePage.vue
<template>
  <v-container>
    <v-layout
    text-center
    wrap
    >
      <v-flex mb-4>
        <h2>
          データを参照するページ。
        </h2>
+       <v-btn @click="changeData">！！！?</v-btn>
        <CaseDetail/>
      </v-flex>
    </v-layout>
  </v-container>
</template>

<script lang="ts">
import {Component, Vue } from 'vue-property-decorator';
import CaseDetail from '../parts/CaseDetail.vue';
+ import Cases from '@/store/modules/Cases';

@Component({
  components: {
    CaseDetail,
  },
})
export default class ReferencePage extends Vue {
+  public changeData() {
+    Cases.salesforce_Case();
+  }
}
```

```vue:CaseDetail.vue
<template>
  ~~省略~~
</template>

<script lang="ts">
import {Component, Vue, Prop } from 'vue-property-decorator';
import CASE from '@/store/modules/Cases';

@Component({
  components: {
  },
})
export default class CaseDetail extends Vue {
- // public cases = CASE.CASE_LIST;
+ get cases() { // 算出プロパティにしないと、データの変更が反映されません。 
    return CASE.CASE_LIST;
  }
}
```
以上で `yarn build` します。

#### 6.manifest.jsonの追加

`yarn build`で生成されたdist内に、manifest.jsonという名称で以下の内容のファイルを作成します。

```json:manifest.json
{
   "landing-pages" : 
    [
        {
         "path": "index.html",
         "apex-controller": "CaseController"
         }
     ]
 }
```
**CaseControllerは、準備編で作成したApexのクラス名になります。**
manifest.jsonは`yarn build`する度に削除（厳密には無い状態で生成）されてしまいます。
圧縮前に毎回追加することを忘れないようにしてください。

manifest.jsonを追加したdistを圧縮して、静的リソースを更新して保存します。
プレビューで表示、ボタンクリックでデータが変更されると思います。

## 仕上げ
salesforceのデータを初期表示できるようにします。
callApexをcreatedで呼び出す方法にしました。

```vue:ReferencePage.vue
<template>
  <v-container>
    <v-layout
    text-center
    wrap
    >
      <v-flex mb-4>
        <h2>
          データを参照するページ。
        </h2>
-        <!-- <v-btn @click="changeSalesforce">!!!?</v-btn> -->
        <CaseDetail/>
      </v-flex>
    </v-layout>
  </v-container>
</template>

<script lang="ts">
import {Component, Vue } from 'vue-property-decorator';
import CaseDetail from '../parts/CaseDetail.vue';
import Cases from '@/store/modules/Cases';

@Component({
  components: {
    CaseDetail,
  },
})
export default class ReferencePage extends Vue {
-  //public changeData() {
+  created() {
    Cases.salesforce_Case();
  }
}
```

Cases.tsも少し変更しています。
冒頭で擬似データを入れていると、一瞬だけ擬似データが顔を出してしまうので、resultが無い場合に擬似データが入るように変えているだけです。

```ts:Cases.ts
import {Module, VuexModule, Mutation, Action, getModule} from 'vuex-module-decorators';
import {ICaseList } from '../types';
import store from '@/store/store';
import LCC from 'lightning-container';

@Module({store, dynamic: true, namespaced: true, name: 'caseList' })

class CASE extends VuexModule implements ICaseList {
  public CASE_LIST = [
+    {Id: '', Subject: '', Type: ''},
-   // {Id: '1', Subject: 'subject test1', Type: 'new'},
-   // {Id: '2', Subject: 'subject test2', Type: 'close'},
-   // {Id: '3', Subject: 'subject test3', Type: 'test'},
  ];
    
    @Mutation
    public salesforceResponse(result: any, event: any ): void {
+      if(result){
        this.CASE_LIST = result;
+      }else{
+      this.CASE_LIST = [
+          {ID: '1', Subject: 'subject test1', Type: 'new'},
+          {ID: '2', Subject: 'subject test2', Type: 'close'},
+          {ID: '3', Subject: 'subject test3', Type: 'test'},
+        ];}
      }
    @Action({})
    public salesforce_Case(): object {
        LCC.callApex(
            'CaseController.getCases',
            3,
            this.salesforceResponse,
            {
                buffer: true, // default true
                escape: true, // default true
                timeout: 30000, // default 30000 max 120000
            },
        );
    }
}
export default getModule(CASE);
```

以上となります。
あとは、Apexへのデータの投げ方とか。それを受けるApexの処理とか。データが返ってこなかった時の処理とか。そんなところを地道に作り込んでいくだけかなと。
ただ正直なところ、ただ繋げただけでベストプラクティスはよく分かりません。


### 感想
salesforceのlightning container接続を試みましたが、細かい部分で躓いたので全体の流れとしての投稿でとても長い記事になってしまいました。最後まで読んでくださりありがとうございます。
情報が少なく様々な方の記事に助けてもらいながら試行錯誤したので投稿しましたが、実際にsalesforceで仕事をしている方達的にはlightning containerの使用用途は低いらしく。また、VisualForceでの接続方法を踏襲している部分もあってlightning containerで検索すると情報が少ないのもそのためなんだろうなと思いました。

### 今後の課題
- callApexでeventが返ってこない。のは仕様なのか？もう少し調査が必要です。
- salesforceでのhistoryモードの実装。
- エラー対処とかは未実装。

なので、記事の状態では実務的には耐えられる水準ではないと思います。