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

Lightning アクションについて

学習の目的

この単元を完了すると、次のことができるようになります。
  • ユーザ入力に基づいて項目に入力し、フィードバックメッセージをユーザに表示する Aura コンポーネントやアクションを作成する。
  • サードパーティシステムと統合する Aura コンポーネントやアクションを作成する。
  • JavaScript カスタムボタンの機能の Lightning 対応の代替法への移行を開始する。

Lightning アクション: スマート、スピーディ、モバイル

これまで、Lightning Experience と Salesforce Classic の両方で機能し、JavaScript ボタンの優れた代替法となるいくつかのソリューションを見てきました。ただし、こうした宣言型やプログラム型のソリューションではすべての使用事例に対処できないことを当社は認識しています。幸い、これらのソリューション以外にも手段があります。ではここで、Lightning コンポーネントをコールするクイックアクション、Lightning アクションをご紹介します。
メモ

メモ

Spring '19 リリース (API バージョン 45.0) では、Lightning Web コンポーネントモデルと従来の Aura コンポーネントモデルの 2 つのプログラミングモデルを使用して Lightning コンポーネントを作成できます。Lightning Web コンポーネントと Aura コンポーネントは、ページ上に共存可能で、同時に使用できます。このコンテンツでは、Aura コンポーネントについて説明します。

Lightning アクションは、既存の Lightning コンポーネントフレームワーク上に構築されます。既存の Aura コンポーネントを簡単にアクションに変換して、Salesforce モバイルアプリケーションや Lightning Experience で使用することができます。

Aura コンポーネントをアクションとして起動可能にするために、force:lightningQuickActionforce:lightningQuickActionWithoutHeader のいずれかのインターフェースをコンポーネントに追加します。1 つ目のインターフェースは、[保存] ボタンと [キャンセル] ボタンのある標準ヘッダーを Lightning アクションのオーバーレイに追加しますが、2 つ目のインターフェースでは追加しません。

Lightning アクション用のもう 1 つの有用なインターフェースは force:hasRecordId で、レコードページから起動時にそのレコードコンテキストをコンポーネントに提供します。Lightning アクションをグローバルクイックアクションとして設定する場合、レコードコンテキストは不要です。ただし、レコードのデータまたはメタデータにアクセスするときは、force:hasRecordId を実装する必要があります。

メモ

メモ

Aura コンポーネントをまだ構築したことがない方は、Lightning Development Center にアクセスして、Aura コンポーネントクイックスタートプロジェクトを実行し、「Aura コンポーネントの基本」モジュールを修了してください。

では、Lightning アクションを詳しく見ていきましょう。次は、JavaScript ボタンの機能のうち、Lightning アクションで代用できるものについて説明します。
  • ユーザ入力に基づいて項目に入力し、データ入力中にフィードバックメッセージを表示する。
  • サードパーティ API と統合する。

ユーザ入力に基づく項目への入力およびフィードバックメッセージのユーザへの表示

現在、JavaScript ボタンを使用して、データを検証または操作し、ユーザがレコードを使って作業するときにフィードバックや指示を表示しているとします。この例では、ユーザがレコードを作成または更新する前に、Lightning コンポーネントでデータをいかに簡単に検証または操作できるかを示します。

サンプル組織に、さまざまな研究プロジェクトの追跡に使用する Case Study (事例) というカスタムオブジェクトを作成しました。新規のテストユーザを追加するときは、そのユーザの名前とメールアドレスを取得します。メールアドレスは当社の主要な連絡手段であるため、正しく入力されるようにしたいと考えています。また、テストユーザの匿名性を確保するために、各ユーザに一意のニックネームを作成することにします。

名前項目とメールアドレス項目を表示して、メールアドレスを検証し、名と乱数を組み合わせてニックネームを自動生成する Lightning アクションを作成していきます。

この Lightning アクションがコールするコンポーネントのコードは、次のとおりです。
  • CreateUser.cmp — アクションを開いたときに表示される Lightning コンポーネント。UI 実装 (テキスト項目、ボタン、アクションのタイトルなど) が含まれます。
  • CreateUserController.js — コンポーネントと、初回読み込み、テキスト入力、ボタンのクリックなど発生したすべての UI イベントをリスンするコントローラ。この役割は、UI イベントを CreateUserHelper.js ヘルパークラスに委任することです。
  • CreateUserHelper.js — パスワード項目およびメール項目の検証など、あらゆるビジネスロジックに対処するコード。

CreateUser.cmp

