使用Vue构建可重用的分页组件


Posted in Javascript onMarch 26, 2018

Web应用程序中资源分页不仅对性能很有帮助,而且从用户体验的角度来说也是非常有用的。在这篇文章中,将了解如何使用Vue创建动态和可用的分页组件。

基本结构

分页组件应该允许用户访问第一个和最后一个页面,向前和向后移动,并直接切换到近距离的页面。

大多数应用程序在用户每次更改页面时都会发出API请求。我们需要确保组件允许这样做,但是我们不希望在组件内发出这样的请求。这样,我们将确保组件在整个应用程序中是可重用的,并且请求都是在操作或服务层中进行的。我们可以通过使用用户单击的页面的数字触发事件来实现此目的。

有几种可能的方法来实现API端点上的分页。对于这个例子,我们假设API告诉我们每个页面的结果数、页面总数和当前页面。这些将是我们的动态 props 。

相反,如果API只告诉记录的总数,那么我们可以通过将结果的数量除以每一页的结果数来计算页数: totalResults / resultsPerPage 。

我们想要渲染一个按钮到 第一页 、 上一页 、 页面数量范围 、 下一页 和 最后一页 :

[first] [next] [1] [2] [3] [previous] [last]

比如像下图这样的一个效果:

使用Vue构建可重用的分页组件 

尽管我们希望渲染一个系列的页面,但并不希望渲染所有可用页面。让我们允许在我们的组件中设置一个最多可见按钮的 props 。

既然我们知道了我们想要的组件要做成什么,需要哪些数据,我们就可以设置HTML结构和所需要的 props 。

<template id="pagination">
  <ul class="pagination">
    <li>
      <button type="button">« First</button>
    </li>
    <li>
      <button type="button">«</button>
    </li>
    <!-- 页数的范围 -->
    <li>
      <button type="button">Next »</button>
    </li>
    <li>
      <button type="button">»</button>
    </li>
  </ul>
</template>
Vue.component('pagination', {
  template: '#pagination',
  props: {
    maxVisibleButtons: {
      type: Number,
      required: false,
      default: 3
    },
    totalPages: {
      type: Number,
      required: true
    },
    total: {
      type: Number,
      required: true
    },
    currentPage: {
      type: Number,
      required: true
    }
  }
})

上面的代码注册了一个 pagination 组件,如果调用这个组件:

<div id="app">
  <pagination></pagination>
</div>

这个时候看到的效果如下:

使用Vue构建可重用的分页组件 

注意,为了能让组件看上去好看一点,给组件添加了一点样式。

事件监听

现在我们需要通知父组件,当用户单击按钮时,用户点击了哪个按钮。

我们需要为每个按钮添加一个事件监听器。 v-on 指令 允许侦听DOM事件。在本例中,我将使用 v-on 的快捷键 来侦听单击事件。

为了通知父节点,我们将使用 $emit 方法 来发出一个带有页面点击的事件。

我们还要确保分页按钮只有在页面可用时才唯一一个当前状态。为了这样做,将使用 v-bind 将 disabled 属性的值与当前页面绑定。我们还是使用 :v-bind 的快捷键 : 。

为了保持我们的 template 干净,将使用 computed 属性 来检查按钮是否被禁用。使用 computed 也会被缓存,这意味着只要 currentPage 不会更改,对相同计算属性的几个访问将返回先前计算的结果,而不必再次运行该函数。

<template id="pagination">
  <ul class="pagination">
    <li>
      <button type="button" @click="onClickFirstPage" :disabled="isInFirstPage">« First</button>
    </li>
    <li>
      <button type="button" @click="onClickPreviousPage" :disabled="isInFirstPage">«</button>
    </li>
    <li v-for="page in pages">
      <button type="button" @click="onClickPage(page.name)" :disabled="page.isDisabled"> {{ page.name }}</button>
    </li>
    <li>
      <button type="button" @click="onClickNextPage" :disabled="isInLastPage">Next »</button>
    </li>
    <li>
      <button type="button" @click="onClickLastPage" :disabled="isInLastPage">»</button>
    </li>
  </ul>
