進行状況の追跡を始めよう
Trailhead のホーム
Trailhead のホーム

フォームを使用したデータの入力

学習の目的

この単元を完了すると、次のことができるようになります。
  • フォームを作成して現在の値を表示し、新しいユーザ入力を受け入れる。
  • フォーム要素から値を読み取る。
  • ユーザ入力を検証し、無効な入力のエラーメッセージを表示する。
  • コンポーネントのコントローラからヘルパーにコードをリファクタリングする。

フォームを使用したデータの入力

この単元まで、helloWhatever スタイルのコンポーネントについて説明してきました。ここからは、前に見た経費追跡ミニアプリケーションを作成して組み立てていきます。この単元の多くの部分では、新しい経費の作成を許可するフォームを作成して理解を深めます。

expenses アプリケーションコンテナ

始める前に、地味でまったく美しくないコンポーネントの作成も終わらせておきましょう。最初に、Salesforce Lightning Design System (SLDS) を取り込み、アプリケーションで「有効」にします。これを行うに当たり、アプリケーションコンテナについて少し説明します。

メモ

メモ

SLDS そのものについては、この単元でも、モジュールの残りの部分でも説明しません。ここでは、SLDS をアプリケーションに追加して、コード例内で SLDS クラスを使用することに焦点を当てますが、SLDS クラスについて詳しくは説明しません。SLDS について学ぶ、多くのさまざまな方法については、「リソース」を参照してください。

今日、Lightning Experience または Salesforce アプリケーションでコンポーネントを実行するとき、コンポーネントで SLDS が自動的に使用可能になります。one.app コンテナで動作している SLDS をコールすることがあります。この組み込みバージョンは、多くの標準 Lightning コンポーネントによって使用されるものと同じです。ただし、スタンドアロンアプリケーションでは SLDS はデフォルトで使用可能にならず、Visualforce の Lightning Out または Lightning コンポーネントでコンポーネントを使用するときにも、SLDS は使用可能になりません。これらは別のアプリケーションコンテナであり、別のサービスとリソースを提供します。Expenses アプリケーションは、これらすべてのコンテキストで動作して適切に表示される方法によって作成します。幸いなことに、これは実際にはそれほど難しくありません。

これを行うには、SLDS をハーネスアプリケーションに追加します。このようにすると、「実際の」Expenses アプリケーション (トップレベルコンポーネントおよびそのすべての子コンポーネント) 内で、SLDS リソース (スタイルシート、アイコンなど) の場所について気にせずに、SLDS ツールと技法を使用できるようになります。つまり、アプリケーションコンテナ (ハーネスアプリケーション) がコンテキスト内でリソースを設定するため、そのコンテナ内で動作するすべてのアプリケーションでは、必要なリソースを使用できるようになります。

この長ったらしいコンセプトをコードに変換してみましょう。次のマークアップを使用して、新しい expensesApp.app Lightning アプリケーションを作成します。

<aura:application extends="force:slds">
        <!-- This component is the real "app" -->
        <!-- c:expenses/ -->
</aura:application>

このコードについて説明しましょう。extends="force:slds" 属性により、Lightning Experience と Salesforce アプリケーションによって提供される、Lightning Design System スタイルが組み込まれて、このアプリケーションで SLDS が有効になります。ただし、このハーネスアプリケーションは、単なるラッパー、つまりシェルです。実際のアプリケーションは expenses コンポーネントであり、これはまだ作成していません(<!-- c:expenses/ --> の部分であり、expenses コンポーネントが実際に存在することになるまでアプリケーションを保存できないため、コメントになっています)。

このコンポーネントは、ラッパーアプリケーション経由で extends="force:slds" メカニズムを使用し、このアプリケーションから実行されているとき、SLDS にアクセスします。コードを変更せずに Lightning Experience または Salesforce アプリケーション内で実行すると、そのコンテナで自動的に組み込まれる SLDS が使用されます。

この場合、結局は同じことになります。ただし、外部ハーネスアプリケーションを使用してコンテキストを設定し、実際のアプリケーションではコンテキストの違いを気にする必要がないというコンセプトは、スタイルリソースに限られません。このコンセプトを利用して、置換イベントハンドラなどを提供できますが、これは先走りしすぎです。飛んでみる前に、歩き方を習いましょう。

