進行状況の追跡を始めよう
Trailhead のホーム
Trailhead のホーム

サンプルアプリケーションのインストール

学習の目的

この単元を完了すると、次のことができるようになります。
  • Record Viewer サンプルアプリケーションをインストールする。
  • Record Viewer アプリケーションの構造を理解する。
  • Record Viewer アプリケーションでレコードを表示する。

Record Viewer アプリケーションのインストール

コードに入りましょう! ユーザインターフェース API について学ぶために、Record Viewer サンプルアプリケーションをインストールします。Record Viewer アプリケーションを使用して、レコードの作成、参照、更新、削除ができます。これはサンプルアプリケーションであるため、ユーザインターフェース API からの JSON 応答を表示するユーザインターフェースも含まれています。 Record Viewer サンプルアプリケーションで商談レコードを作成します。
ヒント

ヒント

この単元の最後にあるハンズオン Challenge を実行するには、次の手順を完了する必要があります。Trailhead Playground がない場合は、Challenge までスクロールダウンして今すぐ作成してください。

  1. Git がインストールされていない場合は、まずインストールします。
  2. Record Viewer Git リポジトリをコピーします。リポジトリをコピーする方法がわからない場合は、GitHub ヘルプの「Cloning a repository (リポジトリのコピー)」を参照してください。
  3. Record Viewer は Docker コンテナで実行されます。Docker Engine (Community) の安定バージョンをインストールします。アカウントを作成してソフトウェアをダウンロードするように指示されます。
  4. Docker を起動します。
メモ

メモ

Windows の場合は、[Docker Settings (Docker の設定)] で [Shared Drives (共有ドライブ)] を選択し、Record Viewer アプリケーションの複製先となるローカルドライブを指定します。

Record Viewer は接続アプリケーション (API 経由で Salesforce と通信するアプリケーション) です。このアプリケーションは Salesforce プラットフォーム上では実行されませんが、API 経由で接続されます。Salesforce での Record Viewer アプリケーションの認証方法を定義するには、[Setup (設定)] で接続アプリケーションを作成します。

  1. Trailhead Playground で、[Setup (設定)] から、[Quick Find (クイック検索)] ボックスに「App (アプリケーション)」と入力し、[App Manager (アプリケーションマネージャ)] を選択します。
  2. [New Connected App (新規接続アプリケーション)] をクリックし、次のように設定を入力します。
    • 接続アプリケーション名: RecordViewer
    • 連絡先メール: 自身のメールアドレス
  3. [Enable OAuth Settings (OAuth 設定の有効化)] を選択し、次の設定を入力します。
    • コールバック URL: https://localhost:8443/oauth-redirect
    • 選択した OAuth 範囲: データへのアクセスと管理 (api)
  4. [Save (保存)] をクリックします。
  5. [New Connected App (新規接続アプリケーション)] ページで [Continue (続行)] をクリックします。
  6. コンシューマ鍵 (OAuth クライアント ID とも呼ばれる) をコピーして保存します。これは後で必要になります。
Record Viewer が Web ブラウザから Salesforce に HTTP 要求を実行するのを許可するには、Trailhead Playground でクロスオリジンリソース共有 (CORS) を設定します。
  1. [Setup (設定)] から、「CORS」と入力し、[CORS] を選択します。
  2. [New (新規)] をクリックします。
  3. [Origin URL Pattern (オリジンの URL パターン)] に「https://localhost:8443」と入力します。
  4. [Save (保存)] をクリックします。

Record Viewer アプリケーションをビルドしてサーバを起動するには、MacOS ターミナルや Windows コマンドプロンプトなどのコマンドラインインターフェースを使用します。

まず、cd コマンドを使用して、ローカルドライブに複製した RecordViewer ディレクトリにディレクトリを切り替えます。次のパスを、RecordViewer ディレクトリへのパスに置き換えます。たとえば、RecordViewer ディレクトリが Mac のユーザディレクトリにある場合は、cd ~/RecordViewer と入力して
cd /replace/with/path/to/RecordViewer