<aura:component implements="force:lightningQuickActionWithoutHeader,force:hasRecordId">
    <aura:attribute name="user" type="Test_User__c" default="{}"/>
    <aura:attribute name="hasErrors" type="Boolean" description="Indicate whether there were failures or not" />
    <aura:attribute name="caseStudy" type="String" />
    <aura:attribute name="recordId" type="String"/>
    <aura:attribute name="errorFromCreate" type="String"/>

    <!-- <aura:handler name="init" value="{!this}" action="{!c.init}" />  -->

    <force:recordData aura:id="frd" mode="EDIT" layoutType="FULL"/>
    
    <div class="slds-page-header" role="banner">
    	<p class="slds-text-heading--label">Case Study</p>
        <h1 class="slds-page-header__title slds-m-right--small slds-truncate slds-align-left" title="Case Study Title">{!v.caseStudy}</h1>
    </div>
    <br/>
    
    <aura:if isTrue="{!v.hasErrors}">
        <div class="userCreateError">
            <ui:message title="Error" severity="error" closable="true">
                Please review the error messages.
            </ui:message>
        </div>
    </aura:if>

    <div class="slds-form--stacked">
                
        <div class="slds-form-element">
            <label class="slds-form-element__label" for="firstName">Enter first name: </label>
            <div class="slds-form-element__control">
              <ui:inputText class="slds-input" aura:id="firstName" value="{!v.user.First}" required="true" keydown="{!c.updateNickname}" updateOn="keydown"/>
            </div>
        </div>
        
        <div class="slds-form-element">
            <label class="slds-form-element__label" for="lastName">Enter last name: </label>
            <div class="slds-form-element__control">
              <ui:inputText class="slds-input" aura:id="lastName" value="{!v.user.Last}" required="true" />
            </div>
        </div>
        
        <div class="slds-form-element">
            <label class="slds-form-element__label" for="nickname">Enter nickname: </label>
            <div class="slds-form-element__control">
              <ui:inputText class="slds-input" aura:id="nickname" value="{!v.user.Nickname}" required="false"/>
            </div>
        </div>
        
        <div class="slds-form-element">
        	<label class="slds-form-element__label" for="userEmail">Enter user's email:</label>
            <div class="slds-form-element__control">
        		<ui:inputEmail class="slds-input" aura:id="userEmail" value="{!v.user.Email__c}" required="true"/>
            </div>
        </div>
        
        <div class="slds-form-element">
        	<label class="slds-form-element__label" for="userPassword">Enter user's password:</label>
            <div class="slds-form-element__control">
        		<ui:inputSecret class="slds-input" aura:id="userPassword" value="{!v.user.Password__c}" required="true"/>
            </div>
        </div>
        
        <div class="slds-form-element">
        	<ui:button class="slds-button slds-button--neutral" press="{!c.cancel}" label="Cancel" />
        	<ui:button class="slds-button slds-button--brand" press="{!c.saveUserForm}" label="Save User" />
        </div>
    </div>

</aura:component>

CreateUserController.js

({    
    /**
     * Auto generate the username from firstName on tab
     */
    updateNickname: function(component, event, helper) {
        // Update the nickname field when 'tab' is pressed
        if (event.getParams().keyCode == 9) {
        	var nameInput = component.find("firstName");
        	var nameValue = nameInput.get("v.value");
        	var nickName = component.find("nickname");
            var today = new Date();
        	nickName.set("v.value", nameValue + today.valueOf(today));   
        }
    },
 
    /**
     * Capture the Inputs and invoke the helper.save with the input params 
     */
	saveUserForm : function(component, event, helper) {
        var name = component.get("v.user.First");
        var last = component.get("v.user.Last");
        var password = component.get("v.user.Password__c");
        var email = component.get("v.user.Email__c");
        var nickname = component.get("v.user.Nickname");
        
        var passwordCmp = component.find("userPassword");
        var emailCmp = component.find("userEmail");
        
        helper.validatePassword(component, event, helper);
        helper.validateEmail(component, event, helper);

        if (passwordCmp.get("v.errors") == null && emailCmp.get("v.errors") == null) {
            component.set("v.hasErrors", false);
        	helper.save(component, name + " " + last, password, email, nickname);         
        } else {
            component.set("v.hasErrors", true);
        }
    },
    
    cancel : function(component, event, helper) {
        $A.get("e.force:closeQuickAction").fire();
    }
})

CreateUserHelper.js

