コンポーネントを利用して、人物検索アプリを作ってみた

#目次
1. はじめに
2. 実装方法
    1. コンポーネントの作成方法
    2. コンポーネントのコーディング
    3. 2で作成した関数を親Formで使用する
    4. 親フォーム画面の作成と作成したコンポーネントの取り込み
    5. 親フォームのコーディングとアクションの紐付け
3. まとめ
4. 参考

#はじめに
アプリで名前を検索する時にいくつか候補が出てくる機能はみたことあるかと思います。
例)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592264/1635655e-5568-fc6a-c318-589cb943f124.png)
こちらの候補リストをみると、1つ1つが似たような構成でできているのがわかると思います。
この仕組みは1つのコンポーネントを複製し、氏名を変えてリスト化しています。

今回は以下をご紹介したいと思います。
1. コンポーネントの作成
2. コンポーネントのコーディング
3. ２で作成した関数を親Formで使用する
4. 親フォーム画面の作成と作成したコンポーネントの取り込み
5. 親フォームのコーディングとアクションの紐付け
6. デモ

完成イメージ
<blockquote class="twitter-tweet"><p lang="und" dir="ltr"><a href="https://t.co/Uazjr6uf5S">pic.twitter.com/Uazjr6uf5S</a></p>&mdash; Kony (@Kony12763790) <a href="https://twitter.com/Kony12763790/status/1349623276931219460?ref_src=twsrc%5Etfw">January 14, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
テキストに名前を入力すると候補が表示され、候補の中から1つ選択すると、上に選択した名前が表示されます。
また、上に表示されている名前の右にある×ボタンを押すと、削除されます。

#実装方法
######1. コンポーネントの作成方法
コンポーネントの作成については[こちら](https://qiita.com/Kony_Team/items/a25b322d9524f9f1ac92)の記事をご参照ください。
こちらの記事では簡単に説明します。

左側メニューの`Template` > `Component`のプルダウンをクリックしてください。
`New` > `with Contract` をクリックしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592264/14fcef99-3e55-4b8f-9b92-d432b692a5aa.png)

NamespaceとNameを入力し、`OK`をクリックしてください。
※ここではNamespaceを**component**、Nameを**ListAccount**としています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592264/3fb9a755-5001-db64-b5f1-abe50d93a005.png)

コンポーネントの画面構成はこのようになっています。
**flxSegList**の中に**segListAccount**があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592264/df61949f-1f90-e95a-df6c-4439c9c8cb77.png)

**segListAccount**のRow Templateのこのような感じで作成しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592264/47104405-339e-8a52-9b6c-00d31f15e4a7.png)

######2. コンポーネントのコーディング
コンポーネントで実装したいことはユーザーが検索するときにアカウントの一覧を表示することです。

```ListAccountController.js
define(function() {

  return {
    dataFilter : [],
    constructor: function(baseConfig, layoutConfig, pspConfig) {

    },
    //Logic for getters/setters of custom properties
    initGettersSetters: function() {

    },
    filterAccountName : function(dataFilter) {
      if(dataFilter.length > 0) {
        var item; 
        var listItemPush = [];
        for(var i = 0 ; i < dataFilter.length ; i++){
          item = {
            // 名前
            lblAccountName: dataFilter[i].lblAccountName,
            // 画像            
imgAccount : "https://3.bp.blogspot.com/-As7Bl3jHRRU/WKFjA6EypgI/AAAAAAABBsI/XJ4w9lDupc8LngEYB1Dn7tfPWXvPiZXZwCLcB/s800/face_smile_woman4.png",
            id :  dataFilter[i].id
          };
          listItemPush.push(item);
        }
        // セグメントにセットする
       	this.view.segListAccount.setData(listItemPush); 
      }
    }
  };
});
```
######3. 2で作成した関数を親Formで使用する
`component.ListAccount`をクリックしてください。

まずはイベントの設定を行っていきます。
右側のメニューで `Properties` > `Action` > `Manage Events` をクリックしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592264/7d01442b-f850-d35e-d2f7-460e5609b329.png)
今回設定したいイベントはセグメントをクリックするイベントなので、以下のように設定します。
設定が終わったら、 `Apply` をクリックしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592264/64ca4ab8-6d09-53c9-1d26-3d9ff3740d67.png)