Docker コマンドを実行します。

docker-compose build && docker-compose up -d
Record Viewer アプリケーションを起動するには、ブラウザで https://localhost:8443 を開きます。
メモ

メモ

Record Viewer アプリケーションは自己署名 SSL 証明書を使用するため、ブラウザから接続が非公開ではないと通知される場合があります。そのままクリックしてアプリケーションを読み込んでも安全です。Chrome では、[Advanced (詳細)][Proceed to the site (このサイトにアクセスする)] をクリックします。Firefox では、[Advanced (詳細)] をクリックし、例外を追加します。Internet Explorer では、[Continue to this website (このサイトの閲覧を続行する)] をクリックします。Safari では、[Show Details (詳細を表示)][visit this website (この Web サイトにアクセスする)] をクリックします。

メモ

メモ

localhost で接続が拒否された場合は、マシンの IP アドレス (例: https://192.168.34.34:8443) を使用します。CORS オリジン URL パターンにもこの値を使用します。

Record Viewer に Salesforce への要求実行を許可するには、Salesforce のログイン URL「https://login.salesforce.com」と、接続アプリケーションから保存したコンシューマ鍵を入力し、[Login (ログイン)] をクリックします。Record Viewer アプリケーションの [Login (ログイン)] 画面。

Record Viewer アプリケーションに Salesforce 組織へのアクセス権限を付与するには、ユーザ名が正しいことを確認し、[Allow (許可)] をクリックします。[Allow Access (アクセスを許可しますか?)] 画面。

メモ

メモ

この後、ユーザは Salesforce からログアウトされます。アプリケーションを再認証するには、ログインページ https://localhost:8443/login を読み込みます。Salesforce のログイン URL「https://login.salesforce.com」と、接続アプリケーションから保存したコンシューマ鍵を入力します。

Record Viewer アプリケーションの使用

Record Viewer アプリケーションを使用して、Salesforce レコードの表示、編集、作成、削除ができます。Record Viewer はユーザインターフェース API を使用し、Salesforce レコードに対してこれらの操作を実行します。また、ユーザインターフェース API からの JSON 応答を簡単に表示することもできます。

Record Viewer でレコードを表示する前に、比較できるように Salesforce でレコードを表示しましょう。
  1. ブラウザで RecordViewer 接続アプリケーションを作成するのに使用した Salesforce 組織を開きます。
  2. [Accounts (取引先)] をクリックして、[New (新規)] をクリックします。
  3. [Account Name (取引先名)] に「Universal Containers」と入力します。
  4. [Phone (電話)] に 「206 555 1212」と入力し、[Save (保存)] をクリックします。
  5. 新規レコードのレコード詳細ページで、[Details (詳細)] をクリックします。
Lightning Experience の取引先レコードの詳細
今度は、Record Viewer アプリケーションで同じレコードを表示しましょう。
  1. ブラウザで、https://localhost:8443 を開きます。
  2. [Recent Items (最近使ったデータ)] で Universal Containers をクリックします。
    メモ

    メモ

    Universal Containers が表示されない場合は、ページを更新します。

Record Viewer アプリケーションにレコード詳細ページが表示されます。Record Viewer アプリケーションの取引先レコードの詳細。

すでに説明したように、UI API の優れた機能として、Salesforce 組織でのメタデータの変更が応答に反映されます。そこで、Salesforce 組織の取引先レイアウトから項目を削除し、Record Viewer でどうなるかを確認しましょう。

Universal Containers には電話番号がありますが、レコード詳細ページには [Fax] 項目もあります。Fax 機を持っている人はもういないので、ページレイアウトから削除しましょう。

  1. Trailhead Playground で、[Setup (設定)] から、「Object (オブジェクト)」と入力し、[Object Manager (オブジェクトマネージャ)] を選択します。
  2. [Account (取引先)] | [Page Layouts (ページレイアウト)] | [Account Layout (取引先レイアウト)] をクリックします。
  3. ページレイアウトエディタで、[Fax] 項目をクリックして削除し、[Save (保存)] をクリックします。

[Fax] 項目が選択されたページレイアウトエディタ。

次に Record Viewer アプリケーションに戻り、[View Record (レコードを表示)] をクリックして Universal Containers レコードを再読み込みします。できました! [Fax] 項目が消えました。しかも 1 行のコードも記述していません。[Fax] 項目のない Record Viewer アプリケーションの取引先レコードの詳細。

今回は、レイアウトから項目を削除しました。実際のシナリオでは、システム管理者はレイアウトを変更した後にユーザに通知する必要はありません。アプリケーションが反応するだけです!

Record Viewer アプリケーションの構造

ユーザインターフェース API を操作する場合、HTTP 要求を実行可能なものであれば、どの Web フレームワークまたは言語でも使用できます。Polymer、React、Angular、Vue、iOS、Android など、最適なテクノロジを使用してください。

Record Viewer アプリケーションは、React、Redux、および Redux-Saga を使用して構築された単一ページアプリケーションです。これらのテクノロジについては、アプリケーションのファイル構造を確認しながら簡単に説明します。React や Redux に慣れていなくても問題ありません。概念だけ把握してください。

ここでの目標は、ユーザインターフェース API リソースにアクセスする方法とその応答を処理する方法について学習することです。こうしたタスクの実行方法はフレームワークや言語ごとに少し異なりますが、主要な概念は同じであり、API の動作も同じです。

Record Viewer アプリケーションは以下を使用して構築されています。

  • Pug — サーバ側テンプレートから HTML ページを生成します。
  • React — カスタムコンポーネントを作成し、そのコンポーネントを組み合わせてユーザインターフェースを作成するための JavaScript ライブラリ。
  • Redux — React アプリケーションのステートをストア (store) という単一の不変な JavaScript オブジェクトに保持します。React コンポーネントは、アクション (action) と呼ばれる JavaScript オブジェクトをディスパッチし、このアクションがレデューサー (reducer) によってインターセプトされます。レデューサーは JavaScript 関数で、Redux ステートを更新するための入力としてアクションを使用します。
  • Redux-Saga — レデューサーと同様に、saga は JavaScript 関数で、レデューサーがアクションをインターセプトするときに実行されます。saga は、ユーザインターフェース API への非同期要求を管理します。非同期操作の終了時に、saga がアクションをディスパッチし、そのアクションをインターセプトしたレデューサーがストアを更新します。saga は、コールバックが複雑に絡み合うのを防止します。
  • Node.js — JavaScript アプリケーション用のサーバ側ランタイム。

次は、こうした抽象的な概念が実世界ではどう機能するのかを確認しましょう。お気に入りの IDE を起動し、GitHub からコピーした RecordViewer フォルダを開きます。 VS Code での Record Viewer アプリケーションのファイル構造。actions フォルダ、components フォルダ、containers フォルダ、helpers フォルダ、reducers フォルダ、sagas フォルダ。

最初に、views フォルダに移動しましょう。ここには、Pug HTML テンプレートが保持されています。このアプリケーションには 3 つのビューがあります。Salesforce ログイン情報を入力したログインページ、アプリケーションのメインコンテナであるレコードビューページ、できれば見ずにすませたいエラーページです。4 つ目の Pug テンプレートは、OAuth 認証リダイレクトです。

正常に認証されると、recordView.pug テンプレートが、root.js ファイルに定義されている renderRoot を読み込み、コールします。これ以降、Record Viewer アプリケーションは、この単一 Web ページに読み込まれたクライアント側コードを実行します。
<!-- /views/recordView.pug -->

doctype html
html
  block head
    title Record Viewer
    // Metadata and style sheet links removed to keep sample short

    script(type='text/javascript', src='https://code.jquery.com/jquery-2.0.3.min.js')
    script(type='text/javascript', src='root.js')

  body
    .slds-grid.slds-wrap
      nav.slds-col.slds-size--1-of-1
        .slds-page-header
          p.slds-page-header__title.slds-truncate.slds-align-middle
            a(href='/recordView') Record Viewer
          p.slds-page-header__info powered by User Interface API
    #root
    script.
      $( document ).ready(function() {
        let rootNode = document.getElementById("root");
        renderRoot("#{targetUrl}", "#{accessToken}", "#{recordId}", rootNode);
      });
/client-src/root.js ファイルがアプリケーションのブートストラップを実行します。ルートの RecordViewerWrapper コンポーネントが Redux ステートレデューサーのルートおよび Saga ミドルウェアと接続されます。レデューサーはステートを更新します。saga はユーザインターフェース API への要求を実行し、結果を保持するアクションをディスパッチします。
/* Excerpt from /client-src/root.js */

// Root renderer for record viewer.
global.renderRoot = function(instanceUrl, accessToken, recordId, rootNode) {
  const sagaMiddleware = createSagaMiddleware()
  const store = createStore(rootReducer, {login:{instanceUrl, accessToken}, record:{recordId}}, applyMiddleware(sagaMiddleware));
  sagaMiddleware.run(rootSaga);

  ReactDOM.render(
    <Provider store={store}>
      <div>
        <RecordViewerWrapper />
      </div>
    </Provider>,
    rootNode);
}
RecordViewerWrapper.js React コンポーネントは containers フォルダ内にあります。このコンポーネントは、RecordViewer コンポーネントをラップする、最上位の接続コンポーネントです。mapStateToProps 関数は、Redux ステートを RecordViewer のプロパティにバインドする方法を示します。
/* Excerpt from /client-src/containers/RecordViewerWrapper.js */

// Presentational Component that uses state to decide how to
// construct the RecordViewer.
const mapStateToProps = (state) => {
  if (state.record.record) {
    return {
      screen: 'RECORD',
      record: state.record.record,
      headerRecordId: state.header.recordId,
      mode: state.record.mode,
      context: state.context,
      prevMode: state.record.prevMode,
      creds: state.login,
      picklists : state.picklists,
      depGraph: state.depGraph,
      rawjson: state.rawjson,
      error: state.error
    }
  } else if (state.record.recordId) {
    return {
      screen: 'FETCH_RECORD',
      recordId: state.record.recordId,
      mode: 'View',
      context: state.context,
      creds: state.login,
      rawjson: state.rawjson,
      error: state.error
    }
  }
 
  // Additional mappings removed to shorten sample
RecordViewWrapper には mapDispatchToProps 関数もあります。これは、Redux アクションを React プロパティに対応付けて、React コンポーネントがさまざまなイベントに対してアクションをディスパッチできるようにします。再確認しましょう。アクションはレデューサーへの入力として使用されます。レデューサーはアプリケーションのステートを変更する関数です。
/* Excerpt from /client-src/containers/RecordViewerWrapper.js */

const mapDispatchToProps = (dispatch) => {
  return {
    onFetchRecord: (creds, id, context) => {
      dispatch(actions.fetchRecord(creds, id, context))
    },
    onRecordClick: (creds, id, context) => {
      dispatch(actions.fetchRecord(creds, id, context))
    },
    onViewRecordClick: (creds, id, context) => {
      dispatch(actions.fetchRecord(creds, id, context))
    },
    onFormFactorSelect: (formFactor, recordId) => {
      dispatch(actions.updateFormFactor(formFactor, recordId))
    },

   // Additional mappings removed to shorten sample
最後に、connect() メソッドはこれら 2 つの対応付けをシンプルな RecordViewer コンポーネントに接続します。
/* Excerpt from /client-src/containers/RecordViewerWrapper.js */

const RecordViewerWrapper = connect(
  mapStateToProps,
  mapDispatchToProps
) (RecordViewer)

export default RecordViewerWrapper

アクション、レデューサー、saga はそれぞれ (わかりやすく) actionsreducers、および sagas フォルダ内にあります。

アクションは、単純オブジェクトで、必須の type プロパティとそれ以外の任意のプロパティがあります。type プロパティがアクションを識別します。すべてのアクションは /actions/index.js ファイルに定義されています。
/* Excerpt from /client-src/actions/index.js */

export const fetchEntities = (creds) => {
  return {
    type: 'FETCH_ENTITIES',
    creds
  }
}

export const receiveEntities = (entities) => {
  return {
    type: 'RECEIVE_ENTITIES',
    entities,
    receivedAt: Date.now()
  }
}
では、fetchEntities アクションの正常なパスを辿ってみましょう。rootSaga.js ファイルはすべての saga を Record Viewer アプリケーションに登録します。アクションがストアにディスパッチされ、その typetakeEvery 関数の文字列と一致すると、関数は対応する saga を起動します。アクションの type FETCH_ENTITIES によって entitiesFetcher saga が発生します。
/* /client-src/sagas/rootSaga.js */

import { takeEvery } from 'redux-saga'

import recentItemsFetcher from './recentItemsFetcher'
import recordFetcher from './recordFetcher'
import recordDeleter from './recordDeleter'
import recordUpdater from './recordUpdater'
import entitiesFetcher from './entitiesFetcher'
import recordCreator from './recordCreator'
import cloneDefaultsFetcher from './cloneDefaultsFetcher'
import createDefaultsFetcher from './createDefaultsFetcher'
import picklistsFetcher from './picklistsFetcher'
import depGraphValueUpdater from './depGraphValueUpdater'

export default function* rootSaga() {
  yield takeEvery('FETCH_RECORD', recordFetcher)
  yield takeEvery('FETCH_RECENT_ITEMS', recentItemsFetcher)
  yield takeEvery('DELETE_RECORD', recordDeleter)
  yield takeEvery('SAVE_RECORD', recordUpdater)
  yield takeEvery('FETCH_ENTITIES', entitiesFetcher)
  yield takeEvery('CREATE_RECORD', recordCreator)
  yield takeEvery('FETCH_CREATE_DEFAULTS', createDefaultsFetcher);
  yield takeEvery('FETCH_CLONE_DEFAULTS', cloneDefaultsFetcher);
  yield takeEvery('FETCH_PICKLISTS', picklistsFetcher);
  yield takeEvery('UPDATE_DEP_GRAPH_FIELD_VALUE', depGraphValueUpdater)
}
entitiesFetcher.js saga はユーザインターフェース API に対する要求を実行し、API がサポートするオブジェクトのリストを取得します。このUI API リソースは /ui-api/object-info です。完全な URL の作成方法については、後の単元で説明します。Salesforce REST API に慣れている方は、同じパターンがすべてで使用されているので、これをすでに理解しています。他の Salesforce REST API と同様に、ユーザインターフェース API でも OAuth 2.0 を使用します。ベアラトークンは、Authorization ヘッダーで渡されます。X-Chatter-Entity-Encoding ヘッダーは false に設定されるため、応答はエンコードされません。
/* /client-src/sagas/entitiesFetcher.js */

import { call, put } from 'redux-saga/effects'

import { receiveEntities } from '../actions'

export default function* entitiesFetcher(action) {

  let mruUrl = action.creds.instanceUrl + '/services/data/v47.0/ui-api/object-info'

  let req = {
    method: 'GET',
    headers: {
      'Authorization' : 'Bearer ' + action.creds.accessToken,
      'X-Chatter-Entity-Encoding': false}
    }

  try {
    const response = yield call(fetch, mruUrl, req)
    const responseJson = yield response.json()
    yield put(receiveEntities(responseJson))
  } catch(err) {
    console.error('Describe sobjects error: ' + JSON.stringify(err))
  }
}
yield put(receiveEntities(responseJson)) という行を見てください。前の /actions/index.js からのコードサンプルで receiveEntities を探します。receiveEntities アクションがありますか? そうです。UI API の操作が正常に終了すると、saga は JSON 応答を保持する receiveEntities アクションを送信します。entities.js レデューサーはこのアクションをインターセプトし、入力として使用して Redux ステートを更新します。
/* /client-src/reducers/entities.js */

const entities = (state = {sobjects: []}, action) => {
  switch (action.type) {
    case 'RECEIVE_ENTITIES':
      return {
        sobjects: action.entities.objects,
        receivedAt: action.receivedAt
      }
    default:
      return state
  }
}

export default entities

レデューサーが更新するのは Redux ステートの一部です。変更が行われた後、ステートのそのビットにサブスクライブしている React コンポーネントが自動的に更新されます。

Record Viewer アプリケーションでは、HTML を作成する React コンポーネントは components フォルダにあります。これらのコンポーネントは、アプリケーションのユーザインターフェースの各部分を定義します。CreateableEntitiesList.js コンポーネントは、Record Viewer アプリケーションでメニューを表示する UI を定義します。このメニューには、/ui-api/object-info へのコールで返されるサポート対象オブジェクトのリストが含まれます。RecordViewer アプリケーションはエンティティを受信して CreateableEntitiesList コンポーネントで表示します。作成するオブジェクトのリストが含まれる新規レコードポップアップメニュー。
ヒント

ヒント

RecordViewer コードでは頻繁にエンティティ (entity) という用語が使用されます。これはオブジェクトを意味します。たとえば、CreateableEntitiesList は、作成可能なオブジェクトのリストと解釈できます。言い換えれば、このリストのオブジェクトからレコードを作成できます! これらのオブジェクトはユーザインターフェース API のサポート対象です。

React では、コンポーネントから成る UI を作成します。つまり、ネストされた子コンポーネントで構成される親コンポーネントを作成します。アプリケーションを作成するときは、機能を単純なコンポーネントに分解し、それらを組み合わせてより複雑なコンポーネントを作成します。

components フォルダ内のコンポーネント名を見ると、Record.js、RecordButton.js、RecordSection.js など、Record Viewer アプリケーションはレコードを処理するアプリケーションであることがわかるでしょう。コンポーネントコードを見ると、ネストされた子コンポーネントがあります。たとえば、RecordSection コンポーネントにはネストされた RecordRow コンポーネントが含まれます。
/* Excerpt from /client-src/components/RecordSection.js */

// Component that displays a Record section.
const RecordSection = ({section, error, editValues, picklists, onFieldValueUpdate, allowEdit, index, objectInfo, onEditDepGraph, uiMode, recordView}) => {

  return (
    <tbody>
      { section.useHeading &&
       <tr>
         <td colSpan="4" key={'sectionHeading' + index} className="slds-text-heading--small slds-p-left--medium slds-p-top--small slds-p-bottom--medium">
           {section.heading}
         </td>
       </tr>
      }
      {section.rows.map((row, i) =>
        <RecordRow
          key={'sectionRow' + index + ',' + i}
          allowEdit={allowEdit}
          uiMode={uiMode}
          picklists={picklists}
          onFieldValueUpdate={onFieldValueUpdate}
          error={error}
          editValues={editValues}
          row={row}
          sectionIndex={index}
          rowIndex={i} 
          objectInfo={objectInfo}
          recordView={recordView}
          onEditDepGraph={onEditDepGraph}/>
      )}
   </tbody>
  );
}
実際に IDE に移動して Record コンポーネント (Record.js) を見てみましょう。<RecordSection> コンポーネント、<DepGraphEditor> コンポーネント、および複数の <RecordButton> コンポーネントが含まれています。

最後に大切なことがあります。helpers フォルダにはいくつかの JavaScript ヘルパー関数が保持されています。これらの関数はユーザインターフェース API からの応答を Record Viewer の UI に使用されるデータモデルに変換します。recordLayout.js コードは、レイアウトの内部データモデルを作成し、depGraphHelper.js コードは連動選択リストエディタを作成します。これらのファイルについては、後で説明します。