Salesforce DX 使ってみた（その１）

# はじめに

## Salesforce DX とは

[TRAILHEAD：Salesforce DX の設定](https://trailhead.salesforce.com/ja/trails/sfdx_get_started/modules/sfdx_app_dev/units/sfdx_app_dev_setup_dx) から抜粋。

_Salesforce DX は、開発ライフサイクル全体を合理化する新しいツールセットです。Salesforce DX によって、チーム開発とコラボレーションが向上し、自動テストと継続的インテグレーションが促進され、リリースサイクルがより効率的で俊敏になります。
Salesforce DX は、単に新しいツールセットというだけにとどまらず、情報源を組織からバージョン管理システム (VCS) へと移行する新しい開発パラダイムです。Salesforce DX によって、開発の焦点が、組織ベースの開発からアーティファクトベースの開発へと移ります。_

# 目的

TRAILHEADを元に、Salesforce DX の基本的な機能を使ってみて、操作方法を学ぶ。
細かい部分は、[TRAILHEAD：Salesforce DX を使用したアプリケーション開発](https://trailhead.salesforce.com/ja/trails/sfdx_get_started/modules/sfdx_app_dev) を参照。

# 前提

+ Windows10 64 ビット
+ GitHub

# 構成

1. ハブ組織作成
2. Salesforce CLIをインストール
3. ハブ組織にログイン
4. プロジェクト作成
5. スクラッチ組織作成
6. スクラッチ組織に変更を加える -1-
7. 変更の取得 -1-
8. gitでリポジトリ作成
9. git init ~ git push
10. サンプルデータ作成
11. データ格納ディレクトリ作成
12. Apexコントローラクラス作成
13. Lightning コンポーネントの作成
14. スクラッチ組織に変更を加える -2-
15. 変更の取得 -2-
16. 追加の Lightning コンポーネントの作成
17. スクラッチ組織に変更を加える -3-
18. 変更の取得 -3-
19. AccountMap コンポーネントの作成
20. 地図にマーカーを追加する Lightning イベントの作成
21. git push
22. アプリケーションの検証

## 1. ハブ組織作成
+ ハブ組織とは、組織を管理する組織のこと。
+ 有料の組織（Enterpriseとか）で有効化すると使える。
+ とりあえず試したいなら、[Dev Hub トライアル組織](https://developer.salesforce.com/promotions/orgs/dx-signup) が30日間無料で使える。

ということで、無料組織にサインアップ。

# 2. Salesforce CLIをインストール
[Windows 64 ビット版](https://sfdc.co/sfdx_cli_win64) をインストール。インストールしたら、`sfdx`して、以下を確認できれば、OK。

```shell:
$ sfdx
Usage: sfdx COMMAND

Help topics, type sfdx help TOPIC for more details:

 force    tools for the Salesforce developer
 plugins  create a new sfdx-cli plugin
 update   update the sfdx CLI
```

## 3. ハブ組織にログイン
```shell:
# ハブ組織に接続（と同時にデフォルト組織、エイリアス設定）
$ sfdx force:auth:web:login -d -a DevHub

# ハブ組織を開く
$ sfdx force:org:open -u DevHub

```

以下、おまけ。

```shell:
# Sandboxとかも接続可能
$ sfdx force:auth:web:login -r https://test.salesforce.com -a FullSandbox

# 接続組織のリスト
$ sfdx force:org:list --verbose
```

## 4. プロジェクト作成
```shell:
$ sfdx force:project:create -n geolocation
```

## 5. スクラッチ組織作成
```shell:
$ cd geolocation
$ sfdx force:org:create -s -f config/project-scratch-def.json -a GeoAppScratch
```

## 6. スクラッチ組織に変更を加える -1-
```shell:
$ sfdx force:org:open
```

[TRAILHEAD：アプリケーション作成の準備](https://trailhead.salesforce.com/ja/trails/sfdx_get_started/modules/sfdx_app_dev/units/sfdx_app_dev_create_app) の カスタム項目の作成 を参照。

## 7. 変更の取得 -1-
```shell:
$ sfdx force:source:pull
```

## 8. gitでリポジトリ作成
省略。

## 9. git init ~ git push
```shell:
$ git init
$ git remote add origin <url>
$ git add .
$ git commit -m "commit_1"
$ git push origin master
```

## 10. サンプルデータ作成
```shell:
$ sfdx force:org:open
```

[TRAILHEAD：アプリケーション作成の準備](https://trailhead.salesforce.com/ja/trails/sfdx_get_started/modules/sfdx_app_dev/units/sfdx_app_dev_create_app) の サンプルデータの作成 を参照。

## 11. データ格納ディレクトリ作成
```shell:
$ mkdir data
$ sfdx force:data:tree:export -q "SELECT Name, Location__Latitude__s, Location__Longitude__s FROM Account WHERE Location__Latitude__s != NULL AND Location__Longitude__s != NULL" -d ./data
```

中身はこんな感じ。

```json:Account.json
{
    "records": [
        {
            "attributes": {
                "type": "Account",
                "referenceId": "AccountRef1"
            },
            "Name": "Marriott Marquis",
            "Location__Latitude__s": 37.785143,
            "Location__Longitude__s": -122.403405
        },
        {
            "attributes": {
                "type": "Account",
                "referenceId": "AccountRef2"
            },
            "Name": "Hilton Union Square",
            "Location__Latitude__s": 37.786164,
            "Location__Longitude__s": -122.410137
        },
        {
            "attributes": {
                "type": "Account",
                "referenceId": "AccountRef3"
            },
            "Name": "Hyatt",
            "Location__Latitude__s": 37.794157,
            "Location__Longitude__s": -122.396311
        }
    ]
}
```

インポートする時は、以下。

```shell:
$ sfdx force:data:tree:import --sobjecttreefiles data/Account.json
```

## 12. Apexコントローラクラス作成

```shell:
$ sfdx force:apex:class:create -n AccountController -d force-app/main/default/classes
```

```java:AccountController.cls
public with sharing class AccountController {
@AuraEnabled
public static List<Account> findAll() {
return [SELECT Id, Name, Location__Latitude__s, Location__Longitude__s
   FROM Account
   WHERE Location__Latitude__s != NULL AND Location__Longitude__s !=
          NULL
   LIMIT 50];
  }
}
```

```shell:
$ sfdx force:source:push
```

## 13. Lightning コンポーネントの作成

```shell:
$ sfdx force:lightning:component:create -n AccountLocator -d force-app/main/default/aura
```

```html:AccountLocator.cmp
<aura:component implements="force:appHostable">
  <div>
    <div>AccountMap goes here</div>
    <div>AccountList goes here</div>
  </div>
</aura:component>
```

```css:AccountLocator.css
.THIS {
  position:absolute;
  height: 100%;
  width: 100%;
  background: #FFFFFF;
}

.THIS>div {
  height: 50%;
}
```

```shell:
$ sfdx force:source:push
```

## 14. スクラッチ組織に変更を加える -2-
```shell:
$ sfdx force:org:open
```

[TRAILHEAD：Salesforce CLI を使用した地理位置情報アプリケーションの作成](https://trailhead.salesforce.com/ja/trails/sfdx_get_started/modules/sfdx_app_dev/units/sfdx_app_dev_build_app) の Lightning コンポーネントを開くタブの設定 を参照。


## 15. 変更の取得 -2-
```shell:
$ sfdx force:source:pull
```

## 16. 追加の Lightning コンポーネントの作成
```shell:
$ sfdx force:lightning:component:create -n AccountListItem -d force-app/main/default/aura
```

```html:AccountListItem.cmp
<aura:component>
  <aura:attribute name="account" type="Account"/>
  <li><a>{!v.account.Name}</a></li>
</aura:component>
```

```css:AccountListItem.css
.THIS {
    border-bottom: solid 1px #DDDDDD;
}

.THIS a {
    display: block;
    padding: 20px;
    color: inherit;
}

.THIS a:active {
    background-color: #E8F4FB;
}
```

```shell:
$ sfdx force:lightning:component:create -n AccountList -d force-app/main/default/aura
```

```html:AccountList.cmp
<aura:component controller="AccountController">

    <aura:attribute name="accounts" type="Account[]"/>
    <aura:handler name="init" value="{!this}" action="{!c.doInit}" />

    <ul>
    <aura:iteration items="{!v.accounts}" var="account">
        <c:AccountListItem account="{!account}"/>
    </aura:iteration>
    </ul>

</aura:component>
```

```javascript:AccountListController.js
({
    doInit : function(component, event) {
        var action = component.get("c.findAll");
        action.setCallback(this, function(a) {
          component.set("v.accounts", a.getReturnValue());
        });
        $A.enqueueAction(action);
    }
})
```

```css:AccountList.css
.THIS {
    list-style-type: none;
    padding: 0;
    margin: 0;
    background: #FFFFFF;
     height: 100%;
}
```

```html:AccountLocator.cmp
<aura:component implements="force:appHostable">
    <div>
        <div>AccountMap goes here</div>
        <div>
          <c:AccountList/>
        </div>
    </div>
</aura:component>
```

```shell:
$ sfdx force:source:push
```

## 17. スクラッチ組織に変更を加える -3-
```shell:
$ sfdx force:org:open
```

[TRAILHEAD：取引先の地図表示の作成](https://trailhead.salesforce.com/ja/trails/sfdx_get_started/modules/sfdx_app_dev/units/sfdx_app_dev_create_visuals) の スクラッチ組織への Leaflet のダウンロードとインストール を参照。

## 18. 変更の取得 -3-
```shell:
$ sfdx force:source:pull
```

## 19. AccountMap コンポーネントの作成

```shell:
$ sfdx force:lightning:component:create -n AccountMap -d force-app/main/default/aura
```

```html:AccountMap.cmp
<aura:component>

    <aura:attribute name="map" type="Object"/>

    <ltng:require styles="/resource/leaflet/leaflet.css"
        scripts="/resource/leaflet/leaflet.js"
        afterScriptsLoaded="{!c.jsLoaded}" />

    <div id="map"></div>

</aura:component>
```

```javascript:AccountMapController.js
({
   jsLoaded: function(component, event, helper) {

      var map = L.map('map', {zoomControl: false}).setView([37.784173, -122.401557], 14);
      L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}',
        {
              attribution: 'Tiles © Esri'
        }).addTo(map);
      component.set("v.map", map);
  }
})
```

```css:AccountMap.css
.THIS {
   width: 100%;
   height: 100%;
}
```

```html:AccountLocator.cmp
<aura:component implements="force:appHostable">

    <div>
        <div>
            <c:AccountMap />
        </div>

        <div>
            <c:AccountList />
        </div>
    </div>

</aura:component>
```

```shell:
$ sfdx force:source:push
```

## 20. 地図にマーカーを追加する Lightning イベントの作成

```shell:
$ sfdx force:lightning:event:create -n AccountsLoaded -d force-app/main/default/aura
```

```html:AccountsLoaded.evt
<aura:event type="APPLICATION">
    <aura:attribute name="accounts" Type="Account[]"/>
</aura:event>
```

```html:AccountList.cmp
<aura:component controller="AccountController">
    <aura:registerEvent name="accountsLoaded" type="c:AccountsLoaded"/>
    <aura:attribute name="accounts" type="Account[]"/>
    <ltng:require styles="/resource/leaflet/leaflet.css" scripts="/resource/leaflet/leaflet.js" afterScriptsLoaded="{!c.doInit}" />
    <aura:handler name="init" value="{!this}" action="{!c.doInit}" />

    <ul>
    <aura:iteration items="{!v.accounts}" var="account">
        <c:AccountListItem account="{!account}"/>
    </aura:iteration>
    </ul>

</aura:component>
```

```javascript:AccountListController.js
({
    doInit : function(component, event) {
        var action = component.get("c.findAll");
        action.setCallback(this, function(a) {
            component.set("v.accounts", a.getReturnValue());
            var event = $A.get("e.c:AccountsLoaded");
            event.setParams({"accounts": a.getReturnValue()});
            event.fire();
        });
    $A.enqueueAction(action);
    }
})
```

```html:AccountMap.cmp
<aura:component>

  <aura:attribute name="map" type="Object"/>
  <aura:handler event="c:AccountsLoaded" action="{!c.accountsLoaded}"/>
  <ltng:require styles="/resource/leaflet/leaflet.css" scripts="/resource/leaflet/leaflet.js" afterScriptsLoaded="{!c.jsLoaded}" />
  <div id="map"></div>

</aura:component>
```

```javascript:AccountMapController.js
({
    jsLoaded: function(component, event, helper) {

        var map = L.map('map', {zoomControl: false}).setView([37.784173, -122.401557], 14);
        L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}',
             {
                 attribution: 'Tiles © Esri'
             }).addTo(map);
        component.set("v.map", map);
    },

    accountsLoaded: function(component, event, helper) {

        // Add markers
        var map = component.get('v.map');
        var accounts = event.getParam('accounts');
        for (var i=0; i<accounts.length; i++) {
            var account = accounts[i];
            var latLng = [account.Location__Latitude__s, account.Location__Longitude__s];
            L.marker(latLng, {account: account}).addTo(map);
        }  
    }
})
```

```shell:
$ sfdx force:source:push
```

## 21. git push

```shell:
$ git add .
$ git commit -m "commit_2"
$ git push origin master
```

## 22. アプリケーションの検証

```shell:
# テスト組織作成
$ sfdx force:org:create -f config/project-scratch-def.json -a GeoTestOrg

# テスト組織にソースをpush
$ sfdx force:source:push -u GeoTestOrg

# テスト組織のユーザに権限セットを付与
$ sfdx force:user:permset:assign -n Geolocation -u GeoTestOrg

# データをインポート
$ sfdx force:data:tree:import -f data/Account.json -u GeoTestOrg

# 環境を開く
$ sfdx force:org:open -u GeoTestOrg
```

# 最後に

上手くいけばこんな感じ。次回に続く...

![UI.png](https://qiita-image-store.s3.amazonaws.com/0/261260/0b4209c2-0ac8-ca4e-3313-ce53ccb13f55.png)