本章节,咱们作分页组件,这是一个很是经常使用的组件。grid, listview都离不开它。所以其各类形态也有。javascript
本章节教授的是一个比较纯正的形态,bootstrap风格的那种分页栏。css
咱们创建一个ms-pager目录,控制台下使用npm init初始化仓库。html
而后咱们添加dependencies配置项,尝试使用一些更强大的loader!java
"dependencies": { "file-loader":"~0.9.0", "url-loader": "0.5.7", "node-sass": "^3.8.0", "sass-loader": "^3.2.2", "style-loader": "~0.13.1", "css-loader": "~0.8.0", "raw-loader":"~0.5.1", "html-minify-loader":"~1.1.0", "webpack": "^1.13.1" },
而后npm install,安装几百个nodejs模块……node
此次咱们打算使用boostrap的样式,所以重心就只有这两部分。react
<ul class="pagination"> <li class="first" ms-class='{disabled: @currentPage === 1}'> <a ms-attr='{href:@getHref("first"),title:@getTitle("first")}' ms-click='cbProxy($event, "first")' > {{@firstText}} </a> </li> <li class="prev" ms-class='{disabled: @currentPage === 1}'> <a ms-attr='{href:@getHref("prev"),title:@getTitle("prev")}' ms-click='cbProxy($event, "prev")' > {{@prevText}} </a> </li> <li ms-for='page in @pages' ms-class='{active: page === @currentPage}' > <a ms-attr='{href:@getHref(page),title:@getTitle(page)}' ms-click='cbProxy($event, page)' > {{page}} </a> </li> <li class="next" ms-class='{disabled: @currentPage === @totalPages}'> <a ms-attr='{href:@getHref("next"),title: @getTitle("next")}' ms-click='cbProxy($event, "next")' > {{@nextText}} </a> </li> <li class="last" ms-class='{disabled: @currentPage === @totalPages}'> <a ms-attr='{href:@getHref("last"),title: @getTitle("last")}' ms-click='cbProxy($event, "last")' > {{@lastText}} </a> </li> </ul>
一个分页,大概有这么属性:jquery
此外是类名,href, title的动态生成。webpack
var avalon = require('avalon2') avalon.component('ms-pager', { template: require('./template.html'), defaults: { getHref: function (href) { return href }, getTitle: function (title) { return title }, showPages: 5, pages: [], totalPages: 15, currentPage: 1, firstText: 'First', prevText: 'Previous', nextText: 'Next', lastText: 'Last', onPageClick: avalon.noop,//让用户重写 cbProxy: avalon.noop, //待实现 onInit: function (e) { var a = getPages.call(this, this.currentPage) this.pages = a.pages this.currentPage = a.currentPage } } }) function getPages(currentPage) { var pages = [] var s = this.showPages var total = this.totalPages var half = Math.floor(s / 2) var start = currentPage - half + 1 - s % 2 var end = currentPage + half // handle boundary case if (start <= 0) { start = 1; end = s; } if (end > total) { start = total - s + 1 end = total } var itPage = start; while (itPage <= end) { pages.push(itPage) itPage++ } return {currentPage: currentPage, pages: pages}; }
这样分页栏的初始形态就出来。最复杂就是中间显示页数的计算。git
咱们当即检验一下咱们的分页栏好很差使。建一个main.js做为入口文件github
var avalon = require('avalon2') require('./index') avalon.define({ $id: 'test' }) module.exports = avalon //注意这里必须返回avalon,用于webpack output配置
创建一个page.html,引入bootstrap的样式
<!DOCTYPE html> <html> <head> <title>分页栏</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link href="http://netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet"> <script src="./dist/index.js"></script> </head> <body ms-controller="test"> <wbr ms-widget="{is:'ms-pager'}" /> </body> </html>
而后建webpack.config开始构建工程:
var webpack = require('webpack'); var path = require('path'); function heredoc(fn) { return fn.toString().replace(/^[^\/]+\/\*!?\s?/, ''). replace(/\*\/[^\/]+$/, '').trim().replace(/>\s*</g, '><') } var api = heredoc(function () { /* avalon的分页组件 使用 兼容IE6-8 <wbr ms-widget="[{is:'ms-pager'}, @config]"/> 只支持现代浏览器(IE9+) <ms-pager ms-widget="@config"> </ms-pager> */ }) module.exports = { entry: { index: './main' }, output: { path: path.join(__dirname, 'dist'), filename: '[name].js', libraryTarget: 'umd', library: 'avalon' }, //页面引用的文件 plugins: [ new webpack.BannerPlugin('分页 by 司徒正美\n' + api) ], module: { loaders: [ //ExtractTextPlugin.extract('style-loader', 'css-loader','sass-loader') //http://react-china.org/t/webpack-extracttextplugin-autoprefixer/1922/4 // https://github.com/b82/webpack-basic-starter/blob/master/webpack.config.js {test: /\.html$/, loader: 'raw!html-minify'} ] }, 'html-minify-loader': { empty: true, // KEEP empty attributes cdata: true, // KEEP CDATA from scripts comments: true, // KEEP comments dom: {// options of !(htmlparser2)[https://github.com/fb55/htmlparser2] lowerCaseAttributeNames: false, // do not call .toLowerCase for each attribute name (Angular2 use camelCase attributes) } }, resolve: { extensions: ['.js', '', '.css'] } }
执行webpack --watch,打包后打开页面:
目前尚未加入事件。但加入事件也是垂手可得的事,但这个事件有点特别,它分别要做用第一页,最后一页,前一页,后一页及中间页上。这要传入不一样的参数。此外,它还要排除disabled状态与active状态的页码。虽然当咱们点击页码时,页码上已经有disabled, active 这样的类名,但这要访问元素节点,这与MVVM的理念不一致。所以咱们要另寻他法。此时,咱们再看一下咱们的模板,发现类名的生成部分太混乱,须要抽象一下。把添加了disabled与active 的页面存放起来,这样之后就不用访问元素节点了。
咱们抽象出一个toPage方法,用于将first, last, prev, next转换页码
toPage: function (p) { var cur = this.currentPage var max = this.totalPages switch (p) { case 'first': return 1 case 'prev': return Math.max(cur - 1, 0) case 'next': return Math.min(cur + 1, max) case 'last': return max default: return p } },
而后添加一个$buttons
对象,这是用于存放first, last, prev, next的disabled状态。之因此用$开头,那是由于这样作就不用转换为子VM,提升性能。
抽象一个isDisabled方法
isDisabled: function (name, page) { return this.$buttons[name] = (this.currentPage === page) },
那么页面的对应位置就能够改为disabled: @isDisabled('first', 1)
而后优化getHref方法,内部调用toPage方法,这样就能看到地址栏的hash变化。
getHref: function(){ return '#page-' + this.toPage(a) }
实现cbProxy。你们看到我命名的方式是否是很怪,什么XXXProxy, isXXX。那是从java的设计模式过来的。
cbProxy: function (e, p) { if (this.$buttons[p] || p === this.currentPage) { e.preventDefault() return //disabled, active不会触发 } var cur = this.toPage(p) var obj = getPages.call(this, cur) this.pages = obj.pages this.currentPage = obj.currentPage return this.onPageClick(e, p) },
重写onInit,方便它直接从地址栏获得当前参数。
onInit: function () { var cur = this.currentPage var match = /(?:#|\?)page\-(\d+)/.exec(location.href) if (match && match[1]) { var cur = ~~match[1] if (cur < 0 || cur > this.totalPages) { cur = 1 } } var obj = getPages.call(this, cur) this.pages = obj.pages this.currentPage = obj.currentPage }
固然,有的用户会重写getHref方法,地址栏的参数也同样。所以最好这个正则也作成可配置。
rpage : /(?:#|\?)page\-(\d+)/
注意,avalon2.1如下有一个BUG(2.1.2已经修复),会将VM中的正则转换一个子VM,所以须要你们打开源码,修改其isSkip方法
var rskip = /function|window|date|regexp|element/i function isSkip(key, value, skipArray) { // 断定此属性可否转换访问器 return key.charAt(0) === '$' || skipArray[key] || (rskip.test(avalon.type(value))) || (value && value.nodeName && value.nodeType > 0) }
而后咱们再打包一下:
接着是样式问题。我最开始说过,咱们是用bootstrap样式,但我并不须要整个库,那么在这里将pagination的相关部分扒下来就是。
创建一个style.scss文件
// // Pagination (multiple pages) // -------------------------------------------------- $gray-base: #000 !default; $gray-light: lighten($gray-base, 46.7%) !default; // #777 $gray-lighter: lighten($gray-base, 93.5%) !default; // #eee $brand-primary: darken(#428bca, 6.5%) !default; // #337ab7 //** Global textual link color. $link-color: $brand-primary !default; //** Link hover color set via `darken()` function. $link-hover-color: darken($link-color, 15%) !default; $border-radius-base: 4px !default; $line-height-large: 1.3333333 !default; // extra decimals for Win 8.1 Chrome $border-radius-large: 6px !default; $padding-base-vertical: 6px !default; $padding-base-horizontal: 12px !default; $font-size-base: 14px !default; //** Unit-less `line-height` for use in components like buttons. $line-height-base: 1.428571429 !default; // 20/14 //** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc. $line-height-computed: floor(($font-size-base * $line-height-base)) !default; // ~20px $cursor-disabled: not-allowed !default; $pagination-color: $link-color !default; $pagination-bg: #fff !default; $pagination-border: #ddd !default; $pagination-hover-color: $link-hover-color !default; $pagination-hover-bg: $gray-lighter !default; $pagination-hover-border: #ddd !default; $pagination-active-color: #fff !default; $pagination-active-bg: $brand-primary !default; $pagination-active-border: $brand-primary !default; $pagination-disabled-color: $gray-light !default; $pagination-disabled-bg: #fff !default; $pagination-disabled-border: #ddd !default; // Single side border-radius @mixin border-right-radius($radius) { border-bottom-right-radius: $radius; border-top-right-radius: $radius; } @mixin border-left-radius($radius) { border-bottom-left-radius: $radius; border-top-left-radius: $radius; } .pagination { display: inline-block; padding-left: 0; margin: $line-height-computed 0; border-radius: $border-radius-base; > li { display: inline; // Remove list-style and block-level defaults > a, > span { position: relative; float: left; // Collapse white-space padding: $padding-base-vertical $padding-base-horizontal; line-height: $line-height-base; text-decoration: none; color: $pagination-color; background-color: $pagination-bg; border: 1px solid $pagination-border; margin-left: -1px; } &:first-child { > a, > span { margin-left: 0; @include border-left-radius($border-radius-base); } } &:last-child { > a, > span { @include border-right-radius($border-radius-base); } } } > li > a, > li > span { &:hover, &:focus { z-index: 2; color: $pagination-hover-color; background-color: $pagination-hover-bg; border-color: $pagination-hover-border; } } > .active > a, > .active > span { &, &:hover, &:focus { z-index: 3; color: $pagination-active-color; background-color: $pagination-active-bg; border-color: $pagination-active-border; cursor: default; } } > .disabled { > span, > span:hover, > span:focus, > a, > a:hover, > a:focus { color: $pagination-disabled-color; background-color: $pagination-disabled-bg; border-color: $pagination-disabled-border; cursor: $cursor-disabled; } } }
而后在index.js加上
require('./style.scss')
而后在webpack.config.js加上
{test: /\.scss$/, loader: "style!css!sass"}
咱们再尝试将样式独立成一个请求,有效利用页面缓存。
npm install extract-text-webpack-plugin --save-dev
修改构建工具:
var webpack = require('webpack'); var path = require('path'); function heredoc(fn) { return fn.toString().replace(/^[^\/]+\/\*!?\s?/, ''). replace(/\*\/[^\/]+$/, '').trim().replace(/>\s*</g, '><') } var api = heredoc(function () { /* avalon的分页组件 getHref: 生成页面的href getTitle: 生成页面的title showPages: 5 显示页码的个数 totalPages: 15, 总数量 currentPage: 1, 当前面 firstText: 'First', prevText: 'Previous', nextText: 'Next', lastText: 'Last', onPageClick: 点击页码的回调 使用 兼容IE6-8 <wbr ms-widget="[{is:'ms-pager'}, @config]"/> 只支持现代浏览器(IE9+) <ms-pager ms-widget="@config"> </ms-pager> */ }) var ExtractTextPlugin = require('extract-text-webpack-plugin'); var cssExtractor = new ExtractTextPlugin('/[name].css'); module.exports = { entry: { index: './main' }, output: { path: path.join(__dirname, 'dist'), filename: '[name].js', libraryTarget: 'umd', library: 'avalon' }, //页面引用的文件 plugins: [ new webpack.BannerPlugin('分页 by 司徒正美\n' + api) ], module: { loaders: [ //http://react-china.org/t/webpack-extracttextplugin-autoprefixer/1922/4 // https://github.com/b82/webpack-basic-starter/blob/master/webpack.config.js {test: /\.html$/, loader: 'raw!html-minify'}, {test: /\.scss$/, loader: cssExtractor.extract( 'css!sass')} ] }, 'html-minify-loader': { empty: true, // KEEP empty attributes cdata: true, // KEEP CDATA from scripts comments: true, // KEEP comments dom: {// options of !(htmlparser2)[https://github.com/fb55/htmlparser2] lowerCaseAttributeNames: false, // do not call .toLowerCase for each attribute name (Angular2 use camelCase attributes) } }, plugins: [ cssExtractor ], resolve: { extensions: ['.js', '', '.css'] } }
修改页面的link为
<link href="./dist/index.css" rel="stylesheet"/>
但这时咱们的CSS与JS尚未压缩,这个很简单,
webpack -p
因而dist目录下的js, css所有压成一行了!
最后你们能够在这里下到这个工程
相关连接