この記事では、Aura プログラミングモデルにおけるカスタムモーダル画面の作成方法をご紹介します。
モーダルの作成方法の種類
Aura でモーダル画面を作成するにはいくつかの方法がありますが、今回はよく使うであろう2通りをご紹介します。
それぞれに利点がありますが、実装方法が大きく異なるのでご注意ください。
- 
lightning:overlayLibrary を使った方法
- 動的にモーダル画面を作成し、都度コンポーネントインスタンスの作成と削除を行う
- ヘッダー、ボディ、フッターをコンポーネント化するので、同様のモーダルを大量に作成する場合は再利用性が高まる
 
- 
Salesforce Lightning Design System を使った方法
- 静的なモーダル画面を作成し、SLDS のクラスの付け替えで表示を制御する
- 一つのコンポーネントで完結するので、ちょっとしたモーダルを作りたいときに便利
 
lightning:overlayLibrary を使った方法
ボディとフッターが連携する
通常のモーダルの使い方として、ボディにフォームを配置しフッターに保存ボタンを配置することがあります。
しかし、lightning:overlayLibrary ではボディとフッターが別コンポーネントとなるため、互いに連携する手段が必要です。
例: フッターで「保存」が押されたら、ボディで保存処理をする
今回は アプリケーションイベント を使った連携方法も合わせてご紹介します。
イベントを作成
CustomModalEvent.evt
<aura:event type="APPLICATION" description="Custom Modal Event">
  <aura:attribute type="String" name="type" /> <!-- SAVE/ERROR -->
</aura:event>
フッターを作成
CustomModalFooter.cmp
<aura:component>
  <!-- Private Attributes -->
  <aura:attribute access="private" type="Boolean" name="isDisabled" default="false" />
  <!-- Registered Events -->
  <aura:registerEvent type="c:CustomModalEvent" name="customModalEvent" />
  <!-- Overlay Library -->
  <lightning:overlayLibrary aura:id="overlayLib" />
  <!-- User Interface -->
  <div>
    <lightning:button label="キャンセル" onclick="{!c.onCancelClicked}" disabled="{!v.isDisabled}" />
    <lightning:button label="保存" variant="brand" onclick="{!c.onSaveClicked}" disabled="{!v.isDisabled}" />
  </div>
</aura:component>
CustomModalFooterController.js
({
  onCancelClicked: function(c, e, h) {
    c.find('overlayLib').notifyClose();
  },
  onSaveClicked: function(c, e, h) {
    c.set('v.isDisabled', true);
    $A.get(`e.c:CustomModalEvent`)
      .setParams({
        type: 'SAVE'
      }).fire();
  }
});
ボディを作成
CustomModalBody.cmp
<aura:component>
  <!-- Public Attributes -->
  <aura:attribute access="public" type="String" name="lastName" />
  <aura:attribute access="public" type="String" name="firstName" />
  <aura:attribute access="public" type="String" name="email" />
  <!-- Event Handlers -->
  <aura:handler event="c:CustomModalEvent" action="{!c.onCustomModalEvent}" />
  <!-- Overlay Library -->
  <lightning:overlayLibrary aura:id="overlayLib" />
  <!-- User Interface -->
  <div>
    <lightning:input type="text" label="姓" value="{!v.lastName}" />
    <lightning:input type="text" label="名" value="{!v.firstName}" />
    <lightning:input type="email" label="メール" value="{!v.email}" />
    <lightning:spinner aura:id="spinner" class="slds-hide" variant="brand" size="large" />
  </div>
</aura:component>
CustomModalBodyController.js
({
  onCustomModalEvent: function(c, e, h) {
    const eventType = e.getParams().type;
    if (eventType === 'SAVE') {
      h.showSpinner(c);
      // TODO: ここで値の保存処理をする
      // 保存が完了したらモーダルを閉じる
      setTimeout($A.getCallback(function() {
        h.hideSpinner(c);
        c.find('overlayLib').notifyClose();
      }), 3000);
    }
  }
});
CustomModalBodyHelper.js
({
  showSpinner: function(c) {
    const spinner = c.find('spinner');
    $A.util.removeClass(spinner, 'slds-hide');
  },
  hideSpinner: function(c) {
    const spinner = c.find('spinner');
    $A.util.addClass(spinner, 'slds-hide');
  }
});
CustomModalBody.css
.THIS {
  position: relative;
}
モーダルを呼び出すには...
CustomModalButton.cmp
<aura:component access="global" implements="flexipage:availableForAllPageTypes,forceCommunity:availableForAllPageTypes">
  <!-- Overlay Library -->
  <lightning:overlayLibrary aura:id="overlayLib" />
  <!-- User Interface -->
  <lightning:button variant="brand" label="モーダルを開く" onclick="{!c.onButtonClick}" />
</aura:component>
CustomModalButtonController.js
({
  onButtonClick: function(c, e, h) {
    // モーダルボディのコンポーネントを作成
    const createCustomModalBodyPromise = h.createComponent(c, h, 'c:CustomModalBody', {
      lastName: '林',
      firstName: '恵美',
      email: 'ehayashi@example.com'
    });
    // モーダルフッターのコンポーネントを作成
    const createCustomModalFooterPromise = h.createComponent(c, h, 'c:CustomModalFooter', {});
    Promise.all([createCustomModalBodyPromise, createCustomModalFooterPromise])
      .then(
        $A.getCallback(function([customModalBody, customModaFooter]) {
          c.find('overlayLib').showCustomModal({
            header: '顧客情報の編集',
            body: customModalBody,
            footer: customModaFooter
          });
        })
      )
      .catch(function(reason) {
        console.error(reason);
      });
  }
});
CustomModalButtonHelper.js
({
  createComponent: function(c, h, componentDef, componentAttributes) {
    return new Promise(function(resolve, reject) {
      $A.createComponent(componentDef, componentAttributes, function(
        newComponent,
        status,
        errorMessage
      ) {
        if (status === "SUCCESS") resolve(newComponent);
        else if (status === "ERROR") reject(errorMessage);
      });
    });
  }
});
Salesforce Lightning Design System を使った方法
モーダル本体を作成
CustomModal.cmp
<aura:component access="global">
    
    <!-- Public Attributes -->
    <aura:attribute access="public" type="String" name="header"  />
    <aura:attribute access="public" type="String" name="lastName"  />
    <aura:attribute access="public" type="String" name="firstName"  />
    <aura:attribute access="public" type="String" name="email"  />
    
    <!-- Privte Attributes -->
    <aura:attribute access="private" type="Boolean" name="showModal" default="false" />
    
    <!-- Component Method -->
    <aura:method name="open" action="{!c.onOpenCalled}" />
    
    <!-- User Interface -->
    <div>
        <section role="dialog" tabindex="-1" class="{!'slds-modal' + if(v.showModal, ' slds-fade-in-open', '')}">
            <div class="slds-modal__container">
                <header class="slds-modal__header">
                    <lightning:buttonIcon size="large" class="slds-modal__close" iconName="utility:close" variant="bare-inverse" title="close" alternativeText="Close window" onclick="{!c.onCloseClicked}"/>
                    <h2 class="slds-text-heading_medium slds-hyphenate">{!v.header}</h2>
                </header>
                <div class="slds-modal__content slds-p-around_medium">
                    <lightning:input type="text"  label="姓"     value="{!v.lastName}"/>
                    <lightning:input type="text"  label="名"     value="{!v.firstName}"/>
                    <lightning:input type="email" label="メール" value="{!v.email}"/>
                </div>
                <footer class="slds-modal__footer">
                    <lightning:button label="キャンセル" onclick="{!c.onCancelClicked}" />
                    <lightning:button label="保存" variant="brand" onclick="{!c.onSaveClicked}" />
                </footer>
            </div>
             <lightning:spinner aura:id="spinner" class="slds-hide" variant="brand" size="large" />
        </section>
        <div class="{!'slds-backdrop' + if(v.showModal, ' slds-backdrop_open', '')}"></div>
    </div>
</aura:component>
CustomModalController.js
({
    onOpenCalled: function(c, e, h) {
        c.set('v.showModal', true);
    },
    onCloseClicked: function(c, e, h) {
        c.set('v.showModal', false);
    },
    onCanceClicked: function(c, e, h) {
        c.set('v.showModal', false);
    },
    onSaveClicked:  function(c, e, h) {
        h.showSpinner(c);
        
        // TODO: ここで値の保存処理をする
        
        // 保存が完了したらモーダルを閉じる
        setTimeout($A.getCallback(function() {
            h.hideSpinner(c);
            c.set('v.showModal', false);
        }), 3000);
    },
})
CustomModalHelper.js
({
  showSpinner: function(c) {
    const spinner = c.find('spinner');
    $A.util.removeClass(spinner, 'slds-hide');
  },
  hideSpinner: function(c) {
    const spinner = c.find('spinner');
    $A.util.addClass(spinner, 'slds-hide');
  }
});
モーダルを呼び出すには...
CustomModalButton.cmp
<aura:component access="global" implements="flexipage:availableForAllPageTypes,forceCommunity:availableForAllPageTypes">
  <!-- User Interface -->
  <lightning:button variant="brand" label="モーダルを開く" onclick="{!c.onButtonClick}" />
  <c:CustomModal aura:id="modal" header="顧客情報の編集" lastName="林" firstName="恵美" email="ehayashi@example.com" />
</aura:component>
CustomModalButtonController.js
({
  onButtonClick: function(c, e, h) {
    c.find('modal').open();
  }
});

