本章节,我们做分页组件,这是一个非常常用的组件。grid, listview都离不开它。因此其各种形态也有。

本章节教授的是一个比较纯正的形态,bootstrap风格的那种分页栏。

我们建立一个ms-pager目录,控制台下使用npm init初始化仓库。

然后我们添加dependencies配置项,尝试使用一些更强大的loader!

  1. "dependencies": {
  2. "file-loader":"~0.9.0",
  3. "url-loader": "0.5.7",
  4. "node-sass": "^3.8.0",
  5. "sass-loader": "^3.2.2",
  6. "style-loader": "~0.13.1",
  7. "css-loader": "~0.8.0",
  8. "raw-loader":"~0.5.1",
  9. "html-minify-loader":"~1.1.0",
  10. "webpack": "^1.13.1"
  11. },

然后npm install,安装几百个nodejs模块……

编写模板与VM

这次我们打算使用boostrap的样式,因此重心就只有这两部分。

  1. <ul class="pagination">
  2. <li class="first"
  3. ms-class='{disabled: @currentPage === 1}'>
  4. <a ms-attr='{href:@getHref("first"),title:@getTitle("first")}'
  5. ms-click='cbProxy($event, "first")'
  6. >
  7. {{@firstText}}
  8. </a>
  9. </li>
  10. <li class="prev"
  11. ms-class='{disabled: @currentPage === 1}'>
  12. <a ms-attr='{href:@getHref("prev"),title:@getTitle("prev")}'
  13. ms-click='cbProxy($event, "prev")'
  14. >
  15. {{@prevText}}
  16. </a>
  17. </li>
  18. <li ms-for='page in @pages'
  19. ms-class='{active: page === @currentPage}' >
  20. <a ms-attr='{href:@getHref(page),title:@getTitle(page)}'
  21. ms-click='cbProxy($event, page)'
  22. >
  23. {{page}}
  24. </a>
  25. </li>
  26. <li class="next"
  27. ms-class='{disabled: @currentPage === @totalPages}'>
  28. <a ms-attr='{href:@getHref("next"),title: @getTitle("next")}'
  29. ms-click='cbProxy($event, "next")'
  30. >
  31. {{@nextText}}
  32. </a>
  33. </li>
  34. <li class="last"
  35. ms-class='{disabled: @currentPage === @totalPages}'>
  36. <a ms-attr='{href:@getHref("last"),title: @getTitle("last")}'
  37. ms-click='cbProxy($event, "last")'
  38. >
  39. {{@lastText}}
  40. </a>
  41. </li>
  42. </ul>

一个分页,大概有这么属性:

  1. currentPage: 当前页, 选中它,它应该会高亮,加一个active类名给它。
  2. totalPages: 总页数
  3. showPages: 要显示出来的页数。1万页不可能都全部生成出来。
  4. firstText, lastText, prevText, nextText这些按钮或链接的文本,有的人喜欢文字,有的喜欢图标,要做成可配置。
  5. onPageClick, 事件回调,它应该在该页disabled或active时不能触发事件。但我们需要将它一层。onPageClick是用户的方法,而处理disabled, active则是组件的事。因此我们模仿上一节的弹出层,外包一个cbProxy。

