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

Lightning の地図コンポーネントと Apex の inherited sharing の使用

学習の目的

この単元を完了すると、次のことができるようになります。

  • 継承された共有を使用して Apex コードを保護する。
  • Lightning コンポーネントの新機能を理解する。
  • lightning:map コンポーネントを使用する。
  • 継承された共有を適用するクラスを作成する。

概要

この単元では、Winter '19 の新機能のうち、Apex の継承された共有と lightning:map コンポーネントの 2 つを確認し、実際に試行します。

 

継承された共有を使用した Apex コードの保護

Apex クラスで inherited sharing キーワードを指定できるようになりました。このキーワードでは、クラスがそのコール元のクラスの共有モードを実行できます。inherited sharing を使用することでセキュリティレビューに合格でき、予期しない方法や安全でない方法での特権 Apex コードの使用を防止できます。inherited sharing のある Apex クラスは、Visualforce ページコントローラ、Apex REST サービス、または Apex トランザクションの開始ポイントとして使用する場合、with sharing として実行されます。

共有宣言のない Apex は、デフォルトでは安全ではありません。実行時に with sharing または without sharing モードで実行可能な Apex クラスの設計は高度な手法であり、特定の共有宣言が誤って省略されている場所を特定することは困難な場合があります。明示的な inherited sharing 宣言は意図が明確なため、省略された宣言による曖昧さやセキュリティ分析ツールの誤検出を回避できます。

inherited sharing のある Apex クラスと省略された共有宣言のある Apex クラスには、明確な違いがあります。クラスが Apex トランザクションの開始点として使用されている場合、省略された共有宣言は without sharing として実行されます。一方、inherited sharing ではデフォルトで確実に with sharing として実行されます。inherited sharing として宣言されたクラスが without sharing として実行されるのは、すでに確立されている without sharing コンテキストから明示的にコールされた場合のみです。

この例では、inherited sharing のある Apex クラスとその Apex コードの Visualforce 呼び出しを宣言します。inherited sharing 宣言により、実行ユーザが共有アクセス権を持つ取引先責任者のみが表示されます。この宣言が省略されている場合、宣言の省略による安全でないデフォルトの動作により、ユーザが参照権限を持たない取引先責任者も表示されます。

public inherited sharing class InheritedSharingClass{
   public List<Contact> getAllTheSecrets(){
       return [SELECT Name FROM Contact];
   }
}
<apex:page controller="InheritedSharingClass">
   <apex:repeat value="{!allTheSecrets}" var="record">
       {!record.Name}
   </apex:repeat>
</apex:page>
メモ

メモ

この変更は、Enterprise Edition、Performance Edition、Unlimited Edition、および Developer Edition の Lightning Experience および Salesforce Classic に適用されます。

新規 Lightning コンポーネント

さまざまな新しいコンポーネントを使用することで、すばやくユーザインターフェースを構築できます。次のコンポーネントには、API バージョン 44.0 以降が必要です。

lightning:empApi

lightning:empApi コンポーネントをカスタム Lightning コンポーネントに埋め込み、ストリーミングイベントチャネルへの登録とイベント通知の受信を行います。Lightning プラットフォームの任意のイベントチャネル種別 (プラットフォームイベント、PushTopic、汎用イベント、変更データキャプチャ (開発者プレビュー) イベントのチャネルなど) に登録できます。lightning:empApi コンポーネントは、共有 CometD ベースストリーミング API 接続を使用して、ブラウザで複数のストリーミングアプリケーションを実行できるようにします。

lightning:map

lightning:map コンポーネントは、Google マップを使用して 1 つ以上のロケーションの地図を安全に表示します。マーカーをこのコンポーネントに渡して、地図を表示するロケーションを定義できます。マーカーは、緯度と経度の座標ペアや、住所要素 (市区郡、国、郵便番号、都道府県、町名・番地) のセットになります。

次に、1 つの住所がある地図コンポーネントを示します。

1 つの住所がある lightning:map コンポーネントのスクリーンショット

複数のロケーションを指定すると、地図コンポーネントによって各ロケーションのクリック可能なタイルが作成されます。住所のリストの上部に表示されるタイトルを指定できます。[Google マップで開く] リンクのあるフッターを表示したり、地図の初期ズームレベルを指定したりできます。

その他の地図の画像は、https://lightningdesignsystem.com/components/map/ を参照してください。

lightning:menuDivider

このコンポーネントは、lightning:buttonMenu の子コンポーネントとして使用されます。lightning:menuDivider を使用して、メニュー項目の後に区切り線を作成します。デフォルトでは、区切り線の上下にスペースが追加されます。lightning:menuDividervariant="compact" を使用して、スペースを狭くします。

区切り線のあるボタンメニューのスクリーンショット

lightning:menuSubheader

このコンポーネントは、lightning:buttonMenu の子コンポーネントとして使用されます。lightning:menuSubheader を使用して、メニュー項目のリストにサブヘッダーを作成します。label 属性を使用してヘッダーのテキストを指定します。

サブヘッダーのあるメニューのスクリーンショット

リソース

ハンズオン Challenge への準備

以下のハンズオン Challenge では、lightning:map コンポーネントを実際に試行し、Apex の継承された共有を実装します。作成される地図を興味深いものにするため、少しだけ設定が必要です。 

Out and About Communications では、鉄塔 (基地局) を設置して、遠隔地での携帯電話のサービスエリアを強化しています。最近、米国西部のいくつかの取引先所在地に新しい鉄塔を設置しました。

