使用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 Global对象
Aug 13 Javascript
Dom操作之兼容技巧分享
Sep 20 Javascript
关于jQuery对象数据缓存Cache原理以及jQuery.data详解
Apr 07 Javascript
jQuery如何实现点击页面获得当前点击元素的id或其他信息
Jan 09 Javascript
纯JS实现本地图片预览的方法
Jul 31 Javascript
包含中国城市的javascript对象实例
Aug 03 Javascript
Bootstrap的Refresh Icon也spin起来
Jul 13 Javascript
jQuery表单验证插件解析(推荐)
Jul 21 Javascript
关于javascript中限定时间内防止按钮重复点击的思路详解
Aug 16 Javascript
浅谈Vue 函数式组件的使用技巧
Jun 16 Javascript
javascript全局自定义鼠标右键菜单
Dec 08 Javascript
React实现动效弹窗组件
Jun 21 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下过滤HTML代码的函数
2007/12/10 PHP
PHP实现随机发扑克牌
2020/04/22 PHP
jquery 简短右键菜单 多浏览器兼容
2010/01/01 Javascript
对 lightbox JS 图片控件进行了一下改造, 使其他支持复杂的图片说明
2010/03/20 Javascript
jquery URL参数判断,确定菜单样式
2010/05/31 Javascript
JQuery的Alert消息框插件使用介绍
2010/10/09 Javascript
JQuery 常用方法和事件详细介绍
2013/04/18 Javascript
js闭包的用途详解
2014/11/09 Javascript
nodejs初步体验篇
2015/11/23 NodeJs
jQuery增加与删除table列的方法
2016/03/01 Javascript
AngularJs解决跨域问题案例详解(简单方法)
2016/05/19 Javascript
AngularJS中isolate scope的用法分析
2016/11/22 Javascript
React-router v4 路由配置方法小结
2017/08/08 Javascript
解决select2在bootstrap modal中不能正常使用的问题
2018/08/09 Javascript
详解在Node.js中发起HTTP请求的5种方法
2019/01/10 Javascript
详解wepy开发小程序踩过的坑(小结)
2019/05/22 Javascript
七行JSON代码把你的网站变成移动应用过程详解
2019/07/09 Javascript
jQuery实现鼠标移入显示蒙版效果
2020/01/11 jQuery
小程序按钮避免多次调用接口和点击方案实现(不用showLoading)
2020/04/15 Javascript
python进阶教程之函数参数的多种传递方法
2014/08/30 Python
Django中的“惰性翻译”方法的相关使用
2015/07/27 Python
Python爬虫包BeautifulSoup学习实例(五)
2018/06/17 Python
Python+pandas计算数据相关系数的实例
2018/07/03 Python
Python使用装饰器模拟用户登陆验证功能示例
2018/08/24 Python
Python 读取用户指令和格式化打印实现解析
2019/09/02 Python
Python中关于浮点数的冷知识
2019/09/22 Python
python如何用matplotlib创建三维图表
2021/01/26 Python
HTML5有哪些新特征
2015/12/01 HTML / CSS
受希腊女神灵感的晚礼服、鸡尾酒礼服和婚纱:THEIA
2018/04/15 全球购物
澳大利亚优质葡萄酒专家:Vintage Cellars
2019/01/08 全球购物
自我鉴定范文
2013/11/10 职场文书
爱情检讨书大全
2014/01/21 职场文书
《狼》教学反思
2014/03/02 职场文书
学生会主席演讲稿
2014/04/25 职场文书
css3实现背景图片颜色修改的多种方式
2021/04/13 HTML / CSS
详解Java ES多节点任务的高效分发与收集实现
2021/06/30 Java/Android