此外是类名,href, title的动态生成。


  1. var avalon = require('avalon2')
  2. avalon.component('ms-pager', {
  3. template: require('./template.html'),
  4. defaults: {
  5. getHref: function (href) {
  6. return href
  7. },
  8. getTitle: function (title) {
  9. return title
  10. },
  11. showPages: 5,
  12. pages: [],
  13. totalPages: 15,
  14. currentPage: 1,
  15. firstText: 'First',
  16. prevText: 'Previous',
  17. nextText: 'Next',
  18. lastText: 'Last',
  19. onPageClick: avalon.noop,//让用户重写
  20. cbProxy: avalon.noop, //待实现
  21. onInit: function (e) {
  22. var a = getPages.call(this, this.currentPage)
  23. this.pages = a.pages
  24. this.currentPage = a.currentPage
  25. }
  26. }
  27. })
  28. function getPages(currentPage) {
  29. var pages = []
  30. var s = this.showPages
  31. var total = this.totalPages
  32. var half = Math.floor(s / 2)
  33. var start = currentPage - half + 1 - s % 2
  34. var end = currentPage + half
  35. // handle boundary case
  36. if (start <= 0) {
  37. start = 1;
  38. end = s;
  39. }
  40. if (end > total) {
  41. start = total - s + 1
  42. end = total
  43. }
  44. var itPage = start;
  45. while (itPage <= end) {
  46. pages.push(itPage)
  47. itPage++
  48. }
  49. return {currentPage: currentPage, pages: pages};
  50. }

这样分页栏的初始形态就出来。最复杂就是中间显示页数的计算。

构建工程

我们立即检验一下我们的分页栏好不好使。建一个main.js作为入口文件

  1. var avalon = require('avalon2')
  2. require('./index')
  3. avalon.define({
  4. $id: 'test'
  5. })
  6. module.exports = avalon //注意这里必须返回avalon,用于webpack output配置

建立一个page.html,引入bootstrap的样式

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>分页栏</title>
  5. <meta charset="UTF-8">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <link href="http://netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
  8. <script src="./dist/index.js"></script>
  9. </head>
  10. <body ms-controller="test">
  11. <wbr ms-widget="{is:'ms-pager'}" />
  12. </body>
  13. </html>

然后建webpack.config开始构建工程:

  1. var webpack = require('webpack');
  2. var path = require('path');
  3. function heredoc(fn) {
  4. return fn.toString().replace(/^[^\/]+\/\*!?\s?/, '').
  5. replace(/\*\/[^\/]+$/, '').trim().replace(/>\s*</g, '><')
  6. }
  7. var api = heredoc(function () {
  8. /*
  9. avalon的分页组件
  10. 使用
  11. 兼容IE6-8
  12. <wbr ms-widget="[{is:'ms-pager'}, @config]"/>
  13. 只支持现代浏览器(IE9+)
  14. <ms-pager ms-widget="@config">
  15. </ms-pager>
  16. */
  17. })
  18. module.exports = {
  19. entry: {
  20. index: './main'
  21. },
  22. output: {
  23. path: path.join(__dirname, 'dist'),
  24. filename: '[name].js',
  25. libraryTarget: 'umd',
  26. library: 'avalon'
  27. }, //页面引用的文件
  28. plugins: [
  29. new webpack.BannerPlugin('分页 by 司徒正美\n' + api)
  30. ],
  31. module: {
  32. loaders: [
  33. //ExtractTextPlugin.extract('style-loader', 'css-loader','sass-loader')
  34. //http://react-china.org/t/webpack-extracttextplugin-autoprefixer/1922/4
  35. // https://github.com/b82/webpack-basic-starter/blob/master/webpack.config.js
  36. {test: /\.html$/, loader: 'raw!html-minify'}
  37. ]
  38. },
  39. 'html-minify-loader': {
  40. empty: true, // KEEP empty attributes
  41. cdata: true, // KEEP CDATA from scripts
  42. comments: true, // KEEP comments
  43. dom: {// options of !(htmlparser2)[https://github.com/fb55/htmlparser2]
  44. lowerCaseAttributeNames: false, // do not call .toLowerCase for each attribute name (Angular2 use camelCase attributes)
  45. }
  46. },
  47. resolve: {
  48. extensions: ['.js', '', '.css']
  49. }
  50. }

执行webpack --watch,打包后打开页面:

优化与打磨

目前还没有加入事件。但加入事件也是轻而易举的事,但这个事件有点特别,它分别要作用第一页,最后一页,前一页,后一页及中间页上。这要传入不同的参数。此外,它还要排除disabled状态与active状态的页码。虽然当我们点击页码时,页码上已经有disabled, active 这样的类名,但这要访问元素节点,这与MVVM的理念不一致。因此我们要另寻他法。此时,我们再看一下我们的模板,发现类名的生成部分太混乱,需要抽象一下。把添加了disabled与active 的页面存放起来,这样以后就不用访问元素节点了。