次に関数（メソッド）の設定を行なっていきます。
右側のメニューで `Properties` > `Action` > `Manage Methods` をクリックしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592264/b21dd0f0-a431-8d1c-7dfe-b98df10f5cd5.png)
上のタブを`Custom`にし、以下のように設定します。
設定が終わったら、 `Apply` をクリックしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592264/073fe9d2-534e-0d4d-11a5-2cf4f1bc2e81.png)

######4. 親フォーム画面の作成と作成したコンポーネントの取り込み
コンポーネントの画面構成はこのようになっています。
上にタイトル（lblTitle）、真ん中にテキストボックス（txtApprover）、下にコンポーネント（ListAccountApprover）があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592264/379fed57-cf17-23e2-0269-b10d08ed9fd9.png)

コンポーネントの取り込む方法は
フォームをクリックし、表示させてください。
左側メニューで `Templates` > `Components` > 該当のコンポーネントをドラッグしてフォームが表示されているところに持っていきます。
<blockquote class="twitter-tweet"><p lang="und" dir="ltr"><a href="https://t.co/zfAEj0Y6GL">pic.twitter.com/zfAEj0Y6GL</a></p>&mdash; Kony (@Kony12763790) <a href="https://twitter.com/Kony12763790/status/1349614231134433281?ref_src=twsrc%5Etfw">January 14, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

######5. 親フォームのコーディングとアクションの紐付け
このフォームで実装したいことは以下です。

* ユーザーが検索するときにアカウントの一覧を表示
* 候補の中から1つ選択すると、上に選択した名前が表示
* 上の名前の右にある×ボタンを押すと削除

