专栏首页salesforce零基础学习Salesforce LWC学习(三十三) lightning-datatable 翻页bug处理

Salesforce LWC学习(三十三) lightning-datatable 翻页bug处理

本来lightning-datatable这种标签,基本上任何的项目都会用到而且很精通,所以当时感觉没有太大的单独一篇写的必要,在Salesforce LWC学习(三十) lwc superbadge项目实现 中也有使用这个标签的demo,所以有类似需要的小伙伴参考一下也可以照猫画虎搞定需求。项目中遇见了两个datatable的问题,解决以后感觉有必要写一下,后期遇见这种坑的小伙伴可以快速对应。话不多说,先弄一个简单的分页效果的UI,UI很丑,旨在实现功能。

AccountListController.cls:一个简单的搜索list返回

public without sharing class AccountListController {
    public static final Integer DEFAULT_PAGE_SIZE = 100;
    @AuraEnabled(cacheable=false)
    public static List<Account> fetchAccountList(String serializedAccount){

        Account account = (Account)JSON.deserialize(serializedAccount,Account.class);
        String fetchAccountSQL = 'SELECT Id,Name,Industry,AccountSource,Owner.Name FROM Account WHERE IsDeleted = false ';

        if(String.isNotBlank(account.name)) {
            fetchAccountSQL += 'AND Name like' +  '\'%' + account.name + '%\'';
        }

        if(String.isNotBlank(account.industry)) {
            fetchAccountSQL += ' AND industry = \'' + account.industry + '\'';
        }

        if(String.isNotBlank(account.AccountSource)) {
            fetchAccountSQL += ' AND AccountSource = \'' + account.AccountSource + '\'';
        }

        Integer accountSize = DEFAULT_PAGE_SIZE;


        fetchAccountSQL += ' LIMIT ' + accountSize;
        List<Account> accountList = Database.query(fetchAccountSQL);
        return accountList;
    }
}

accountSearchForm.html

<template>
    <!--use LDS to bind account -->
    <lightning-record-edit-form
        object-api-name='Account'
        onsubmit={handleRecordFormSubmit}
    >
        <!--used to show section close/open-->
        <lightning-accordion allow-multiple-sections-open
            active-section-name={activeSections}>

            <lightning-accordion-section name="basic" label="basic">
                <lightning-layout multiple-rows="true">
                    <lightning-layout-item  padding="around-small" flexibility='auto' size='4'>
                        <lightning-input-field field-name='Name'></lightning-input-field>
                    </lightning-layout-item>

                    <lightning-layout-item padding="around-small" flexibility='auto' size='4'>
                        <lightning-input-field field-name="AccountSource"></lightning-input-field>
                    </lightning-layout-item>

                    <lightning-layout-item padding="around-small" flexibility='auto' size='4'>
                        <lightning-input-field field-name="Industry"></lightning-input-field>
                    </lightning-layout-item>
                </lightning-layout>
            </lightning-accordion-section>
        </lightning-accordion>

        <!-- button group area -->
        <lightning-layout horizontal-align="center">
            <lightning-layout-item >
                <lightning-button variant="brand" label="search" type="submit" name="search"></lightning-button>
            </lightning-layout-item>
        </lightning-layout>
    </lightning-record-edit-form>
</template>

accountSearchForm.js

import { LightningElement,track } from 'lwc';

export default class AccountSearchForm extends LightningElement {
    @track activeSections = ['basic'];

    @track account;

    handleRecordFormSubmit(event) {
        event.preventDefault();
        this.account = event.detail;
        this.dispatchEvent(new CustomEvent('searchaccount',{detail:this.account}));
    }

}

myAccountList.html

<template>
    <lightning-card title="list for account data" icon-name="standard:account">
        <div style="height: 150px;">
            <lightning-datatable
                data={accountListForCurrentPage}
                columns={columns}
                show-row-number-column
                key-field="id">
            </lightning-datatable>
        </div>

        <lightning-layout horizontal-align="center">
            <lightning-layout-item flexibility='auto' size='1'>
                <lightning-combobox
                    value={perSize}
                    options={entryList}
                    placeholder="choose size"
                    onchange={handlePerSizeChange} >

                </lightning-combobox>
            </lightning-layout-item>
            <lightning-layout-item flexibility='auto' size='9' class="slds-text-align_center">
                totalSize: {totalSize}
            </lightning-layout-item>
            <lightning-layout-item flexibility="auto" size="2">
                <lightning-button variant="brand" label="<--" disabled={isFirstPage} onclick={handlePreviousPageClick}></lightning-button>
                <lightning-button variant="brand" label="-->" disabled={isLastPage} title="next" onclick={handleNextPageClick}></lightning-button>
            </lightning-layout-item>
        </lightning-layout>
    </lightning-card>