ハンズオン Challenge に使用する組織を起動し、次のカスタムオブジェクトと 2 つのカスタム項目を作成します。

  • Out and About Communications が所有する米国西部のさまざまな鉄塔に関する情報の保存に使用するカスタムオブジェクトを作成します。
    • 表示ラベル: Tower (鉄塔)
    • 表示ラベル (複数形): Towers (鉄塔)
  • 新しいカスタム項目を作成して、鉄塔と取引先の間に主従関係を確立します。[Towers (鉄塔)] 関連リストを取引先ページレイアウトに追加します。
    • 項目の表示ラベル: State (州)
    • 型: Master-Detail (主従)
    • 項目名: State
    • 子リレーション名: Towers (鉄塔)
  • 新しいカスタム項目を作成して、各鉄塔のロケーションの緯度と経度を入力します。
    • 項目表示ラベル: Location (位置)
    • 項目名: Location (位置)
    • 型: Geolocation (地理位置情報)
    • 緯度および経度表示の表記法: Decimal (小数)
    • 小数点の位置: 6

次に、データを追加します。

  • 地域を表す 2 つの新しい取引先レコードを作成します ([名前] 項目のみが必須)。
    • Utah (ユタ)
    • Idaho (アイダホ)
  • 4 つの新規鉄塔レコードを作成します。
    • 名前: Lightning Ridge
      • 州: Utah (ユタ)
      • 緯度: 40.490684
      • 経度: -110.908727
    • 名前: Craters
      • 州: Idaho (アイダホ)
      • 緯度: 43.555375
      • 経度: -113.70069
    • 名前: Nuckols
      • 州: Idaho (アイダホ)
      • 緯度: 47.516694
      • 経度: -115.939163
    • 名前: Rainbow
      • 州: Utah (ユタ)
      • 緯度: 37.060663
      • 経度: -110.975708

これでオブジェクトの設定は完了です。以下のコードブロックを使用して Challenge を完了します。

  • UtilityClass コードブロック:
public class UtilityClass {
     public static List<sObject> queryObjects(String theObject, List<String> theFields, List<String> theFilters, String sortField, String sortOrder) {
          String theQuery = 'SELECT ' + string.join(theFields, ',');
          theQuery += ' FROM ' + theObject;
          boolean firstFilter = true;
          for (String filter : theFilters) { //loop through the filters
               String clauseToUse = (firstFilter) ? ' WHERE ' : ' AND '; //get the right clause
               filter = filter.trim();
               filter = filter.replaceAll('(\\s+)', ' ');  //remove white spaces
               theQuery += clauseToUse + filter;  //add the filter to the query
               firstFilter = false;  //changes the filter clause
          }
          if(!String.isEmpty(sortField)) {
               theQuery += ' ORDER BY ' + sortField;
               if(!String.isEmpty(sortOrder)) {
                    theQuery += ' ' + sortOrder;
               }
          }
          String theQueryResult = string.escapeSingleQuotes(theQuery);  //escapes the string
          return database.query(theQueryResult);
     }
}
  • TowerMapController コードブロック:
public class TowerMapController {
     @AuraEnabled
     public static List<Tower__c> getAllTowers() {
          String theObject = 'Tower__c';
          List<String> theFields = new List<String>{'Id', 'Name', 'State__r.Name', 'Location__Latitude__s', 'Location__Longitude__s'};
          List<String> theFilters = new List<String>();  //empty filter list
          String sortField = 'Name';
          String sortOrder = 'ASC';
          List<Tower__c> allTowers = UtilityClass.queryObjects(theObject, theFields, theFilters, sortField, sortOrder);
          return allTowers;
     }
}
  • Towermap Lightning コンポーネントコードブロック:
<aura:component implements="flexipage:availableForAllPageTypes" controller="TowerMapController" access="global" >
     <aura:attribute name="mapMarkers" type="Object" access="PRIVATE" />
     <aura:attribute name="markersTitle" type="String" access="PRIVATE" />
     <aura:handler name="init" value="{!this}" action="{!c.handleInit}"/>
     <aura:if isTrue="{!!empty(v.mapMarkers)}" >
          <!-- Create lightning:map here -->
     </aura:if>
</aura:component>
  • コントローラコードブロック:
({
     handleInit: function (component, event, helper) {
          helper.initHelper(component, event, helper);
     }
})
  • ヘルパーコードブロック:
({
     initHelper : function(component, event, helper) {
          helper.utilSetMarkers(component, event, helper);
     },
     utilSetMarkers : function(component, event, helper) {
          let action = component.get("c.getAllTowers");
          action.setCallback(this, function(response) {
               const data = response.getReturnValue();
               const dataSize = data.length;
               let markers = [];
               for(let i=0; i < dataSize; i += 1) {
                    const Tower = data[i];
                    markers.push({
                        'location': {
                             'Latitude' : Tower.Location__Latitude__s,
                             'Longitude' : Tower.Location__Longitude__s
                        },
                        'icon': 'utility:Tower',
                        'title' : Tower.Name,
                        'description' : Tower.Name + ' Tower Location at ' + Tower.State__r.Name
                   });
               }
               component.set('v.markersTitle', 'Out and About Communications Tower Locations');
               component.set('v.mapMarkers', markers);
          });
          $A.enqueueAction(action);
     }
})

retargeting