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

レコードを表示するための UI の作成

学習の目的

この単元を完了すると、次のことができるようになります。
  • ユーザインターフェース API に対する要求を実行してレコードデータとメタデータを取得する。
  • フォーム要素、レイアウト種別、およびアクセスモードを要求する理由と方法を理解する。
  • 子レコードを要求する理由と方法を理解する。

レコードの取得

ユーザインターフェース API は、UI の表示に必要なものがまとめて提供されるという点でも優れています。UI API に何回 HTTP 要求を実行すれば、Universal Containers レコードを表示するのに必要なオブジェクトメタデータ、レイアウトメタデータ、項目データを取得できるでしょうか? 1 回です。

/ui-api/record-ui/{recordIds}

Record Viewer コードで、要求がどうなっているかを見てみましょう。/client-src/sagas/recordFetcher.js ファイルに移動します。GitHub またはローカルマシン上で見つけることができます。

この行は UI API のターゲット URL を作成します。
let recordViewUrl = action.creds.instanceUrl + '/services/data/v48.0/ui-api/record-ui/' 
  + action.recordId + '?formFactor=' + action.context.formFactor + '&layoutTypes=Full&modes=View,Edit';

要求は 1 つのレコード ID action.recordId を送信します。これは、ユーザが [Recent Items (最近使ったデータ)] リストから選択するレコードです(エンドポイントでは複数のレコード ID がサポートされますが、Record Viewer アプリケーションで要求するのは 1 つだけです)。

応答にはレイアウト情報が含まれます。この情報から、項目がどこに移動し、どの UI セクションにあるかがわかります。また、どのセクションが折りたたまれているか (レイアウトユーザステート) もわかります。

レイアウト情報に何を含めるかを指定するために、要求ではパラメータ formFactor (フォーム要素)、layoutTypes (レイアウト種別)、modes (モード) を使用します。

フォーム要素は項目のレイアウトを変更します。アプリケーションが実行されているハードウェアの種類と一致するフォーム要素を選択します。Large (大) はデスクトップクライアント用のレイアウトです。Medium (中) はタブレット用のレイアウト、Small (小) はスマートフォン用のレイアウトです。大と中のフォーム要素では、セクションは 2 列レイアウトになります。小のフォーム要素では、セクションは 1 列レイアウになります。

レイアウト種別 によって、返される項目の数が決まります。有効なレイアウト種別は、Full (フル) と Compact (コンパクト) です。デフォルトのフルレイアウトには、現在のユーザに割り当てられているページレイアウトからすべての項目が含まれます。コンパクトレイアウトには、現在のユーザに割り当てられているコンパクトレイアウトからすべての項目が含まれます。どちらのレイアウトも [Setup (設定)] で編集できます。レイアウト種別に関係なく、応答には、ユーザにアクセス権がある項目のみが含まれます。

モードは、ユーザが実行しているタスクに対応して Create (作成)、View (表示)、Edit (編集) のいずれかにします。レイアウト情報は、モードに応じて異なります。たとえば、作成モードでは、レイアウトには [System Information (システム情報)] セクション ([Created By (作成者)] や [Last Modified By (最終更新者)] のような項目が含まれる) は含まれません。

Record Viewer アプリケーションは表示モードと編集モードを要求するため、ユーザが [Edit (編集)] をクリックした場合、アプリケーションにはすでに UI を表示するのに必要な情報があります。

ヒント

ヒント

2 つのレコードを結合するクエリを作成せずに子レコードを返すには、childRelationships パラメータを使用します。応答はページ設定され、1 レベルの子リレーションが含まれます。たとえば、次の要求は取引先レコードとその子である商談レコードを返します。

/ui-api/record-ui/001R0000003IG0vIAG?childRelationships=Account.Opportunities

要求の送信とグローバルステートの更新

RecordViewer アプリケーションは REST 要求を UI API エンドポイントに送信し、その情報で Redux ステートを非同期に更新します。それを待機していた React コンポーネントツリーは、ステート変更に反応し、UI を更新します。

Redux saga はアプリケーションの REST 要求を管理します。/ui-api/record-ui/{recordIds} 要求は、recordFetcher.js saga を使用して実行されます。このコードは REST URL を作成し、OAuth アクセストークンをベアラとして使用して要求を発行します。また、X-Chatter-Entity-Encoding ヘッダーを false に設定して、返される JSON 内の特殊文字が HTML エスケープ処理されないようにします。

応答が返され、JSON が正常に解析されると、saga は JSON 応答が含まれる RECEIVE_RECORD アクションを送信して完了します。Redux モデルでは、アクションは、グローバルステートを変更する意図を表明するために発行されます。操作を外部要求の実行、ステートの変更、コンポーネントの更新に分離することで、ゆるやかな結合が維持されます。