expenses アプリケーションコンポーネント

次のステップでは、Expenses アプリケーションの最上位のコンポーネントを作成します(「アプリケーション」と呼んでいますが、実際には別の Lightning コンポーネントです)。開発者コンソールで「Expenses」という名前の新しい Aura コンポーネントを作成し、デフォルトのマークアップを次のもので置き換えます。

<aura:component>
    <!-- PAGE HEADER -->
    <lightning:layout class="slds-page-header slds-page-header_object-home">
        <lightning:layoutItem>
            <lightning:icon iconName="standard:scan_card" alternativeText="My Expenses"/>
        </lightning:layoutItem>
        <lightning:layoutItem padding="horizontal-small">
            <div class="page-section page-header">
                <h1 class="slds-text-heading_label">Expenses</h1>
                <h2 class="slds-text-heading_medium">My Expenses</h2>
            </div>
        </lightning:layoutItem>
    </lightning:layout>
    <!-- / PAGE HEADER -->
    <!-- NEW EXPENSE FORM -->
    <lightning:layout>
        <lightning:layoutItem padding="around-small" size="6">
        <!-- [[ expense form goes here ]] -->
        </lightning:layoutItem>
    </lightning:layout>
    <!-- / NEW EXPENSE FORM -->
</aura:component>

ここで作成するのは、<lightning:layout> および <lightning:layoutItem> コンポーネントで提供されるグリッドレイアウトを使用するページヘッダーです。size="6" によって、全幅の 50% (サイズ 6/12) の <div> コンテナを作成します。お気づきと思いますが、lightning 名前空間のコンポーネントは、Lightning Experience および Salesforce アプリケーションのコンポーネントと似ています。この名前空間には、ボタンやレイアウト以外にも多くの便利なコンポーネントがあり、自動的に SLDS スタイルとシームレスに連動します。

メモ

メモ

<lightning:icon> タグに注目してください。このコンポーネントでは、お気に入りの SLDS アイコンをすぐに表示できます。SLDS アイコンを表示するためにヘルパーコンポーネントを作らなければならなかった時代は終わりました。

実際の .app<c:expenses/> タグのコメントを解除し、現在は空のシェルになっているもののプレビューを開きます。次のような画面が表示されます。

Expenses の基本フォーム

まだ多くは進んでいませんが、SLDS スタイルがすでに有効になっています。SLDS マークアップについてはほとんど説明しませんが、マークアップにコメントが組み込まれています。アプリケーションのヘッダーの作成方法について理解したり、アイデアを把握したりできるようになっています。

Expenses の新規フォーム

フォームに取り掛かる前に、これから行うことが仮のものであることを前もってご理解ください。アプリケーションを個別の小さいコンポーネントに分解して、そこから構築することについて長々と説明したことを覚えていますか? ここではそれを行いません (後から行います)。正直に言うと、少しごまかしをします。

ただし、このごまかしは、コードが急速に複雑になってしまうことを防止するためのものです。このようにするのは、1 回に 1 つの課題に集中できるようにするためです。「ごった返す」まで 1 つのコンポーネント内で構築してからリファクタリングし、小さいサブコンポーネントに分解することは、自分で物事に取り組む場合には悪い方法ではありません。ただし、リファクタリングを忘れないでください。

<> expenses コンポーネントで、<!-- [[ expense form goes here ]] --> のコメントを [Add Expense (経費を追加)] フォームの次のコードで置き換えてください。

    <!-- CREATE NEW EXPENSE -->
    <div aria-labelledby="newexpenseform">
        <!-- BOXED AREA -->
        <fieldset class="slds-box slds-theme_default slds-container_small">
        <legend id="newexpenseform" class="slds-text-heading_small
          slds-p-vertical_medium">
          Add Expense
        </legend>
        <!-- CREATE NEW EXPENSE FORM -->
        <form class="slds-form_stacked">
            <lightning:input aura:id="expenseform" label="Expense Name"
                             name="expensename"
                             value="{!v.newExpense.Name}"
                             required="true"/>
            <lightning:input type="number" aura:id="expenseform" label="Amount"
                             name="expenseamount"
                             min="0.1"
                             formatter="currency"
                             step="0.01"
                             value="{!v.newExpense.Amount__c}"
                             messageWhenRangeUnderflow="Enter an amount that's at least $0.10."/>
            <lightning:input aura:id="expenseform" label="Client"
                             name="expenseclient"
                             value="{!v.newExpense.Client__c}"
                             placeholder="ABC Co."/>
            <lightning:input type="date" aura:id="expenseform" label="Expense Date"
                             name="expensedate"
                             value="{!v.newExpense.Date__c}"/>
            <lightning:input type="checkbox" aura:id="expenseform" label="Reimbursed?"
                             name="expreimbursed"
                             checked="{!v.newExpense.Reimbursed__c}"/>
            <lightning:button label="Create Expense"
                              class="slds-m-top_medium"
                              variant="brand"
                              onclick="{!c.clickCreate}"/>
        </form>
        <!-- / CREATE NEW EXPENSE FORM -->
      </fieldset>
      <!-- / BOXED AREA -->
    </div>
    <!-- / CREATE NEW EXPENSE -->

長いコードで、すぐには理解できないように見えるかもしれません。ただし、そうでもありません。SLDS マークアップとクラスを取り除くと、このフォームは、一連の入力項目とフォーム送信ボタンになります。

その結果、フォームは次のようになります。

Expenses の新規フォーム
メモ

メモ

<lightning:input> は、洗練された SLDS スタイルを採用した入力項目用のもので、十得ナイフのように万能です。さまざまな <ui:input> コンポーネント (<ui:inputText><ui:inputNumber> など) を利用する場合には常にこちらを使用します。ui 名前空間のコンポーネントには SLDS スタイルは付属せず、従来のコンポーネントと見なされます。

最初に、ここで作成しているのは特定のデータ型を持つ、<lightning:input> コンポーネントの複数のインスタンスです。つまり、日付項目には当然 type="date" を使用し、他の項目についても同様にします。指定したデータ型以外にもさまざまなデータ型がありますが、コンポーネントの種類をデータ型と一致させることが、常に最適な方策となります。データ型を指定しない場合、デフォルトでテキストになります。その理由はまだ理解できないかもしれませんが、このアプリケーションを電話に展開するときには理解できるでしょう。型が固有のコンポーネントにより、フォーム要素に最適な入力ウィジェットを提供できるようになるのです。たとえば、日付ピッカーは、どこでアクセスするかに応じて、マウスかフィンガーチップに最適化されます。

次に、各入力コンポーネントに表示ラベルが設定されており、label テキストは入力項目の横に自動的に表示されます。requiredplaceholdertypeminstep など、これまでにまだ見ていない属性もあります。これらの属性のほとんどは、対応する HTML の属性と似ています。たとえば、min は入力の最小値を指定します。属性の用途がわからない場合は、Lightning コンポーネントライブラリで調べることができます。(これについては後で説明します)。

さらに、aura:id 属性が各タグに設定されています。これは何のためにあるのでしょうか? 追加される各タグに、ローカルに固有な ID を設定し、その ID によってフォーム項目から値を読み取るのです。この例では、項目はすべて同じ ID を共有しているため、項目検証をするために配列としてアクセスできます。これについては、この後すぐに説明します。

Salesforce オブジェクト (sObject) の属性

先に、value 属性について説明する必要があります。各タグには値があり、式に設定されています。たとえば、{!v.newExpense.Amount__c} などです。式の形式から、何かを推測できるはずです。

  • v は、表示値プロバイダのプロパティであるという意味です。つまり、コンポーネントの属性です(まだ作成していません)。
  • ドット表記法に基づいて、newExpense が、ある種の構造化されたデータ型であることがわかります。つまり、newExpense 自体にはプロパティがあります。項目といった方が良いでしょうか?
  • 多くのプロパティ名の末尾に “__c” が付いていることから、これらがカスタム項目に対応付けられ、経費カスタムオブジェクトに対応付けられる可能性が最も高いことが予想されます。
  • そのため、newExpense は経費オブジェクトであると思われます。

これについてはまだ説明していませんでした。実際の属性定義は次のようになります。コンポーネントの先頭、<aura:component> 開始タグの右に追加してください。

    <aura:attribute name="newExpense" type="Expense__c"
         default="{ 'sobjectType': 'Expense__c',
                        'Name': '',
                        'Amount__c': 0,
                        'Client__c': '',
                        'Date__c': '',
                        'Reimbursed__c': false }"/>

