0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Trailhead 用自転車セレクタアプリケーションのメモ(1):画面描画

Last updated at Posted at 2020-02-07

ようやく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)

図は大体こんな感じ
image.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;
    }
}

image.png
image.png
(なぜか、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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?