我们抽象出一个toPage方法,用于将first, last, prev, next转换页码

  1. toPage: function (p) {
  2. var cur = this.currentPage
  3. var max = this.totalPages
  4. switch (p) {
  5. case 'first':
  6. return 1
  7. case 'prev':
  8. return Math.max(cur - 1, 0)
  9. case 'next':
  10. return Math.min(cur + 1, max)
  11. case 'last':
  12. return max
  13. default:
  14. return p
  15. }
  16. },

然后添加一个$buttons对象,这是用于存放first, last, prev, next的disabled状态。之所以用$开头,那是因为这样做就不用转换为子VM,提高性能。

抽象一个isDisabled方法

  1. isDisabled: function (name, page) {
  2. return this.$buttons[name] = (this.currentPage === page)
  3. },

那么页面的对应位置就可以改成disabled: @isDisabled('first', 1)

然后优化getHref方法,内部调用toPage方法,这样就能看到地址栏的hash变化。

  1. getHref: function(){
  2. return '#page-' + this.toPage(a)
  3. }

实现cbProxy。大家看到我命名的方式是不是很怪,什么XXXProxy, isXXX。那是从java的设计模式过来的。

  1. cbProxy: function (e, p) {
  2. if (this.$buttons[p] || p === this.currentPage) {
  3. e.preventDefault()
  4. return //disabled, active不会触发
  5. }
  6. var cur = this.toPage(p)
  7. var obj = getPages.call(this, cur)
  8. this.pages = obj.pages
  9. this.currentPage = obj.currentPage
  10. return this.onPageClick(e, p)
  11. },