ここで行われていることは、実際には非常に単純です。名前属性は、ご存じのとおりです。型は、驚いたことに、カスタムオブジェクトの API 参照名です。順調です。

デフォルト属性は新しくありませんが、その値の形式は新しくなっています。ただし、理解できないほど難しいわけではありません。sObject の JSON 表現であり、オブジェクトの種類 (ここでも API 参照名)、およびデフォルトで設定される、各項目の値を指定しているのです。ここでは、基本的に、空の値の表現にすべてを設定しています。

sObject についての説明は、これでほとんど終わりです。ここからは、Lightning コンポーネントフレームワークにより、JavaScript とマークアップで Salesforce のレコードであるかのように newExpense を扱います。Salesforce からの読み込みはまだ行いません。

アクションハンドラでのフォーム登録の処理

フォームは出来上がりました。いま、新しい経費を作成するために情報を入力してボタンをクリックすると、どうなるでしょうか? 先に進んで作成していない限り、コントローラアクションがないというエラーが発生します。<lightning:button> で指定されているコントローラもアクションハンドラも作成していないためです。

開発者コンソールで expenses コンポーネントの [Controller (コントローラ)] ボタンをクリックし、コントローラリソースを作成します。次に、デフォルトのコードを次のコードで置き換えてください。

({
    clickCreate: function(component, event, helper) {
        let validExpense = component.find('expenseform').reduce(function (validSoFar, inputCmp) {
            // Displays error messages for invalid fields
            inputCmp.showHelpMessageIfInvalid();
            return validSoFar && inputCmp.get('v.validity').valid;
        }, true);
        // If we pass error checking, do some real work
        if(validExpense){
            // Create the new expense
            let newExpense = component.get("v.newExpense");
            console.log("Create expense: " + JSON.stringify(newExpense));
            helper.createExpense(component, newExpense);
        }
    }
})

すべて新項目なので、詳しく説明します。まず、このアクションハンドラ機能は、基本的に次の 3 つのセクション、つまりステップに分かれています。

  1. 設定
  2. フォームの値を処理
  3. エラーがなければ、何かを実行

この構造には慣れているかもしれません。Web アプリケーションでユーザ入力を処理する、非常に基本的な方法だからです。各ステップについて、Lightning コンポーネントでどのように動作するかを見てみましょう。

設定では、エラーチェックの状態を初期化するだけです。有効な経費であるかどうかという、単純なフラグです。clickCreate アクションハンドラが呼び出されるたびに、経費データが OK であると仮定して開始し、問題が見つかったら無効にします。以下は validExpense フラグの概要です。初期値は true に設定されています。

  • component.find('expenseform') は、検証が必要な、<lightning:input> 項目の配列への参照を取得します。ID が一意の場合、参照はコンポーネントを返します。この場合、ID は一意ではなく、参照はコンポーネントの配列を返します。
  • JavaScript reduce() メソッドは、配列を validSoFar で取得された単一値にします。この値は無効な項目が見つかるまで true のままです。見つかると validSoFar は false に変更されます。無効な項目として、空の必須項目、指定された最小値未満の数値が含まれる項目などがあります。
  • inputCmp.get('v.validity').valid は、配列内の現在の入力項目の有効性を返します。
  • inputCmp.showHelpMessageIfInvalid() は、無効な項目のエラーメッセージを表示します。<lightning:input> では、デフォルトのエラーメッセージが提供されます。これは、経費フォームの例のように messageWhenRangeUnderflow などの属性を使用してカスタマイズできます。

では興味深い詳細に進みましょう。helloMessageInteractive では、クリックされたボタンの表示ラベルテキストを取得するために find() を使用しませんでした。必要なかったためです。そのボタンの参照は、event.getSource() を使用して event パラメータから引き出すことによって直接取得できました。そのような贅沢がいつも許されるわけではありません。ユーザ入力の必要なものすべてがイベントのみから来ることは滅多にありません。

子コンポーネントにアクセスする方法がコントローラで必要であるときは、最初にそのコンポーネントのマークアップで aura:id を設定し、component.find(theId) を使用して実行時にコンポーネントの参照を取得します。

メモ

メモ

