本章節,咱們作分頁組件,這是一個很是經常使用的組件。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所有壓成一行了!
最後你們能夠在這裏下到這個工程
相關連接