</template>

Vue.component('pagination', {
  template: '#pagination',
  props: {
    maxVisibleButtons: {
      type: Number,
      required: false,
      default: 3
    },
    totalPages: {
      type: Number,
      required: true
    },
    total: {
      type: Number,
      required: true
    },
    currentPage: {
      type: Number,
      required: true
    }
  },
  computed: {
    isInFirstPage: function () {
      return this.currentPage === 1
    },
    isInLastPage: function () {
      return this.currentPage === this.totalPages
    }
  },
  methods: {
    onClickFirstPage: function () {
      this.$emit('pagechanged', 1)
    },
    onClickPreviousPage: function () {
      this.$emit('pagechanged', this.currentPage - 1)
    },
    onClickPage: function (page) {
      this.$emit('pagechanged', page)
    },
    onClickNextPage: function () {
      this.$emit('pagechanged', this.currentPage + 1)
    },
    onClickLastPage: function () {
      this.$emit('pagechanged', this.totalPages)
    }
  }
})

在调用 pagination 组件时,将 totalPages 和 total 以及 currentPage 传到组件中:

<div id="app">
  <pagination :total-pages="11" :total="120" :current-page="currentPage"></pagination>
</div>

let app = new Vue({
  el: '#app',
  data () {
    return {
      currentPage: 2
    }
  }
})

运行上面的代码,将会报错:

使用Vue构建可重用的分页组件 

不难发现,在 pagination 组件中,咱们还少了 pages 。从前面介绍的内容,我们不难发现,需要计算出 pages 的值。

Vue.component('pagination', {
  template: '#pagination',
  props: {
    maxVisibleButtons: {
      type: Number,
      required: false,
      default: 3
    },
    totalPages: {
      type: Number,
      required: true
    },
    total: {
      type: Number,
      required: true
    },
    currentPage: {
      type: Number,
      required: true
    }
  },
  computed: {
    isInFirstPage: function () {
      return this.currentPage === 1
    },
    isInLastPage: function () {
      return this.currentPage === this.totalPages
    },
    startPage: function () {
      if (this.currentPage === 1) {
        return 1
      }
      if (this.currentPage === this.totalPages) {
        return this.totalPages - this.maxVisibleButtons + 1
      }
      return this.currentPage - 1
    },
    endPage: function () {
      return Math.min(this.startPage + this.maxVisibleButtons - 1, this.totalPages)
    },
    pages: function () {
      const range = []
      for (let i = this.startPage; i <= this.endPage; i+=1) {
        range.push({
          name: i,
          isDisabled: i === this.currentPage
        })
      }
      return range
    }
  },
  methods: {
    onClickFirstPage: function () {
      this.$emit('pagechanged', 1)
    },
    onClickPreviousPage: function () {
      this.$emit('pagechanged', this.currentPage - 1)
    },
    onClickPage: function (page) {
      this.$emit('pagechanged', page)
    },
    onClickNextPage: function () {
      this.$emit('pagechanged', this.currentPage + 1)
    },
    onClickLastPage: function () {
      this.$emit('pagechanged', this.totalPages)
    }
  }
})

这个时候得到的结果不再报错,你在浏览器中将看到下图这样的效果:

使用Vue构建可重用的分页组件 

添加样式

现在我们的组件实现了最初想要的所有功能,而且添加了一些样式,让它看起来更像一个分页组件,而不仅像是一个列表。

我们还希望用户能够清楚地识别他们所在的页面。让我们改变表示当前页面的按钮的颜色。

为此,我们可以使用对象语法将HTML类绑定到当前页面按钮上。当使用对象语法绑定类名时,Vue将在值发生变化时自动切换类。

虽然 v-for 中的每个块都可以访问父作用域范围,但是我们将使用 method 来检查页面是否处于 active 状态,以便保持我们的 templage 干净。

