Vue2 排序表格组件 —— render渲染
2018-04-28 本文已影响118人
yancolin
效果图
需求:
渲染一个表格,表格的某几项可以升序,降序排序。点击添加数据,可以添加一条数据。
分析完,这里我们需要两个数据数组,一组是表头,另一组是表身。表格部分用表格组件完成,还需要一个点击按钮。分配给表格的数据为表头数组和表身数组。
同样,我们用四个文件来完成此需求: index.html,index.js,table.js,style.css。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>可排序的表格组件</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="app" v-cloak>
<v-table :data="data" :columns="columns"></v-table>
<button @click="handleAddData">添加数据</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="table.js"></script>
<script src="index.js"></script>
</body>
</html>
从index.html中看出我们要准备数组data(表身数组),column(表头数组),表头数组中还要标明哪些是可以排序的列。另还要准备一个添加数据的方法handleAddData()。
index.js
var app = new Vue({
el:'#app',
data:{
// columns的每一项是一个对象,其中title和key字段是必填的,用来标识这列的表头标题,
// key是对应data中列内容的字段名。sortable是选填字段,如果值为true,说明该列需要排序。
columns:[
{
title: '姓名',
key: 'name'
},
{
title: '年龄',
key: 'age',
sortable: true
},
{
title: '出生日期',
key: 'birthday',
sortable: true
},
{
title: '地址',
key: 'address'
}
],
data:[
{
name: '王小明',
age: 18,
birthday: '1999-02-21',
address: '北京市朝阳区芍药居'
},
{
name: '张小刚',
age: 25,
birthday: '1992-01-23',
address: '北京市海淀区二旗'
},
{
name: '李小红',
age: 30,
birthday: '1987-11-10',
address: '上海市浦东新区世纪大道'
},
{
name: '周小伟',
age: 26,
birthday: '1991-10-10',
address: '深圳市南山区深南大道'
}
]
},
methods:{
handleAddData:function () {
this.data.push({
name: '刘小天',
age: 19,
birthday: '1998-05-30',
address: '北京市东城区东直门'
});
}
}
});
为了不影响原始数组的数据,在组件中我们要copy一份在组件中操作。
table.js
Vue.component('vTable',{
props:{
data: {
type: Array,
default:function(){
return [];
}
},
columns:{
type:Array,
default:function(){
return [];
}
}
},
data:function(){
return {
currentColumns: [],
currentData: []
}
},
// h就是createElement
// render的时候,需要新建一个table元素,里面包含thead,tbody,然后thead和tbody里面又包含th,tr和tr,td
// 这里返回的数据trs是一个二维数组包含tr和td
render:function(h){
var _this = this;
var ths = [];
this.currentColumns.forEach(function(col,index){
// 如果column里面sortable为真,我们需要标↑,↓,并点击来执行排序,若不为真或没有sortable,就直接渲染column中的title数据
// 排序的标签在鼠标放上的时候高亮,且当前排序存在的话也要显示高亮,这里我们用一个class:on
// 排序的方法统一为handleSort()函数,参数为当前column的下标以及排序的方法('asc'或者'desc')
if( col.sortable ){
ths.push(h('th'),[
h('span',col.title),
// 升序
h('a',{
class: {
on: col._sortType === 'asc'
},
on:{
click: function(){
_this.handleSort(index,'asc');
}
}
},'↑'),
// 降序
h('a',{
class:{
on: col._sortType === 'desc'
},
on: {
click: function(){
_this.handleSort(index,'desc');
}
}
},'↓')
]);
}else{
ths.push(h('th',col.title));
}
});
var trs = [];
// 先遍历所有的column,取出column下的key对应的data数据,新建td元素的值并保存在tds数组中,然后再遍历所有的data形成各行,最终合并在trs数组中
this.currentData.forEach(function(row){
var tds = [];
_this.currentColumns.forEach(function(cell){
tds.push(h('td',row[cell.key]));
});
trs.push(h('tr',tds));
});
return h('table',[
h('thead',[
h('tr',ths)
]),
h('tbody',trs)
]);
},
// 在methods选项里定义两个方法来赋值,并在mounted钩子内调用
methods:{
makeColumns: function(){
// map() 是JavaScript数组的一个方法,根据传入的函数重新构造一个新数组
this.currentColumns = this.columns.map(function(col,index){
// 添加一个字段标识当前列排序的状态,没有排序的状态为'normal',升序为'asc',降序为'desc'
col._sortType = 'normal';
// 添加一个字段标识当前列在数组中的索引,方便后面会用到
col._index = index;
return col;
});
},
makeData: function(){
this.currentData = this.data.map(function(row,index){
// 添加一个字段标识当前行在数组中的索引,后面会用到
row._index = index;
return row;
});
},
handleSort: function(index,type){
var key = this.currentColumns[index].key;
// 先把column下面的_sortType先置为normal
this.currentColumns.forEach(function(col){
col._sortType = 'normal';
});
// 再更新当前index下的column排序规则
this.currentColumns[index]._sortType = type;
// 排序使用JavaScript数组的sort()方法,之所以返回1和-1,而不直接返回a[key] < b[key],也就是true或false,
// 因为在部分浏览器(比如safari)对sort()的处理不同,而1和-1可以做到兼容
this.currentData.sort(function(a,b){
if( type === 'asc' ){
return a[key] > b[key] ? 1 : -1;
}else{
return a[key] < b[key] ? -1 : 1;
}
});
}
},
// 父组件修改了data数据,currentData也应该更新,如果某列已经存在排序状态,更新后应该直接处理一次排序
watch: {
data:function(){
this.makeData();
var sortedColumn = this.currentColumns.filter(function(col){
return col._sortType !== 'normal';
});
if( sortedColumn.length > 0 ){
if( sortedColumn[0]._sortType === 'asc' ){
// 最开始同步父组件column数据的时候,在此数据中添加的_index现在用上了
this.handleSort(sortedColumn[0]._index,'asc');
}else{
this.handleSort(sortedColumn[0]._index,'desc');
}
}
}
},
mounted(){
// v-table初始化调用
this.makeColumns();
this.makeData();
}
});
style.css
[v-cloak]{
display:none;
}
button{
margin-top:10px;
padding:5px 7px;
}
table{
width: 100%;
margin-botton: 24px;
border-collapse: collapse;
border-spacing: 0;
empty-cells: show;
border:1px solid #e9e9e9;
}
table th{
background: #f7f7f7;
color:#5c6b77;
font-weight:600;
white-space: nowrap;
}
table td,table th{
padding:8px 16px;
border:1px solid #e9e9e9;
text-align: left;
}
table th a{
display: inline-block;
margin:0 4px;
cursor:pointer;
}
table th a.on{
color:#3399ff;
}
table th a:hover{
color:#3399ff;
}
用template来实现以上项目
对比render和template实现,在template中如果要bind:class,那么在v-for里面只能用方法来实现,而render里面直接可以用绝对等于实现。但是template更直观。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>可排序的表格组件</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="app" v-cloak>
<v-table :data="data" :columns="columns"></v-table>
<button @click="handleAddData">添加数据</button>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="table.js"></script>
<script src="index.js"></script>
</html>
index.js
var app = new Vue({
el:'#app',
data:{
// columns的每一项是一个对象,其中title和key字段是必填的,用来标识这列的表头标题,
// key是对应data中列内容的字段名。sortable是选填字段,如果值为true,说明该列需要排序。
columns:[
{
title: '姓名',
key: 'name',
width:'15%'
},
{
title: '年龄',
key: 'age',
sortable: true,
width:'15%'
},
{
title: '出生日期',
key: 'birthday',
sortable: true,
width:'15%'
},
{
title: '地址',
key: 'address',
width:'55%'
}
],
data:[
{
name: '王小明',
age: 18,
birthday: '1999-02-21',
address: '北京市朝阳区芍药居'
},
{
name: '张小刚',
age: 25,
birthday: '1992-01-23',
address: '北京市海淀区二旗'
},
{
name: '李小红',
age: 30,
birthday: '1987-11-10',
address: '上海市浦东新区世纪大道'
},
{
name: '周小伟',
age: 26,
birthday: '1991-10-10',
address: '深圳市南山区深南大道'
}
]
},
methods:{
handleAddData:function () {
this.data.push({
name: '刘小天',
age: 19,
birthday: '1998-05-30',
address: '北京市东城区东直门'
});
}
}
});
table.js
Vue.component('vTable',{
props:{
columns:{
type: Array,
default: function () {
return [];
}
},
data:{
type: Array,
default: function () {
return [];
}
}
},
// 为了让排序后的columns和data不影响原始数据,也给组件的data选项添加两个对应的数据,组件操作就依赖于它,不对原始数据做任何处理
data:function () {
return {
currentColumns: [],
currentData:[]
}
},
template:'<table>'
+ '<thead>'
+ '<tr>'
+ '<template v-for="(item,index) in currentColumns" >'
+ '<th v-if="item.sortable"><span>{{ item.title }}</span><a :class="isAscOn(index)" @click="handleSort(index,\'asc\')">↑</a><a :class="isDescOn(index)" @click="handleSort(index,\'desc\')">↓</a></th>'
+ '<th v-else>{{ item.title }}</th>'
+ '</template>'
+ '</tr>'
+ '</thead>'
+ '<tbody>'
+ '<tr v-for="(row,index) in currentData">'
+ '<td>{{row.name}}</td>'
+ '<td>{{row.age}}</td>'
+ '<td>{{row.birthday}}</td>'
+ '<td>{{row.address}}</td>'
+ '</tr>'
+ '</tbody>'
+ '</table>',
methods:{
// map()是JavaScript数组的一个方法,根据传入的函数重新构造一个新数组。
makeColumns: function () {
this.currentColumns = this.columns.map(function (col,index) {
// 添加一个字段标识当前列排序的状态,后续使用
col._sortType = 'normal';
// 添加一个字段标识当前列在数组中的索引,后续使用
col._index = index;
return col;
});
},
makeData: function () {
this.currentData = this.data.map(function (row,index) {
// 添加一个字段标识当前行在数组中的索引,后续使用
row._index = index;
return row;
});
},
isAscOn:function (index) {
if(this.currentColumns[index]._sortType === 'asc'){
return 'on';
}else{
return '';
}
},
isDescOn:function (index) {
if(this.currentColumns[index]._sortType === 'desc'){
return 'on';
}else{
return '';
}
},
handleSort: function (index,type) {
var key = this.currentColumns[index].key;
this.currentColumns.forEach(function (col) {
col._sortType = 'normal';
});
this.currentColumns[index]._sortType = type;
// 排序使用JavaScript数组的sort()方法,之所以返回1和-1,而不直接返回a[key] < b[key],也就是true或false,
// 因为在部分浏览器(比如safari)对sort()的处理不同,而1和-1可以做到兼容
this.currentData.sort(function (a, b) {
if (type === 'asc') {
return a[key] > b[key] ? 1 : -1;
} else {
return a[key] < b[key] ? 1 : -1;
}
});
}
},
watch:{
data:function () {
this.makeData();
// 从column中找出哪个是有排序的
var sortedColumn = this.currentColumns.filter(function (col) {
return col._sortType !== 'normal';
});
if( sortedColumn.length > 0 ){
if(sortedColumn[0]._sortType === 'asc'){
this.handleSort(sortedColumn[0]._index,'asc');
}else{
this.handleSort(sortedColumn[0]._index,'desc');
}
}
}
},
mounted(){
// v-table初始化调用
this.makeColumns();
this.makeData();
}
});
style.css共用一个。