component.find() では、コントロールとヘルパーからコンポーネントとその子コンポーネントのみにアクセスできます。コンポーネント階層を歩き回って何かを読み取ったり変更したりすることは、マジックではありません。コンポーネントは自己完結型になるはずですが、そのやり取りについては後で説明します。

<lightning:input> での検証では、基礎となる HTML 入力要素の機能を利用してフォーム値を処理するため、ほとんどの場合、自分で処理する必要はありません。電話番号の検証が必要な場合は、type="tel" を使用し、正規表現を使用する pattern 属性を定義します。パーセント値の検証が必要な場合は、type="number"formatter="percent" を使用します。その他の検証が必要な場合は、<lightning:input> に面倒な作業は任せましょう。

検証がエラーになった場合も、物事が面白くなってきます。ユーザが無効な値を入力したときには、次の 2 つのことを行います。

  1. 経費を作成しない。
  2. 有用なエラーメッセージを表示する。

1 つ目に対し、inputCmp.get('v.validity').valid で参照される項目の有効性が false と評価されたら、validExpense フラグを false に設定します。2 つ目に対し、組み込みの検証エラーを使用するか、カスタムエラーメッセージを指定します。経費フォームで、必須名前項目が空の場合、項目に [Complete this field (この項目を入力してください)] と表示されるので、フォームの送信を試みます。ただし、messageWhenValueMissing="Did you forget me?" を指定することで独自のカスタムメッセージを指定できます。

これとは反対に、項目が検証に合格したら、validExpense フラグが true と評価され、エラーは表示されません。

次にステップ 3 に進み、フォーム登録を処理します。実際には経費を作成します。ご覧のとおり、その準備のために、component.get("v.newExpense") によって newExpense オブジェクト全体をコンポーネント属性から取得します。1 つの変数が手に入り、それを使用して新しい経費レコードを作成できるようになります。

その前に、次の質問に答えてください。フォームの値を newExpense から引き出さないのはなぜですか? 一連の長い find().get() コールになる可能性があるものの代わりに、アクションハンドラの最初で、構造化された変数を取得すれば、そのプロパティにアクセスするだけで済むのではないですか?

その理由は簡単です。項目に対して showHelpMessageIfInvalid() をコールできるようにするには、各項目への参照が必要だからです。未加工のフォームデータを検証するのも良い方法です。検証ロジックは、newExpense オブジェクト内でどのような種類の処理が行われるかを認識しません。

新規経費の作成

経費フォームをメインコンポーネントに置くことは少しごまかしであると説明したのを覚えていますか? このセクションでは、「少し」という修飾語がなくなります。ここで行おうとしていることは、レコードを本当に作成するという複雑なことをあからさまに避けることです。複雑なことを避けるのは、それが次の単元になるからです。ここでは要約して簡単なものにしますが、重要なコンセプトについては漏らさず説明します。

まず、新しい経費を「保存」する場所を作成しましょう。単に経費のローカル専用配列を作成して、そこに経費を保存します。expenses コンポーネントマークアップの最上位で、newExpense 属性の直前に新しい expenses 属性を追加し、経費オブジェクトの配列を保存します。

    <aura:attribute name="expenses" type="Expense__c[]"/>

必要なことは、expenses 配列を更新することだけです。これは結果的に簡単ですが (フォームの「ごまかし」バージョン)、重要なコンセプトを示しています。

コントローラでは、helper.createExpense(component, newExpense) という関数コールの背後に、新しい経費を実際に作成するという作業が隠されています。ソフトウェア開発では、「隠す」ことを抽象化といいますごまかしを抽象化するために、ヘルパーと呼ばれるものを使用しています。

ヘルパーについては、以前、簡潔に説明しました。このモジュールでは、ヘルパーについてさらに詳しく説明することはしません。ここでは、ヘルパーについて、次の 3 点を説明します。

  • コンポーネントのヘルパーは、さまざまなアクションハンドラ間でコードを共有する、適切な場所です。
  • コンポーネントのヘルパーは、複雑な処理の詳細を配置する、適切な場所になるため、アクションハンドラのロジックは明確で合理的なものになります。
  • ヘルパー関数には、どのような関数の署名でも含めることができます。つまり、コントローラのアクションハンドラがどのようになるかは制限されません(なぜでしょうか? ヘルパー関数をコードから直接コールしているからです。これに対し、フレームワークはフレームワークランタイム経由でアクションハンドラをコールします)。常にヘルパー関数の最初のパラメータとしてコンポーネントを指定することが、慣習的な推奨の方法です。