```FormSelectAccountController.js
define({ 
  //dummy data
  listAccount : [
    {
      id : 1, 
      imgAccount : "",
      lblAccountName : "Yamada Taro"
    },
    {
      id : 2, 
      imgAccount : "",
      lblAccountName : "Tanaka Hanako"
    },
    {
      id : 3, 
      imgAccount : "",
      lblAccountName : "Sasaki Taro"
    },
    {
      id : 4, 
      imgAccount : "",
      lblAccountName : "Yamamoto Jiro"
    },
  ],
  count : 0,
  selectedAccountName : "",
  lstAccount: [],
  lstApprover: [],
	
  //Display a list of accounts when user searches
  onTextChange : function(){
    var txtApprover = this.view.txtApprover.text.toLowerCase();
    var listFilteredApprover = this.filterName(txtApprover);
    if(listFilteredApprover.length > 0 && txtApprover !== "" ){
      this.view.ListAccountApprover.filterAccountName(listFilteredApprover);
      this.view.ListAccountApprover.isVisible = true;
    }else{
      this.view.ListAccountApprover.isVisible = false;
    }
  },

  //account search
  filterName : function(textbox){
    var listFiltered = this.listAccount.filter(elem => {
      return elem.lblAccountName.toLowerCase().includes(textbox.trim());
    });
    var rs = listFiltered.sort((a,b) => {
      var nameSplit = a.lblAccountName.split(" ");
      var name = nameSplit[nameSplit.length-1].substring(1);
      if(name.includes(textbox) ) return 1;
      return -1;
    });
    return rs;
  },
  
 //displays the account when the user chooses an account in the segment
  onRowClickSegmentApprover : function(rowData){
    this.selectedAccountName = rowData.focusedItem.lblAccountName;
    this.view.txtApprover.text = this.selectedAccountName;
    this.view.ListAccountApprover.isVisible = false;
    this.count++;
    this.createFlexAccount(this.selectedAccountName);
    this.lstApprover.push(this.selectedAccountName);
    this.view.txtApprover.setFocus(true);
    this.view.forceLayout();
  },

  //create account 
  createFlexAccount : function(data){
    var flexAccount = new kony.ui.FlexContainer({
      "id": "flxAccount"+this.count,
      "top": "5dp",
      "bottom":"5dp",
      "left": "0dp",
      "width": "230dp",
      "height": "28dp",
      "zIndex": 1,
      "clipBounds": false,
      "isVisible": true,
      "skin" : "sknFlxSegListBorder"
    }, {
      "padding": [0, 0, 0, 0]
    }, {});
    var flxImage = this.createFlexImage();
    var lblAccount = this.createLabelAccount(data);
    flexAccount.add(lblAccount);
    flexAccount.add(flxImage);
    this.view.flxItems.add(flexAccount);
    this.view.txtApprover.text = "";
    this.lstAccount.push(lblAccount);
  },

  //create label account 
  createLabelAccount : function(data){
    var lblAccount = new kony.ui.Label({
      "id": "lblAccount"+this.count,
      "left": "10dp",
      "width": kony.flex.USE_PREFERRED_SIZE,
      "height": kony.flex.USE_PREFERED_SIZE,
      "zIndex": 1,
      "maxNumberOfLines":1,
      "textTruncatePosition": constants.TEXT_TRUNCATE_END,
      "centerY" : "50%",
      "text" :  data,
      "isVisible": true,
      "skin" : "sknLblGrayDark"
    }, {
      "padding": [0, 0, 0, 0]
    }, {});
    return lblAccount;
  },

  //create flex image
  createFlexImage : function(){
    var flexImage = new kony.ui.FlexContainer({
      "id": "flexImage"+this.count,
      "right": "3dp",
      "width": "17dp",
      "height": "17dp",
      "centerY": "50%",
      "zIndex": 100,
      "isVisible": true,
      "skin" : "sknBorderCircleWhite",
      "onClick" : this.deleteFlexAccount
    }, {
      "padding": [0, 0, 0, 0]
    }, {});
    var imgX = this.createImageX();
    flexImage.add(imgX);
    return flexImage;
  },

  //create image X
  createImageX : function(){
    var imgAccount = kony.ui.Image2({
      "id": "imgX"+this.count,
      "src": "x_icon_red.png",
      "width": "50%",
      "height": "50%",
      "centerY": "50%",
      "centerX": "50%",
      "zIndex": 100,
    }, {
      "padding": [0, 0, 0, 0]
    }, {});
    return imgAccount;
  },

  //delete account
  deleteFlexAccount : function(flxImage){
    var id = flxImage.id;
    var length = id.length;
    var indexItem = id.substr(9,length - 1);
    var flxAccount = this.view["flxAccount" + indexItem];
    var index = this.lstAccount.indexOf(flxAccount["lblAccount" + indexItem].text);
    if (index > -1) {
      this.lstAccount.splice(index, 1);
    }
    this.view.flxItems.remove(flxAccount);
    this.view.txtApprover.setFocus(true);
  },
});
```
上を説明します。
**listAccount**：ダミーデータ。
**onTextChange**：テキストを入力する度に候補リストを表示。ここで先ほどコンポーネント上で作成した関数（`filterAccountName`）が使われています。`txtApprover`のAction（`onTextChange`）に紐付ける。
**filterName**：名前を検索
**onRowClickSegmentApprover**：候補リストから1つ選択した時に上に選択した人物を表示。こちらは先ほど作成したコンポーネントのイベントに紐づける。
**createFlexAccount**,**createLabelAccount**,**createFlexImage**,**createImageX**：上に表示するwidget。
**deleteFlexAccount**：**createFlexAccount**で作成したwidgetを削除する関数。**createFlexAccount**のAction（onClick）に紐付いている。

Actionを紐づけていきます。
1つ目：`txtApprover` > `Action` > `onTextChange` に**onTextChange**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592264/d379b773-ad60-b0e2-0f24-513c953e6e95.png)
2つ目：`ListAccountApprover` > `Action` > `onRowClickSegment` に**onRowClickSegmentApprover**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592264/99af76d5-09be-a22b-e160-6c85135ace84.png)

実際に動作を見てみましょう:eyes:
<blockquote class="twitter-tweet"><p lang="und" dir="ltr"><a href="https://t.co/Uazjr6uf5S">pic.twitter.com/Uazjr6uf5S</a></p>&mdash; Kony (@Kony12763790) <a href="https://twitter.com/Kony12763790/status/1349623276931219460?ref_src=twsrc%5Etfw">January 14, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

#まとめ
人物検索だけでなく、商品リスト等コンポーネントを利用したアプリはたくさんあります。
開発の際にコンポーネントが使えるかもと思った時には参考にしていただけたらと思います。
ぜひ試してみてください:wave:

#参考
Kony Visualizerでコンポーネントを作成する：
https://qiita.com/Kony_Team/items/a25b322d9524f9f1ac92

Use Components：
https://docs.kony.com/konylibrary/visualizer/visualizer_user_guide/Content/C_UsingComponents.htm