({  
    save: function(component, name, password, email, nickname) {
        // Create a user record, save it, and close the panel
        var userRecord = {apiName: 'Test_User__c', fields: {}};
        userRecord.fields.Name = {value: name};
        userRecord.fields.Password__c = {value: password};
        userRecord.fields.Email__c = {value: email};
        userRecord.fields.Nickname__c = {value: nickname};
        userRecord.fields.Case_Study__c = {value: component.get("v.recordId")};
        // get the force:recordData and set the targetRecord
        component.find("frd").set('v.targetRecord', userRecord); 
        // invoke saveRecord of force:recordData
        component.find("frd").saveRecord($A.getCallback(function(response) {
            if (component.isValid() && response.state == "SUCCESS") {
                $A.get("e.force:closeQuickAction").fire();
                var toastEvent = $A.get("e.force:showToast");
                toastEvent.setParams({
                	"title": "Success!",
                    "message": "The test user has been created."
                });
                toastEvent.fire();		
                $A.get('e.force:refreshView').fire();
            } else if (response.state == "ERROR") {
                console.log('There was a problem and the state is: '+ response.state);
            }
        }));
    },
    
    validatePassword : function(component, event, helper) {
        var inputCmp = component.find("userPassword");
        var value = inputCmp.get("v.value");
        
        if (value == undefined) {
           inputCmp.set("v.errors", [{message: "You must enter a password."}]);
        } else if (value.length < 7 || value.length > 15) {
            inputCmp.set("v.errors", [{message: "The password is the wrong length (must be <= 15): " + value.length}]);
        } else if (value.search(/[0-9]+/) == -1) {
            inputCmp.set("v.errors", [{message: "The password must contain at least one number."}]);
        } else if (value.search(/[a-zA-Z]+/) == -1) {
            inputCmp.set("v.errors", [{message: "The password must contain at least one letter."}]);
        } else {
            inputCmp.set("v.errors", null);
        }
	},
    
    validateEmail : function(component, event, helper) {
        var inputCmp = component.find("userEmail");
        var value = inputCmp.get("v.value");
        
        if (value == undefined) {
           inputCmp.set("v.errors", [{message: "You must enter an email."}]);
           return;
        }
        
        var apos = value.indexOf("@");
        var dotpos = value.lastIndexOf(".");

        if (apos<1||dotpos-apos<2){
            inputCmp.set("v.errors", [{message: "Email is not in the correct format: " + value}]);
        } else if (value.substring(apos+1, dotpos) != "gmail") {
            inputCmp.set("v.errors", [{message: "Email must be a gmail account: " + value.substring(apos+1, dotpos)}]);
        } else {
            inputCmp.set("v.errors", null);
        }
	}
})

Lightning コンポーネントを作成したら、アクションに割り当てます。Case Study のオブジェクト管理設定で、[ボタン、リンク、およびアクション] に移動して、[新規アクション] をクリックし、以下のパラメータを使用してアクションを設定します。

項目
オブジェクト名 Case Study
アクション種別 Lightning コンポーネント
Lightning コンポーネント c:CreateUser
高さ 500px
表示ラベル Create Test User (テストユーザの作成)
名前 CreateUser

次に、新しい Lightning アクションを Case Study ページレイアウトに追加します。ユーザが [Case Study (事例)] レコードページからこのアクションを起動すると、作成した Lightning アクションが表示されます。

[Create Test User (テストユーザの作成)] Lightning アクションのオーバーレイ

この Lightning アクションの優れた点は、Salesforce モバイルアプリケーションでも機能することです。

Salesforce アプリケーションの [Case Study (事例)] アクション

サードパーティ API の統合

JavaScript ボタンを、サードパーティシステムとのインテグレーションに使用することもあるかと思われます。これを Lightning アクションで代用することは可能でしょうか? はい、可能です。この主な違いは、Lightning アクションを使用したインテグレーションの場合、サーバ側のコントローラを使用する必要があることです。その見返りとして、ログイン情報のセキュリティが強化され、非同期および一括 API コールに Apex を使用できるようになります。

SMS メッセージを送信する Twilio と統合する方法を見てみましょう。この例では、有名人や VIP を対象とする高級旅行事業を運営しているとします。カスタマーサービス担当者がクライアントと連絡を取れるようにする一方で、クライアントの個人情報が漏れることのないようにしたいと考えています。取引先責任者オブジェクトに Lightning アクションを作成して、担当者が取引先責任者の電話番号を表示することなくメッセージを送信できるようにします。

Case Study の例と同様に、Aura コンポーネントとヘルパークラスを作成してから、このコンポーネントを起動するクイックアクションを作成します。

[SMS] Lightning アクションのオーバーレイ

この Lightning アクションは、Aura コンポーネント、JavaScript コントローラ、Apex コントローラで構成されます。この Apex コントローラが、Twilio インテグレーションを処理するライブラリクラスを参照します。

SendTwilioSMS.cmp