それでは、先に進みましょう。開発者コンソールで expenses コンポーネントの [Helper (ヘルパー)] ボタンをクリックして関連ヘルパーリソースを作成してから、コード例を次のコードで置き換えます。

({
    createExpense: function(component, expense) {
        let theExpenses = component.get("v.expenses");
        // Copy the expense to a new object
        // THIS IS A DISGUSTING, TEMPORARY HACK
        let newExpense = JSON.parse(JSON.stringify(expense));
        theExpenses.push(newExpense);
        component.set("v.expenses", theExpenses);
    }
})

さしあたって、うんざりするようなハッキングの部分は無視します。その他 3 行のコードは、以前確認して繰り返し使用することになる、取得して処理して設定するという、一般的なパターンを示しています。最初に、経費の配列を expenses 属性から取得します。次に、新しい経費「レコード」を追加します。その後、変更した配列で expenses 属性を更新 (set) します。

参照はコレクションではない

ここではコレクション、つまり配列を初めて更新します。経験豊富なプログラマであれば、「なぜここで set() が必要なの?」といぶかっていることでしょう。

component.get("v.expenses") により、component 属性に保存されている配列の参照を取得します。component.set("v.expenses", theExpenses) では、単に component 属性を同じ参照に設定します。たしかに、その間に配列のコンテンツは追加されますが、コンテナは同じです。配列の参照は実際には変更されません。では、なぜ更新するのでしょうか?

これがどういう意味なのか理解できない場合は、2 つのログ記録ステートメントを重要なステートメントの前後に追加し、theExpenses のコンテンツをコンソールに出力してください。

console.log("Expenses before 'create': " + JSON.stringify(theExpenses));
theExpenses.push(newExpense);
component.set("v.expenses", theExpenses);
console.log("Expenses after 'create': " + JSON.stringify(theExpenses));

アプリケーションを再読み込みして実行し、経費を最低 2 個追加して、theExpenses の構造を調べます。component.set() の行をコメントにして、もう一度実行してください。

あれ? component.set()theExpenses にまったく影響していないではないですか。どういうことでしょう? 実際には何を実行しているのでしょうか?

このように疑問に思って当然です。その答えは、マジックです。

component.set() がここで実行していることは、expenses 属性の値の更新ではありません。expenses 属性が変更されたことの通知をトリガしているのです。

その結果、expenses 属性を式で参照したアプリケーションのすべての場所で、その式の値が更新され、expenses 属性が使用されたすべての場所にその更新がカスケードされます。すべて更新されて、新しいコンテンツに基づいて再表示されます。すべてはバックグラウンドで行われ、{!v.expenses} を使用するときに実行される自動ワイヤリングの一部として、Lightning コンポーネントフレームワークによって処理されます。一言でいえば、マジックです。

まとめると、簡単な JavaScript であれば、component.set() は必要ありません。Aura コンポーネントプログラミングモデルに組み込まれている、基になる効果を引き出すには、それが必要です。コントローラやヘルパーのコードを作成してテストしても何も実行されない場合は、必要な component.set() を行ったことを確認してください。

こうした「うんざりするようなハッキング」では、参照での同じような問題を回避しています。問題を確認するには、その行を変更して 2 つの JSON コールを削除し、アプリケーションをテストします。何が問題なのかは、すぐにわかります。次の単元でその問題を解決するので、ここでは詳しく説明しません。

経費のリストの表示

{!v.expenses} を使用するすべてを「マジックのように」更新することについて説明しましたが、どう思いますか? まだ、他の場所でそれを使用していません。修正していきます。

