見出し画像

LWC で使える Salesforce の API を使ってコンポーネントを作ってみた。

こんにちは、co-meetingの町田です。

Trailhead をやったり、リファレンスを読んでいた際に、LWC 開発に使えそうな Salesforce の API がいくつかありました。しかし、実際の LWC 開発では使う機会がなく、使い心地を確かめる機会がありませんでした。

ということで、コンポーネントを作って使い心地を確かめてみました。

LWC 開発に使えそうな Salesforce の API

使ったことなくて今後使えそうだなと思ったものは以下の2つです。

これらを使ってコンポーネントを作ってみました。

作ったコンポーネントの概要

「誰がどのページを開いてるか」コンポーネントを作りました。
このコンポーネントは、アプリケーションのユーティリティバーに設置することで、自分の見ているページを別ユーザーに共有できます。

誰がどのページを開いてるかアプリ

続いて、このアプリの機能と使っている要素を解説します。

機能1. 開いているページの共有

この機能は、以下の赤枠に表示されているように、開いているページを共有する機能です。

開いているページの共有

こちらの機能は Platform Event を使って実現しています。

機能2. アラートの送信

この機能は、以下の赤枠のアイコンをクリックすることで、他ユーザーにアラートを送信する機能です。

アラートの送信

アラートが送信されると、他ユーザーのユーティリティバーが開きます。

アラートを送信すると他ユーザーのユーティリティバーが開く

こちらの機能は、Platform Event と Utility Bar Api を使っています。

実際に動いているコード

使い方は、最初にあげた公式のドキュメント通りです。
コード解説はコメントにあるため省略させていただきます🙇‍♂️

LWC

javascript

import { LightningElement, track, wire } from "lwc";
import { subscribe } from "lightning/empApi";
import { CurrentPageReference, NavigationMixin } from "lightning/navigation";
import Id from "@salesforce/user/Id";
import publish from "@salesforce/apex/PlatformEventService.publishViewing";
import { open, EnclosingUtilityId } from "lightning/platformUtilityBarApi";
/**
 * @typedef {{UserId__c: string, Action__c: string, PageReferenceJson__c: string, PageTitle__c: string, CreatedById: string, CreatedDate: string}} ViewingMemberEvent
 */

const channel = "/event/ViewingMemberEvent__e";
const convertViewingUserEvent = (/** @type {ViewingMemberEvent} */ event) => ({
  isNotMe: event.UserId__c !== Id,
  userId: event.UserId__c,
  pageReferenceJson: event.PageReferenceJson__c,
  pageTitle: event.PageTitle__c,
  createdDate: new Date(event.CreatedDate),
  isAlerted: false
});
const handlePublish = (promise) => {
  promise
    .then((res) => {
      console.log("publish result", JSON.stringify(res));
    })
    .catch((err) => {
      console.error("publish error", JSON.stringify(err));
    });
};

export default class ViewingUserList extends NavigationMixin(LightningElement) {
  // 設置されているユーティリティバーのId
  @wire(EnclosingUtilityId) utilityId;

  /**
   * このコンポーネントを開いているユーザーのリスト
   *
   * @type {{userId: string, pageReferenceJson: string, pageTitle: string, createdDate: Date }[]}
   */
  @track
  viewingUserList = [];

  publishTimeout = null;

  currentPageReference;
  @wire(CurrentPageReference)
  setCurrentPageReference(pageReference) {
    console.log(pageReference);
    this.currentPageReference = pageReference;
    
    // ページを移動したら他ユーザーへプラットフォームイベントを送信
    this.publishViewing();
  }