<aura:component controller="TwilioSendSMSController" implements="flexipage:availableForAllPageTypes,force:hasRecordId,force:lightningQuickAction" >
   <aura:attribute name="textMessage" type="String" />
   <aura:attribute name="destinationNumber" type="String" />
   <aura:attribute name="messageError" type="Boolean" />
   <aura:attribute name="recordId" type="String" />

      <aura:handler name="init" value="{!this}" action="{!c.init}" />

   <aura:if isTrue="{!v.messageError}">
      <!-- Load error -->
      <div class="userCreateError">
         <ui:message title="Error" severity="error" closable="true">
            Unable to send message. Please review your data and try again.
         </ui:message>
      </div>
   </aura:if>

   <div class="slds-form--stacked">
      <label class="slds-form-element__label" for="instructMsg">Please enter the message (max 160 char) below: </label>
      <br/>
      <div class="slds-form-element__control">
         <ui:inputText class="slds-input" aura:id="message" label="Text Message" value="{!v.textMessage}" required="true" maxlength="160" size="165" />
      </div>
      <div class="centered">
         <ui:button class="slds-button slds-button--brand" press="{!c.sendMessage}" label="Send Message"/>
      </div>
   </div>
</aura:component>

SendTwilioSmsController.js

({
   init : function(component, event, helper) {
      var action = component.get("c.getPhoneNumber");
      action.setParams({"contactId": component.get("v.recordId")});
      action.setCallback(this, function(response) {
         var state = response.getState();
         if(component.isValid() && state == "SUCCESS"){
            component.set("v.destinationNumber", response.getReturnValue());
         } else {
            component.set("v.messageError", true);
         }
      });
      $A.enqueueAction(action);
   },

      sendMessage : function(component, event, helper) {
      var smsMessage = component.get("v.textMessage");
      var number = component.get("v.destinationNumber");
      var recordId = component.get("v.recordId")

      var action = component.get("c.sendMessages");
      action.setParams({"mobNumber": number, "message": smsMessage, "contactId": component.get("v.recordId")});
      action.setCallback(this, function(response) {
         var state = response.getState();
         if(component.isValid() && state == "SUCCESS"){
            $A.get("e.force:closeQuickAction").fire();
            var toastEvent = $A.get("e.force:showToast");
            toastEvent.setParams({
               "title": "Success!",
               "message": "SMS has been sent woo hoo!"
            });
            toastEvent.fire();
         } else {
            component.set("v.messageError", true);
         }
      });
      $A.enqueueAction(action);
   }
})

SendTwilioSmsController.apxc

/*
* Apex controller that currently contains only one method to send sms message
*/
global class TwilioSendSMSController {

   /*
   * This method uses the Twilio for Salesforce library class and method to
   * send the message using the Twilio api.
   */
   @AuraEnabled
      webService static String sendMessages(String mobNumber, String message, Id contactId) {
         System.debug('the mobNumber is: '+ mobNumber + ' and the message is: '+ message + ' and contactId is: ' + contactId);

         if (mobNumber == null) {
            mobNumber = getPhoneNumber(contactId);
         }

         try {
            TwilioRestClient client = TwilioAPI.getDefaultClient();

            Map<String,String> params = new Map<String,String> {
               'To' => mobNumber,
               'From' => '15555551234',
               'Body' => message
               };
            TwilioSMS sms = client.getAccount().getSMSMessages().create(params);
            return sms.getStatus();
         } catch(exception ex) {
            System.debug('oh no, it failed: '+ex);
            return 'failed';
         }
      }

      @AuraEnabled
      public static String getPhoneNumber(Id contactId) {
         Contact currentRecord = [SELECT Phone FROM Contact WHERE Id = :contactId];
         return currentRecord.Phone.replace(' ', '').replace('-', '').replace(')', '').replace('(', '');
   }
}

コンポーネントを作成したら、Lightning アクションを作成します。この時点でアクションを取引先責任者ページレイアウトに追加して、担当者がアクセスできるようにします。

前述のとおり、このアクションの優れた点は、Salesforce モバイルアプリケーションでも使用できることです。送迎サービス業者が空港にいる顧客に連絡しようとする場合、運転手がモバイルデバイスから顧客に簡単に連絡することができます。

Salesforce アプリケーションの [SMS] Lightning アクション

Lightning アクションは、Lightning Experience の未来形のプログラム型アクションです。当社では、この一連のソリューションを、JavaScript ボタンの優れた代替法として考えていただくことを期待しています。

JavaScript ボタンを使用するパートナーアプリケーションを多用しているという方には、当社の多数のパートナーがすでにアプリケーションを Lightning に移行およびアップグレードし始めていることは朗報でしょう。AppExchange では、Lightning Experience 用に更新されたアプリケーションが増えています。

高度な操作

Twilio を試してみたいという方は、Salesforce Github ライブラリ (https://github.com/twilio/twilio-salesforce) で、サードパーティインテグレーションのサンプルに使用した Twilio for Salesforce ライブラリクラスの非管理パッケージを入手できます。

Twilio for Salesforce および Twilio API を使用する前に、Twilio アカウントを作成して電話番号を設定し、アカウントを Salesforce に接続します。詳しい手順については、Github ライブラリの TwilioApi.cls を参照してください。