antd自定义分页器_从零开始实现类 antd 分页器(二):分页核心代码
本文是使用 Typescript 开发类 antd 分页器,并发布 npm 的第二篇,因为最近在业务中使用了 antd 中的 Pagination 开发了相关业务,而自己又对组件的封装由很有兴趣,所以打算用 ts 来封装一个具有 antd 所有功能的 Pagination。相信你也能轻轻松松的发布一个 npm 组件了。
相关系列文章
从零开始实现类 antd 分页器(一):搭建项目架构
从零开始实现类 antd 分页器(二):分页核心代码
从零开始实现类 antd 分页器(三):发布npm
写作过程中有些方法的具体实现没有讲到,大家可以自行查看源码。本案例项目仓库:
闲来无事,造个轮子 —— darrell-wheels
为了写作方便,antd 的 分页器我统一以 antd-pagination 代替,自己的分页器统一以 my-pagination 代替。
效果图
基础 + 更多
跳转页数 [ showQuickJumper ] + 改变页数 [ showSizeChanger ]
迷你 [ size: 'small' ]
简洁 [ simple: true ]
显示总数 [ showTotal ]
修改上一步和下一步相应文字 [ itemRender ]
分页逻辑
这里面的逻辑是实现这个分页器最最关键的地方,同时也是分页器的难点所在。
我们要计算出每一页的页码排布情况,什么时候该显示省略号,什么时候改成全部显示等等。
antd 的分页
我试了一下 antd-pagination 的页码显示,假设有 30 页,它的页码是如下分布的,我们以 allPages 代表总页数,current 代表当前是第几页:
// allPages = 30
当 current = 1, 显示 1 2 3 4 5 ... 30
当 current = 2, 显示 1 2 3 4 5 ... 30
当 current = 3, 显示 1 2 3 4 5 ... 30
当 current = 4, 显示 1 2 3 4 5 6 ... 30
当 current = 5, 显示 1 ... 3 4 5 6 7 ... 30
...
当 current = 13, 显示 1 ... 11 12 13 14 15 ... 30
当 current = 14, 显示 1 ... 12 13 14 15 16 ... 30
当 current = 15, 显示 1 ... 13 14 15 16 17 ... 30
当 current = 16, 显示 1 ... 14 15 16 17 18 ... 30
...
当 current = 26, 显示 1 ... 24 25 26 27 28 ... 30
当 current = 27, 显示 1 ... 25 26 27 28 29 30
当 current = 28, 显示 1 ... 26 27 28 29 30
当 current = 29, 显示 1 ... 26 27 28 29 30
当 current = 30, 显示 1 ... 26 27 28 29 30
复制代码
在 antd-pagination 有一个参数 showLessItems,当它为 true 时,意思是当前页面 左右显示各 1 个,为 false 时,代表当前页面 左右各显示 2 个。在这里我们把 当前页面 左右显示几个 暂且记为 pageBufferSize,
也就是说,antd-pagination 的 pageBufferSize 只有两个值 1 和 2,不过两个已经完全够了。
上面的例子,pageBufferSize = 2,及 showLessItems 为 false。
找规律
接着观察上面这一组数据,我们可以找一下规律,粗粗一看,我们会发现:
第 1 页 到 第 3 页 显示的页码是一样的,只有 一个后面省略号
第 4 页 比 前 3 页 多了一个页码,但是还是只有 一个后面省略号(临界点)
第 5 页 开始,到 第 26 页 出现了 两个省略号,并且中间都是 5 个
第 27 页 跟第四页类似 又回到了 一个前面省略号(临界点)
第 28 页 到 第 30 页,显示的页面一样,并只有 一个前面省略号。
代码实现
我们在这里先暂时不考虑复杂的 dom 输出,只使用简单的字符串输出。
这里笔者想到两种思路:
第一种思路
简单粗暴:就是把用代码翻译上面我们发现的规律,我们可以用 allPages、current、pageBufferSize 来表示:
// 临界点
当 current = 5,转化为代码 1 + pageBufferSize * 2
当 current = 26,转化为代码 allPages - pageBufferSize * 2
// 在临界点 区域内,都是双省略号
current >= 1 + pageBufferSize * 2 && current <= allPages + pageBufferSize * 2
// 然后在对 第4页 和 第27页做一下特殊处理
// 这两页的页码虽然只有一个省略号,但是比正常的会多出一个页码
当 current = 4,转化为代码 pageBufferSize * 2
当 current = 27,转化为代码 allPages - pageBufferSize * 2 + 1
// 接下来就是最简单的
当 current = 1 || 2 || 3,转化为代码 < pageBufferSize * 2 - 1
当 current = 28 || 29 || 30,> allPages - pageBufferSize * 2 + 1
复制代码
第二种思路
我们主要来讲一下第二种思路,这个也是 antd-pagination 内使用的方法,我们接下来的方法也只满足 pageBufferSize 为 1 或者 2 。
我们定义一个函数 showPages 方法来实现相关的逻辑;
/**
* 输出每一页 显示的页码 的 字符串
* @param current:当前页码;
* @param allPages:总页数;
* @param pageBufferSize:页数左右两边显示几个(1个 或 2个)
*/
function showPages (current, allPages, pageBufferSize){
let str = '';
...
// 待完成的逻辑
...
return str.trim();
}
// 总页数
let total = 30;
// 循环输出每页的页码字符串
for (let i = 1; i <= total; i++) {
console.log(showPages(i, total, 2));
}
复制代码
首先 antd-pagination 当总页数小于等于 5 + 2 * pageBufferSize 的时候,不管 current 是 第几页,所有的页码都会展现,我们可以在 showPages 添加如下代码:
// 当总页数 <= 5 + 2 * pageBufferSize
function showPages (current, allPages, pageBufferSize){
if (allPages <= 5 + pageBufferSize * 2) {
for (let i = 1; i <= allPages; i++) {
str = str + ' ' + i;
}
}
}
复制代码
此时我们设置 allPages = 8,在浏览器中我们可以看到如下图所示:
观察上面的字符串我们发现,当有两个省略号的时候,前面字符肯定是 1 ...,最后两个字符肯定是 ... 30。
1. 先循环输出 当前页码 与 页码前后 pageBufferSize 个数
我们在代码里面定义一个 left,代表字符串循环从这个数开始;再定义 right,代表字符串循环到此结束。left 的值根据前几页的 current 和 pageBufferSize ,我们很简单的可以得到。
let left = Math.max(1, current - pageBufferSize);
复制代码
同理可以得到 right 的值:
let right = Math.min(current + pageBufferSize, allPages);
复制代码
因为 前三页 和 后三页 都是显示 5 个数的,所以在这种情况下,我们要对 left 和 right 在根据 pageBufferSize 做一个调整:
if (current - 1 <= pageBufferSize) {
right = 1 + pageBufferSize * 2;
}
if (allPages - current <= pageBufferSize) {
left = allPages - pageBufferSize * 2;
}
复制代码
接着我们便可以循环输出了:
for (let i = left; i <= right; i++) {
str = str + ' ' + i;
}
复制代码
2. 然后再去拼相关的省略号
接下来我们就要给上面的 str 拼省略号了。
这这我们在第一种思路里面讲过,我们可以很快的写出如下代码:
if (current - 1 >= pageBufferSize * 2) {
str = '... ' + str;
}
if (allPages - current >= pageBufferSize * 2) {
str = str + ' ...';
}
复制代码
这里面需要注意一个点,这个判断方法在 pageBufferSize = 1 的时候,在第3页 和 allPages - 2 页会出现问题,如图所示:
所以我们要在加上对这个的判断:
if (current - 1 >= pageBufferSize * 2 && current !== 1 + 2) {
str = '... ' + str;
}
if (allPages - current >= pageBufferSize * 2 && current !== allPages - 2) {
str = str + ' ...';
}
复制代码
3. 最后再去比较第一个值和最后一个值 是不是 1 和 总页数。
省略号我们加完之后,我们就只剩最后一步了。我们需要判断 第一步的 left 和 right 是不是第 1 页和 最后一页,我们可以写出以下代码:
if (left !== 1) {
str = 1 + ' ' + str;
}
if (right !== allPages) {
str = str + ' ' + allPages;
}
复制代码
到这里我们的分页逻辑算是大功告成,完整的代码如下:
function showPages (current, allPages, pageBufferSize){
let str = '';
if (allPages <= 5 + pageBufferSize * 2) {
for (let i = 1; i <= allPages; i++) {
str = str + ' ' + i;
}
} else {
let left = Math.max(1, current - pageBufferSize);
let right = Math.min(current + pageBufferSize, allPages);
if (current - 1 <= pageBufferSize) {
right = 1 + pageBufferSize * 2;
}
if (allPages - current <= pageBufferSize) {
left = allPages - pageBufferSize * 2;
}
for (let i = left; i <= right; i++) {
str = str + ' ' + i;
}
if (current - 1 >= pageBufferSize * 2 && current !== 1 + 2) {
str = '... ' + str;
}
if (allPages - current >= pageBufferSize * 2 && current !== allPages - 2) {
str = str + ' ...';
}
if (left !== 1) {
str = 1 + ' ' + str;
}
if (right !== allPages) {
str = str + ' ' + allPages;
}
}
return str.trim();
}
复制代码
打开浏览器,分别测试一下 pageBufferSize 为 1 和 2 时,如下图:
总算是搞定了,终于可以快乐的进行下一步的开发了。
大家如果还有什么不懂,还可以看一下这篇文章 十五分钟--分页逻辑--包学包会,写的很详细。
基本架构
插件的基本架构
首先我们在 src/components/pagination/src 下创建 pagination,并新建 index.ts 文件,
import '../../../../styles/normal.less';
import '../../../../styles/pagination/index.less';
// 默认配置
import defaults from "./defaults";
import { PaginationConfig } from '../../../../types/index';
class Pagination {
private options: PaginationConfig;
private pageElement: any;
private total: any;
private current: any;
private pageCount: any;
private pageSize: any;
constructor(selector: any, options = {}) {
// 默认配置
this.options = Object.assign(defaults, options);
this.init(selector);
}
/**
* 初始化相应的 分页函数
* @param selector
*/
private init (selector:any) {
// 分页器元素
this.pageElement = ?(selector)[0];
// 数据总数
this.total = this.options.total;
// 当前页码
this.current = this.options.current || this.options.defaultCurrent;
// 一页显示多少数据
this.pageSize = this.options.pageSize || this.options.defaultPageSize;
// 总页数
this.pageCount = Math.ceil(this.total / this.pageSize);
// 渲染相应的 Dom
this.renderPages();
// 改变页数并触发事件
this.changePage();
}
}
复制代码
同时我们在 src/types 文件夹下新建 index.ts,此文件定义了 my-pagination 参数的接口:
// 一些配置参数
// 解释在 option.ts 上
export interface PaginationConfig {
// 当前页数
current?: number;
// 默认第几页
defaultCurrent?: number;
// 默认的 pageSize
defaultPageSize?: number;
// 是否 disables
disabled?: boolean;
// 在只有一页的时候,是否隐藏
hideOnSinglePage?: boolean;
// 用于自定义页码的结构,可用于优化 SEO
itemRender?: (
page: number,
type: 'page' | 'prev' | 'next' | 'jump-prev' | 'jump-next',
originalElement: any
) => any;
// 每页条数
pageSize?: number;
// 指定每页可以显示多少条
pageSizeOptions?: string[];
// 这个是页数周围各显示几页;true:2;false:1;
showLessItems?: boolean;
// 是否可以快速跳转至某页
showQuickJumper?: boolean;
// 是否可以改变 pageSize
showSizeChanger?: boolean;
// 用于显示数据总量和当前数据顺序
showTotal?: (total: number, ranger: any) => any;
simple?: boolean; // 当添加该属性时,显示为简单分页
// 当为「small」时,是小尺寸分页
size?: string;
// 数据总数
total: number;
// 页码改变的回调,参数是改变后的页码及每页条数
onChange?: (page: number, pageSize: number) => void;
//pageSize 变化的回调
onShowSizeChange?: (current: number, size: number) => void;
}
复制代码
首先我们在 src/components/pagination/src 新建 defaults.ts 文件,定义分页器的默认参数:
import { PaginationConfig } from '../../../../types/index'
import { noop } from '../../../../helpers/utils'
const defaults: PaginationConfig = {
defaultCurrent: 1,
defaultPageSize: 10,
hideOnSinglePage: false,
pageSizeOptions: ['10', '20', '30', '40'],
showLessItems: false,
showQuickJumper: false,
showSizeChanger: false,
size: '',
total: 0,
onChange: noop,
onShowSizeChange: noop,
}
export default defaults;
复制代码
插件的工具函数
我们在 src/helpers 新建 utils.ts,包含了一些在代码中使用到的工具函数:
noop:空函数
addClass(elem: any, className: any):添加 class 名字
removeClass(elem: any, className: any):移除 class 名字
hasClass(elem: any, className: any):是否拥有相应的 class
addEvent(element: any, type: any, handler: any):添加事件的方法函数
removeHandler(element: any, type: any, handler: any):移除事件的方法函数
?(selector:any, context?:any):模仿 jQuery $()
渲染 Dom
Dom 结构
为了更加清晰的讲一下 dom 结构,我们可以看一下完成后是怎么样的。如下图:
我们可以看到分页器的 Dom 结构是 以 ul 为包裹元素,每一个子元素都是一个 li,在这里有两种情况。
li 是 页数,在 li 之中是 a 标签,标签里页数。
li 是 操作页数的按钮(上一页/下一页),在 li 之中是 a 标签,标签上是相应的符号。
渲染
首先完善上面的 renderPages 函数。
/**
* 渲染分页器
*/
private renderPages () {
if (!this.pageElement) return console.error('请设置包裹容器');
this.pageElement.innerHTML = ""; // 清空 html
const current = this.current;
const pageSize = this.pageSize;
const total = this.total;
const pageCount = this.pageCount;
let fragment: any = ''; // 创建代码片段
fragment = this.showPages();
let UlEle = document.createElement("ul"); // 创建包裹元素
UlEle.appendChild(fragment);
UlEle.setAttribute('class', 'darrell-pagination');
this.pageElement.appendChild(UlEle); // 塞入到 分页器中
}
复制代码完善 showPages 函数,此函数将 分页逻辑 里面得出的字符串输出的转化为 Dom 输出:
/**
* 通过页数,渲染相应的 分页 html
*/
private showPages () {
// 当前页数
const current = this.current;
// 总页数
const allPages = this.pageCount;
// 页数周围显示几个
const pageBufferSize = this.options.showLessItems ? 1 : 2;
// 代码片段
let fragment = document.createDocumentFragment();
if (allPages <= 5 + pageBufferSize * 2) {
// 通过 renderDom 渲染 html
const fragmentHtml = this.renderDom(1, allPages);
fragment.appendChild(fragmentHtml);
} else {
let left = Math.max(1, current - pageBufferSize);
let right = Math.min(current + pageBufferSize, allPages);
if (current - 1 <= pageBufferSize) {
right = 1 + pageBufferSize * 2;
}
if (allPages - current <= pageBufferSize) {
left = allPages - pageBufferSize * 2;
}
// 通过 renderDom 渲染 html
const fragmentHtml = this.renderDom(left, right);
fragment.appendChild(fragmentHtml);
if (current - 1 >= pageBufferSize * 2 && current !== 1 + 2) {
// 添加前面的省略号
this.addFragmentBefore(fragment, [Pagination.PAGE_INFOS[2]]);
}
if (allPages - current >= pageBufferSize * 2 && current !== allPages - 2) {
// 添加后面的省略号
this.addFragmentAfter(fragment, [Pagination.PAGE_INFOS[3]]);
}
if (left !== 1) {
// 如果不是第一页,插入第一页
this.addFragmentBefore(fragment, [
this.getPageInfos('darrell-pagination-item', 1)
]);
}
if (right !== allPages) {
// 如果不是最后一页,插入最后
this.addFragmentAfter(fragment, [
this.getPageInfos('darrell-pagination-item', allPages)
]);
}
}
// 添加 前一页的 dom
this.addFragmentBefore(fragment, [Pagination.PAGE_INFOS[0]]);
// 添加 后一页的 dom
this.addFragmentAfter(fragment, [Pagination.PAGE_INFOS[1]]);
return fragment;
}
复制代码完善 renderDom 函数,这个主要用来渲染页码;同时完善 addFragmentBefore 与 addFragmentAfter 函数,向代码片段前后插入代码。
/**
* 循环渲染相应的 Li 元素
* @param begin
* @param end
*/
private renderDom (begin: number, end: number) {
let fragment = document.createDocumentFragment();
let str = "";
for (let i = begin; i <= end; i++) {
this.addFragmentAfter(fragment, [
this.getPageInfos('darrell-pagination-item', i);
]);
}
return fragment;
}
/**
* 往 html 片段中 前 添加 html
* @param fragment
* @param datas
*/
private addFragmentBefore (fragment: any, datas: any) {
fragment.insertBefore(this.createLiHtml(datas), fragment.firstChild);
}
/**
* 往 html 片段中 后 添加 html
* @param fragment
* @param datas
*/
private addFragmentAfter (fragment: any, datas: any) {
fragment.appendChild(this.createLiHtml(datas));
}
复制代码完善 createLiHtml 函数,这个是最终我们生成 Dom 节点的函数。 在之前的所有函数我们其实都没有真正意义上生成 li 的 Dom 节点。
/**
* 创建 Li 元素
* @param liItemInfo
*/
private createLiHtml (liItemInfo: Array) {
let fragment = document.createDocumentFragment();
// 创建 li 标签
let liEle = document.createElement("li");
// liItemInfo 会传入相应的 id,className,和 a 标签中的内容
const id = liItemInfo[0].id;
const className = liItemInfo[0].className;
const content = liItemInfo[0].content;
// 当前页码
const current = this.current;
// 添加样式、id、与 页码
liEle.setAttribute('class', `${className} liEventTarget`);
liEle.setAttribute('id', id);
liEle.setAttribute('data-page', content);
let aEle;
if (id === 'prev') {
// 渲染上一页的 icon,getEveryIconType() 函数是返回一个 icon 的 dom
aEle = this.getIcon(this.getEveryIconType().prevIconEle)
} else if (id === 'next') {
// 渲染下一页的 icon
aEle = this.getIcon(this.getEveryIconType().nextIconEle)
} else if (id === 'jump-prev') {
// 渲染上一批的 icon
aEle = this.getIcon(this.getEveryIconType().jumpPrevWrapEle)
} else if (id === 'jump-next') {
// 渲染下一批的 icon
aEle = this.getIcon(this.getEveryIconType().jumpNextWrapEle)
} else if (id === 'page') {
// 渲染页数
if (current === parseInt(content, 10)) {
// 高亮选中的页数
addClass(liEle, Pagination.CLASS_NAME.ITEM_ACTIVE);
}
let aEleNew = document.createElement("a");
aEleNew.innerHTML = content;
aEle = aEleNew
}
// 添加到代码片段中
liEle.appendChild(aEle);
fragment.appendChild(liEle);
return fragment;
}
复制代码
一些说明
上面我们列出的函数里有些函数没有讲到,具体是如下的作用。
Pagination.PAGE_INFOS:返回 前一步/后一步/前几步/后几步 的 id 与 样式
getPageInfos :返回页数的数据
CLASS_NAME:样式名的定义
this.getEveryIconType():此函数返回图标的相关 dom ,如图:
事件绑定
事件绑定我们可以通过 事件委托 来做。
事件委托就是利用冒泡的原理,把事件加到父元素或祖先元素上,触发执行效果,大家可以参考
我们将事件绑定在 this.pageElement 上,同时判断元素 li 上的 id 属性来进行分发事件:
import { addEvent } from '../../../../helpers/utils';
/**
* 改变页数
*/
private changePage () {
if (!this.pageElement) return;
// 获取根元素
let pageElement = this.pageElement;
// 给根元素绑定事件
addEvent(pageElement, 'click', (ev: any) => {
let e = ev || window.event;
let target = e.target || e.srcElement;
// 通过 a 元素,找到其父元素 li 元素。
const parentNode = target.parentNode;
if (parentNode && parentNode.nodeName.toLocaleLowerCase() == "li") {
if (parentNode.id === "prev") {
// 前一页
if (!this.hasPrev()) return false;
this.prevPage();
} else if (parentNode.id === "next") {
if (!this.hasNext()) return;
// 后一页
this.nextPage();
} else if (parentNode.id === "jump-prev") {
// 前一批
this.jumpPrevPage();
} else if (parentNode.id === "jump-next") {
// 后一批
this.jumpNextPage();
} else if (parentNode.id === "page") {
// 页面跳转
const page = parentNode.dataset.page;
this.goPage(parseInt(page, 10));
} else {
// 直接 return,不做任何事情
return;
}
// 重新渲染分页器
this.renderPages();
}
});
}
复制代码里面涉及到的页面跳转函数我这里就不列举了,具体可以看源码。
回调事件
回调事件有两个:
onChange:页数改变
onShowSizeChange:pageSize 变化的回调
我们先来实现 onChange,onShowSizeChange 我们在后面会讲到。
其实很简单,只要在 changePage 函数里面,重新渲染函数 renderPages 之后,执行一些回调函数就行:
private changePage () {
...
// 重新渲染分页器
this.renderPages();
// 如果有参数有 onChange,就将 页数,和 pagesize 传入执行
this.options.onChange && this.options.onChange(this.current, this.pageSize);
...
}
复制代码
my-pagination 更多参数
更多参数其实我们都是围绕 renderPages 这个函数进行的。
disabled
禁用分页器,其实就是在包裹元素 ul 加上 darrell-pagination-disabled 样式。同时在事件绑定的地方,需要加上分页器是 disabled,就不绑定相应的事件
/**
* 渲染分页器
*/
private renderPages () {
...
const isDisabled = this.options.disabled;
...
let UlEle = document.createElement("ul");
UlEle.appendChild(fragment);
UlEle.setAttribute('class', 'darrell-pagination');
if (isDisabled) {
addClass(UlEle, 'darrell-pagination-disabled');
}
...
}
/**
* 改变页数
*/
private changePage () {
...
const isDisabled = this.options.disabled;
// 不是 disabled 时,做事件绑定
if (!isDisabled) {
// 做事件绑定
}
}
复制代码
hideOnSinglePage
只有一页时隐藏,只要在 renderPages 中,渲染 dom 之前判断 总页数是否为一
/**
* 渲染分页器
*/
private renderPages () {
...
const hideOnSinglePage = this.options.hideOnSinglePage;
// 如果是 一页,就直接不去渲染 dom 了。
if (hideOnSinglePage) {
if (pageCount === 1) {
return false;
}
}
...
}
复制代码
size
当 size=small 时,在包裹元素 ul 加上 mini 就行
/**
* 渲染分页器
*/
private renderPages () {
...
const isSmall = this.options.size;
...
let UlEle = document.createElement("ul");
UlEle.appendChild(fragment);
UlEle.setAttribute('class', 'darrell-pagination');
if (isSmall) {
addClass(UlEle, 'mini');
}
...
}
复制代码
simple
当其为 true 时,我们只需要简单的展示当前页数和总页数就行了
/**
* 渲染分页器
*/
private renderPages () {
...
const simple = this.options.simple;
...
let fragment: any = '';
if (!simple) {
fragment = this.showPages();
} else {
// 渲染简单分页的 dom
fragment = this.simplePages();
}
...
}
复制代码this.simplePages() 这个参数时用来生成简单分页的 dom 的,具体可以看源码
total
数据总数,用于显示数据总量和当前数据顺序,是一个函数,输出一个以 页数 与 总页数 相关的字符串。
/**
* 渲染分页器
*/
private renderPages () {
...
const showTotal = this.options.showTotal;
...
// 有 showTotal 并且不是 simple
if (showTotal && typeof showTotal === 'function' && !simple) {
let LiTextEle = document.createElement("li");
// 加上相关的样式
LiTextEle.setAttribute('class', 'darrell-pagination-total-text');
// 将总页数,和这页显示从第几条开始到第几条结束 的 range 传入 showTotal
const TotalText = showTotal(
this.total,
[
this.total === 0 ? 0 : (current - 1) * pageSize + 1,
current * pageSize > total ? total : current * pageSize,
]
);
LiTextEle.innerHTML = TotalText;
UlEle.insertBefore(LiTextEle, UlEle.firstChild);
}
...
}
复制代码具体字符串有使用者定义 ,
showQuickJumper
是否可以快速跳转至某页,我们要生成一个快速跳转的 dom,插入到包裹容器之中 。
/**
* 渲染分页器
*/
private renderPages () {
...
const showQuickJumper = this.options.showQuickJumper;
...
if (showQuickJumper && !simple) {
// 生成 快速跳转至某页的 dom
let quickJumperEle = this.createQuickJumperDom();
UlEle.appendChild(quickJumperEle);
}
...
}
复制代码this.createQuickJumperDom():这个函数是快速生成到某页的 Dom,具体可以看源码
showSizeChanger
是否可以改变 pageSize,我们要生成一个页面改变的类 select 元素,插入到包裹容器之中。
关于类 select 对象,我的做法是新建一个 select 类,做法和分页器一样,有时间我会在写一个仿 Antd 的 select 框,使用就是 new 一个 select 对象,并传入 包裹 id 和 相关的参数。
/**
* 渲染分页器
*/
private renderPages () {
...
const showSizeChanger = this.options.showSizeChanger;
...
if (showSizeChanger && !simple) {
// 放改变 size 的框
let LiEle = document.createElement("li");
LiEle.setAttribute('id', 'select');
LiEle.setAttribute('class', 'darrell-pagination-options');
UlEle.appendChild(LiEle);
}
...
this.pageElement.appendChild(UlEle);
if (showSizeChanger && !simple) {
new Select('#select', {
// 当前的 pageSize
value: this.pageSize,
// 当前页数
currentPage: this.current,
// 传入是否 disabled
disabled: isDisabled,
// 执行 pageSize 改变后的回调
onShowSizeChange: (current: number, size: number) => {
// pageSize
this.pageSize = size;
// 总页数
this.pageCount = Math.ceil(this.total / this.pageSize);
// 当前页数
if (current > this.pageCount) {
current = this.pageCount;
} else if (current < 1) {
current = 1
}
this.current = current;
// 重新渲染 分页器
this.renderPages();
// 执行 pageSize 改变后的回调
this.options.onShowSizeChange && this.options.onShowSizeChange(current, size);
},
});
}
...
}
复制代码select 类,我写在了 src/components/pagination/src/select 这个文件夹下,用于生生相应的 select 元素,具体可以看源码
itemRender
就是我们可以自定义分页器的文案,增强 seo,其本身也是一个函数,接受三个参数 current:当前页数;type:按钮类型;originalElement:元素的 dom;从而使我们能自定义文字内容。
这个函数运行的时机是在渲染元素时候去做,即在函数 createLiHtml 中
/**
* 默认的 render 方法
*/
static defaultItemRender = (page: number, type: 'page' | 'prev' | 'next' | 'jump-prev' | 'jump-next', element: any) => {
return element;
}
/**
* 创建 Li 元素
* @param liItemInfo
*/
private createLiHtml (liItemInfo: Array) {
// itemRender 函数
const itemRender = this.options.itemRender || Pagination.defaultItemRender;
if (id === 'prev') {
...
// 前一页
aEle = itemRender(
this.getPrevPage(),
'prev',
this.getIcon(this.getEveryIconType().prevIconEle)
)
} else if (id === 'next') {
...
// 后一页
aEle = itemRender(
this.getNextPage(),
'next',
this.getIcon(this.getEveryIconType().nextIconEle)
)
} else if (id === 'jump-prev') {
...
// 前一批
aEle = itemRender(
this.getJumpPrevPage(),
'jump-prev',
this.getIcon(this.getEveryIconType().jumpPrevWrapEle)
)
} else if (id === 'jump-next') {
...
// 后一批
aEle = itemRender(
this.getJumpNextPage(),
'jump-next',
this.getIcon(this.getEveryIconType().jumpNextWrapEle)
)
} else if (id === 'page') {
...
// 渲染页数
aEle = itemRender(
parseInt(content, 10),
'page',
aEleNew,
)
}
...
}
复制代码this.getIcon:这个方法是返回 icon 的 dom 元素
this.getEveryIconType:上面我们有讲到过,具体可以看源码
my-pagination 样式
对于样式,笔者在这里就不细说了,主要就是沿用了 antd-pagination 的样式,同时还增加了 normal.css,用于对页面的初始化。具体的大家可以参考样式源码。
小结
这一节我们详细的讲了一下分页核心逻辑的开发,从思路分析到具体的代码实现,同时笔者也参考了 手把手教你用原生JavaScript造轮子 和 十五分钟--分页逻辑--包学包会 这两篇文章,那么接下去我们就可以将组件发布到 npm 上去了,大家可以移步下一节 从零开始实现类 antd 分页器(三):发布npm 查看具体的 npm 发布流程。
参考内容
antd自定义分页器_从零开始实现类 antd 分页器(二):分页核心代码相关推荐
- start uml怎么自动生成代码_通过UML类图,自动生成相关代码
因为去年帮我的师父开发这个小软件,所以去年整了将近一个多月的时候,把这个基本的工程弄好了.姑且就叫做 版本1.0吧. 其实,功能很好理解,就是当你把UML(这里需要注意的是,只能用在VS,所以 ros ...
- matlab数组平方的计算自定义函数_从零开始的matlab学习笔记——(38)简单数论计算函数:取整,gcd,lcm,质数,全排列...
matlab应用--求极限,求导,求积分,解方程,概率统计,函数绘图,三维图像,拟合函数,动态图,傅里叶变换,随机数,优化问题....更多内容尽在个人专栏:matlab学习 翻了翻优化工具箱,发现内容 ...
- python -- 计算 平方、乘方、平方根_从零开始学习PYTHON3讲义(二)把Python当做计算器...
<从零开始PYTHON3>第二讲 上一讲我们说过了如何启动Python IDLE集成开发学习环境,macOS/Linux都可以在命令行执行idle3.Windows则从开始菜单中去寻找ID ...
- python做数学计算器_从零开始学习PYTHON3讲义(二)把Python当做计算器
<从零开始PYTHON3>第二讲 上一讲我们说过了如何启动Python IDLE集成开发学习环境,macOS/Linux都可以在命令行执行idle3.Windows则从开始菜单中去寻找ID ...
- devc代码补全没效果_从零开始写文本编辑器(二十八):自动补全(上)
前言 我本没打算这么早就写"自动补全"功能的. 但是在写XML资源编辑时,为了实现自动引用已有资源@string/xxx,需要一个合适的列表来让我选择.这样能防止拼写错误. 也就是 ...
- python鼠标移动事件_给turtle屏幕增加鼠标移动事件核心代码
当鼠标移动时彩色方块会变色,标题栏会显示坐标值.以下是部分代码预览: """ 给turtle的屏幕增加鼠标移动事件. 本程序演示了如何给turtle.py模块增加鼠标移动 ...
- python黄金走势预测_使用python爬虫获取黄金价格的核心代码
继续练手,根据之前获取汽油价格的方式获取了金价,暂时没钱投资,看看而已 #!/usr/bin/env python # -*- coding: utf-8 -*- """ ...
- 安卓 类微信开发(二)
目录 一.主要内容 二.核心代码 三.效果展示 在UI的基础上: 安卓 类微信界面开发(一)_qingsongxyz的博客-CSDN博客 一.主要内容 对聊天主界面chatFragement进行完善, ...
- mybatis 无法初始化类_从零开始手写 mybatis(一)MVP 版本
什么是 MyBatis ? MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射. MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集. MyB ...
最新文章
- windows7环境下使用pip安装MySQLdb
- 他用几个公式解释了现金贷业务的风控与运营 (下) 2017-09-18 22:04 风控/运营/违约 “金额如此小的业务,成本极度敏感,刚开始的时候我们在数据成本和坏账成本之间特别纠结。” 以上是许
- word2vec原理之CBOW与Skip-Gram模型基础
- ASP.net远程调试笔记
- nyoj 710 外星人的供给站
- linux下简单限制网卡速度
- Fiddler绕过前端直接和后台进行交互
- 为什么说:“你不合适学Python?”醍醐灌顶!
- 操作系统原理(二)操作系统逻辑结构、CPU的态和中断机制
- JS控制浏览器捕捉键盘
- 切图工具:又一个处理大图的例子
- html的日期插件标签,CaretTab - 新式可以显示时间和日期的标签
- 在 react 中添加enter键出搜索
- 少室山论道——天下武功
- 如何在微信公众号文章中怎么添加附件?
- 保险行业的“偿二代”
- 汉字风格迁移篇---MF-NET一种新颖的少镜头风格化多语言字体生成
- 计算机flash听课记录范文,听课记录5篇
- H5(HTML)网页制作基础
- 在linux系统下运行jar包的命令如下: