本来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的修复。偏中有错误欢迎指出,有不懂欢迎留言。有更好方式欢迎交流。

Salesforce LWC学习(三十三) lightning-datatable 翻页bug处理的更多相关文章

  1. Salesforce LWC学习(二十三) Lightning Message Service 浅谈

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

  2. Salesforce LWC学习(三十) lwc superbadge项目实现

    本篇参考:https://trailhead.salesforce.com/content/learn/superbadges/superbadge_lwc_specialist 我们做lwc的学习时 ...

  3. Salesforce LWC学习(三十九) lwc下quick action的recordId的问题和解决方案

    本篇参考: https://developer.salesforce.com/docs/component-library/bundle/force:hasRecordId/documentation ...

  4. Salesforce LWC学习(三十七) Promise解决progress-indicator的小问题

    本篇参考:https://developer.salesforce.com/docs/component-library/bundle/lightning-progress-indicator/exa ...

  5. Salesforce LWC学习(三十五) 使用 REST API实现不写Apex的批量创建/更新数据

    本篇参考: https://developer.salesforce.com/docs/atlas.en-us.224.0.api_rest.meta/api_rest/resources_compo ...

  6. Salesforce LWC学习(三) import & export / api & track

    我们使用vs code创建lwc 时,文件会默认生成包含 template作为头的html文件,包含了 import LightningElement的 js文件以及对应的.js-meta.xml文件 ...

  7. Salesforce LWC学习(三十六) Quick Action 支持选择 LWC了

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

  8. Salesforce LWC学习(三十一) Quick Action适配

    本篇参考:https://www.lightningdesignsystem.com/components/modals/ 随着salesforce lwc的优化,越来越多的项目从aura转到了lwc ...

  9. Salesforce LWC学习(三十二)实现上传 Excel解析其内容

    本篇参考:salesforce lightning零基础学习(十七) 实现上传 Excel解析其内容 上一篇我们写了aura方式上传excel解析其内容.lwc作为salesforce的新宠儿,逐渐的 ...

  10. Salesforce LWC学习(三十四) 如何更改标准组件的相关属性信息

    本篇参考: https://www.cnblogs.com/zero-zyq/p/14548676.html https://www.lightningdesignsystem.com/platfor ...

随机推荐

  1. TypeScript系列 -> 遇到报错 Cannot find name ‘console‘. Do you need to change your target library?ging the ‘lib‘ compiler option

    学习ts遇到的报错 Cannot find name 'console'. Do you need to change your target library?ging the 'lib' compi ...

  2. .netcore 以widnows服务方式运行

    应用需要 Microsoft.AspNetCore.Hosting.WindowsServices 的包引用. 生成主机时会调用 IHostBuilder.UseWindowsService. 若应用 ...

  3. mysql 增加自定义函数

    查看mysql当前是否支持编写自定义 SHOW variables like '%fun%'; 开启自定义函数 set global log_bin_trust_function_creators=1 ...

  4. 微信小程序开发遇到的注意事项及奇怪事

    1.wx.uploadFile上传文件时只支持本地文件(相册或者拍摄的),网络文件不可以,可以将网络文件用wx.downloadFile下载到本地在下载,下载以后会返回一个微信临时地址然后再下载 2. ...

  5. windows根据文件名找到进程,并杀死进程。

    背景:最近因为工作原因,装了360杀毒引擎,完了就卸载了.发现一直提示文件正在使用无法删除.文件无法访问等等.经过一系列操作,安全模式下都无法删除,恶心死了... 1.shirt + del 按文件夹 ...

  6. Linux deploy 32位系统 怎么安装宝塔怎么安装linux系统安装宝塔后搭建网站

    getconf LONG_BIT 获取当前linux系统位数

  7. 2月26日Android开发学习

    1.App运行日志 Android采用Log工具打印日志,他讲各类日志划分为五个等级 (1)Log.e:表示错误信息,比如可能导致程序崩溃的异常. (2)Log.w:表示警告信息. (3)Log.i: ...

  8. [fiddler的使用]添加常用字段(请求耗时,客户端请求时间,IP地址)

    1. /* 显示请求耗时 */ function BeginRequestTime(oS: Session) { if (oS.Timers != null) { return oS.Timers.C ...

  9. Python+Django(4)——创建其他网页(模板继承)

    模板继承: 1,修改主页 父模板:抽取通用元素,在index.html同级目录下新建base.html <p> <a href="{% url 'learning_logs ...

  10. springboot 注解属性配置

    参考: https://blog.csdn.net/ouyangguangfly/article/details/106646378 https://www.cnblogs.com/cbzj/p/94 ...