0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

DatatableのInfinite Loadingを実装してみた (lwc)

Last updated at Posted at 2024-01-27

背景

Salesforceにおいて、DatatableとのRowに対するアクションは使用頻度と重要度が高いのは、Spreadsheetライクに利用したい需要があるからだと考えています。ただデータの量が多くなると、データの処理能力と表示スピードを考慮した設計にする必要があります。

Datatableの形としては2種類あります。

  1. Datatableでrow actionができるテーブルを作成する
  2. Iterationで、擬似的テーブルを作成する

大量データの表示方法としても2種類あります。

  1. PaginationでoffsetをPage数を利用してインクリメント取得する
  2. Infinite loadingで、延々終わるまで取得する

また、データをインクリメント的な取得方法も二つあります。

  1. SOQLのOffsetをすでに取得したデータ量で設定しクエリする
  2. IdCreatedDateをインクリメントごとで再設定しそれ以降を取得する

今回は、Infinite Loadingを試してみました。

Offsetの最大は2000です。それ以上をSOQLの回避策はQueryMoreで、カーディナリティが高く、一意の値が多い項目を使用することを勧めています。
Workaround for offset 2000 limit on SOQL query (Salesforce)
SOAP API - queryMore (Salesforce)
OFFSET (Salesforce)

デモ

50件ずつインクリメント取得しています。
infinite-loading-demo-640.gif

構成

  • Lead
  • Lwc * 1
  • Apex * 1

下準備

前回の投稿のMockarooでダミーデータを作成
Mockarooでダミーデータを作成してみた

内容

まずデータの取得です。私はmapで包んで投げるのが好きです。parametersはひとつずつで問題ないのでそこはお好みで。

SOQL

SOQL (getLeads) 返すデータはLeadのリスト、変数はstringSetかdateSetでまとめて管理しています。データ数はLIMIT 50です。

DateTimeはJavaScriptではyyyy-mm-ddT00:00:00.000Zなので渡すMapの型がDateTimeであればそのままで問題ないですが、String型で渡す場合は、yyyy-mm-dd 00:00:00に成形し渡した先でDateTime.valueof(string)としてDateTime型に戻す手間があります。今回は、dateSetがDateTimeのMapなのでそのままです。

@AuraEnabled
    public static List<Lead> getLeads(String searchTerm, Map<String, String> stringSet, Map<String, DateTime> dateSet) {
        DateTime lastCreatedDate = dateSet.get('lastCreatedDate');
        String lastId = stringSet.get('lastId');
        String ownerAlias = stringSet.get('ownerAlias');
        String status = stringSet.get('status');     
        String leadSource = stringSet.get('leadSource');     
        String query = 'SELECT Id, Company, LastName, FirstName, Email, Phone, Status, LeadSource, Website, Industry, OwnerId, Owner.Alias, CreatedDate, CreatedBy.Alias, (SELECT Id, Type, OwnerDivision__c, Owner.Alias FROM Cases__r), (SELECT Id, CreatedDate, Type, CreatedById FROM Tasks ORDER BY CreatedDate DESC LIMIT 10) FROM Lead';    
        List<String> conditions = new List<String>();
        conditions.add('Status != \'Unqualified\'');
        conditions.add('IsConverted = false');
        if (lastCreatedDate != null && String.isNotBlank(lastId)) {
            conditions.add('(CreatedDate < :lastCreatedDate OR (CreatedDate = :lastCreatedDate AND Id < :lastId))');
        }
        if (String.isNotBlank(ownerAlias)) {
            conditions.add('Owner.Alias = :ownerAlias');
        }
        if (String.isNotBlank(leadSource)) {
            conditions.add('LeadSource = :leadSource');
        } 
        if (String.isNotBlank(searchTerm)) {
            String keyword = '%' + searchTerm + '%';
            conditions.add('(Company LIKE :keyword OR Email LIKE :keyword OR LastName LIKE :keyword OR FirstName LIKE :keyword)');
        }    
        if (!conditions.isEmpty()) {
            query += ' WHERE ' + String.join(conditions, ' AND ');
        }
        
        query += ' ORDER BY CreatedDate DESC, Id DESC LIMIT 50';
        system.debug(query);
        return Database.query(query);
    }

