一步步编写avalon组件02:分页组件
本章节,我们做分页组件,这是一个非常常用的组件。grid, listview都离不开它。因此其各种形态也有。
本章节教授的是一个比较纯正的形态,bootstrap风格的那种分页栏。
我们建立一个ms-pager目录,控制台下使用npm init初始化仓库。
然后我们添加dependencies配置项,尝试使用一些更强大的loader!
"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模块……
编写模板与VM
这次我们打算使用boostrap的样式,因此重心就只有这两部分。
<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>
一个分页,大概有这么属性:
- currentPage: 当前页, 选中它,它应该会高亮,加一个active类名给它。
- totalPages: 总页数
- showPages: 要显示出来的页数。1万页不可能都全部生成出来。
- firstText, lastText, prevText, nextText这些按钮或链接的文本,有的人喜欢文字,有的喜欢图标,要做成可配置。
- onPageClick, 事件回调,它应该在该页disabled或active时不能触发事件。但我们需要将它一层。onPageClick是用户的方法,而处理disabled, active则是组件的事。因此我们模仿上一节的弹出层,外包一个cbProxy。
此外是类名,href, title的动态生成。
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};
}
这样分页栏的初始形态就出来。最复杂就是中间显示页数的计算。
构建工程
我们立即检验一下我们的分页栏好不好使。建一个main.js作为入口文件
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全部压成一行了!
最后大家可以在这里下到这个工程
相关链接
一步步编写avalon组件02:分页组件的更多相关文章
- 打通前后端全栈开发node+vue进阶【课程学习系统项目实战详细讲解】(3):用户添加/修改/删除 vue表格组件 vue分页组件
第三章 建议学习时间8小时 总项目预计10章 学习方式:详细阅读,并手动实现相关代码(如果没有node和vue基础,请学习前面的vue和node基础博客[共10章] 演示地址:后台:demo ...
- 一步步编写avalon组件01:弹出层组件
avalon2已经稳定下来,是时候教大家如何使用组件这个高级功能了. 组件是我们实现叠积木开发的关键. avalon2实现一个组件非常轻松,并且如何操作这个组件也比以前的avalon2,还是react ...
- 手把手教你使用Vue/React/Angular三大框架开发Pagination分页组件
DevUI是一支兼具设计视角和工程视角的团队,服务于华为云DevCloud平台和华为内部数个中后台系统,服务于设计师和前端工程师.官方网站:devui.designNg组件库:ng-devui(欢迎S ...
- 基于Vue的简单通用分页组件
分页组件是每一个系统里必不可少的一个组件,分页组件分为两部分.第一部分是模版部分,用于显示当前分页组件的状态,例如正在获取数据.没有数据.没有下一页等等:第二部分是分页数据对象,用于封装一个分页组件的 ...
- drf框架 - 过滤组件 | 分页组件 | 过滤器插件
drf框架 接口过滤条件 群查接口各种筛选组件数据准备 models.py class Car(models.Model): name = models.CharField(max_length=16 ...
- drf-过滤组件|分页组件|过滤器
目录 drf-过滤组件|分页组件|过滤器 群查接口各种筛选组件数据准备 drf过滤组件 搜索过滤组件 | SearchFilter 案例: 排序过滤组件 | OrderingFilter 案例: dr ...
- 基于avalon1.4.x ----分页组件编写
avalon分页组件 (1.4.x版本) 随着avalon2的推出,avalon1的官网已经不再维护了,现在似乎是找不到avalon 1.4版本的官方文档了,所以本文章所有的内容均不保证正确性,只能保 ...
- Fit项目分页组件的编写
项目中涉及列表显示的地方都会用到分页控件,为了能更好地与当前网站的样式匹配,这次要自己实现一个. 所以选择了模板中提供的分页样式,基于模板改造以能够动态生成: 一 控件的行为规则 a) 可设置显示几个 ...
- avalon实现分页组件
前言 分页组件比较常见,但是用avalon实现的见的不多,这个分页组件,可以适配2种分页方式, 第一种是每次点击下一页,就请求一次后台,并返回当页数据和总条数,我称之为假分页: 第二种是一次性把所有数 ...
随机推荐
- WCF终结点配置
错误信息:已有针对 IP 终结点 127.0.0.1:8235 的侦听器.如果有其他应用程序已在侦听此终结点,或者,如果在服务主机中具有多个服务终结点,这些终结点具有相同的 IP 终结点但绑定配置不兼 ...
- 防刷新jq左侧滚动条导航展示
html代码: <div class="fangchan_navcont"> <div class="fangchan_nav" ...
- c++成员函数的存储方式---11
原创博客:转载请标明出处:http://www.cnblogs.com/zxouxuewei/ 成员函数属于一个类的成员,出现再类体中.可以被指定为公有,私有或受保护的. 1.在类外面定义成员函数时, ...
- 导航position:absolute
<html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="C ...
- mysql添加外键
语法:alter table 表名 add constraint FK_ID foreign key(你的外键字段名) REFERENCES 外表表名(对应的表的主键字段名); 例: alter ta ...
- 2016HUAS_ACM暑假集训2E - I Hate It
又是一个线段树的应用,不过跟上一题(D-排兵布阵)不同的是,这次是求某段区间上的最值,而不是某段区间和.当然,数据更新是必须的.D题注释已经很详细了,所以这题注释少点. 大致题意:给你N个已经排好的学 ...
- .net中常用的几种页面间传递参数的方法
转自:http://www.cnblogs.com/lxshanye/archive/2013/04/11/3014207.html 参考:http://www.cnblogs.com/zhangka ...
- eclipse 使用(一)单步调试
昨天终于将取数据的流程走通了.但是没有成功获得数据.原因是,把服务器中的数据库还原到了本地.而测试数据是写到了本地.把数据给覆盖了.早上来了之后,赶紧在服务器上把数据弄了一下. 之后开始跑代码. 项目 ...
- SQL总结(二)连表查询
---恢复内容开始--- SQL总结(二)连表查询 连接查询包括合并.内连接.外连接和交叉连接,如果涉及多表查询,了解这些连接的特点很重要. 只有真正了解它们之间的区别,才能正确使用. 1.Union ...
- [Hibernate] - Generic Dao
使用泛型写了一个通用的Hibernate DAO类. GenericDao接口 package com.my.dao; import java.io.Serializable; import java ...