  connectedCallback() {
    handlePublish(
      publish({
        userId: Id,
        action: "Entering",
        pageReferenceJson: JSON.stringify(this.currentPageReference),
        pageTitle: document.title
      })
    );

    subscribe(
      channel,
      -1,
      (/** @type {{data: {payload: ViewingMemberEvent}}} */ event) => {
        const index = this.viewingUserList.findIndex(
          (e) => e.userId === event.data.payload.UserId__c
        );

        console.log("action", event.data.payload.Action__c);
        if (event.data.payload.Action__c === "Entering") {
          // だれかが新しくページを開いた場合、自分が開いているページを送る。
          this.publishViewing();
        } else if (event.data.payload.Action__c === "Viewing") {
          // だれかがページを開いたら、ユーザー情報を更新
          if (index >= 0) {
            this.viewingUserList.splice(index, 1);
          }
          this.viewingUserList.unshift(
            convertViewingUserEvent(event.data.payload)
          );
        } else if (event.data.payload.Action__c === "Alert") {
          // アラートが来たらユーティリティバーを開く
      open(this.utilityId, {
            autoFocus: true
          });

          if (index !== -1) {
            this.viewingUserList[index].isAlerted = true;
          }
        }
      }
    )
      .then((res) => {
        console.log("subscribe result", JSON.stringify(res));
      })
      .catch((err) => {
        console.error("subscribe error", JSON.stringify(err));
      });
  }

  publishViewing() {
    // 連続したページ遷移の場合、最後のページ遷移のみを送信するためにタイムアウトを設定している。
    if (this.publishTimeout) {
      clearTimeout(this.publishTimeout);
    }
    this.publishTimeout = setTimeout(() => {
      handlePublish(
        publish({
          userId: Id,
          action: "Viewing",
          pageReferenceJson: JSON.stringify(this.currentPageReference),
          pageTitle: document.title
        })
      );
    }, 1000);
  }

  handlePageTitleClick(event) {
    const user = this.viewingUserList.find(
      (u) => u.userId === event.target.value
    );
    console.log(
      user,
      user.Id,
      user.pageReferenceJson,
      JSON.parse(user.pageReferenceJson)
    );
    if (user) {
      this[NavigationMixin.Navigate](JSON.parse(user.pageReferenceJson));
    }
  }

  handleSayHiClick() {
    handlePublish(
      publish({
        userId: Id,
        action: "Alert",
        pageReferenceJson: JSON.stringify(this.currentPageReference),
        pageTitle: document.title
      })
    );
  }
}

HTML

<template>
  <template for:each={viewingUserList} for:item="user">
    <div class="user" key={user.userId}>
      <c-user-info user-id={user.userId} is-alerted={user.isAlerted}>
        <template if:true={user.isNotMe}>
          <lightning-icon
            lwc:if={user.isAlerted}
            icon-name="utility:anywhere_alert"
            alternative-text="Alerted"
            size="x-small"
            variant="warning"
          ></lightning-icon>
          <lightning-button-icon
            lwc:else
            class="slds-m-left_x-small"
            icon-name="utility:anywhere_alert"
            alternative-text="Say Hi"
            size="medium"
            title="Say Hi"
            type="button"
            value={user.userId}
            onclick={handleSayHiClick}
            variant="bare"
          ></lightning-button-icon>
        </template>
      </c-user-info>
      <div class="viewing-page">
        <lightning-button
          variant="base"
          label={user.pageTitle}
          title={user.pageTitle}
          onclick={handlePageTitleClick}
          value={user.userId}
        ></lightning-button>
      </div>
    </div>
  </template>
</template>

Apex

public with sharing class PlatformEventService {
  @AuraEnabled
  public static void publishViewing(
    String userId,
    String action,
    String pageReferenceJson,
    String pageTitle
  ) {
    ViewingMemberEvent__e event = new ViewingMemberEvent__e(
      UserId__c = userId,
      Action__c = action,
      PageReferenceJson__c = pageReferenceJson,
      PageTitle__c = pageTitle
    );
    EventBus.publish(event);
  }
}

作ってみた感想

コンポーネント同士つなげる感覚が新しく、Salesforceのプラットフォーム上でできることが色々とわかってきた感じがします。
今後のLWC開発において、Salesforce内のLWCが相互に作用する機能を作るときに、今回の経験が助けになるかな、と思いました。

この記事が気に入ったらサポートをしてみませんか?