Trailhead：「フォームを入力したデータの入力」Challenge

Trailheadやってて一番苦痛だったモジュールを、アウトプットがてら詳しく見ていきます。翻訳はGoogle翻訳から。

#事前準備

>In this challenge you'll create a form to enter new items, a list to display the items entered, and add SLDS styling. 
この課題では、新しい項目を入力するためのフォーム、入力された項目を表示するためのリストを作成し、SLDSスタイルを追加します。
>First, to make our camping list look more appealing, change the campingHeader component to use lightning:layout and SLDS. 
まず、キャンプリストをより魅力的に見せるには、campingHeaderコンポーネントをlightning：layoutとSLDSを使用するように変更します。
>Similar to the unit, style the Camping List H1 inside the slds-page-header. Add the action:goal SLDS icon using lightning:icon.
ユニットと同様に、slds-page-header内にCamping List H1をスタイルします。 lightning：iconを使用してaction：goal SLDSアイコンを追加します。

ここはユニットどおりのSLDSを割り当てるだけ。見栄えにsalesforce感が出ます。

```campingHeader.cmp
<aura:component >
    <lightning:layout class="slds-page-header">
        <lightning:icon iconName="action:goal" />
        <h1>Camping List</h1>
    </lightning:layout>
</aura:component>
```

>Next, modify the campingList component to contain a new item input form and an iteration of campingListItem components for displaying the items entered. 
次に、campingListコンポーネントを変更して、新しいアイテム入力フォームと、入力したアイテムを表示するためのcampingListItemコンポーネントの繰り返しを含めます。
>Here are additional details for the modifications to the campingList component.
これは、campingListコンポーネントに対する変更の詳細です。


この文章は5つのナカグロの概要を説明しています。

#手順1

>Add an attribute named items with the type of an array of camping item custom objects.
キャンプアイテムのカスタムオブジェクトの配列の型を持つitemsという名前の属性を追加します。

ここは、ユニットの「まず、新しい経費を「保存」する場所を作成しましょう。単に経費のローカル専用配列を作成して、そこに経費を保存します。」に対応します。apexクラスで言うnewするイメージで、レコードを保持するための受け皿を用意します。
`<aura:component >`直下に以下を追加します。

```
<aura:attribute name="items" type="Camping_Item__c[]"/>
```

#手順2

>Add an attribute named newItem of type Camping_Item__c with default quantity and price values of 0.
デフォルトの `Quantity__c` と `Price__c` の値が0で、タイプCamping_Item__cのnewItemという名前の属性を追加します。

ユニットの「sObject の JSON 表現であり、オブジェクトの種類 (ここでも API 参照名)、およびデフォルトで設定される、各項目の値を指定しているのです。ここでは、基本的に、空の値の表現にすべてを設定しています。」に対応しています。これもapexクラスで言うnewするイメージです。JSON形式の文字列であることを覚えておきましょう。
先ほど追加した配列に、デフォルト値を設定する以下を追加しましょう。

```
    <aura:attribute name="newItem" type="Camping_Item__c" description=""
                    default="{ 'sobjectType': 'Camping_Item__c',
                             'Name': '',
                             'Quantity__c': 0,
                             'Price__c': 0,
                             'Packed __c': false}" />
```

#手順3

>The component displays the Name, Quantity, Price, and Packed form fields with the appropriate input component types and values from the newItem attribute. The Quantity field accepts a number that's at least 1.
コンポーネントは、[Name]、[Quantity__c]、[Price__c]、[Packed]フォームの各フィールドに、適切な入力コンポーネントの種類とnewItem属性の値を表示します。 [Quantity__c]フィールドには、1以上の数字を入力できます。

入力フォームを用意します。ユニットでは「ここで作成しているのは特定のデータ型を持つ、<lightning:input> コンポーネントの複数のインスタンスです。」と説明されている長いコードです。
以下のコードを続けて追加します。

* min：入力できる最小値。
* step：小数点以下をどれだけ細分するか指定できます。デフォルトは1で、細かさを気にしない場合はanyを入力します。
* formatter:数値を特定の形式で表示したいときに使います。`decimal, percent, percent-fixed, and currency`が対象だそうです。
* messageWhenRangeUnderflow：値が少なすぎたときに出るエラーです。他にも不正入力や文字数長すぎ短すぎといったエラーメッセージを細かく指定できます。

```
    <!-- 名前  -->
    <lightning:input aura:id="itemform" label="Item Name"
                     name="itemname"
                     value="{!v.newItem.Name}"/> 
    <!-- 数量  -->
    <lightning:input type="number" aura:id="itemform" label="quantity"
                     name="itemquantity"
                     value="{!v.newItem.Quantity__c}"
                     min="1"
                     step="any"
                     messageWhenRangeUnderflow="Enter an Quantity that's at least 1."/>
    <!-- 価格  -->
    <lightning:input type="number" aura:id="itemform" label="Price"
                     name="itemprice"
                     value="{!v.newItem.Price__c}"
                     formatter="currency"
                     step="0.01"/>
    <!--  梱包済み -->
    <lightning:input type="checkbox" aura:id="itemform" label="Packed!"  
                     name="itempacked"
                     checked="{!v.newItem.Packed__c}"/>
```

#手順4
Submitting the form executes the action clickCreateItem in the JavaScript controller.
フォームを送信すると、JavaScriptコントローラのアクションclickCreateItemが実行されます。

コンポーネントにボタンを用意します。
以下のコードを追加します。

```
    <!--  登録ボタン -->
    <lightning:button label="Create Item" 
                      class="slds-m-top--medium"
                      variant="brand"
                      onclick="{!c.clickCreateItem}"/>
```

#手順5

>If the form is valid, the JavaScript controller pushes the newItem onto the array of existing items, triggers the notification that the items value provider has changed, and resets the newItem value provider with a blank sObjectType of Camping_Item__c. For this challenge, place the code in your component's controller, not the helper.
フォームが有効な場合、JavaScriptコントローラはnewItemを既存のアイテムの配列にプッシュし、アイテム値プロバイダーが変更されたという通知をトリガーします。そして、newItem値プロバイダをCamping_Item__cの空白のsObjectTypeでリセットします。
この課題では、コードをヘルパーではなくコンポーネントのコントローラに配置します。

ここで注意が必要なのは、ユニットではコントローラとヘルパーに分けていた処理をすべてコントローラにまとめることです。
JS初心者にとって、ここだけはコピペで何とかなる範囲を超えています(初心者が手を出すべきでないことはトレイルの冒頭で述べていましたが...)。

##手順5-1
>フォームが有効な場合、

順番に見ていきましょう。まず、フォームが有効かどうかチェックします。ここは別記事でねっとり理解したのでよろしければ[ご覧ください](https://qiita.com/takeakiooiwa/items/e5f98d49df84443017fe)。
今回、入力フォームの`aura:id`はitemformですので、ここだけかえてコピペします。

```
({
    clickCreate: function(component, event, helper) {
        var validItem = component.find('itemform').reduce(function (validSoFar, inputCmp) {
            //エラーメッセージをフィールドに表示
            inputCmp.showHelpMessageIfInvalid();
            return validSoFar && inputCmp.get('v.validity').valid;
        }, true);
        //エラーが無ければ登録処理
        if(validItem){
        }
    }
})
```

##手順5-2

>JavaScriptコントローラはnewItemを既存のアイテムの配列にプッシュし、アイテム値プロバイダーが変更されたという通知をトリガーします。

ユニットでは登録処理をヘルパーで行っていました。処理を冷静に分解できればそこまで難しくありません。

* `component.get("v.newItem")`：値を入力したインスタンスを取得します。
* ` JSON.stringify()`：JSオブジェクトを文字列にデコードします。
* `JSON.parse()`：文字列をJSオブジェクトにエンコードします。

取得したインスタンスを文字列にデコードして再びエンコードする理由が分かりませんが...。

```
            //JSON文字列をオブジェクト型に構築。
            var newItem = JSON.parse(JSON.stringify(component.get("v.newItem")));
            //レコード登録
            theItems.push(newItem);
```

##手順5-3
>そして、newItem値プロバイダをCamping_Item__cの空白のsObjectTypeでリセットします。

ここがわかりませんでした。リストを初期化する部分は「参照はコレクションではない」の項で説明されています。
しかし`newItem`の初期化はJSのオブジェクトを直接渡すことで実現できるようです。
コンポーネントで記述したインスタンスのデフォルト値が使えないのかと思いましたが、画面上で既に値が書き換わっているのでコントローラ側でも生成する必要がある...ということでしょうか。


```
            //登録したレコードを追加したリストで初期化
            component.set("v.items", theItems);
            //デフォルト値を初期化
            component.set("v.newItem", { 'sobjectType': 'Camping_Item__c',
                                        'Name': '',
                                        'Quantity__c': 0,
                                        'Price__c': 0,
                                        'Packed__c': false});
```

最終的にコントローラは以下のようになります。

```
({
    clickCreate: function(component, event, helper) {
        var validItem = component.find('itemform').reduce(function (validSoFar, inputCmp) {
            //エラーメッセージをフィールドに表示
            inputCmp.showHelpMessageIfInvalid();
            return validSoFar && inputCmp.get('v.validity').valid;
        }, true);
        //エラーが無ければ登録処理
        if(validItem){
            //JSON文字列をオブジェクト型に構築。
            var newItem = JSON.parse(JSON.stringify(component.get("v.newItem")));
            //レコード登録
            theItems.push(newItem);
            //配列を取得
            var theItems = component.get("v.items");
            //登録したレコードを追加したリストで初期化
            component.set("v.items", theItems);
            //デフォルト値を初期化
            component.set("v.newItem", { 'sobjectType': 'Camping_Item__c',
                                        'Name': '',
                                        'Quantity__c': 0,
                                        'Price__c': 0,
                                        'Packed__c': false});
        }
    }
})
```