Vue.component('pagination', {
  template: '#pagination',
  props: {
    maxVisibleButtons: {
      type: Number,
      required: false,
      default: 3
    },
    totalPages: {
      type: Number,
      required: true
    },
    total: {
      type: Number,
      required: true
    },
    currentPage: {
      type: Number,
      required: true
    }
  },
  computed: {
    isInFirstPage: function () {
      return this.currentPage === 1
    },
    isInLastPage: function () {
      return this.currentPage === this.totalPages
    },
    startPage: function () {
      if (this.currentPage === 1) {
        return 1
      }
      if (this.currentPage === this.totalPages) {
        return this.totalPages - this.maxVisibleButtons + 1
      }
      return this.currentPage - 1
    },
    endPage: function () {
      return Math.min(this.startPage + this.maxVisibleButtons - 1, this.totalPages)
    },
    pages: function () {
      const range = []
      for (let i = this.startPage; i <= this.endPage; i+=1) {
        range.push({
          name: i,
          isDisabled: i === this.currentPage
        })
      }
      return range
    }
  },
  methods: {
    onClickFirstPage: function () {
      this.$emit('pagechanged', 1)
    },
    onClickPreviousPage: function () {
      this.$emit('pagechanged', this.currentPage - 1)
    },
    onClickPage: function (page) {
      this.$emit('pagechanged', page)
    },
    onClickNextPage: function () {
      this.$emit('pagechanged', this.currentPage + 1)
    },
    onClickLastPage: function () {
      this.$emit('pagechanged', this.totalPages)
    },
    isPageActive: function (page) {
      return this.currentPage === page;
    }
  }
})

接下来,在 pages 中添加当前状态:

<li v-for="page in pages">
  <button type="button" @click="onClickPage(page.name)" :disabled="page.isDisabled" :class="{active: isPageActive(page.name)}"> {{ page.name }}</button>
</li>

这个时候你看到效果如下:

使用Vue构建可重用的分页组件 

但依然还存在一点点小问题,当你在点击别的按钮时, active 状态并不会随着切换:

使用Vue构建可重用的分页组件 

继续添加代码改变其中的效果:

let app = new Vue({
  el: '#app',
  data () {
    return {
      currentPage: 2
    }
  },
  methods: {
    onPageChange: function (page) {
      console.log(page)
      this.currentPage = page;
    }
  }
})

在调用组件时:

<div id="app">
  <pagination :total-pages="11" :total="120" :current-page="currentPage" @pagechanged="onPageChange"></pagination>
</div>

这个时候的效果如下了:

使用Vue构建可重用的分页组件 

到这里,基本上实现了咱想要的分页组件效果。

无障碍化处理

熟悉Bootstrap的同学都应该知道,Bootstrap中的组件都做了无障碍化的处理,就是在组件中添加了WAI-ARIA相关的设计。比如在分页按钮上添加 aria-label 相关属性:

使用Vue构建可重用的分页组件 

在我们这个组件中,也相应的添加有关于WAI-ARIA相关的处理:

<template id="pagination">
  <ul class="pagination" aria-label="Page navigation">
    <li>
      <button type="button" @click="onClickFirstPage" :disabled="isInFirstPage" aria-label="Go to the first page">« First</button>
    </li>
    <li>
      <button type="button" @click="onClickPreviousPage" :disabled="isInFirstPage" aria-label="Previous">«</button>
    </li>
    <li v-for="page in pages">
      <button type="button" @click="onClickPage(page.name)" :disabled="page.isDisabled" :aria-label="`Go to page number ${page.name}`"> {{ page.name }}</button>
    </li>
    <li>
      <button type="button" @click="onClickNextPage" :disabled="isInLastPage" aria-label="Next">Next »</button>
    </li>
    <li>
      <button type="button" @click="onClickLastPage" :disabled="isInLastPage" aria-label="Go to the last page">»</button>
    </li>
  </ul>
</template>

这样有关于 aria 相关的属性就加上了:

使用Vue构建可重用的分页组件 

最终的效果如下,可以点击下面的连接访问:

https://codepen.io/airen/pen/mxMLrG

