LWCでDatatableのソートと検索の実装

### 要件
取引先画面に自作商談の関連リストを表示したいです。「全件表示」ボタンの代り、関連リストにソートと全件検索を機能する。
### 案
LWCのDatatableで関連リストを実装する
Auraバージョンの実装があるから、下記参照になる。
https://qiita.com/richard_hcq/items/087c8c89d4b1bbd93cd7

### 実装
#### Apex側
基本的に汎用のSelectメソッドを実装する。来るSoqlを実行し、SObjectのリストを返す。
```

public with sharing class CommonDmlUtil {

    @AuraEnabled(cacheable=true)
    public static List<SObject> commonSelectRecords(String strSql) {

        return Database.query(strSql);
       
    }
}
```
### Html
#### point1 縦軸のスクロール
```
<div class="slds-scrollable_y" style="height:10rem">…</div>
```
#### source
dtableOppForAccount.html
```
<template>
    <lightning-card   
        icon-name="custom:custom66"
    >
        <h1 slot="title" style="font-size:14px">商談リスト（{recCount}）</h1>
        <div slot="actions">
                <div class="slds-form_horizontal slds-form slds-grid slds-wrap">
                <lightning-input value={keyword} 
                    class="slds-size_3-of-4"
                    placeholder="キーワード検索"
                    onchange={handleSearchMem}></lightning-input>
                <lightning-button-icon
                    class="slds-size_1-of-4"
                    icon-name="utility:search" ></lightning-button-icon>
                </div>
            </div>
        <div class="slds-scrollable_y" style="height:10rem">
            <template if:true={results}>
                <lightning-datatable
                    key-field="Sequence_Number__c"
                    data={data}
                    columns={columns}
                    sorted-by={sortedBy}
                    sorted-direction={sortedDirection}
                    onsort={sortColumns}
                    show-row-number-column
                >
                </lightning-datatable>
            </template>
        </div>
    </lightning-card>
</template>
```


#### Javascript
dtableOppForAccount.js
```

import { LightningElement,api, wire } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import commonSelectRecords from '@salesforce/apex/CommonDmlUtil.commonSelectRecords';

const COLS = [
    { label: '商談名', fieldName: 'linkName', type: 'url',
       typeAttributes: {label: { fieldName: 'Name' }},sortable: true },
    { label: 'フェース', fieldName: 'StageName'},
    { label: '金額', fieldName: 'Amount',
        type: 'number',cellAttributes: { alignment: 'left' } },
    { label: '取引先', fieldName: 'accountLinkName',type: 'url',
        typeAttributes: {label: { fieldName: 'Account.Name' }},sortable: true },
    { label: '終了予定日', fieldName: 'CloseDate' }    
];
export default class DtableOppForAccount extends LightningElement {
    columns = COLS;
    @api recordId; // parent id
    sortedDirection = 'asc';
    sortedBy = 'Name';
    results;
    data;
    recCount = 0;
    keyword = '';



    connectedCallback(){
    //exec
    var sql = 'Select '
        + ' Id, Name,StageName,'
        + '  Amount,'
        + ' AccountId,'
        + ' Account.Name,'
        + ' CloseDate'
        + ' From Opportunity'
        + " Where AccountId = '" +this.recordId +"'";
        + ' order by Name';
        console.log('test 1 sql>' + sql);
        commonSelectRecords({strSql: sql })
        .then(result => {
            //console.log('test 1 result>' + result);
           //this.results = result;
            
           // var a  = JSON.parse(JSON.stringify(this.results));
            var arr = [];
        
            for (var i = 0; i < result.length; i++) {
                console.log('1.5)'+(JSON.stringify(result[i])));
                var rec = {};
                var key = Object.keys(result[i]);
                for( var j = 0; j < key.length; j++ ) {
                    console.log('key )'+ key[j] + ' -- ' + result[i][key[j]]);
                    rec[ key[j] ] = result[i][ key[j] ];
                    if(key[j]=='Account'){
                        rec['Account.Name'] =  result[i][ key[j] ].Name;
                    }
                }
                rec['linkName'] = '/' + result[i].Id;
                rec['accountLinkName'] = '/' + result[i].AccountId;
                arr.push(rec);
             }
             this.results =  arr;
             this.data = arr;
             this.recCount = arr.length;
             //console.log('2)'+JSON.stringify(this.results ));
        })
        .catch(error => {
            /*eslint no-console: "off"*/
            console.log(error);
            this.result = error;
            this.dispatchEvent( new ShowToastEvent({
                title: '処理結果',
                message: 'レコード取得が失敗しました。\n' + error.body.message,
                variant: 'error',
                mode: 'dismissable'
            }));
        })

    }

    sortColumns( event ) {
        this.sortedBy = event.detail.fieldName;
        
        this.sortedDirection = event.detail.sortDirection;
        var reverse = this.sortedDirection !== 'asc';
        let arr = this.results;
        //console.log('data -- ' + JSON.stringify(arr[0]));
        arr.sort(this.sortBy(this.sortedBy, reverse))
        this.data = [...arr];
        
        //return this.results;
        
    }

    sortBy(fieldname, reverse, primer) {
        var key = primer ?
            function(x) {return primer(x[fieldname])} :
            function(x) {return x[fieldname]};
            reverse = !reverse ? 1 : -1;
        return function (a, b) {
            return a = key(a)?key(a):'', b = key(b)?key(b):'', reverse * ((a > b) - (b > a));
        }
    }
    
    handleSearchMem(event) {
        this.keyword = event.target.value;
        var term = this.keyword;
        var arr = this.results;
        console.log('term >' + term);
        var regex = new RegExp(term, "i");
        try {
            //console.log('arr: ' + JSON.stringify(arr));
            var tmpData = arr.filter(row=>regex.test(row['Name']));
            //console.log('tmpData: ' + JSON.stringify(tmpData));
            this.data = [...tmpData];
            this.recCount = this.data.length;
        }catch(e){
            alert(e);
        }
        

    }
}
```


#### point2 複合名前の値の取る方法（例 AccountId.Name)
Dataがサーバから帰ってるとき、順番に取り出し、複合名前のコルムを作成し、値を設定する

```
 if(key[j]=='Account'){
      rec['Account.Name'] =  result[i][ key[j] ].Name;
 }
```
#### point3 dataに値だけを変更されても、画面に再描写されない。
メモリが再設定する
```
this.data = [...arr];
```

#### point4 sort and seach
やり方はあまりわからないですが、どうかコピーされて使える

### 結果
sort機能と快速検索が効く、素早く感じです。
問題は件数が何万件以上は、Limit件数が入れ、サーバ側の再検索がいります。（ToDo）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/801991/ae5a6220-f841-5aea-7f08-9b7e554a6fe3.png)

### 追記①　Lightning search
検索のところは自分で検索アイコンとインプットの組み合わせで実現したが、実際SalesforceのSearchのインプットがある。入れ替えると下記になる(コメント付き)。
```
       <div slot="actions">
            <div class="slds-form_horizontal slds-form slds-grid slds-wrap">
                    <lightning-input type="search" 
                    label="" 
                    placeholder="フリーワード" 
                    value={keyword} 
                    onchange={handleSearchMem} 
                    onkeydown={handleSearchDB}>
                </lightning-input>
            </div>
            <div style="font-size:12px">
                検索可能項目：店舗名/共通ID/会社名/ショップコード
            </div>
        </div
```
image
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/801991/9fc3a92c-662f-03d3-136b-cf9ba18ad078.png)