Redux では、レデューサーがアクションをインターセプトして使用し、グローバルステートの局面を転換します。この場合、グローバルステートの「レコード」部分を更新するために、record.js レデューサーは RECEIVE_RECORD アクションをインターセプトし、JSON を処理します。
/* /reducers/record.js */
case 'RECEIVE_RECORD':
      return {
        record: recordLayout.getLayoutModel(action.recordId, action.record),
        mode: 'View'
      }

JSON ペイロードは、action.record に保持されます。変更後の新しいステートは、state.record を介してコンポーネントで使用できます。record.js レデューサーからのステートのこの部分は、reducers/index.js のグローバルステートにまとめられます。

JSON 応答の解析とレコードの表示

次は、record.js ステートレデューサーで使用されている recordLayout.getLayoutModel() ヘルパーの内部を見てみましょう。このヘルパーは、/ui-api/record-ui/{recordIds} JSON 内のレコードデータ、レイアウト、オブジェクト情報を調整してデータ構造を作成します。このデータ構造が、アプリケーションの React コンポーネントを駆動するステートになります。

/client-src/helpers/recordLayout.js ファイルの下部では、getLayoutModel() が次の情報を保持するオブジェクトをまとめています。
  • レイアウト情報
  • UI で項目値に加えられた変更を追跡するために使用する editValues の対応付け
  • 手元のレコードに対応する 1 つの objectInfo
  • レコード ID

recordLayout.getLayoutModel() 関数は、レイアウトをループしながらレイアウト情報を処理します。応答の各レイアウトのセクションごとに、getLayoutSectionModel() をコールします。このメソッドは、セクション JSON からセクションヘッダー情報を取得します。次に、セクション内の各行をループしながら、各行内でレイアウトの各項目をループします。

レイアウト項目ごとに、getLayoutItemModel() は次の情報を保持するオブジェクトを作成します。
  • ネストされた値のリスト (住所のような項目には複数の値がある可能性がある)
  • リンク情報 (他のレコードへのリンク)
  • カスタムリンク情報 (外部 URL へのリンク)

各値には、テキストとして表示するもの、表示ラベル、項目メタデータ、編集可能性、および対応する UI API 選択リストの URL (該当する場合) に関する情報が保持されます。

このメソッドでは、選択リスト値、参照、日付、非項目のアイテムには特殊な処理を行うため、いくつか特殊なケースがあります。通常のケースでは、日付以外の項目に基づくレイアウト項目の場合、レコードデータ JSON の対応するエントリから displayValue および value プロパティが抽出されます。
} else if (record.fields[compValue]) {
        var displayValue = record.fields[compValue].displayValue;
        let rawValue = record.fields[compValue].value;
        if (displayValue == null && rawValue != null) {
          displayValue = rawValue.toString();
        }
        values.push(
          {displayValue: displayValue,
           value: rawValue,
           label:component.label,
           field:compValue,
           fieldInfo,
           picklistUrl,
           editableForNew:item.editableForNew,
           editableForUpdate:item.editableForUpdate,
           isNull:displayValue == null})

レイアウトコンポーネントの表示

ここまで学習したように、recordLayout.getLayoutModel() から返されたオブジェクトは、グローバルステートに state.record.record として保存されます。このステートは、UI に対応するコンポーネントを表示するのに使用されます。最上位コンテナの RecordViewerWrapper.js は、state.record.record が使用可能かどうかを確認し、record プロパティ内のネストされた RecordViewer.js コンポーネントに渡します。RecordViewer.js コンポーネント内にネストされた RecordSection.js および RecordRow.js は、そのセクションと行を表示します。

RecordRow.js、特に getViewItemCells() を見てみましょう。行に各セルを表示するとき、正常なパスでは項目のコンポーネントごとに <div> が作成されます。<div> は React で要求されるため、キーを指定します。displayValue を使用して、画面上に表示されるテキストを描画します。
{ item.values.map((component, i) => {
             if (component.displayValue && component.displayValue.length > 0) {
               if (component.fieldInfo.htmlFormatted) {
                 return (
                   <div key={'component' + itemLabel + ',' + i} dangerouslySetInnerHTML={{__html: component.displayValue}}></div>
                 )
               } else {
                 return (
                   <div key={'component' + itemLabel + ',' + i}>{component.displayValue}</div>
                 )
               }
             } else {
               return null
             }
           }
          )}

結果として、アプリケーションの動作は、UI API から返されたレイアウトとオブジェクト情報によって動的に進みます。レイアウトに項目と項目位置をハードコード化する代わりに、UI API を使用して、システム管理者が Salesforce でレイアウトやオブジェクトを更新したら自動的に再配置可能な UI を作成しましょう。