</template>

myAccountList.js

import { LightningElement,api, track } from 'lwc';

const columns = [
    {label: 'Account Name', fieldName: 'Name'},
    {label: 'Account Industry', fieldName: 'Industry'},
    {label: 'Account Source', fieldName: 'AccountSource'},
    {label: 'Owner Name', fieldName: 'OwnerName'}
];

export default class MyAccountList extends LightningElement {
    //searched account list used to show in component table
    @api accountList;
    //indicator if table header checkbox check or not, true for check
    @api totalChecked;
    //list total entryList
    @api totalSize;
    //per size for show in table
    @api perSize;
    //current page index
    @api currentPageIndex;
    //list show in table
    @api accountListForCurrentPage;
    //indicator if current page is first page
    @api isFirstPage;
    //indicator if current page is last page
    @api isLastPage;

    @track columns = columns;

    get entryList() {
        return [
            { label: '3', value: '3' },
            { label: '5', value: '5' },
            { label: '10', value: '10' },
        ];
    }


    /**
     * description: item checkbox check/uncheck, dispatch itemcheck custom event and parent component handle this
     * @param event system event,used to get which item check/uncheck
     */
    handleItemCheckboxClick(event) {
        this.dispatchEvent(new CustomEvent('itemcheck',{detail:{id:event.currentTarget.value,checked:event.currentTarget.checked}}));
    }

    /**
     * description: table header checkbox check/check, dispatch allcheck custom event
     * @param  event system event, used to get if table header checkbox check/uncheck
     */
    handleAllCheckboxClick(event) {
        this.dispatchEvent(new CustomEvent('allcheck',{detail:{checked:event.currentTarget.checked}}));
    }

    handleNextPageClick() {
        this.dispatchEvent(new CustomEvent('nextpage'));
    }

    handlePreviousPageClick() {
        this.dispatchEvent(new CustomEvent('previouspage'));
    }

    handlePerSizeChange(event) {
        let currentSize = event.detail.value;
        this.dispatchEvent(new CustomEvent('persizechange',{detail:{currentSize:currentSize}}));
    }
}

accountListContainer.html

<template>
    <c-account-search-form onsearchaccount={handleSearchAccountEvent}></c-account-search-form>
    <c-my-account-list is-first-page={isFirstPage} is-last-page={isLastPage} account-list={accountList} account-list-for-current-page={accountListForCurrentPage} total-checked={totalChecked} onitemcheck={handleItemCheckEvent} onallcheck={handleAllCheckedEvent} onnextpage={handleNextPageEvent} onpreviouspage={handlePreviousPageEvent} total-size={totalSize} per-size={perSize} onpersizechange={handlePerSizeChangeEvent}></c-my-account-list>

</template>

accountListContainer.js

import { LightningElement,track } from 'lwc';
import fetchAccountList from '@salesforce/apex/AccountListController.fetchAccountList';

export default class AccountListContainer extends LightningElement {
    //account list used to show in account table
    @track accountList = [];
    //account form used to store form information user searched
    @track accountForm;
    //errors when fetch account list error
    @track errors;
    //indicator if table header total check box checked or not, true means checked
    @track totalChecked = false;
    //indicator if list modal close
    @track showSelectedListModal = false;

    @track data = [];

    //list total size
    @track totalSize;
    //per size for show in table
    @track perSize = 5;
    //current page index
    @track currentPageIndex;
    //list show in table
    @track accountListForCurrentPage;
    @track isFirstPage = true;
    @track isLastPage = true;