重写onInit,方便它直接从地址栏得到当前参数。

  1. onInit: function () {
  2. var cur = this.currentPage
  3. var match = /(?:#|\?)page\-(\d+)/.exec(location.href)
  4. if (match && match[1]) {
  5. var cur = ~~match[1]
  6. if (cur < 0 || cur > this.totalPages) {
  7. cur = 1
  8. }
  9. }
  10. var obj = getPages.call(this, cur)
  11. this.pages = obj.pages
  12. this.currentPage = obj.currentPage
  13. }

当然,有的用户会重写getHref方法,地址栏的参数也一样。因此最好这个正则也做成可配置。

  1. rpage : /(?:#|\?)page\-(\d+)/

注意,avalon2.1以下有一个BUG(2.1.2已经修复),会将VM中的正则转换一个子VM,因此需要大家打开源码,修改其isSkip方法

  1. var rskip = /function|window|date|regexp|element/i
  2. function isSkip(key, value, skipArray) {
  3. // 判定此属性能否转换访问器
  4. return key.charAt(0) === '$' ||
  5. skipArray[key] ||
  6. (rskip.test(avalon.type(value))) ||
  7. (value && value.nodeName && value.nodeType > 0)
  8. }

然后我们再打包一下:

接着是样式问题。我最开始说过,我们是用bootstrap样式,但我并不需要整个库,那么在这里将pagination的相关部分扒下来就是。

建立一个style.scss文件

  1. //
  2. // Pagination (multiple pages)
  3. // --------------------------------------------------
  4. $gray-base: #000 !default;
  5. $gray-light: lighten($gray-base, 46.7%) !default; // #777
  6. $gray-lighter: lighten($gray-base, 93.5%) !default; // #eee
  7. $brand-primary: darken(#428bca, 6.5%) !default; // #337ab7
  8. //** Global textual link color.
  9. $link-color: $brand-primary !default;
  10. //** Link hover color set via `darken()` function.
  11. $link-hover-color: darken($link-color, 15%) !default;
  12. $border-radius-base: 4px !default;
  13. $line-height-large: 1.3333333 !default; // extra decimals for Win 8.1 Chrome
  14. $border-radius-large: 6px !default;
  15. $padding-base-vertical: 6px !default;
  16. $padding-base-horizontal: 12px !default;
  17. $font-size-base: 14px !default;
  18. //** Unit-less `line-height` for use in components like buttons.
  19. $line-height-base: 1.428571429 !default; // 20/14
  20. //** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
  21. $line-height-computed: floor(($font-size-base * $line-height-base)) !default; // ~20px
  22. $cursor-disabled: not-allowed !default;
  23. $pagination-color: $link-color !default;
  24. $pagination-bg: #fff !default;
  25. $pagination-border: #ddd !default;
  26. $pagination-hover-color: $link-hover-color !default;
  27. $pagination-hover-bg: $gray-lighter !default;
  28. $pagination-hover-border: #ddd !default;
  29. $pagination-active-color: #fff !default;
  30. $pagination-active-bg: $brand-primary !default;
  31. $pagination-active-border: $brand-primary !default;
  32. $pagination-disabled-color: $gray-light !default;
  33. $pagination-disabled-bg: #fff !default;
  34. $pagination-disabled-border: #ddd !default;
  35. // Single side border-radius
  36. @mixin border-right-radius($radius) {
  37. border-bottom-right-radius: $radius;
  38. border-top-right-radius: $radius;
  39. }
  40. @mixin border-left-radius($radius) {
  41. border-bottom-left-radius: $radius;
  42. border-top-left-radius: $radius;
  43. }
  44. .pagination {
  45. display: inline-block;
  46. padding-left: 0;
  47. margin: $line-height-computed 0;
  48. border-radius: $border-radius-base;
  49. > li {
  50. display: inline; // Remove list-style and block-level defaults
  51. > a,
  52. > span {
  53. position: relative;
  54. float: left; // Collapse white-space
  55. padding: $padding-base-vertical $padding-base-horizontal;
  56. line-height: $line-height-base;
  57. text-decoration: none;
  58. color: $pagination-color;
  59. background-color: $pagination-bg;
  60. border: 1px solid $pagination-border;
  61. margin-left: -1px;
  62. }
  63. &:first-child {
  64. > a,
  65. > span {
  66. margin-left: 0;
  67. @include border-left-radius($border-radius-base);
  68. }
  69. }
  70. &:last-child {
  71. > a,
  72. > span {
  73. @include border-right-radius($border-radius-base);
  74. }
  75. }
  76. }
  77. > li > a,
  78. > li > span {
  79. &:hover,
  80. &:focus {
  81. z-index: 2;
  82. color: $pagination-hover-color;
  83. background-color: $pagination-hover-bg;
  84. border-color: $pagination-hover-border;
  85. }
  86. }
  87. > .active > a,
  88. > .active > span {
  89. &,
  90. &:hover,
  91. &:focus {
  92. z-index: 3;
  93. color: $pagination-active-color;
  94. background-color: $pagination-active-bg;
  95. border-color: $pagination-active-border;
  96. cursor: default;
  97. }
  98. }
  99. > .disabled {
  100. > span,
  101. > span:hover,
  102. > span:focus,
  103. > a,
  104. > a:hover,
  105. > a:focus {
  106. color: $pagination-disabled-color;
  107. background-color: $pagination-disabled-bg;
  108. border-color: $pagination-disabled-border;
  109. cursor: $cursor-disabled;
  110. }
  111. }
  112. }

然后在index.js加上

  1. require('./style.scss')

然后在webpack.config.js加上

  1. {test: /\.scss$/, loader: "style!css!sass"}

我们再尝试将样式独立成一个请求,有效利用页面缓存。

  1. npm install extract-text-webpack-plugin --save-dev

修改构建工具:

  1. var webpack = require('webpack');
  2. var path = require('path');
  3. function heredoc(fn) {
  4. return fn.toString().replace(/^[^\/]+\/\*!?\s?/, '').
  5. replace(/\*\/[^\/]+$/, '').trim().replace(/>\s*</g, '><')
  6. }
  7. var api = heredoc(function () {
  8. /*
  9. avalon的分页组件
  10. getHref: 生成页面的href
  11. getTitle: 生成页面的title
  12. showPages: 5 显示页码的个数
  13. totalPages: 15, 总数量
  14. currentPage: 1, 当前面
  15. firstText: 'First',
  16. prevText: 'Previous',
  17. nextText: 'Next',
  18. lastText: 'Last',
  19. onPageClick: 点击页码的回调
  20. 使用
  21. 兼容IE6-8
  22. <wbr ms-widget="[{is:'ms-pager'}, @config]"/>
  23. 只支持现代浏览器(IE9+)
  24. <ms-pager ms-widget="@config">
  25. </ms-pager>
  26. */
  27. })
  28. var ExtractTextPlugin = require('extract-text-webpack-plugin');
  29. var cssExtractor = new ExtractTextPlugin('/[name].css');
  30. module.exports = {
  31. entry: {
  32. index: './main'
  33. },
  34. output: {
  35. path: path.join(__dirname, 'dist'),
  36. filename: '[name].js',
  37. libraryTarget: 'umd',
  38. library: 'avalon'
  39. }, //页面引用的文件
  40. plugins: [
  41. new webpack.BannerPlugin('分页 by 司徒正美\n' + api)
  42. ],
  43. module: {
  44. loaders: [
  45. //http://react-china.org/t/webpack-extracttextplugin-autoprefixer/1922/4
  46. // https://github.com/b82/webpack-basic-starter/blob/master/webpack.config.js
  47. {test: /\.html$/, loader: 'raw!html-minify'},
  48. {test: /\.scss$/, loader: cssExtractor.extract( 'css!sass')}
  49. ]
  50. },
  51. 'html-minify-loader': {
  52. empty: true, // KEEP empty attributes
  53. cdata: true, // KEEP CDATA from scripts
  54. comments: true, // KEEP comments
  55. dom: {// options of !(htmlparser2)[https://github.com/fb55/htmlparser2]
  56. lowerCaseAttributeNames: false, // do not call .toLowerCase for each attribute name (Angular2 use camelCase attributes)
  57. }
  58. },
  59. plugins: [
  60. cssExtractor
  61. ],
  62. resolve: {
  63. extensions: ['.js', '', '.css']
  64. }
  65. }

修改页面的link为

  1. <link href="./dist/index.css" rel="stylesheet"/>

但这时我们的CSS与JS还没有压缩,这个很简单,

  1. webpack -p

于是dist目录下的js, css全部压成一行了!

最后大家可以在这里下到这个工程

相关链接

  1. jQuery分页插件
  2. 12个基于 jQuery 框架的 Ajax 分页插件

一步步编写avalon组件02:分页组件的更多相关文章

  1. 打通前后端全栈开发node+vue进阶【课程学习系统项目实战详细讲解】(3):用户添加/修改/删除 vue表格组件 vue分页组件

    第三章 建议学习时间8小时      总项目预计10章 学习方式:详细阅读,并手动实现相关代码(如果没有node和vue基础,请学习前面的vue和node基础博客[共10章] 演示地址:后台:demo ...

  2. 一步步编写avalon组件01:弹出层组件

    avalon2已经稳定下来,是时候教大家如何使用组件这个高级功能了. 组件是我们实现叠积木开发的关键. avalon2实现一个组件非常轻松,并且如何操作这个组件也比以前的avalon2,还是react ...

  3. 手把手教你使用Vue/React/Angular三大框架开发Pagination分页组件

    DevUI是一支兼具设计视角和工程视角的团队,服务于华为云DevCloud平台和华为内部数个中后台系统,服务于设计师和前端工程师.官方网站:devui.designNg组件库:ng-devui(欢迎S ...

  4. 基于Vue的简单通用分页组件

    分页组件是每一个系统里必不可少的一个组件,分页组件分为两部分.第一部分是模版部分,用于显示当前分页组件的状态,例如正在获取数据.没有数据.没有下一页等等:第二部分是分页数据对象,用于封装一个分页组件的 ...

  5. drf框架 - 过滤组件 | 分页组件 | 过滤器插件

    drf框架 接口过滤条件 群查接口各种筛选组件数据准备 models.py class Car(models.Model): name = models.CharField(max_length=16 ...

  6. drf-过滤组件|分页组件|过滤器

    目录 drf-过滤组件|分页组件|过滤器 群查接口各种筛选组件数据准备 drf过滤组件 搜索过滤组件 | SearchFilter 案例: 排序过滤组件 | OrderingFilter 案例: dr ...

  7. 基于avalon1.4.x ----分页组件编写

    avalon分页组件 (1.4.x版本) 随着avalon2的推出,avalon1的官网已经不再维护了,现在似乎是找不到avalon 1.4版本的官方文档了,所以本文章所有的内容均不保证正确性,只能保 ...

  8. Fit项目分页组件的编写

    项目中涉及列表显示的地方都会用到分页控件,为了能更好地与当前网站的样式匹配,这次要自己实现一个. 所以选择了模板中提供的分页样式,基于模板改造以能够动态生成: 一 控件的行为规则 a) 可设置显示几个 ...

  9. avalon实现分页组件

    前言 分页组件比较常见,但是用avalon实现的见的不多,这个分页组件,可以适配2种分页方式, 第一种是每次点击下一页,就请求一次后台,并返回当页数据和总条数,我称之为假分页: 第二种是一次性把所有数 ...

随机推荐

  1. WCF终结点配置

    错误信息:已有针对 IP 终结点 127.0.0.1:8235 的侦听器.如果有其他应用程序已在侦听此终结点,或者,如果在服务主机中具有多个服务终结点,这些终结点具有相同的 IP 终结点但绑定配置不兼 ...

  2. 防刷新jq左侧滚动条导航展示

    html代码: <div class="fangchan_navcont">        <div class="fangchan_nav" ...

  3. c++成员函数的存储方式---11

    原创博客:转载请标明出处:http://www.cnblogs.com/zxouxuewei/ 成员函数属于一个类的成员,出现再类体中.可以被指定为公有,私有或受保护的. 1.在类外面定义成员函数时, ...

  4. 导航position:absolute

    <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="C ...

  5. mysql添加外键

    语法:alter table 表名 add constraint FK_ID foreign key(你的外键字段名) REFERENCES 外表表名(对应的表的主键字段名); 例: alter ta ...

  6. 2016HUAS_ACM暑假集训2E - I Hate It

    又是一个线段树的应用,不过跟上一题(D-排兵布阵)不同的是,这次是求某段区间上的最值,而不是某段区间和.当然,数据更新是必须的.D题注释已经很详细了,所以这题注释少点. 大致题意:给你N个已经排好的学 ...

  7. .net中常用的几种页面间传递参数的方法

    转自:http://www.cnblogs.com/lxshanye/archive/2013/04/11/3014207.html 参考:http://www.cnblogs.com/zhangka ...

  8. eclipse 使用(一)单步调试

    昨天终于将取数据的流程走通了.但是没有成功获得数据.原因是,把服务器中的数据库还原到了本地.而测试数据是写到了本地.把数据给覆盖了.早上来了之后,赶紧在服务器上把数据弄了一下. 之后开始跑代码. 项目 ...

  9. SQL总结(二)连表查询

    ---恢复内容开始--- SQL总结(二)连表查询 连接查询包括合并.内连接.外连接和交叉连接,如果涉及多表查询,了解这些连接的特点很重要. 只有真正了解它们之间的区别,才能正确使用. 1.Union ...

  10. [Hibernate] - Generic Dao

    使用泛型写了一个通用的Hibernate DAO类. GenericDao接口 package com.my.dao; import java.io.Serializable; import java ...