如何使用 Custom Elements 和 Bootstrap 创建自定义分页器组件

前言

在日常的前端开发中,分页器是一个非常常见的组件。虽然 Bootstrap 提供了默认的分页器组件,但是在某些应用场景中,我们往往需要更加个性化的分页器组件,以符合业务需要。本文将介绍如何使用 Custom Elements 和 Bootstrap,创建一个高度自定义的分页器组件,以及相关的技术细节和实现方案。

准备工作

在开始编写自定义分页器组件之前,需要准备以下几个工具和技术:

  1. Bootstrap 4
  2. Custom Elements

在本文中,我们将使用 Bootstrap 4 来构建分页器的样式,同时使用 Custom Elements 来实现分页器的自定义组件。

分页器组件的基本结构

在开始编写组件之前,首先需要明确分页器组件的基本结构。一个基本的分页器组件应该包含以下几个部分:

  1. 上一页按钮
  2. 下一页按钮
  3. 当前页码的输入框
  4. 跳转到指定页码的按钮

下面是一个简单的示意图:

在分页器组件中,我们需要提供以下一些属性:

  1. page:表示当前页码,需要和输入框中的值进行同步。
  2. pageSize:表示每页展示的数据条数。
  3. total:表示数据总条数。
  4. showQuickJumper:是否展示跳转输入框和按钮。

编写分页器组件

1. 创建 Custom Element

首先,我们需要创建一个 Custom Element,用于代表分页器组件。我们给这个元素定义一个 pagination-element 的标签名:

<template id="pagination-template">
  <div class="pagination">
    <!-- 分页器组件的具体结构 -->
  </div>
</template>

<script>
  class PaginationElement extends HTMLElement {
    constructor() {
      super();
      const template = document.getElementById('pagination-template').content;
      this.shadow = this.attachShadow({ mode: 'open' })
        .appendChild(template.cloneNode(true));
    }
  }
  customElements.define('pagination-element', PaginationElement);
</script>

在以上代码中,我们使用了 attachShadow 方法来创建了一个 Shadow DOM,将模板插入到其中。接下来我们就可以在模板中编写分页器组件的具体结构了。

2. 编写分页器的基本结构

在模板中,我们可以结合 Bootstrap 4 的样式,编写一个最基本的分页器结构:

<!-- 分页器组件的具体结构 -->
<style>
  .pagination {
    display: flex;
    justify-content: center;
    align-items: center;
    margin: 20px 0;
  }

  .page-item {
    display: inline-block;
    padding: 0 10px;
    font-size: 14px;
    line-height: 1.5;
    border: 1px solid #dee2e6;
    border-radius: 2px;
    color: #666;
  }

  .page-item.active {
    color: #fff;
    background-color: #007bff;
    border-color: #007bff;
    cursor: default;
  }

  .page-item.disabled {
    color: #999;
    background-color: #fff;
    border-color: #dee2e6;
    cursor: not-allowed;
  }
</style>

<div class="pagination">
  <ul class="pagination">
    <li class="page-item">
      <a class="page-link" href="#">上一页</a>
    </li>

    <li class="page-item active">
      <a class="page-link" href="#">1</a>
    </li>

    <li class="page-item">
      <a class="page-link" href="#">2</a>
    </li>

    <li class="page-item">
      <a class="page-link" href="#">3</a>
    </li>

    <li class="page-item disabled">
      <a class="page-link" href="#">...</a>
    </li>

    <li class="page-item">
      <a class="page-link" href="#">5</a>
    </li>

    <li class="page-item">
      <a class="page-link" href="#">下一页</a>
    </li>
  </ul>
</div>

以上分页器结构包含了分页器的基本元素,即上一页、下一页按钮,以及页码列表。注意,在列表中,我们添加了一个 ... 的占位符,方便展示更多的页码。

3. 实现分页器组件的功能

有了基本的结构之后,接下来我们需要对这个分页器组件进行功能实现。具体来说,我们需要添加以下一些功能:

  1. 上一页和下一页功能
  2. 页码列表渲染
  3. 当前页码输入框和跳转功能

上一页和下一页功能

<li class="page-item">
  <a class="page-link" href="#" id="prev">上一页</a>
</li>

<li class="page-item">
  <a class="page-link" href="#" id="next">下一页</a>
</li>

通过给上一页和下一页的按钮添加一个 id 值,我们可以方便地通过 JavaScript 获取这个节点,然后给这个节点添加一个点击事件。

constructor() {
  super();
  const template = document.getElementById('pagination-template').content;
  this.shadow = this.attachShadow({ mode: 'open' })
    .appendChild(template.cloneNode(true));

  // 绑定上一页和下一页按钮的点击事件
  this.shadow.getElementById('prev')
    .addEventListener('click', this.prevPage.bind(this));
  this.shadow.getElementById('next')
    .addEventListener('click', this.nextPage.bind(this));
}

prevPage() {
  // 上一页逻辑
}

nextPage() {
  // 下一页逻辑
}

接下来,我们还需要给每一个 page-item 添加一个点击事件,方便用户进行手动跳转:

<li class="page-item" id="page-item-1">
  <a class="page-link" href="#">1</a>
</li>

<li class="page-item" id="page-item-2">
  <a class="page-link" href="#">2</a>
