一覧画面で1レコードごとにボタン配置

# 前置き
12月、アドベントカレンダーの季節ですね、去年は見ていただけでしたが、
今年は私にとって挑戦の年でしたので、今回参加してみました。とはいっても、もともと書くつもりはなく、
Part2の12月9日の枠はこの記事を書いている現在、9日朝8時の時点で空枠で寂しいので
急遽明日の記事を書くことを決めました。
書きながらいろいろ見つけたり実験したりしたので、少し長い記事になります。

# 経緯
私自身、カスタマイズビューを作ることができるようになったが、
やはり、純正に近い形で便利にしたい。カスタマイズビューでレスポンシブなビューも作りましたが、
今回は普段使っている一覧の**レコード一件ずつボタンを配置するカスタマイズ**を紹介します。
今まで紹介していなかった理由がありますので、是非ご意見をお願いします。

# カスタマイズをするとこんな画面
![ボタン配置_LI.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/511519/090aa49e-9908-31a4-24fb-764d46db7b50.jpeg)

# 用意するもの
## フィールド
とりあえず、フィールドを何かしら用意します。
ここでは一覧でボタンを設置したいので一覧に表示できるフィールド。
kintone.app.getFieldElements(fieldcode)で要素を取得できるフィールドとなります。

|フィールド|タイプ|フィールドコード|
|:--|:--|:--|
|ボタン用フィールド|文字列一行|field_for_button|
|ボタン表示内容|文字列一行|ボタン表示内容|

## jQuery
[Cybozu CDN](https://developer.cybozu.io/hc/ja/articles/202960194-Cybozu-CDN)より

# ソースコード

```Javascript:button.js
  kintone.events.on('app.record.index.show',function(event){
    
    const field = 'field_for_button';
    const records = event.records;
    let element = kintone.app.getFieldElements(field);
    
    for(let i in records){
      
      let rec_json = JSON.stringify(records[i]);
      $(`
        <button id="button_${ records[i]['レコード番号'].value }" class="button_add" name='${ rec_json }'>
          ${ records[i]['ボタン表示内容'].value }
        </button>
      `)
      .appendTo(element[i]);

    }
    
    $('.button_add').on('click',function(e){
      
      const  record = JSON.parse($(this).attr('name'));
      console.log(record);

    });
    
  });
```

## やっていること
感覚的に、ゴリ押しかなって思って作りましたが、
フィールドエレメントを取得した後にボタンを子要素として追加しています。
name属性に**レコード情報をJSON化してぶち込みます。**
**name="${rec_json}"** とするとJSON化したレコード内容に反応してエラーを起こします。
シングルクォーテーションで囲いましょう。

# 実際のところ、どうなの
HTMLは今まで、ネットで見たものを試しながら書いたりしていました。
**”本来の使用方法とは全く異なること”**くらいは理解していますが、
テストアプリ上ではスムーズに操作できているので、**まぁいいや**ってことで使っていたカスタマイズでした。
name属性を綺麗に収めるのであればレコード番号を入れておいてクリックイベントでAPIでGETする。
なんてこともできそうですが、運用次第ではすぐに制限に引っ掛かります。

## ちょっと待った
結構昔に作ったカスタマイズだったので、記事を書きながらコード整理をしていました。

>name属性を綺麗に収めるのであればレコード番号を入れておいてクリックイベントでAPIでGETする。
なんてこともできそうですが、運用次第ではすぐに制限に引っ掛かります。

ん、。？よく考えたら。
まてまて、そのレコード番号は**どっから持ってきとっとや**、index.showイベントのevent.recordsから**だろたい？**
そんなかで再検索かければ**もっと綺麗じゃなかと**？
すみません、肥後(熊本)の血が騒ぎました。

# 再コーディング

```Javascript
  kintone.events.on('app.record.index.show',function(event){
    
    if(event.viewId != 5738202) return;
    
    const field = 'field_for_button';
    const records = event.records;
    let element = kintone.app.getFieldElements(field);
    
    for(let i in records){
      
      let rec_id = records[i].$id.value;
      $(`
        <button id="button_${ rec_id }" class="button_add" name='${ rec_id }'>
          ${records[i]['ボタン表示内容'].value}
        </button>
      `)
      .appendTo(element[i]);
      
    }
    
    $('.button_add').on('click',function(e){
      
      const  id = $(this).attr('name');
      let record = "";
      for(let i in records){
        if(records[i].$id.value == id){
          record = records[i];
        }
      }
      console.log(record);

    });
    
  });
```

## 綺麗になった
記事を書きながら思いついてやってみたら、シンプルに仕上がりました。
name属性にはidのみ格納したうえで、クリックイベントでクリックされたボタンのname属性から
idを取得→event.recordsをループで回してidが一致すればrecordとしてクリックイベント内で宣言。

# ついでに実験してみた。
## 気になること
name属性に**JSONをぶち込むやり方**と、idのみ格納してクリックイベントで表示中の一覧から一致するものを
**idから再検索でヒットさせる方法**のどちらが速いのか。

## 予測
カスタマイズに利用している項目は少なく、さらに、覧で表示できる最大件数は100件であるため
速度はさほど変わらないと予測する。

## 実験方法
**performance.mark()**を使用

* レコード内容の多いアプリでボタン配置の処理スピードを計測
* 同アプリにて、クリックしてからコンソールで表示されるまでのスピードを計測

# 結果
下記に2パターンの結果を記す。

ボタン設置に関して、json化→nameに格納するパターンは、
nameにidのみを格納するパターンに比べ**3倍ほど**時間がかかっていることがわかる。
実験を行ったアプリのフィールド数は56うちテーブルが1フィールド存在する。

## name="レコード番号"

ボタン設置

|平均|最大|最小|
|--:|--:|--:|
|5.13ms|5.45ms|4.87ms|

クリックしてレコードをコンソールに出すまで

|クリック|結果|
|:--|:--:|
|1 回目 |0.05 ms |
|2 回目 |0.04 ms |
|3 回目 |0.06 ms |
|4 回目 |0.04 ms |
|5 回目 |0.04 ms |
|6 回目 |0.05 ms |
|7 回目 |0.04 ms |
|8 回目 |0.04 ms |
|9 回目 |0.04 ms |
|10 回目 |0.04 ms |

## name='JSON'

ボタン設置

|平均|最大|最小|
|--:|--:|--:|
|13.84ms|16.25ms|12.72ms|

クリックしてレコードをコンソールに出すまで

|クリック|結果|
|:--|:--:|
|1 回目 |0.14 ms |
|2 回目 |0.09 ms |
|3 回目 |0.06 ms |
|4 回目 |0.06 ms |
|5 回目 |0.06 ms |
|6 回目 |0.1 ms |
|7 回目 |0.06 ms |
|8 回目 |0.06 ms |
|9 回目 |0.07 ms |
|10 回目 |0.07 ms |

# 考察
さほど差は出ないと予測したが、ボタンの設置には差があった。
JSON.stringify()の処理が関係しているといえる。
1アプリで10回しかテストを行っていないが、フィールド数(1レコードの容量)に比例する可能性が高い。


# 用途・最後に
ボタンじゃなくてもチェックボックスなど追加できると思うので、
ステータス一括処理を実装するとき便利なのかなーって思ってます。

ここからのカスタマイズの枝分かれは相当広いのではないのでしょうか？
調べてもあまり出てこないジャンルのカスタマイズでしたので、記事に残します。

name属性にjsonデータを入れることについて、コメント、意見をお願いします！
パワープレイなのか誰も行ってこなかっただけなのか、意見お待ちしております。

<details><summary>テストコード</summary><div>

```Javascript:test.js
(function() {
  'use strict';
  
  const result_object = {
      id:{
        render:0,
        _click:[]
      },
      json:{
        render:0,
        _click:[]
      }
    };
  let clickCounter = 0;
  
  kintone.events.on('app.record.index.show',function(event){
    
    if(event.viewId != 5738202) return;//指定の一覧以外では実行しない
    
    const field = 'field_for_button'; //文字列一行フィールドコード
    const sec_field = 'field_for_button_json';//文字列一行フィールドコード
    const records = event.records;
    let element = kintone.app.getFieldElements(field);
    let sec_element = kintone.app.getFieldElements(sec_field);
    
    $('<button id="table_show_markdown">table</button>')
    .appendTo(kintone.app.getHeaderMenuSpaceElement());
    
    //id
    performance.mark('idModeRenderingStart');
    for(let i in records){
      
      let rec_id = records[i].$id.value;
      $(`
        <button id="button_${ rec_id }" class="button_add" name='${ rec_id }'>
          ${records[i]['ボタン表示内容'].value}
        </button>
      `)
      .appendTo(element[i]);
      
    }
    performance.mark('idModeRenderingEnd');
    performance.measure(
      'idModeRenderingPerformance',
      'idModeRenderingStart',
      'idModeRenderingEnd'
    );
    let render_result = performance.getEntriesByName('idModeRenderingPerformance');
    console.log(render_result);
    result_object.id.render = render_result;
    console.log('idModeRenderingPerformance');
    console.log(Math.round(Number(render_result[0].duration)*100)/100 + ' ms');
    
    //json
    performance.mark('jsonModeRenderingStart');
    for(let i in records){
      
      let rec = JSON.stringify(records[i]);
      $(`
        <button id="button_${ records[i].$id.value }" class="button_add_json" name='${ rec }'>
          ${records[i]['ボタン表示内容'].value}
        </button>
      `)
      .appendTo(sec_element[i]);
      
    }
    performance.mark('jsonModeRenderingEnd');
    performance.measure(
      'jsonModeRenderingPerformance',
      'jsonModeRenderingStart',
      'jsonModeRenderingEnd'
    );
    let json_render_result = performance.getEntriesByName('jsonModeRenderingPerformance');
    console.log(json_render_result);
    result_object.id.render = Math.round(Number(json_render_result[0].duration)*100)/100;
    console.log('idModeRenderingPerformance');
    console.log(Math.round(Number(json_render_result[0].duration)*100)/100 + ' ms');
    
    //id
    $('.button_add').on('click',function(e){
      
      const id = $(this).attr('name');
      
      performance.mark(`id_onClick_${clickCounter}`);
      
      let record = "";
      for(let i in records){
        if(records[i].$id.value == id){
          record = records[i];
        }
      }
      
      performance.mark(`id_foundRecord_${clickCounter}`);
      performance.measure(
        `id_clickPerformance_${clickCounter}`,
        `id_onClick_${clickCounter}`,
        `id_foundRecord_${clickCounter}`
      );
      let click_result = performance.getEntriesByName(`id_clickPerformance_${clickCounter}`);
      let resultTime = Math.round(Number(click_result[0].duration)*100)/100;
      console.log(`ClickPerformance_${clickCounter}`);
      console.log(resultTime + ' ms');
      
      result_object.id._click.push(resultTime);
      clickCounter++;
    });
    
    
    //json
    $('.button_add_json').on('click',function(e){
      
      const  record = $(this).attr('name');
      
      performance.mark(`json_onClick_${clickCounter}`);
      
      let records = JSON.parse(record);
      
      performance.mark(`json_foundRecord_${clickCounter}`);
      performance.measure(
        `json_clickPerformance_${clickCounter}`,
        `json_onClick_${clickCounter}`,
        `json_foundRecord_${clickCounter}`
      );
      let click_result = performance.getEntriesByName(`json_clickPerformance_${clickCounter}`);
      let resultTime = Math.round(Number(click_result[0].duration)*100)/100;
      console.log(`ClickPerformance_${clickCounter}`);
      console.log(resultTime + ' ms');
      
      result_object.json._click.push(resultTime);
      clickCounter++;
    });
    
    //Qiitaのテーブルにコピペしたい
    $('#table_show_markdown').on('click',function(){
      //id
      let id_text = '|クリック|結果|\n|:--|:--:|\n';
      let json_text = '|クリック|結果|\n|:--|:--:|\n';

      result_object.id._click.forEach((t,i) => {
        id_text += '|' + (i + 1) +' 回目 |' + t + ' ms |\n';
      });
      
      result_object.json._click.forEach((t,i) => {
        json_text += '|' + (i + 1) +' 回目 |' + t + ' ms |\n';
      });
      console.log('id click');
      console.log(id_text);
      console.log('json click');
      console.log(json_text);
    });
    
  });
  
  
})();

```

</div></details>