ようやくTrailHeadのサンプル「自転車」を少々理解できた段階で、
自分なりにソースをアレンジしてみました。
勉強か、備忘として、画面表示までのプロセスを記録したします。
1、作成準備:
1-1. 以下のアプリケーションサンプルをダウンロード(条件が許容範囲内であれば) https://trailhead.salesforce.com/ja/content/learn/modules/lightning-web-components-basics/handle-events-in-lightning-web-components キーワード: Trailhead 用自転車セレクタアプリケーション1-2. ツール:Lightning Web Components Playground
https://developer.salesforce.com/docs/component-library/tools/playground
2、説明:
playgroundを使用して、Bikesサンプルを再現してみました。 こちらの構造は以下のようになる。 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/578884/d2a7f75e-f429-357d-bd26-706aa6125b53.png)1、まずは静的画面を読み込む。
Selector.html
<template>
<div class="wrapper">
<header class="header">Select a Bike</header>
<section class="content">
<div class="columns">
<main class="main" >
<c-list onproductselecteds={handleProductSelected}></c-list>
</main>
<aside class="sidebar-second">
<c-detail product-id={selectedProductId}></c-detail>
</aside>
</div>
</section>
</div>
</template>
ブラウザーが上から順次に描画、wrapper,header,section基本的に定番のパターンだから、省略。
重要な部分は「c-list」と「c-detail」、「c-list」は命名空間として、同じパスの中に「list」ディレクトリを探って、list.htmlをここに反映する。
同じく、「detail」ディレクトリを探って、detail.htmlを反映する。なぜdetailが右側に行くかはここで追究しません。Cssの仕様をFlexに設定されてるからと思うんだけど。
list.html
<template>
<div class="container">
there should be tiles list below
<template for:each={bikes} for:item="bike">
<c-tile
key={bike.fields.Id.value}
product={bike}
ontileclick={handleTileClick}>
</c-tile>
</template>
</div>
</template>
listの中にtileが1個1個forループで設定されてるから、「bikes」の分だけ設定されてしまう。
ここで{bikes}はlistのプロパティとして、jsの中にデータを振り込まれるので、jsを見てみましょう。
import { LightningElement, track } from 'lwc';
// import { bikes } from 'c/data';
export const input_bikes = [
{"apiName":"Product__c","childRelationships":{},"fields":{"Category__c":{"displayValue":"Mountain","value":"Mountain"},"CreatedDate":{"displayValue":null,"value":"2018-10-09T03:29:52.000Z"},"Description__c":{"displayValue":null,"value":"A durable e-bike with great looks."},"Id":{"displayValue":null,"value":"a0256000001F1arAAC"},"LastModifiedDate":{"displayValue":null,"value":"2018-10-12T02:57:48.000Z"},"Level__c":{"displayValue":"Racer","value":"Racer"},"MSRP__c":{"displayValue":"$7,800","value":7800},"Material__c":{"displayValue":"Carbon","value":"Carbon"},"Name":{"displayValue":null,"value":"DYNAMO X1"},"Picture_URL__c":{"displayValue":null,"value":"https://s3-us-west-1.amazonaws.com/sfdc-demo/ebikes/dynamox1.jpg"},"SystemModstamp":{"displayValue":null,"value":"2018-10-12T02:57:48.000Z"}},"id":"a0256000001F1arAAC","lastModifiedById":null,"lastModifiedDate":"2018-10-12T02:57:48.000Z","recordTypeInfo":null,"systemModstamp":"2018-10-12T02:57:48.000Z"},
{"apiName":"Product__c","childRelationships":{},"fields":{"Category__c":{"displayValue":"Mountain","value":"Mountain"},"CreatedDate":{"displayValue":null,"value":"2018-10-09T03:29:52.000Z"},"Description__c":{"displayValue":null,"value":"A durable e-bike with great looks."},"Id":{"displayValue":null,"value":"a0256000001F1atAAC"},"LastModifiedDate":{"displayValue":null,"value":"2018-10-10T17:26:47.000Z"},"Level__c":{"displayValue":"Racer","value":"Racer"},"MSRP__c":{"displayValue":"$6,802","value":6802},"Material__c":{"displayValue":"Aluminum","value":"Aluminum"},"Name":{"displayValue":null,"value":"DYNAMO X2"},"Picture_URL__c":{"displayValue":null,"value":"https://s3-us-west-1.amazonaws.com/sfdc-demo/ebikes/dynamox2.jpg"},"SystemModstamp":{"displayValue":null,"value":"2018-10-10T17:26:47.000Z"}},"id":"a0256000001F1atAAC","lastModifiedById":null,"lastModifiedDate":"2018-10-10T17:26:47.000Z","recordTypeInfo":null,"systemModstamp":"2018-10-10T17:26:47.000Z"},
{"apiName":"Product__c","childRelationships":{},"fields":{"Category__c":{"displayValue":"Mountain","value":"Mountain"},"CreatedDate":{"displayValue":null,"value":"2018-10-09T03:29:52.000Z"},"Description__c":{"displayValue":null,"value":"A durable e-bike with great looks."},"Id":{"displayValue":null,"value":"a0256000001F1auAAC"},"LastModifiedDate":{"displayValue":null,"value":"2018-10-09T04:37:56.000Z"},"Level__c":{"displayValue":"Enthusiast","value":"Enthusiast"},"MSRP__c":{"displayValue":"$5,601","value":5601},"Material__c":{"displayValue":"Aluminum","value":"Aluminum"},"Name":{"displayValue":null,"value":"DYNAMO X3"},"Picture_URL__c":{"displayValue":null,"value":"https://s3-us-west-1.amazonaws.com/sfdc-demo/ebikes/dynamox3.jpg"},"SystemModstamp":{"displayValue":null,"value":"2018-10-09T04:37:56.000Z"}},"id":"a0256000001F1auAAC","lastModifiedById":null,"lastModifiedDate":"2018-10-09T04:37:56.000Z","recordTypeInfo":null,"systemModstamp":"2018-10-09T04:37:56.000Z"},
{"apiName":"Product__c","childRelationships":{},"fields":{"Category__c":{"displayValue":"Mountain","value":"Mountain"},"CreatedDate":{"displayValue":null,"value":"2018-10-09T03:29:52.000Z"},"Description__c":{"displayValue":null,"value":"A durable e-bike with great looks."},"Id":{"displayValue":null,"value":"a0256000001F1avAAC"},"LastModifiedDate":{"displayValue":null,"value":"2018-10-09T03:29:52.000Z"},"Level__c":{"displayValue":"Enthusiast","value":"Enthusiast"},"MSRP__c":{"displayValue":"$5,500","value":5500},"Material__c":{"displayValue":"Aluminum","value":"Aluminum"},"Name":{"displayValue":null,"value":"DYNAMO X4"},"Picture_URL__c":{"displayValue":null,"value":"https://s3-us-west-1.amazonaws.com/sfdc-demo/ebikes/dynamox4.jpg"},"SystemModstamp":{"displayValue":null,"value":"2018-10-09T03:29:52.000Z"}},"id":"a0256000001F1avAAC","lastModifiedById":null,"lastModifiedDate":"2018-10-09T03:29:52.000Z","recordTypeInfo":null,"systemModstamp":"2018-10-09T03:29:52.000Z"},
{"apiName":"Product__c","childRelationships":{},"fields":{"Category__c":{"displayValue":"Mountain","value":"Mountain"},"CreatedDate":{"displayValue":null,"value":"2018-10-09T03:29:52.000Z"},"Description__c":{"displayValue":null,"value":"A durable e-bike with great looks."},"Id":{"displayValue":null,"value":"a0256000001F1azAAC"},"LastModifiedDate":{"displayValue":null,"value":"2018-10-09T03:29:52.000Z"},"Level__c":{"displayValue":"Enthusiast","value":"Enthusiast"},"MSRP__c":{"displayValue":"$4,600","value":4600},"Material__c":{"displayValue":"Aluminum","value":"Aluminum"},"Name":{"displayValue":null,"value":"FUSE X1"},"Picture_URL__c":{"displayValue":null,"value":"https://s3-us-west-1.amazonaws.com/sfdc-demo/ebikes/fusex1.jpg"},"SystemModstamp":{"displayValue":null,"value":"2018-10-09T03:29:52.000Z"}},"id":"a0256000001F1azAAC","lastModifiedById":null,"lastModifiedDate":"2018-10-09T03:29:52.000Z","recordTypeInfo":null,"systemModstamp":"2018-10-09T03:29:52.000Z"},
{"apiName":"Product__c","childRelationships":{},"fields":{"Category__c":{"displayValue":"Commuter","value":"Commuter"},"CreatedDate":{"displayValue":null,"value":"2018-10-09T03:29:52.000Z"},"Description__c":{"displayValue":null,"value":"A durable e-bike with great looks."},"Id":{"displayValue":null,"value":"a0256000001F1b2AAC"},"LastModifiedDate":{"displayValue":null,"value":"2018-10-09T04:41:56.000Z"},"Level__c":{"displayValue":"Beginner","value":"Beginner"},"MSRP__c":{"displayValue":"$3,200","value":3200},"Material__c":{"displayValue":"Aluminum","value":"Aluminum"},"Name":{"displayValue":null,"value":"ELECTRA X1"},"Picture_URL__c":{"displayValue":null,"value":"https://s3-us-west-1.amazonaws.com/sfdc-demo/ebikes/electrax1.jpg"},"SystemModstamp":{"displayValue":null,"value":"2018-10-09T04:41:56.000Z"}},"id":"a0256000001F1b2AAC","lastModifiedById":null,"lastModifiedDate":"2018-10-09T04:41:56.000Z","recordTypeInfo":null,"systemModstamp":"2018-10-09T04:41:56.000Z"},
{"apiName":"Product__c","childRelationships":{},"fields":{"Category__c":{"displayValue":"Commuter","value":"Commuter"},"CreatedDate":{"displayValue":null,"value":"2018-10-09T03:29:52.000Z"},"Description__c":{"displayValue":null,"value":"A durable e-bike with great looks."},"Id":{"displayValue":null,"value":"a0256000001F1b3AAC"},"LastModifiedDate":{"displayValue":null,"value":"2018-10-09T03:29:52.000Z"},"Level__c":{"displayValue":"Beginner","value":"Beginner"},"MSRP__c":{"displayValue":"$3,200","value":3200},"Material__c":{"displayValue":"Aluminum","value":"Aluminum"},"Name":{"displayValue":null,"value":"ELECTRA X2"},"Picture_URL__c":{"displayValue":null,"value":"https://s3-us-west-1.amazonaws.com/sfdc-demo/ebikes/electrax2.jpg"},"SystemModstamp":{"displayValue":null,"value":"2018-10-09T03:29:52.000Z"}},"id":"a0256000001F1b3AAC","lastModifiedById":null,"lastModifiedDate":"2018-10-09T03:29:52.000Z","recordTypeInfo":null,"systemModstamp":"2018-10-09T03:29:52.000Z"},
{"apiName":"Product__c","childRelationships":{},"fields":{"Category__c":{"displayValue":"Commuter","value":"Commuter"},"CreatedDate":{"displayValue":null,"value":"2018-10-09T03:29:52.000Z"},"Description__c":{"displayValue":null,"value":"A durable e-bike with great looks."},"Id":{"displayValue":null,"value":"a0256000001F1b6AAC"},"LastModifiedDate":{"displayValue":null,"value":"2018-10-09T03:29:52.000Z"},"Level__c":{"displayValue":"Beginner","value":"Beginner"},"MSRP__c":{"displayValue":"$2,700","value":2700},"Material__c":{"displayValue":"Aluminum","value":"Aluminum"},"Name":{"displayValue":null,"value":"ELECTRA X3"},"Picture_URL__c":{"displayValue":null,"value":"https://s3-us-west-1.amazonaws.com/sfdc-demo/ebikes/electrax3.jpg"},"SystemModstamp":{"displayValue":null,"value":"2018-10-09T03:29:52.000Z"}},"id":"a0256000001F1b6AAC","lastModifiedById":null,"lastModifiedDate":"2018-10-09T03:29:52.000Z","recordTypeInfo":null,"systemModstamp":"2018-10-09T03:29:52.000Z"},
{"apiName":"Product__c","childRelationships":{},"fields":{"Category__c":{"displayValue":"Commuter","value":"Commuter"},"CreatedDate":{"displayValue":null,"value":"2018-10-09T03:29:52.000Z"},"Description__c":{"displayValue":null,"value":"A durable e-bike with great looks."},"Id":{"displayValue":null,"value":"a0256000001F1b7AAC"},"LastModifiedDate":{"displayValue":null,"value":"2018-10-09T03:29:52.000Z"},"Level__c":{"displayValue":"Beginner","value":"Beginner"},"MSRP__c":{"displayValue":"$2,700","value":2700},"Material__c":{"displayValue":"Aluminum","value":"Aluminum"},"Name":{"displayValue":null,"value":"ELECTRA X4"},"Picture_URL__c":{"displayValue":null,"value":"https://s3-us-west-1.amazonaws.com/sfdc-demo/ebikes/electrax4.jpg"},"SystemModstamp":{"displayValue":null,"value":"2018-10-09T03:29:52.000Z"}},"id":"a0256000001F1b7AAC","lastModifiedById":null,"lastModifiedDate":"2018-10-09T03:29:52.000Z","recordTypeInfo":null,"systemModstamp":"2018-10-09T03:29:52.000Z"}
];
export default class List extends LightningElement {
// ------------------------------------------------------------------------
bikes = input_bikes;
// ------------------------------------------------------------------------
handleTileClick(evt) {
console.log("listClick1")
// This component wants to emit a productselected event to its parent
const event = new CustomEvent('productselecteds', {
detail: evt.detail
});
console.log("listClick2")
// Fire the event from c-list
this.dispatchEvent(event);
console.log("listClick3")
}
// ------------------------------------------------------------------------
}
この画面表示の段階ではclassの配下のコードが上から順次実行するので、「bikes = input_bikes」だけが実行される。
つまり、bikesがinput_bikesのデータで充填してしまう。
それで、list.htmlの中のforループが実行し、この「input_bikes」分のbikeがtileに与えて、tileを表示。
「input_bikes」少々長いけど、適当に改行して見れば、そんなに難しいデータではありません。
( Trailhead 用自転車セレクタアプリケーションの中のdataというディレクトリのdata.jsの中の内容をそのまま貼り付けただけ、そこを見れは分かる)
「input_bikes」中のjson書式:”[”と”]”の中に配列として、1行ずつ分かれてるため、ループすると、1行ずつ「bike」としてtileに入れ込む、全部で9個のtileが生成される。
もう一回list.htmlを見てみましょう。
list.html
<template>
<div class="container">
there should be tiles list below
<template for:each={bikes} for:item="bike">
<c-tile
key={bike.fields.Id.value}
product={bike}
ontileclick={handleTileClick}>
</c-tile>
</template>
</div>
</template>
[key={bike.fields.Id.value} ]、bikeはこのkey(一意)でデータを割当するらしい。
つまり、bikesはidでbikeを「product」に与える。Tileを表示する。
tile.html
<template>
<div class="container">
<a onclick={tileClick}>
<div class="title">{product.fields.Name.value}</div>
<img class="product-img" src={product.fields.Picture_URL__c.value}></img>
</a>
</div>
</template>
tileは非常にシンプルなソースですが、”{”、”}”の中の部分は「input_bikes」の内容に順を追って探せば出で来る。
tile.js
import { LightningElement, api } from 'lwc';
export default class Tile extends LightningElement {
@api product;
tileClick() {
console.log("product:" + this.product)
console.log("tileClick1")
const event = new CustomEvent('tileclick', {
// detail contains only primitives
detail: this.product.fields.Id.value
});
console.log("tileClick2")
// Fire the event from c-tile
this.dispatchEvent(event);
console.log("tileClick3")
}
}
tile.jsには関数しか書かれていないため、現段階では実行されません。
detail.html
<template>
<template if:true={product}>
<div class="container">
<div>{product.fields.Name.value}</div>
<div class="price">{product.fields.MSRP__c.displayValue}</div>
<div class="description">{product.fields.Description__c.value}</div>
<img class="product-img" src={product.fields.Picture_URL__c.value}></img>
<p>
<lightning-badge label={product.fields.Material__c.value}></lightning-badge>
<lightning-badge label={product.fields.Level__c.value}></lightning-badge>
</p>
<p>
<lightning-badge label={product.fields.Category__c.value}></lightning-badge>
</p>
</div>
</template>
<template if:false={product}>
<div>Select a bike</div>
</template>
</template>
右側のdetailについては、やや複雑ですが、htmlから見てみましょう。
detail.html
<template>
<template if:true={product}>
<div class="container">
<div>{product.fields.Name.value}</div>
<div class="price">{product.fields.MSRP__c.displayValue}</div>
<div class="description">{product.fields.Description__c.value}</div>
<img class="product-img" src={product.fields.Picture_URL__c.value}></img>
<p>
<lightning-badge label={product.fields.Material__c.value}></lightning-badge>
<lightning-badge label={product.fields.Level__c.value}></lightning-badge>
</p>
<p>
<lightning-badge label={product.fields.Category__c.value}></lightning-badge>
</p>
</div>
</template>
<template if:false={product}>
<div>Select a bike</div>
</template>
</template>
プロパティとして「product」の値次第で、配下を決める。
[product]は@trackマークアップされるため、値が変換されるたびにcomponentが再描画する、だけど、detail.jsは描画の段階ではproductは充填してないため、
下のfalseのDomが反映される。よって、「select a bike」しか書かれてないわけ(下の画面参照)。
要注意して欲しいのは、selector.htmlの中に「」という文は「product-id」はdetailの公開パラメータです(@apiのマークアップ)。
setter と getterメソッドは「product-id」がアクセスたびに実行してしまうため、最初に読込み場合に既にアクセスしたため、setterとgetterは実行した。
公式サイト:
Both @track and @api mark a property as reactive.
If the property’s value changes, the component rerenders.
Tracked properties are private,
where properties decorated with @api are public and can be set by another component.
detail.js(一部)
export default class Detail extends LightningElement {
// Ensure changes are reactive when product is updated
@track product;
// Private var to track @api productId
// _productId = undefined; // 書いても良い
// Use set and get to process the value every time it's
// requested while switching between products
set productId(value) {
console.log("setDetail:"+"start")
this._productId = value;
console.log("this._productId:"+this._productId)
this.product = bikes.find(bike => bike.fields.Id.value === value);
console.log("setDetail:"+"end")
}
// getter for productId
@api get productId(){
console.log("getDetail")
// return this._productId;
}
}
(なぜか、getterは実行してないが、知ってたらご教示ください。)
setterが実行したが、value=undefinedのため、this.productが存在しない。
はい、ここまでは画面がなぜこうな風になったのか、説明は以上です。
css、イベントの通知は今回は一旦触れないようにしてます。
何か間違って処があれば、連絡していただければ、感謝です。
リファレンス:
https://trailhead.salesforce.com/ja/content/learn/modules/lightning-web-components-basics
https://developer.salesforce.com/docs/component-library/documentation/lwc/lwc.js_props_public
https://hub.appirio.jp/tech-blog/lightningwebcomponents2
https://developer.salesforce.com/docs/component-library/documentation/lwc/create_lists