目的
- チュートリアルを学習しながら、ポイントとなると思った知識をメモする
- 初めてLWCでの開発を行う事になった時、やるべき事のイメージを大まかに掴む
- 開発するに当たって必要な、大まかな全体像を把握する
- やりたいアレは、LWCではどうやるのか、基本的なところをメモ
ゴール
- LWCでの開発をする・コードを読むに当たって、何をどういう順番でやればいいのか、あたりをつけられるようにする
- 細かい言語仕様や機能は一旦省く
- devhubは今回は省く
内容
開発を始める
- 新規の場合は、ローカル上に、VSCodeでプロジェクトを新規作成した上で、接続したプロジェクトで認証する
- すでにプロジェクトがある場合は、別で管理しているgitからソースを取得して、組織に認証する(DevHubを利用していないため、本来のフローではない)
プロジェクトを作る
- VSCodeでコマンドパレットから以下を実行する
sfdx: Create Project
組織とローカル上のコードをつなぐ
- 接続する環境で認証する
sfdx: Authorize an org
- どの組織に繋ぐのは、コマンド実行後に立ち上がったブラウザで、ユーザを指定してログインする事で指定する
デプロイして、画面に反映する
- まず、デプロイする対象のコンポーネントを新規作成
SFDX: Create Lightning Web Component
コンポーネント名を指定して作成する
デプロイはファイルを右クリックして行う
- 画面に出す
-- コンポーネントを作成したら、メタで以下の設定変更を行う、そうすると、Salesforceからページ編集のカスタムコンポーネントに、候補として表示されるようになる
-- どのページビューの時に利用可能なようにしたいかは、メタのtargetsで設定している
<isExposed>true</isExposed> <- trueにする
<- ターゲットを設定する
<targets>
<target>lightning__AppPage</target>
<target>lightning__RecordPage</target>
<target>lightning__HomePage</target>
</targets>
開発する
概要
- コンポーネントの作成
新規のコンポーネントは、VSCode上でプロジェクトフォルダを右クリックし作成することができる
基本となるファイルは以下の4ファイル
xxXx.html コンポーネントの画面側
xxXx.js コントローラ
xxXx.js-meta.xml このコンポーネントを、Salesforce上のどの画面に利用するかを指定する。ここを指定しないと、Salesforce上から利用することができない
xxXx.css スタイルの定義
-
デプロイ
作成したコンポーネントは、ファイル(コンポーネントレベルなどを指定しまとめてデプロイした方がいい)を右クリックしてデプロイすることができる
デプロイ後は、Salesforce上で設定から編集ページを開くと、Customのなかに表示されるようになる。あとは標準コンポーネントと同じように扱うことができる -
プロジェクトツリー
lwc内に必要なコンポーネントを配置する
force
-main
-default
-application
-classes
-contentassets
-flexipages
-layouts
-lwc <ーこの中にコンポーネントを配置する
-objects
-permissionsets
-staticresources
-tabs
よく使いそうなコンポーネント
とりあえず、チュートリアルを見た所、この辺りのコンポーネントを使えるようになっておけば、
・ 画面からのイベントを取得し、
・ サーバ側に検索を行い、その結果をリストで表示したり
・ 他のコンポーネントとの通信
ができるようになる
利用できるコンポーネントの全量は以下のドキュメントから確認できる
https://developer.salesforce.com/docs/component-library/bundle/lightning-card/documentation
1 lightning-card
<lightning-card title="Lightning Web Component" icon-name="custom:custom14">
- コンポーネントの全体を覆う要素だと思う
- icon-name="standard:people" 属性はアイコンを人にするように指定している
2 lightning-input
<lightning-input label="Name" value={greeting} onchange={handleGreetingChange}></lightning-input>
- 入力項目
3 lightning-map
<lightning-map map-markers={mapMarkers} zoom-level="12"></lightning-map>
- マップを表示
4 class="slds-m-around_medium"
<div class="slds-m-around_medium">
- 要素をdivでまとめるが、関連リスト風のレイアウトにしたい時、パディングを程よい感じにしてくれる
5 lightning-record-form
<lightning-record-form
object-api-name="Contact"
record-id={supervisorId}
layout-type="Compact">
</lightning-record-form>
- コンパクトビューとして表示する
このビューは、例えば以下のように、条件毎の表示切り替えで使える
<テンプレート IF 1>
<コンパクトビュー1>
</テンプレ>
<テンプレート IF 2例えばエラー>
<コンパクトビュー2エラ時>
</テンプレ>
-
template if:true={bear.data}>
の、templateの後に着くif: のような物をディレクティブと言う
6 テンプレートの中にテンプレート
読み込みが終わったら表示する/読み込み結果がエラーだったら表示する、などをifディレクティブで実現可能
<template if:true={bear.data}>
<template if:true={bear.error}>
7 サーバとの通信1 Apexのリスト取得
<lightning-layout multiple-rows="true" pull-to-boundary="small">
<template for:each={bears} for:item="bear">
<lightning-layout-item key={bear.Id} size="3" class="slds-p-around_x-small">
<lightning-card title={bear.Name} class="bear-tile">
<div class="slds-p-horizontal_small">
<div class="slds-media">
<div class="slds-media__figure">
<img src={appResources.bearSilhouette} alt="Bear profile" class="bear-silhouette"/>
</div>
<div class="slds-media__body">
<p class="slds-m-bottom_xx-small">{bear.Sex__c}</p>
<--! 表示する項目 -->
- lightning-layoutが全体のリスト
その中にtemplateのfor:eachデコレータで、繰り返し表示する -
<template for:each={bears} for:item="bear">
bearsのリストを繰り返し取得し、各レコードをbearに格納する - 表示するものは、
<lightning-layout-item key={bear.Id} size="3" class="slds-p-around_x-small">
bearから内容を取得する
itemには、key属性を設定する、このkeyは反復コンテキストで一意にする(SFIDを設定しておけばいい)
このitemをくり返し表示する
8 サーバとの通信2 ワイヤードApexのリスト取得
<lightning-layout multiple-rows="true" pull-to-boundary="small">
<template for:each={bears.data} for:item="bear"> <- bearsで取得していた箇所をbears.dataに変更
<lightning-layout-item key={bear.Id} size="3" class="slds-p-around_x-small">
<lightning-card title={bear.Name} class="bear-tile">
<div class="slds-p-horizontal_small">
<div class="slds-media">
<div class="slds-media__figure">
<img src={appResources.bearSilhouette} alt="Bear profile" class="bear-silhouette"/>
</div>
<div class="slds-media__body">
<p class="slds-m-bottom_xx-small">{bear.Sex__c}</p>
基本的には画面側はあまり変わりがない、 サービス側の実装は後述
よく使いそうな操作の方法
1 プレースホルダ
<h2>Hello {greeting}!</h2>
2 jsー>画面へのデータの送り方(データをバインドする)
trackライブラリをインポートしておく
import { LightningElement, track } from 'lwc';
@trackアノテーション(正しくはデコレータ)で変数を定義し、値を入れる
@track greeting = 'Trailblazer';
js上で、変数の値が置き換わるたびに画面が再描画され、プレースホルダの値も更新される
3変更イベントで実行し、画面->jsへのデータ送り
onchange属性で、js側で定義されたメソッドを指定
<lightning-input label="Name" value={greeting} onchange={handleGreetingChange}></lightning-input>
メソッド
handleGreetingChange(event) {
this.greeting = event.target.value;
}
eventで発生したdomをとり、event.target.属性、で属性値を取得する
3 get
trackと同じように画面に値を表示するが、中身は関数として定義できる
get capitalizedGreeting() {
return 結果;
}
get のプロパティはリアクティブではない(@trackと違う)
そのため、変数の値の変化を問わず、表示の度に実行される
ー>クラスを関数内で作ると、表示のたびにオブジェクトの生成が必要となってしまうので
プロパティに値を出しておき、関数内ではプロパティを参照するようにするべき
@track greeting = 'Trailblazer';
handleGreetingChange(event) {
this.greeting = event.target.value;
}
<- trackされているgreetingは、値が変更される度にコンポーネントを再描画している
その結果、表示される度に実行されるgetは、関数を実行している
その度に、new Date()するのを避けたいので、退避してプロパティだけをget関数に持たせている
currentDate = new Date().toDateString(); <-この処理をget内から出している
get capitalizedGreeting() {
return `Hello ${this.greeting.toUpperCase()}!`; <-
}
4 サービスとの連携
Lightningデータサービスを利用する
import { getRecord } from 'lightning/uiRecordApi';
受け取る項目を定義
const fields = [
'Bear__c.Name',
'Bear__c.Location__Latitude__s',
'Bear__c.Location__Longitude__s'
];
@wireデコレータを利用し、レコードを取得する
@wire(アダプタ関数, {recordId: '$レコードID', 受け取るフィールド名 Obj.項目}
@wire(getRecord, { recordId: '$recordId', fields })
loadBear({ error, data }) {}
@wireでデコレートされた関数は、コンポーネントが表示されるたびに実行され画面も再描画される
getRecordはlightning/uiRecordApiのデータサービスを利用している
Lightningデータサービスでは、受け取る項目を動的に取得することも可能
getFieldValueをインポートし
import { getRecord, getFieldValue } from 'lightning/uiRecordApi';
スキーマからオブジェクトの項目を取得する
import SUPERVISOR_FIELD from '@salesforce/schema/Bear__c.Supervisor__c'; const bearFields = [SUPERVISOR_FIELD];
データの取得
@wire(getRecord, { recordId: '$recordId', fields: bearFields })
bear;
fieldsにスキーマオブジェクトから取得したフィールドを設定し、redordIdをわたし、データサービスでgetRecordする
bearにデータが格納され、画面から参照ができるようになる
<template if:true={bear.data}>
<lightning-record-form
object-api-name="Contact"
record-id={supervisorId}
layout-type="Compact">
</lightning-record-form>
</template>
項目名取得
フィールドをgetFieldValueで取得する
get supervisorId() {
return getFieldValue(this.bear.data, SUPERVISOR_FIELD);
}
5 現在のレコードIDを取得する
apiをimportしておき以下で取得できるようになる
@api recordId;
6 jsから静的リソースにアクセス
リソースをインポートする
import アダプタ名 from '@salesforce/resourceUrl/アプリケーション名';
import ursusResources from '@salesforce/resourceUrl/ursus_park';
アダプタにパスを追加し、静的リソースへのuriを作る ursusResources +'/img/standing-bear-silhouette.png',
appResources = {
bearSilhouette: ursusResources +'/img/standing-bear-silhouette.png',
};
7 サーバからリストを取得し画面に返す(命令型Apex)
作成済みのapexクラスを読み出すアダプタをimportする
import getAllBears from '@salesforce/apex/BearController.getAllBears';
getAllBearsを実行し、結果を受け等る
結果はリストそのものではなくpromiseで返される
なので以下のように結果を取得する
この取得方法を、命令型Apexと呼ぶ
getAllBears().then(result => {
this.bears = result;
}).catch(error => {
this.error = error;
});
promiseとは https://qiita.com/garchomp_otoka/items/7de9efc78e18e4c7e606
8 サーバからリストを取得する(ワイヤードApex)
サーバとのやり取りを簡略化
import { LightningElement, wire } from 'lwc';
import ursusResources from '@salesforce/resourceUrl/ursus_park';
import getAllBears from '@salesforce/apex/BearController.getAllBears';
export default class BearList extends LightningElement {
@wire(getAllBears) bears; <-データの取得はこの一行だけになる
appResources = {
bearSilhouette: ursusResources +'/img/standing-bear-silhouette.png',
};
}
9 画面読み込みと同時に関数実行
connectedCallback() { this.実行する関数 ここでデータ読み込みのメソッドを実行すればいい}
11 ユーザが入力し終わったら、コールする(デバウンス)
- trackデコレータされたリアクティブなsearchTerm入力項目は、値が入る度に更新をする
-
@track searchTerm = '';
= その時、onchengeでhandleSearchTermChangeを読んでいるため、入力中も実行されつづける
この関数は入力ちで検索リクエストをするため、一文字打つ毎にリクエストが発生してしまう
これをまとめたい
300msの遅延を行う
window.clearTimeout(this.delayTimeout);
const searchTerm = event.target.value;
this.delayTimeout = setTimeout(() => {
this.searchTerm = searchTerm;
}, 300);
12 コンポーネントを外に出す
- コンポーネントを新規作成し、レイアウトと処理を切り出した新しいテンプレートを作る
percialComponent
- 切り出したコンポーネントを、元のレイアウトで利用する
<c-percial-component></c-percial-component>
- キャメルケースは、c-を頭につけたハイフンでつなぐ
13 子コンポーネントにイベントを設定する
子コンポーネントのactionスロットを用意し、ボタンとイベントを設定する
- やることの大筋
- 子コンポーネントのactionスロットを使い、イベントを発生させる要素(ボタン)とイベントを定義
<div slot="actions">
<lightning-button-icon icon-name="utility:search" icon-class="bear-tile-button" variant="bare"
alternative-text="Open record" onclick={handleOpenRecordClick}>
</lightning-button-icon>
</div>
- 子コンポーネントのjsでイベントを受け取り、カスタムイベントを作りdispatchEventに設定する
handleOpenRecordClick() {
const selectEvent = new CustomEvent('bearview', {
detail: this.bear.Id <ー親に渡す引数をここで設定 IDをわたしている
});
this.dispatchEvent(selectEvent);
}
- 親側のコンポーネントで、子コンポーネントの属性に
onbearview={handleBearView}
を追加
以下は親コンポーネントで子コンポーネントを呼び出しているループ
<lightning-layout multiple-rows="true" pull-to-boundary="small">
<template for:each={bears.data} for:item="bear">
<lightning-layout-item key={bear.Id} size="3" class="slds-p-around_x-small">
<c-bear-tile bear={bear} onbearview={handleBearView}></c-bear-tile> <-子コンポーネントに属性を追加
</lightning-layout-item>
</template>
</lightning-layout>
- 子コンポーネントのonbearview属性でhandleBearViewが呼び出されている事になり、親コンポーネントの以下のjsのメソッドが実行される
handleBearView(event) {
// Get bear record id from bearview event
const bearId = event.detail; <ー子コンポーネントからdatailに渡したID
// Navigate to bear record page
this[NavigationMixin.Navigate]({
type: 'standard__recordPage',
attributes: {
recordId: bearId,
objectApiName: 'Bear__c',
actionName: 'view',
},
});
}
this[NavigationMixin.Navigate]({
の部分は、画面遷移を拡張する以下のmixinを使用している箇所で、イベントの受け渡しと直接関係はない
import { NavigationMixin } from 'lightning/navigation';
- こうすると親コンポーネントのjsで定義されている
handleBearView
関数を、子コンポーネントから実行できる
14 他のコンポーネントとの通信
他のコンポーネントにパラメータを送り更新する
-
やることの大筋
用意されているMixinを利用して行う
pub/subでメッセージをやり取りする
現在の画面全体に向けてイベントをパブリッシュし、受け取る側はイベントをサブスクライブしておき、受け取ったら動作させる
その他track,wireは、画面の変更をきっかけに検索や再描画の動作をさせるために利用する -
送信側でやること
送信側が以下のコンポーネントをimportしておく
import { NavigationMixin, CurrentPageReference } from 'lightning/navigation'; <ーアダプタ 現在のページを取得できる
import { fireEvent } from 'c/pubsub'; <ー イベント実行に使う
@track bears; <- trackであるため、js側で値が変更される度に再描画される
@wire(CurrentPageReference) pageRef; <ー現在のページを取得しpageRefに格納
@wire(searchBears, {searchTerm: '$searchTerm'}) <- wireは、第一引数はアダプタ関数で、apexの関数を実行する,実行引数はsearchTermで渡す searchTermが変わる度に検索されloadBearsが再実行される
loadBears(result) {
this.bears = result;
if (result.data) {
fireEvent(this.pageRef, 'bearListUpdate', result.data);<ー第一引数で現在のページを設定し、bearListUpdateをイベントをパブリッシュする、受け取り側のコンポーネントは、これを受け取る(サブスクライブ)すればいい、最後に渡すパラメータを第3引数に渡す
}
}
画面上の変更などをきっかけにして、検索結果などを引数に設定したイベントを作る。それをページ全体に向けて発信する
bearListUpdateイベントを作成し、fireEventで、CurrentPageReference宛にパブリッシュしている
- 受け取る側でやること
現在ページを取得するアダプタと、表示非表示にライフサイクルイベントを行うアダプタをインポートする
import { CurrentPageReference } from 'lightning/navigation';
import { registerListener, unregisterAllListeners } from 'c/pubsub';
現在のページをpageRefに保持する
@wire(CurrentPageReference) pageRef;
他コンポーネントから行われた送信(パブリッシュ)
connectedCallbackはコンポーネント表示をきっかけに動作する。ここで、bearListUpdateを受け取れるように設定する
disconnectedCallbackではそれを解除している
connectedCallback() {
// subscribe to bearListUpdate event
registerListener('bearListUpdate', this.handleBearListUpdate, this); <- bearListUpdateに、自コンポーネントのhandleBearListUpdateメソッドを引き当てている
}
disconnectedCallback() {
// unsubscribe from bearListUpdate event
unregisterAllListeners(this);
}
handleBearListUpdate(bears) { <ーイベントを受け取ると、ここが呼ばれ、引数からパラメータを受け取ることができる
自コンポーネントを操作する処理
}
その他
trailheadではまったポイント
-
Trailheadのバリデーションが通らない
正しく操作しても
Make sure the 'bearLocation' and 'bearSupervisor' Lightning web components have been added to the Bear record page.
エラーが発生し完了しない -
原因
Lightningレコードページの、Label名と DeveloperNameが間違っている
日本語で作成した場合、Label名とDeveloperNameが英語とことなる表示になっている -
対策
英語のしようと同じように値を設定する
Label Bear : Record Page
Developer Name : Bear_Record_Page
https://developer.salesforce.com/forums/?id=9062I000000IFpXQAW