開発者コンソールで expenseItem という名前の新しい Aura コンポーネントを作成し、デフォルトのマークアップを次のもので置き換えます。expenseItem をすでに作成した場合は、マークアップを更新してください。経費レコードの項目にアクセスする式はすでに確認しました。このバージョンでは SLDS マークアップが追加され、より洗練された外観にできます。
<aura:component>
    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
    <aura:attribute name="formatdate" type="Date"/>
    <aura:attribute name="expense" type="Expense__c"/>
    <lightning:card title="{!v.expense.Name}" iconName="standard:scan_card"
                    class="{!v.expense.Reimbursed__c ?
                           'slds-theme_success' : ''}">
        <aura:set attribute="footer">
            <p>Date: <lightning:formattedDateTime value="{!v.formatdate}"/></p>
            <p class="slds-text-title"><lightning:relativeDateTime value="{!v.formatdate}"/></p>
        </aura:set>
        <p class="slds-text-heading_medium slds-p-horizontal_small">
           Amount: <lightning:formattedNumber value="{!v.expense.Amount__c}" style="currency"/>
        </p>
        <p class="slds-p-horizontal_small">
            Client: {!v.expense.Client__c}
        </p>
        <p>
            <lightning:input type="toggle"
                             label="Reimbursed?"
                             name="reimbursed"
                             class="slds-p-around_small"
                             checked="{!v.expense.Reimbursed__c}"
                             messageToggleActive="Yes"
                             messageToggleInactive="No"
                             onchange="{!c.clickReimbursed}"/>
        </p>
    </lightning:card>
</aura:component>
経費項目の [Reimbursed? (払い戻し済み?)] 項目がチェックされていると <lightning:card> は SLDS テーマを割り当てます: {!v.expense.Reimbursed__c ? 'slds-theme_success' : ''}。この式を使用して、UI の払い戻し済み経費項目の外観を制御できます。それ以上のカスタマイズをしなければ、expense 項目は、それが含まれる expensesList コンポーネントのスタイルで表示されます。expenseItem コンポーネントをカスタマイズするには、[STYLE (スタイル)] をクリックして次のコードを追加します。
.THIS.slds-card.slds-theme_success {
    background-color: rgb(75, 202, 129);
}

次は、以下のコードを使用してクライアント側コントローラ expenseItemController.js を作成します。ここでは、後でサーバによって JavaScript Date オブジェクトに返される日付を変換し <lightning:formattedDateTime> および <lightning:relativeDateTime> で正しく表示されるようにします。この変換は、コンポーネントの初期化中に <aura:handler> タグで取得されて処理されます。これは、初期化イベントを処理するのに便利な方法です。後で Salesforce からデータを読み込む時にも使用します。

({
    doInit : function(component, event, helper) {
        let mydate = component.get("v.expense.Date__c");
        if(mydate){
            component.set("v.formatdate", new Date(mydate));
        }
    },
})
開発者コンソールで expensesList という名前の Aura コンポーネントを作成し、デフォルトのマークアップを次のもので置き換えます。
<aura:component>
    <aura:attribute name="expenses" type="Expense__c[]"/>
    <lightning:card title="Expenses">
        <p class="slds-p-horizontal_small">
            <aura:iteration items="{!v.expenses}" var="expense">
                <c:expenseItem expense="{!expense}"/>
            </aura:iteration>
        </p>
    </lightning:card>
</aura:component>

ここに新しいことはあまりありません。これは、経費のリストを表示するコンポーネントです。expenses という 1 つの属性がありますが、これは expense (Expense__c) オブジェクトの配列です。<aura:iteration> を使用して、経費オブジェクトごとに <c:expenseItem> を作成しています。その結果は、おわかりのように、経費のリストの表示です。順調です。

ここで、expensesList コンポーネントを expenses コンポーネントの末尾に追加してください。expenses.cmp</aura:component> 終了タグの直前に追加してください。

<c:expensesList expenses="{!v.expenses}"/>

アプリケーションを再読み込みすると、経費セクションがフォームの下に表示されます(ビジュアル的にはあまり適切ではありませんが、今のところはこれで十分です)。

ここでは何を行ったのでしょうか? expensesList コンポーネントを追加し、メインの expenses 属性を渡しましたexpensesList に含まれる expenses のインスタンスは、expenses コンポーネントに含まれる expenses のインスタンスと同じです。これらはレコードの同じ配列を参照し、Lightning コンポーネントのマジックにより、メインの expenses 配列が更新されると、expensesList コンポーネントはそれに「気づき」、リストを再表示します。ぜひ試してみてください。

お疲れ様でした。長い単元になってしまいました。少し休憩を取ってください。立ち上がって、しばらく歩き回ることをお勧めします。

戻って来たら、新しい経費を実際に保存する方法について説明します。