Salesforce DXでLightningアプリの開発をやってみました。
CLIの環境構築とエディタについてはこちら。
DevHub組織への接続
DevHub組織へログインします。
$ sfdx force:auth:web:login -d -a DevHub
コマンドを実行するとブラウザでログインページが表示されるのでいつもどおりログインと認証をすれば完了です。下記コマンドで正しく接続できているか確認できます。
$ sfdx force:org:list
上のコマンドでDevHubというエイリアス名で登録できているので、以後は下記コマンドでログイン可能になります。
$ sfdx force:org:open -u DevHub
オプションの見方
- d : DevHub組織としての登録
- a : エイリアスを登録する際に指定
Salesforce DXのプロジェクト作成
デスクトップにgeolocationという名前のプロジェクトを作成します。
$ cd desktop
$ mkdir app
$ cd app
$ sfdx force:project:create -n geolocation
$ cd geolocation
VS Codeでgeolocationフォルダを表示します。
スクラッチ組織の作成
下記のコマンドでスクラッチ組織を作成できます。スクラッチ組織のエイリアス名は「GeoAppScratch」を指定。
$ sfdx force:org:create -s -f config/project-scratch-def.json -a GeoAppScratch
作成後はDevHub組織でスクラッチ組織の情報にアクセスできます。
スクラッチ組織作成時の重要なオプション
- s : このスクラッチ組織をこのプロジェクトのデフォルト組織にすることを指定します。
これでオブジェクト作成コマンドなどはスクラッチ組織を対象となります。
force:org:listコマンドを確認したところこのようになっていました。
スクラッチ組織にアクセス
下記コマンドでスクラッチ組織を開きます。-sでデフォルト指定しているので組織名は指定不可になっていると思います。
$ sfdx force:org:open
正しくスクラッチ組織にアクセスしているかは組織IDで確認できると思います。マイドメインは自動で登録されました。※DevHub組織との関連性なし。
確認中に気になった点
DevHub組織で作成したカスタムオブジェクトはスクラッチ組織に移行されていませんでした。Sandboxのようには使えないのかもしれません。
カスタム項目の作成
スクラッチ組織でカスタム項目を作成します。
- [取引先] をクリックします。
- [項目とリレーション] セクションで、[新規] をクリックします。
- データ型に [地理位置情報] を選択し、[次へ] をクリックします。
- 次の詳細を入力して、[次へ] をクリックします。
- 項目の表示ラベル: Location (場所)
- 緯度および経度表示の表記法: 小数
- 小数部の桁数: 7
- 項目名: Location (場所)
次の動作確認のため権限は除外します。
Location項目のアクセス権限を付与する権限セットを作成します。
下記のコマンドでユーザに権限セットの割当ができます。
$ sfdx force:user:permset:assign -n Geolocation
※Geolocationは権限セットの名前を指定
無事に割り当てが実行できていました。
スクラッチ組織の変更をローカル環境に反映
下記のコマンドを実行するとスクラッチ組織の変更をローカルのプロジェクトに反映できます。
$ sfdx force:source:pull
変更箇所のみが反映されるようです。ローカルのプロジェクトをみるとlayoutやobjectフォルダが追加されていました。
Gitでバージョン管理
Salesforce DXはGitなどによるバージョン管理を行なうことが前提の仕組みです。GitHubやBitbucketなどありますが、今回はプライベートリポジトリが利用できるBitbucketを使ってみます。
このような感じでリポジトリの新規作成を行いました。
これでssh urlの部分が取得できます。下記のコマンドを実行します。
$ git init
$ git remote add origin <ssh url>
$ git add -A
$ git commit -m "Add custom object and permset"
$ git push origin master
cdコマンドでgeolocationフォルダの一番上に移動している状態で実行しました。
これでローカルのソースコードをBitbucketのリポジトリに保存できました。
注意しないとプロジェクトごとにバラバラになってしまいそうなフォルダの階層管理もDXでは雛形を用意してくれます。またREADME.mdファイルもテンプレートがあるのでこれに合わせていけばよさそうです。
サンプルデータ
組織作成後に一番困るのはサンプルデータの作成です。Salesforce DXではサンプルデータを簡単にインポートできる仕組みが用意されています。
Salesforce組織でテストデータを作成
まずはスクラッチ組織にアクセスします。未ログインの場合はopenコマンドでアクセス可能です。
$ sfdx force:org:open
今回作成するのは取引先レコードです。
データの準備ができたらdataフォルダをプロジェクトに追加します。
$ 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形式でエクスポートされます。インポートする際にJSON形式になっていればいいので、手動で作成したりも可能となっています。
作成したサンプルデータをインポートすれば組織作成後に簡単にテストデータを作成できます。わかりやすくなるように先ほどのデータは削除しました。
インポートは次のコマンドになります。
$ sfdx force:data:tree:import --sobjecttreefiles data/Account.json
スクラッチ組織を確認したところ正常に作成できていました。
こうしたインポート用のサンプルデータもGitリポジトリで管理できるようになります。
入力規則やトリガの追加があるので一度つくったサンプルデータをアップデートしていくのは頻繁に発生すると思います。こうした情報も管理できるようになるのはすごく良さそうです。
地理位置情報アプリケーションの作成
ここまでで開発用組織とサンプルデータ、Gitリポジトリの準備ができました。ここからLightningアプリの開発を行います。
Apex コントローラクラスの作成
次のコマンドでApexクラスを作成できます。
$ sfdx force:apex:class:create -n AccountController -d force-app/main/default/classes
エディタで次のように処理を実装します。 (インデントは適当ですが・・)
保存後に下記のコマンドを実行するとスクラッチ組織にアップロードされます。
$ sfdx force:source:push
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];
}
}
無事に保存できていました。
エラーチェックも当然実施されました。
Lightning コンポーネントの作成
続いてLightning コンポーネントの作成です。下記のコマンドを実行します。
$ sfdx force:lightning:component:create -n AccountListItem -d force-app/main/default/aura
AccountListItemのソースコードはこんな感じ。
【AccountListItem.cmp】
<aura:component>
<aura:attribute name="account" type="Account"/>
<li><a>{!v.account.Name}</a></li>
</aura:component>
【AccountListItem.css】
.THIS {
border-bottom: solid 1px #DDDDDD;
}
.THIS a {
display: block;
padding: 20px;
color: inherit;
}
.THIS a:active {
background-color: #E8F4FB;
}
コーディングはVS Codeから行っています。
AccountList コンポーネントを作成します。
$ sfdx force:lightning:component:create -n AccountList -d force-app/main/default/aura
【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>
【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);
}
})
【AccountList.css】
.THIS {
list-style-type: none;
padding: 0;
margin: 0;
background: #FFFFFF;
height: 100%;
}
AccountLocatorコンポーネントを作成します。
$ sfdx force:lightning:component:create -n AccountLocator -d force-app/main/default/aura
【AccountLocator.cmp】
<aura:component implements="force:appHostable">
<div>
<div>AccountMap goes here</div>
<div>
<c:AccountList/>
</div>
</div>
</aura:component>
【AccountLocator.css】
.THIS {
position:absolute;
height: 100%;
width: 100%;
background: #FFFFFF;
}
.THIS>div {
height: 50%;
}
これでコンポーネントの準備ができました。下記コマンドでスクラッチ組織にプッシュします。
$ sfdx force:source:push
作成したLightningコンポーネントへのアクセスはタブから行います。
このように表示されればひとまずOKです。
スクラッチ組織でタブを作成したのでローカルプロジェクトに持ってきます。
$ sfdx force:source:pull
地図表示処理の実装
Leafletのサイトから最新の安定バージョンをダウンロードします。
Download - Leaflet - a JavaScript library for interactive maps
静的リソースに保存します。
ローカルプロジェクトに反映します。
$ sfdx force:source:pull
試していてちょっとびっくりしたのですがSalesforce DXの機能で管理すればZipファイルの中身がきちんと解凍された状態で取得されました。
AccountMap コンポーネントの作成
AccountMap コンポーネントを作成します。
$ sfdx force:lightning:component:create -n AccountMap -d force-app/main/default/aura
【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>
【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);
}
})
【AccountMap.css】
.THIS {
width: 100%;
height: 100%;
}
【AccountLocator.cmp】
<aura:component implements="force:appHostable">
<div>
<div>
<c:AccountMap />
</div>
<div>
<c:AccountList />
</div>
</div>
</aura:component>
スクラッチ組織にアップロード
$ sfdx force:source:push
Leafletの地図が表示されることを確認できます。
地図にマーカーを追加する Lightning イベントの作成
AccountsLoaded イベントを作成します
$ sfdx force:lightning:event:create -n AccountsLoaded -d force-app/main/default/aura
【AccountsLoaded.evt】
<aura:event type="APPLICATION">
<aura:attribute name="accounts" Type="Account[]"/>
</aura:event>
【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>
【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);
}
})
【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>
【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);
}
}
})
更新を反映します。
$ sfdx force:source:push
マーカーが表示されるようになりました。
アプリケーションの検証
Trailheadにアプリケーション検証用のスクラッチ組織を作成してそちらで検証するのがいいと説明がありました。
開発で使用したスクラッチ組織を使用してテストを行うことももちろんできますが、常に新しいスクラッチ組織で開始することをお勧めします。新しいスクラッチ組織を使用することで、すべてのソースが組織の外部に適切に置かれていることを確認できます。
新しいスクラッチ組織を作成します。
$ sfdx force:org:create -f config/project-scratch-def.json -a GeoTestOrg
ローカルのソースとメタデータをスクラッチ組織に転送します。
$ 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
新しいスクラッチ組織でも問題なく動きました。
今回は基本的な部分なのでその他の部分も覚えないといけないことが多そうです。ひとまずスクラッチ組織の使い方はなんとなく理解できたのでよかったです。(Sandboxみたいに本番組織のコピー組織だと思ってました。)