使用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 相关文章推荐
js中parseInt函数浅谈
Jul 31 Javascript
超级简单实现JavaScript MVC 样式框架
Mar 24 Javascript
JQuery页面地址处理插件jqURL详解
May 03 Javascript
jQuery计算文本框字数及限制文本框字数的方法
Mar 01 Javascript
Node.js批量给图片加水印的方法
Nov 15 Javascript
Jquery鼠标放上去显示全名的实现方法
Feb 06 Javascript
微信小程序tabbar不显示解决办法
Jun 08 Javascript
Angular指令之restict匹配模式的详解
Jul 27 Javascript
node中使用es5/6以及支持性与性能对比
Aug 11 Javascript
Vue 列表上下过渡效果的实例代码
Jun 25 Javascript
js实现登录拖拽窗口
Feb 10 Javascript
原生js实现放大镜组件
Jan 22 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中的错误处理、异常处理机制分析
2012/05/07 PHP
迅速确定php多维数组的深度的方法
2014/01/07 PHP
php简单实现MVC
2015/02/05 PHP
PHP getNamespaces()函数讲解
2019/02/03 PHP
JavaScript 仿关机效果的图片层
2008/12/26 Javascript
Jquery实现搜索框提示功能示例代码
2013/08/13 Javascript
JS可以控制样式的名称写法一览
2014/01/16 Javascript
jquery结婚电子请柬特效源码分享
2015/08/21 Javascript
Javascript小技能总结(推荐)
2016/06/02 Javascript
利用iscroll4实现轮播图效果实例代码
2017/01/11 Javascript
Angular在模板驱动表单中自定义校验器的方法
2017/08/09 Javascript
jquery实现侧边栏左右伸缩效果的示例
2017/12/19 jQuery
解决vue 按钮多次点击重复提交数据问题
2018/05/10 Javascript
CKEditor 4.4.1 添加代码高亮显示插件功能教程【使用官方推荐Code Snippet插件】
2019/06/14 Javascript
jQuery实现提交表单时不提交隐藏div中input的方法
2019/10/08 jQuery
Vue 打包体积优化方案小结
2020/05/20 Javascript
js实现拖拽与碰撞检测
2020/09/18 Javascript
移动端JS实现拖拽两种方法解析
2020/10/12 Javascript
DWR内存兼容及无法调用问题解决方案
2020/10/16 Javascript
[43:43]完美世界DOTA2联赛PWL S2 LBZS vs Forest 第三场 11.29
2020/12/02 DOTA
Python使用matplotlib实现在坐标系中画一个矩形的方法
2015/05/20 Python
Python实现二维有序数组查找的方法
2016/04/27 Python
python 3.0 模拟用户登录功能并实现三次错误锁定
2017/11/01 Python
Python利用itchat对微信中好友数据实现简单分析的方法
2017/11/21 Python
python邮件发送smtplib使用详解
2020/06/16 Python
python模块常用用法实例详解
2019/10/17 Python
利用python 下载bilibili视频
2020/11/13 Python
内刊编辑求职自荐书范文
2014/02/19 职场文书
餐厅销售主管职责范本
2014/02/19 职场文书
施工工地安全标语
2014/06/07 职场文书
机电一体化专业求职信
2014/07/22 职场文书
学校领导班子群众路线整改措施
2014/09/16 职场文书
2015新生加入学生会自荐书
2015/03/24 职场文书
利用Sharding-Jdbc进行分库分表的操作代码
2022/01/22 Java/Android
忘记Grafana不要紧2种Grafana重置admin密码方法详细步骤
2022/04/07 Servers
SQL Server中T-SQL标识符介绍与无排序生成序号的方法
2022/05/25 SQL Server