Javascript 相关文章推荐
JavaScript与Image加载事件(onload)、加载状态(complete)
Feb 14 Javascript
JavaScript中的几个关键概念的理解-原型链的构建
May 12 Javascript
js有序数组的连接问题
Oct 01 Javascript
js判断文本框输入的内容是否为数字
Dec 23 Javascript
JS基于递归实现倒计时效果的方法
Nov 26 Javascript
Bootstrap学习笔记之进度条、媒体对象实例详解
Mar 09 Javascript
关于javascript获取内联样式与嵌入式样式的实例
Jun 01 Javascript
vue组件之间通信方式实例总结【8种方式】
Feb 22 Javascript
JavaScript面向对象编程小游戏---贪吃蛇代码实例
May 15 Javascript
layui实现数据表格自定义数据项
Oct 26 Javascript
toString.call()通用的判断数据类型方法示例
Aug 28 Javascript
three.js显示中文字体与tween应用详析
Jan 04 Javascript
基于jQuery实现Ajax验证用户名是否可用实例
Mar 25 #jQuery
jQuery实现的回车触发按钮事件功能示例
Mar 25 #jQuery
jQuery+Cookie实现切换皮肤功能【附源码下载】
Mar 25 #jQuery
Angular 组件之间的交互的示例代码
Mar 24 #Javascript
Vue中computed与methods的区别详解
Mar 24 #Javascript
javaScript实现鼠标在文字上悬浮时弹出悬浮层效果
Apr 12 #Javascript
使用Angular CLI进行单元测试和E2E测试的方法
Mar 24 #Javascript
You might like
PHP开发者常犯的10个MySQL错误更正剖析
2012/01/30 PHP
php使用curl访问https示例分享
2014/01/17 PHP
WordPress开发中用于获取近期文章的PHP函数使用解析
2016/01/05 PHP
PHP目录操作实例总结
2016/09/27 PHP
利用laravel搭建一个迷你博客实战教程
2017/08/13 PHP
vs2003 js文件编码问题的解决方法
2010/03/20 Javascript
JavaScript 格式字符串的应用
2010/03/29 Javascript
深入分析js中的constructor和prototype
2012/04/07 Javascript
js图片延迟技术一般的思路与示例
2014/03/20 Javascript
js实现的早期滑动门菜单效果代码
2015/08/27 Javascript
Node.js巧妙实现Web应用代码热更新
2015/10/22 Javascript
JS+JSP通过img标签调用实现静态页面访问次数统计的方法
2015/12/14 Javascript
javascript html实现网页版日历代码
2016/03/08 Javascript
深入浅析jQuery对象$.html
2016/08/22 Javascript
JS图片放大效果简单实现代码
2016/09/08 Javascript
使用jquery判断一个元素是否含有一个指定的类(class)实例
2017/02/12 Javascript
javascript中BOM基础知识总结
2017/02/14 Javascript
vue项目中使用ueditor的实例讲解
2018/03/05 Javascript
vue中组件通信的八种方式(值得收藏!)
2019/08/09 Javascript
Vue 权限控制的两种方法(路由验证)
2019/08/16 Javascript
微信小程序如何获取地址
2019/12/24 Javascript
[00:27]DOTA2荣耀之路2:Patience from zhou!
2018/05/24 DOTA
[02:06]2018完美世界全国高校联赛秋季赛开始报名(附彩蛋)
2018/09/03 DOTA
Python实现的本地文件搜索功能示例【测试可用】
2018/05/30 Python
Python global全局变量函数详解
2018/09/18 Python
python3安装OCR识别库tesserocr过程图解
2020/04/02 Python
Python建造者模式案例运行原理解析
2020/06/29 Python
Python类class参数self原理解析
2020/11/19 Python
Java提供了哪些企业应用编程接口
2015/02/13 面试题
JAVA高级程序员面试题
2013/09/06 面试题
Python使用openpyxl复制整张sheet
2021/03/24 Python
刚毕业大学生自荐信范文
2014/02/20 职场文书
幼儿园中班教师寄语
2014/04/03 职场文书
实习生岗位职责
2014/04/12 职场文书
无偿献血倡议书
2014/04/14 职场文书
新年祝酒词大全
2015/08/11 职场文书