取引先責任者一括登録画面(Aura Component)

#1.完成画面イメージ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332907/6b2ae681-d9c4-97f6-6aa6-558201b40826.png)

#2.関連ソース
##2-1.Service Component設計＆実装（CallApex）
[「4.サービスコンポネット実装」](https://qiita.com/xuwenzhen/items/9d1818fe50847be88ab0) を実施してください。
 
##2-2.Service Component設計＆実装（Event）
[「3.汎用イベント定義」 と 「4.サービスコンポネット実装」](https://qiita.com/xuwenzhen/items/70e38fad380e7478578b) を実施してください。

##2-3.dynamicRowItem Component 作成
```html:dynamicRowItem.cmp
<aura:component >
	<!-- サービスコンポネット定義エリア -->
	<c:fw_EventService aura:id="fw_EventService" />

	<!-- attribute定義エリア -->
	<aura:attribute name="ContactInstance" type="Contact"/>
	<aura:attribute name="rowIndex" type="String"/>
	
	<!-- コンテンツ -->
	<tr class="slds-text-title_caps">
		<td> 
			{!v.rowIndex + 1}
		</td>
		<td>
			<force:inputField value="{!v.ContactInstance.FirstName}"/>
		</td>
		<td>
			<force:inputField value="{!v.ContactInstance.LastName}"/>
		</td>
		<td>
			<force:inputField value="{!v.ContactInstance.Phone}"/>
		</td>
		<td>
			<force:inputField value="{!v.ContactInstance.AccountId}"/>
		</td>
		<td>
			<aura:if isTrue="{!v.rowIndex == 0}">
				<a onclick="{!c.doAddNewRow}">
					<lightning:icon iconName="utility:add" class="slds-icon slds-icon_small" size="small" alternativeText="add"/>
					<span class="slds-assistive-text">Add Icon</span>
				</a>
				
				<aura:set attribute="else">
					<a onclick="{!c.doRemoveRow}">
                        <lightning:icon variant="error" iconName="utility:delete" class="slds-icon slds-icon_small" size="small" alternativeText="icon"/>
                        <span class="slds-assistive-text">Delete Icon</span>
					</a>
				</aura:set>
			</aura:if>
		</td>
	</tr>
</aura:component>
```

```javascript:dynamicRowItemController.js
({
	doAddNewRow : function(component, event, helper){
		let eventService = component.find("fw_EventService");
		eventService.fireCompEvent("AddRowEvt");
	},
	doRemoveRow : function(component, event, helper){
		let eventService = component.find("fw_EventService");
		eventService.fireCompEvent("RemoveRowEvt", {"indexVar" : component.get("v.rowIndex") });
	},
})
```

##2-4.dynamicRow Component 作成
```java:DataServiceController.cls
public with sharing class DataServiceController {
    public class CustomBusinessLogicException extends Exception {}
    
    @AuraEnabled
    public static ReturnValue getContacts(String filter) {
        ReturnValue returnValue = new ReturnValue();
        try{
            List<Contact> contactList;
            if(String.isEmpty(filter)) {
                contactList = [SELECT Id, Name, Email FROM Contact LIMIT 10];
            } else {
                String filterString = '%' + filter + '%';
                contactList = [SELECT Id, Name, Email FROM Contact WHERE Name LIKE :filterString LIMIT 10];
            }
            // 正常終了
            returnValue.isSuccess = true;
            returnValue.contactList = contactList;
        } catch(Exception e){
            // 異常終了
            returnValue.isSuccess = false;
            returnValue.message = 'システムエラーが発生しました。' + e.getMessage();
        }
        return returnValue;
    }
    
    @AuraEnabled
    public static ReturnValue saveContacts(List<Contact> contactList) {
        ReturnValue returnValue = new ReturnValue();
        try {
            // ここの処理は要注意（__rの項目をクリアしないと、Insert処理失敗）
            for (Contact item : contactList) {
                item.Account = null;
            }
            insert contactList;
            
            // 正常終了
            returnValue.isSuccess = true;
        } catch(Exception e){
            // 異常終了
            returnValue.isSuccess = false;
            returnValue.message = 'システムエラーが発生しました。' + e.getMessage();
        }
        return returnValue;
    }
    
    public class ReturnValue {
        @AuraEnabled
        public Boolean isSuccess { get; set; }
        
        @AuraEnabled
        public String message { get; set; }
        
        @AuraEnabled
        public List<Contact> contactList { get; set; }
    }
    
}
```

```html:dynamicRow.cmp
<aura:component Implements="force:appHostable,flexipage:availableForAllPageTypes" controller="DataServiceController">
	<!-- サービスコンポネット定義エリア -->
	<c:fw_CallApexService aura:id="callApexService"/>
	<lightning:notificationsLibrary aura:id="notifLib"/>
	
	<!-- 受信イベント定義エリア -->
	<aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
	<aura:handler name="fw_CompEvent" event="c:fw_CompEvent" action="{!c.doCompEvent}"/>

	<!-- 属性定義エリア -->
	<aura:attribute name="contactList" type="Contact[]"/> 
	
	<!-- コンテンツ -->
	<div class="slds-page-header">
		<h1 class="slds-page-header__title">取引先責任者一括登録</h1>
		<p class="slds-text-body_small slds-line-height_reset">サンプル画面</p>
	</div>
	<div class="slds-page-header">
        <p class="slds-text-body_small slds-line-height_reset">データ</p>
		
			<aura:iteration items="{!v.contactList}" var="item" indexVar="index">
                {!item.FirstName}
				
			</aura:iteration>
	</div>
	<table class="slds-table slds-table_bordered slds-table_cell-buffer"> 
		<thead>
			<tr class="slds-text-title_caps">
				<th scope="col">
					<div class="slds-truncate">No</div>
				</th>
				<th scope="col">
					<div class="slds-truncate" title="First Name">姓</div>
				</th>
				<th scope="col">
					<div class="slds-truncate" title="Last Name">名</div>
				</th>
				<th scope="col">
					<div class="slds-truncate" title="Phone">携帯電話</div>
				</th>
				<th scope="col">
					<div class="slds-truncate" title="Phone">取引先</div>
				</th>
			</tr>
		</thead>
		<tbody>
			<aura:iteration items="{!v.contactList}" var="item" indexVar="index">
				<c:dynamicRowItem ContactInstance="{!item}" rowIndex="{!index}" />
			</aura:iteration>
		</tbody>
	</table>
	<br/>
	<button class="slds-button slds-button_brand" onclick="{!c.doSave}">一括登録</button>
</aura:component>
```

```javascript:dynamicRowController.js
({
	doInit: function(component, event, helper) {
		helper.addRow(component, event);
	},
	doCompEvent: function(component, event, helper) {
		let params = event.getParams();
		switch(params.eventKey) {
			case "AddRowEvt": // 行追加イベント
				helper.addRow(component, event);
				break;
			case "RemoveRowEvt": // 行削除イベント
				helper.removeRow(component, event);
				break;
		}
	},
	doSave: function(component, event, helper) {
        let callApexService = component.find("callApexService");
        callApexService.callApex(
            component,
            "c.saveContacts",
            {"contactList": component.get("v.contactList")},
            function(returnValue){
                if (returnValue.isSuccess) {
                    component.set("v.contactList", []);
                    helper.addRow(component, event);
                    alert('データを保存しました。');
                } else {
                    console.log('サーバ側にシステムエラーを発生しました。: ' + returnValue.message);
                    helper.serverErrorHandler(component, 'サーバ側にシステムエラーを発生しました。: ' + returnValue.message)
                }
            }
        );
	},
})
```

```javascript:dynamicRowHelper.js
({
	addRow: function(component, event) {
		var contactList = component.get("v.contactList");
		contactList.push({
			'sobjectType': 'Contact',
			'FirstName': '',
			'LastName': '',
			'Phone': ''
		});
		component.set("v.contactList", contactList);
	},
	removeRow: function(component, event) {
		var index = event.getParams().eventValue.indexVar;
		var contactList = component.get("v.contactList");
		contactList.splice(index, 1);
		component.set("v.contactList", contactList);
	},
    serverErrorHandler : function(component, message) {
        // サーバー処理異常
        component.find('notifLib').showToast({
            "title": "サーバー処理異常",
            "message": message,
            "variant":"error"
        });
    },
})
```


##2-5.Lightning コンポーネントタブ　作成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/332907/a249ae84-e15f-5e54-f7df-2c4fd91ec4d7.png)