</li>

<li class="page-item" id="page-item-3">
  <a class="page-link" href="#">3</a>
</li>

同样的,我们给每一个 page-item 添加一个 id 来获取节点,然后绑定点击事件即可。

constructor() {
  // ...

  // 绑定页码列表的点击事件
  const pageItems = this.shadow.querySelectorAll('.page-item');
  pageItems.forEach((item) => {
    item.addEventListener('click', this.gotoPage.bind(this, parseInt(item.id.slice(-1))));
  });
}

gotoPage(pageNumber) {
  // 跳转到指定页码
}

页码列表渲染

接下来我们需要实现渲染页码列表的逻辑。

首先,我们需要获取当前分页器的属性值,包括 pagetotalpageSize

get page() {
  return parseInt(this.getAttribute('page')) || 1;
}

set page(val) {
  this.setAttribute('page', val);
}

get total() {
  return parseInt(this.getAttribute('total')) || 0;
}

set total(val) {
  this.setAttribute('total', val);
}

get pageSize() {
  return parseInt(this.getAttribute('page-size')) || 10;
}

set pageSize(val) {
  this.setAttribute('page-size', val);
}

接下来,我们可以根据属性值计算出总页数和页面列表。

get totalPages() {
  return Math.ceil(this.total / this.pageSize);
}

get pages() {
  let startPage = 1;
  let endPage = this.totalPages;

  if (this.totalPages > 5) {
    if (this.page <= 3) {
      endPage = 5;
    } else if (this.page > this.totalPages - 3) {
      startPage = this.totalPages - 4;
    } else {
      startPage = this.page - 2;
      endPage = this.page + 2;
    }
  }

  const pages = Array.from(Array(endPage + 1 - startPage).keys())
    .map(i => startPage + i);

  return pages;
}

其中,我们通过计算当前页码,然后判断页码列表的起始页码和结束页码。

最后,我们可以通过遍历计算得到的页面列表,来渲染分页器的页码列表。

render() {
  const pageItems = this.shadow.querySelectorAll('.page-item');
  pageItems.forEach((item) => {
    item.parentNode.removeChild(item);
  });

  const ul = this.shadow.querySelector('ul');

  if (this.total < this.pageSize) {
    for (let i = 1; i <= this.total; i++) {
      const li = this.generatePageItem(i);
      ul.insertBefore(li, ul.lastChild);
    }
  } else {
    const pages = this.pages;
    pages.forEach((page) => {
      const li = this.generatePageItem(page);
      ul.insertBefore(li, ul.lastChild);
    });
  }
}

generatePageItem(num) {
  const li = document.createElement('li');
  li.classList.add('page-item');
  li.setAttribute('id', `page-item-${num}`);

  const a = document.createElement('a');
  a.classList.add('page-link');
  a.setAttribute('href', `#${num}`);
  a.innerText = num;

  li.appendChild(a);

  if (num === this.page) {
    li.classList.add('active');
  }

  return li;
}

以上代码中,我们通过 generatePageItem 方法来生成每一个页码元素,然后使用遍历,将每个页码元素添加到分页器中。

当前页码输入框和跳转功能

最后,我们需要添加一个输入框和跳转按钮,方便用户手动输入页码进行跳转。

<div class="input-group input-group-sm" id="jump-input" style="display: none;">
  <input type="text" class="form-control">
  <div class="input-group-append">
    <button class="btn btn-outline-secondary" type="button" id="jump-btn">Go</button>
  </div>
</div>

以上代码中,我们使用了 Bootstrap 4 的样式来渲染输入框和按钮,并且给输入框和按钮都添加了一个 id 值来方便获取节点。

接下来,我们绑定跳转按钮的点击事件:

constructor() {
  // ...

  this.shadow.getElementById('jump-btn')
    .addEventListener('click', this.jumpPage.bind(this));
}

jumpPage() {
  const inputValue = parseInt(this.shadow.querySelector('#jump-input input').value);
  if (!inputValue || inputValue < 1 || inputValue > this.totalPages) {
    return;
  }
  this.page = inputValue;
  this.render();
}

以上代码中,我们获取到输入框中的值,如果输入值不符合要求则不进行跳转。否则,我们通过设置 page 属性值,然后重新渲染分页器来实现跳转功能。

同时,我们还需要根据 showQuickJumper 属性值,来决定是否展示输入框和按钮:

get showQuickJumper() {
  return this.hasAttribute('show-quick-jumper');
}

set showQuickJumper(val) {
  if (val) {
    this.setAttribute('show-quick-jumper', '');
  } else if (this.hasAttribute('show-quick-jumper')) {
    this.removeAttribute('show-quick-jumper');
  }
}

connectedCallback() {
  if (this.showQuickJumper) {
    this.shadow.querySelector('#jump-input').style.display = 'block';
  }
}

attributeChangedCallback(name, oldValue, newValue) {
  if (name === 'show-quick-jumper') {
    this.shadow.querySelector('#jump-input').style.display = newValue ? 'block' : 'none';
  }
}

总结

通过使用 Custom Elements 和 Bootstrap,我们可以快速实现一个高度自定义的分页器组件。在组件的实现过程中,我们学习了如何创建一个 Custom Element,

来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/65b4e905add4f0e0ffdc16b8