    /**
     * description: search account data by form information and set the result to account list to show in table
     * @param  event system event used to get form detail information
     */
    handleSearchAccountEvent(event) {
        this.totalChecked = false;
        this.accountForm = event.detail;
        fetchAccountList({serializedAccount:JSON.stringify(event.detail)})
            .then(result => {
                this.accountList = result;
                this.totalSize = result.length;
                this.currentPageIndex = 1;
                if(this.totalSize > this.perSize * this.currentPageIndex) {
                    this.setPagination();
                } else {
                    this.accountListForCurrentPage = this.accountList;
                    this.accountListForCurrentPage.forEach(item => {
                        if(item.Owner) {
                            item.OwnerName = item.Owner.Name;
                        }
                    });
                    this.isLastPage = true;
                }
                this.errors = undefined;
            })
            .catch(error =>{
                this.errors = error;
                this.accountList = undefined;
            });
    }

    handlePreviousPageEvent() {
        this.currentPageIndex = this.currentPageIndex - 1;
        this.setPagination();
    }

    handlePerSizeChangeEvent(event) {
        this.perSize = event.detail.currentSize;
        this.currentPageIndex = 1;
        this.setPagination();
    }

    handleNextPageEvent() {
        this.currentPageIndex = this.currentPageIndex + 1;
        this.setPagination();
    }


    setPagination() {
        this.accountListForCurrentPage = [];
        let tmpList = [];
        for(let index = (this.perSize * (this.currentPageIndex - 1)); index < this.totalSize; index++) {
            if(index < this.perSize * this.currentPageIndex) {
                if(this.accountList[index].Owner) {
                    this.accountList[index].OwnerName = this.accountList[index].Owner.Name;
                }
                tmpList.push(this.accountList[index]);
            }
        }
        this.accountListForCurrentPage = tmpList;
        if(this.currentPageIndex === 1) {
            this.isFirstPage = true;
        } else {
            this.isFirstPage = false;
        }
        if(this.perSize * this.currentPageIndex >= this.totalSize) {
            this.isLastPage = true;
        } else {
            this.isLastPage = false;
        }
    }
}

结果展示:

看上去还可以是吧,但是有两个潜在的问题。

按照以下操作步骤,第一页有拖动条,选择了第5条数据

点击翻页以后,两个问题如下:

1)第二页的第五条同样的默认勾选了,尽管这个时候我们如果使用event.detail.selectedRows去获取选中的选项,第二页是没有在这里的,但是UI撒谎了,这个是一个很严重的bug;

2)第二页进去以后,滚动条没有在最上面,即没有默认展示第一条数据。

这两个问题,第二个问题是小问题,可以忍;第一个问题会让用户有concern,属于很严重的问题。但是什么原因呢???其实我也不太清楚是什么原因,datatable官方的设计中也没有翻页的demo,大部分都是loadMore当页增加数据场景,所以可能针对每页的index处选中效果有某个隐藏的bug。当我们尽管更新了list的数据,但是index给渲染了,很容易是render层的bug。所以我们想一下如何去处理这种问题。既然同步的渲染有问题,我们考虑其他方式,setTimeout弄成异步调用或者改成Promise实现。优化后的方案如下所示:

myAccountList.js新增一个方法

@api handleTableScrollTop() {
    this.template.querySelector('div').scrollTop = 0;
 }

accountListContainer.js修改一下 setPagination方法。新增了 setList这个Promise,js执行顺序 : 同步代码 > Promise > setTimeout这种异步方式。

setPagination() {
        this.accountListForCurrentPage = [];
        let tmpList = [];
        for(let index = (this.perSize * (this.currentPageIndex - 1)); index < this.totalSize; index++) {
            if(index < this.perSize * this.currentPageIndex) {
                if(this.accountList[index].Owner) {
                    this.accountList[index].OwnerName = this.accountList[index].Owner.Name;
                }
                tmpList.push(this.accountList[index]);
            }
        }
        //this.accountListForCurrentPage = tmpList;
        if(this.currentPageIndex === 1) {
            this.isFirstPage = true;
        } else {
            this.isFirstPage = false;
        }
        if(this.perSize * this.currentPageIndex >= this.totalSize) {
            this.isLastPage = true;
        } else {
            this.isLastPage = false;
        }

        const setList = () =>
        new Promise((resolve, reject) => {
            resolve(tmpList);
        });

        setList()
        .then((result) => {
            this.accountListForCurrentPage = tmpList;
            this.template.querySelector('c-my-account-list').handleTableScrollTop();
        });
    }

通过以上的代码就可以解决上述的两个问题了。原理的话因为不清楚 datatable的渲染方式,只能找到解决这种问题的workaround的方式,同时作为sf开发人员在开发lightning的过程中,javascript真的是越来越重要了!!!

总结:篇中代码实现了通过 lightning-datatable翻页效果以及针对两个潜在的bug的修复。偏中有错误欢迎指出,有不懂欢迎留言。有更好方式欢迎交流。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Salesforce LWC学习(十八) datatable展示 image

    https://developer.salesforce.com/docs/component-library/bundle/lightning-datatab...

    用户1169343
  • Salesforce LWC学习(三十) lwc superbadge项目实现

    本篇参考:https://trailhead.salesforce.com/content/learn/superbadges/superbadge_lwc_s...

    用户1169343
  • Salesforce LWC学习(二) helloWorld程序在VSCode中的实现

    上一篇我们简单的描述了一下Salesforce DX的配置以及CLI的简单功能使用,此篇主要简单描述一下LWC如何实现helloWorld以及LWC开发时应该注...

    用户1169343
  • Salesforce LWC学习(二十三) Lightning Message Service 浅谈

    https://trailhead.salesforce.com/content/learn/superbadges/superbadge_lwc_specia...

    用户1169343
  • Salesforce LWC学习(三十二)实现上传 Excel解析其内容

    本篇参考:salesforce lightning零基础学习(十七) 实现上传 Excel解析其内容

    用户1169343
  • Aura Component Skills & Tools

    本篇参考: https://trailhead.salesforce.com/content/learn/modules/lex_dev_lc_vf_funda...

    用户1169343
  • Salesforce LWC学习(五) LDS & Wire Service 实现和后台数据交互 & meta xml配置

    之前的几节都是基于前台变量进行相关的操作和学习,我们在项目中不可避免的需要获取数据以及进行DML操作。之前的内容中也有提到wire注解,今天就详细的介绍一下对数...

    用户1169343
  • Salesforce LWC学习(四) 父子component交互 / component声明周期管理 / 事件处理

    我们在上篇介绍了 @track / @api的区别。在父子 component中,针对api类型的变量,如果声明以后就只允许在parent修改,son comp...

    用户1169343
  • Salesforce LWC学习(七) Navigation & Toast

    上一篇我们介绍了针对LWC中常用的LDS的适配的wire service以及@salesforce模块提供的相关的service,其实LWC中还提供其他的好用的...

    用户1169343
  • Salesforce Javascript(二) 箭头函数

    本篇参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions...

    用户1169343
  • Salesforce LWC学习(一)Salesforce DX配置

    最开始做salesforce开发时使用eclipse,后来因为太笨重以及不太方便所以改用了sublime + haoide。sublime + haoide无...

    用户1169343
  • salesforce零基础学习(九十六)Platform Event浅谈

    本篇参考:https://developer.salesforce.com/blogs/2018/07/which-streaming-event-do-i-u...

    用户1169343
  • Salesforce LWC学习(二十一) Error浅谈

    本篇参考:https://developer.salesforce.com/docs/component-library/documentation/en/lw...

    用户1169343
  • Salesforce Integration 概览(二) Remote Process Invocation—Request and Reply(远程进程调用--请求和响应)

    本篇参考:https://resources.docs.salesforce.com/sfdc/pdf/integration_patterns_and_pra...

    用户1169343
  • Salesforce LWC学习(十三) 简单知识总结篇一

    本篇参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript

    用户1169343
  • Salesforce LWC学习(二十五) Jest Test

    https://trailhead.salesforce.com/content/learn/modules/test-lightning-web-compon...

    用户1169343
  • Salesforce针对现场服务人员推全新人工智能支持工具

    Salesforce周三宣布推出一系列针对移动工作人员的新型人工智能支持工具。即Field Service Lightning,一个让工作人员能够响应客户服务电...

    臭豆腐
  • Salesforce Integration 概览(七) Data Virtualization数据可视化

    本篇参考:https://resources.docs.salesforce.com/sfdc/pdf/integration_patterns_and_pra...

    用户1169343
  • Salesforce Integration 概览(六) UI Update Based on Data Changes(UI自动更新基于数据变更)

    Salesforce用户界面必须由于Salesforce数据的更改而自动更新。这个场景其实在我所经历的项目中用到的不是特别多,因为客户可能直接点击刷新按钮就直接...

    用户1169343

扫码关注云+社区

领取腾讯云代金券