lastIdlastCreatedDateが、スクロールによりSpinnerが表示されるたびにDatatableのonloadMoreアクションで呼び出されたmethod内で最後のRecordから取得します。それを、loadDataへ投げて次の50件を取得してくるparameterになります。

LWC

Datatable

Datatable (html)
<lightning-datatable
  key-field="id" 
  data={rows} 
  columns={columns} 
  show-row-number-column="true"
  enable-infinite-loading 
  load-more-offset="20" 
  onloadmore={loadmoreData}>
</lightning-datatable>

Datatableでは、onloadMoreでテーブルの下部に近づいた時に起動するアクションを設定します。load-more-offsetは下部からどのくらい上かpixel単位で数値を設定できます。早めにアクションして欲しい場合は数値を大きくしますが、デフォルトは20pxです。enable-infinite-loadingは必須です。
参照
lightning-datatable (salesforce)

loadData

loadData (JS)
    loadData() {
        return new Promise((resolve, reject) => {
            getLeads({searchTerm: this.searchTerm, stringSet: this.stringSet, dateSet: this.dateSet })
            .then(data => {
    
                if (data.length) {
                    const mappedData = this.mapData(data);
                    this.rows = (this.rows || []).concat(mappedData); // concatenate mapped data
                }
                resolve();
            })
            .catch(error => {
                console.error(error);
            })
            .finally(() => {
                if (this.rows && this.rows.length > 1) {
                    this.tempLastCreatedDate = this.rows[this.rows.length - 1].CreatedDate;
                    this.tempLastId = this.rows[this.rows.length - 1].Id;
                } else {
                    this.tempLastCreatedDate = new Date();
                    this.tempLastId = null;
                }
            });
        });
    }

ここで取得したデータの一番最後のlastIdとlastCreatedDateを取得しています。

this.tempLastCreatedDate = this.rows[this.rows.length - 1].CreatedDate;
this.tempLastId = this.rows[this.rows.length - 1].Id;

loadDataで取得します。@wireでもconnectedCallbackでも実装は可能ですが、@wireがデータ取得が速すぎてspinnerが出ないほどでした。今回はデータ量が50件ごとなのとspinnerが出た方が取得している感が出るので、connectedCallbackを利用しました。

また、データをdatatableに合うようにmapをしますが、mapについてはcellをデータによっては着色したり、iconをTypeAttributeでclass付けしたりダイナミックな表現にするときに噛ませます。詳細は本投稿の最後で。

loadmoreData

loadMoreData (JS)
    async loadmoreData(event) {
        if (this.rows.length < 50) {
            return; // the datatable frame is too short, so it will keep rendering loadmore unless to stop by return here
        }
        const { target } = event;
        target.isLoading = true;
        this.dateSet.lastCreatedDate = this.tempLastCreatedDate;
        this.stringSet.lastId = this.tempLastId;
        try {
            await this.loadData(); 
            target.isLoading = false;
        } catch (error) {
            console.error(error);
        }
    }

loadmoreDataは、スクロールでDatatableの下部に来た時に呼ばれるメソッドで、現在のテーブル上にあるデータが50を下回りテーブルの下部が表示されてしまう高さの場合、DatatableのloadしたらすぐDatatableの下部に触ると見なされ永遠とloadData()をcallしてしまいます。そこで再取得の制御のために、このreturnが必要です。

まとめ

TaskやEvent, Chatterなど明らかにCreatedDateが降順で表示されるべきもの、数値として2000で区切るものでもないものにはInfinite Loadingは選択肢としてありかなと感じました。

参照

lightning-datatable (salesforce)
Workaround for offset 2000 limit on SOQL query (Salesforce)
SOAP API - queryMore (Salesforce)
OFFSET (Salesforce)

関連投稿

Mockarooでダミーデータを作成してみた
Searchable Combobox 検索型選択リストを作ってみた (lwc)

github

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?