Merge pull request #68 from eleme/feat/next-table

Feat/next table
pull/2/head
FuryBean 2016-08-17 11:05:53 +08:00 committed by GitHub
commit 62e3e768c3
11 changed files with 736 additions and 673 deletions

View File

@ -23,7 +23,7 @@
}
},
ready() {
mounted() {
console.log('popup ready');
}
};

View File

@ -1,6 +1,5 @@
<script>
import Vue from 'vue';
let popup = Vue.extend(require('examples/components/table-filter.vue'));
export default {
data() {
return {
@ -81,6 +80,16 @@
};
},
methods: {
handleSelectionChange(val) {
this.singleSelection = val;
},
handleMultipleSelectionChange(val) {
this.multipleSelection = val;
}
},
watch: {
singleSelection(val) {
console.log('selection: ', val);
@ -287,7 +296,7 @@
<el-table-column property="address" label="地址"></el-table-column>
</el-table>
</template>
<script>
export default {
data() {
@ -427,16 +436,16 @@
## 单选
<el-table :data="tableData" selection-mode="single" :selection.sync="singleSelection" style="width: 520px" allow-no-selection>
<el-table :data="tableData" selection-mode="single" @selectionchange="handleSelectionChange" style="width: 520px" allow-no-selection>
<el-table-column property="date" label="日期" width="120"></el-table-column>
<el-table-column property="name" label="姓名" width="120"></el-table-column>
<el-table-column property="address" label="地址"></el-table-column>
</el-table>
<p>{{ singleSelection | json }}</p>
<p>{{ singleSelection }}</p>
```html
<template>
<el-table :data="tableData" selection-mode="single" :selection.sync="singleSelection">
<el-table :data="tableData" selection-mode="single" @selectionchange="handleSelectionChange">
<el-table-column property="date" label="日期" width="120"></el-table-column>
<el-table-column property="name" label="姓名" width="120"></el-table-column>
<el-table-column property="address" label="地址"></el-table-column>
@ -466,6 +475,12 @@
}],
singleSelection: {}
}
},
methods: {
handleSelectionChange(val) {
this.singleSelection = val;
}
}
}
</script>
@ -473,19 +488,23 @@
## 多选
<el-table :data="tableData3" selection-mode="multiple" :selection.sync="multipleSelection" style="width: 520px">
<el-table :data="tableData3" selection-mode="multiple" style="width: 520px" @selectionchange="handleMultipleSelectionChange">
<el-table-column type="selection" width="50"></el-table-column>
<el-table-column property="date" label="日期" width="120"></el-table-column>
<el-table-column inline-template property="date" label="日期" width="120">
<div>{{ row.date }}</div>
</el-table-column>
<el-table-column property="name" label="姓名" width="120"></el-table-column>
<el-table-column property="address" label="地址"></el-table-column>
</el-table>
<p>{{ multipleSelection | json }}</p>
<p>{{ multipleSelection }}</p>
```html
<template>
<el-table :data="tableData3" selection-mode="multiple" :selection.sync="multipleSelection">
<el-table :data="tableData3" selection-mode="multiple" @selectionchange="handleSelectionChange">
<el-table-column type="selection" width="50"></el-table-column>
<el-table-column property="date" label="日期" width="120"></el-table-column>
<el-table-column inline-template property="date" label="日期" width="120">
<div>{{ row.date }}</div>
</el-table-column>
<el-table-column property="name" label="姓名" width="120"></el-table-column>
<el-table-column property="address" label="地址"></el-table-column>
</el-table>
@ -526,6 +545,12 @@
}],
multipleSelection: []
}
},
methods: {
handleSelectionChange(val) {
this.multipleSelection = val;
}
}
}
</script>
@ -584,16 +609,15 @@
| border | 是否带有纵向边框 | boolean | | false |
| selectionMode | 列表项选择模式 | string | 'single', 'multiple', 'none' | 'none' |
| allowNoSelection | 单选模式是否允许选项为空 | boolean | | false |
| selection | 多选模式下返回数组,单选模式下返回选中的元素。 | array/object | | |
| fixedColumnCount | 固定列的个数 | number | | 0 |
## el-table 事件
| 事件名 | 说明 | 参数 |
| ---- | ---- | ---- |
| selection-change | 当选择项发生变化时会触发该事件 | selected |
| cell-mouse-enter | 当单元格 hover 进入时会触发该事件 | row, column, cell, event |
| cell-mouse-leave | 当单元格 hover 退出时会触发该事件 | row, column, cell, event |
| cell-click | 当某个单元格被点击时会触发该事件 | row, column, cell, event |
| selectionchange | 当选择项发生变化时会触发该事件 | selected |
| cellmouseenter | 当单元格 hover 进入时会触发该事件 | row, column, cell, event |
| cellmouseleave | 当单元格 hover 退出时会触发该事件 | row, column, cell, event |
| cellclick | 当某个单元格被点击时会触发该事件 | row, column, cell, event |
## el-table-column API
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
@ -604,3 +628,6 @@
| sortable | 对应列是否可以排序 | boolean | | false |
| type | 对应列的类型。如果设置了 `selection` 则显示多选按钮,如果设置了 `index` 则显示该行的索引(从 1 开始计算) | string | 'selection', 'index' | 0 |
| formatter | 用来格式化内容,在 formatter 执行的时候,会传入 row 和 column | function | | |
| show-tooltip-when-overflow | 当过长被隐藏时显示 tooltip | Boolean | | false |
| inline-template | 指定该属性后可以自定义 column 模板,参考多选的时间列,通过 row 获取行信息。此时不需要配置 property 属性 | | |

View File

@ -46,7 +46,7 @@
"q": "^1.4.1",
"uppercamelcase": "^1.1.0",
"vue-loader": "^9.3.2",
"vue": "^2.0.0-rc.1",
"vue": "^2.0.0-rc.2",
"vue-markdown-loader": "^0.4.0",
"vue-popup": "^0.2.2",
"vue-router": "^2.0.0-beta.2"

View File

@ -0,0 +1,155 @@
const getColumnById = function(grid, columnId) {
let column = null;
grid.columns.forEach(function(item) {
if (item.id === columnId) {
column = item;
}
});
return column;
};
const getColumnByCell = function(grid, cell) {
const matches = (cell.className || '').match(/grid_[^\s]+/gm);
if (matches) {
return getColumnById(grid, matches[0]);
}
return null;
};
import { getValueByPath, getCell, orderBy, getChild } from './util';
export default {
props: {
columns: {},
data: {},
fixed: {},
selection: {
default() {
return [];
}
}
},
render(h) {
return (
<table
class="el-table__body"
cellspacing="0"
cellpadding="0"
border="0">
<tbody>
{
this._l(this.data, (row, $index) =>
<tr
on-click={ ($event) => this.handleClick($event, row) }
on-mouseenter={ _ => this.handleMouseEnter($index) }
class={{
'current-row': row === this.$parent.$parent.selected,
'hover': this.$parent.$parent.hoverRowIndex === $index,
'positive-row': row.$positive,
'info-row': row.$info,
'warning-row': row.$warning,
'negative-row': row.$negative
}}>
{
this._l(this.columns, (column) =>
<td
class={ column.id }
on-mouseenter={ ($event) => this.handleCellMouseEnter($event, row) }
on-mouseleave={ this.handleCellMouseLeave }>
{
column.template
? column.template.call(this._renderProxy, h, { row, column, $index })
: <div class="cell">{ this.$getPropertyText(row, column.property, column.id) }</div>
}
</td>
).concat(this.fixed ? <td class="gutter" /> : '')
}
</tr>
)
}
</tbody>
</table>
);
},
data() {
return {
tooltipDisabled: true
};
},
filters: {
orderBy
},
methods: {
handleCellMouseEnter(event, row) {
let grid = this.$parent;
const cell = getCell(event);
if (cell) {
const column = getColumnByCell(grid, cell);
const hoverState = grid.hoverState = { cell: cell, column: column, row: row };
grid.$emit('cellmouseenter', hoverState.row, hoverState.column, hoverState.cell, event);
}
// 判断是否text-overflow, 如果是就显示tooltip
const cellChild = getChild(event);
if (cellChild.scrollWidth > cellChild.offsetWidth) {
this.tooltipDisabled = false;
} else {
this.tooltipDisabled = true;
}
},
handleCellMouseLeave(event) {
let grid = this.$parent;
const cell = getCell(event);
if (cell) {
const oldHoverState = grid.hoverState;
grid.$emit('cellmouseleave', oldHoverState.row, oldHoverState.column, oldHoverState.cell, event);
}
},
handleMouseEnter(index) {
this.$parent.hoverRowIndex = index;
},
handleClick(event, row) {
let grid = this.$parent;
const cell = getCell(event);
if (cell) {
const column = getColumnByCell(grid, cell);
if (column) {
grid.$emit('cellclick', row, column, cell, event);
}
}
if (grid.selectionMode === 'single') {
grid.selected = row;
grid.$emit('selectionchange', row);
}
grid.$emit('rowclick', row, event);
},
handleCreate(vm) {
document.body.appendChild(vm.$refs.popper);
vm.updatePopper();
},
$getPropertyText(row, property, columnId) {
let grid = this.$parent;
const column = getColumnById(grid, columnId);
if (column && column.formatter) {
return column.formatter(row, column);
}
return getValueByPath(row, property);
}
}
};

View File

@ -1,142 +0,0 @@
<template>
<table class="el-table__body" cellspacing="0" cellpadding="0" border="0">
<tbody>
<tr v-for="row in data"
@click="handleClick($event, row)" @mouseenter="handleMouseEnter($index)"
:class="{
'current-row': row === $parent.$parent.selected,
hover: $parent.$parent.hoverRowIndex === $index,
'positive-row': row.$positive,
'info-row': row.$info,
'warning-row': row.$warning,
'negative-row': row.$negative
}">
<td v-for="column in columns" :class="column.id"
@mouseenter="handleCellMouseEnter($event, row)"
@mouseleave="handleCellMouseLeave($event)">
<partial v-if="column.template" :name="'template:' + column.id"></partial>
<partial v-else name="template:default"></partial>
</td>
<td class="gutter" v-if="!fixed"></td>
</tr>
</tbody>
</table>
</template>
<script type="text/babel">
const getColumnById = function(grid, columnId) {
let column = null;
grid.columns.forEach(function(item) {
if (item.id === columnId) {
column = item;
}
});
return column;
};
const getColumnByCell = function(grid, cell) {
const matches = (cell.className || '').match(/grid_[^\s]+/gm);
if (matches) {
return getColumnById(grid, matches[0]);
}
return null;
};
import { getValueByPath, getCell, orderBy, getChild } from './util';
export default {
props: {
columns: {},
data: {},
fixed: {},
selection: {
default() {
return [];
}
}
},
data() {
return {
tooltipDisabled: true
};
},
filters: {
orderBy
},
partials: {
'template:default': '<div class="cell">{{ $getPropertyText(row, column.property, column.id) }}</div>'
},
methods: {
handleCellMouseEnter(event, row) {
let grid = this.$parent;
const cell = getCell(event);
if (cell) {
const column = getColumnByCell(grid, cell);
const hoverState = grid.hoverState = { cell: cell, column: column, row: row };
grid.$emit('cell-mouse-enter', hoverState.row, hoverState.column, hoverState.cell, event);
}
// text-overflow, tooltip
const cellChild = getChild(event);
if (cellChild.scrollWidth > cellChild.offsetWidth) {
this.tooltipDisabled = false;
} else {
this.tooltipDisabled = true;
}
},
handleCellMouseLeave(event) {
let grid = this.$parent;
const cell = getCell(event);
if (cell) {
const oldHoverState = grid.hoverState;
grid.$emit('cell-mouse-leave', oldHoverState.row, oldHoverState.column, oldHoverState.cell, event);
}
},
handleMouseEnter(index) {
this.$parent.hoverRowIndex = index;
},
handleClick(event, row) {
let grid = this.$parent;
const cell = getCell(event);
if (cell) {
const column = getColumnByCell(grid, cell);
if (column) {
grid.$emit('cell-click', row, column, cell, event);
}
}
if (grid.selectionMode === 'single') {
grid.selected = row;
grid.$emit('selection-change', row);
}
grid.$emit('row-click', row, event);
},
handleCreate(vm) {
document.body.appendChild(vm.popper);
vm.updatePopper();
},
$getPropertyText(row, property, columnId) {
let grid = this.$parent;
const column = getColumnById(grid, columnId);
if (column && column.formatter) {
return column.formatter(row, column);
}
return getValueByPath(row, property);
}
}
};
</script>

View File

@ -0,0 +1,255 @@
import ElCheckbox from 'packages/checkbox/index.js';
import ElTag from 'packages/tag/index.js';
import objectAssign from 'object-assign';
let columnIdSeed = 1;
const defaults = {
default: {
direction: ''
},
selection: {
width: 48,
minWidth: 48,
realWidth: 48,
direction: ''
},
index: {
width: 48,
minWidth: 48,
realWidth: 48,
direction: ''
},
filter: {
headerTemplate: function(h) { return <span>filter header</span>; },
direction: ''
}
};
const forced = {
selection: {
headerTemplate: function(h) { return <div><el-checkbox nativeOn-click={ this.toggleAllSelection } domProps-value={ this.allSelected } on-input={ ($event) => this.$emit('allselectedchange', $event) } /></div>; },
template: function(h, { row }) { return <el-checkbox domProps-value={ row.$selected } on-input={ ($event) => {row.$selected = $event;} } />; },
sortable: false,
resizable: false
},
index: {
headerTemplate: function(h) { return <div>#</div>; },
template: function(h, { row, $index }) { return <div>{ $index + 1 }</div>; },
sortable: false
},
filter: {
headerTemplate: function(h) { return <div>#</div>; },
template: function(h, { row, column }) { return <el-tag type="primary" style="height: 16px; line-height: 16px; min-width: 40px; text-align: center">{ row[column.property] }</el-tag>; },
resizable: false
}
};
const getDefaultColumn = function(type, options) {
const column = {};
objectAssign(column, defaults[type || 'default']);
for (let name in options) {
if (options.hasOwnProperty(name)) {
const value = options[name];
if (typeof value !== 'undefined') {
column[name] = value;
}
}
}
return column;
};
export default {
name: 'el-table-column',
props: {
type: {
type: String,
default: 'default'
},
label: String,
property: String,
width: {},
minWidth: {},
template: String,
sortable: {
type: Boolean,
default: false
},
resizable: {
type: Boolean,
default: true
},
showTooltipWhenOverflow: Boolean,
formatter: Function
},
render(h) {
return <div />;
},
data() {
return {
isChildColumn: false,
columns: [],
row: {}
};
},
components: {
ElCheckbox,
ElTag
},
created() {
let columnId = this.columnId = (this.$parent.gridId || (this.$parent.columnId + '_')) + 'column_' + columnIdSeed++;
let parent = this.$parent;
if (!parent.gridId) {
this.isChildColumn = true;
}
let type = this.type;
let width = this.width;
if (width !== undefined) {
width = parseInt(width, 10);
if (isNaN(width)) {
width = null;
}
}
let minWidth = this.minWidth;
if (minWidth !== undefined) {
minWidth = parseInt(minWidth, 10);
if (isNaN(minWidth)) {
minWidth = 80;
}
} else {
minWidth = 80;
}
let isColumnGroup = false;
let template;
let property = this.property;
if (property) {
template = function(h, { row }) {
return <span>{ this.$getPropertyText(row, property, columnId) }</span>;
};
}
let column = getDefaultColumn(type, {
id: columnId,
label: this.label,
property: this.property,
type,
template,
minWidth,
width,
isColumnGroup,
realWidth: width || minWidth,
sortable: this.sortable,
resizable: this.resizable,
formatter: this.formatter
});
objectAssign(column, forced[type] || {});
let renderColumn = column.template;
let _self = this;
column.template = function(h, data) {
if (_self.$vnode.data.inlineTemplate) {
let costomRender = _self.$options.render;
renderColumn = function(_h) {
return costomRender.call(data, _h);
};
};
return _self.showTooltipWhenOverflow
? <el-tooltip
on-created={ this.handleCreate }
effect={ this.effect }
placement="top"
disabled={ this.tooltipDisabled }>
<div class="cell">{ renderColumn.call(this._renderProxy, h, data) }</div>
<span slot="content">{ renderColumn.call(this._renderProxy, h, data) }</span>
</el-tooltip>
: <div class="cell">{ renderColumn.call(this._renderProxy, h, data) }</div>;
};
this.columnConfig = column;
},
destroyed() {
if (!this.$parent) {
return;
}
let columns = this.$parent.columns;
if (columns) {
let columnId = this.columnId;
for (let i = 0, j = columns.length; i < j; i++) {
let column = columns[i];
if (column.id === columnId) {
columns.splice(i, 1);
break;
}
}
}
if (this.isChildColumn) {
if (this.$parent.$parent.$ready) {
this.$parent.$parent.debouncedReRender();
}
} else {
if (this.$parent.$ready) {
this.$parent.debouncedReRender();
}
}
},
watch: {
label(newVal) {
if (this.columnConfig) {
this.columnConfig.label = newVal;
}
},
property(newVal) {
if (this.columnConfig) {
this.columnConfig.property = newVal;
}
}
},
mounted() {
let parent = this.$parent;
let columnConfig = this.columnConfig;
let columnIndex;
if (!this.isChildColumn) {
columnIndex = [].indexOf.call(parent.$refs.hiddenColumns.children, this.$el);
} else {
columnIndex = [].indexOf.call(parent.$el.children, this.$el);
}
parent.columns.splice(columnIndex, 0, columnConfig);
if (this.isChildColumn) {
parent.columnConfig.columns = parent.columns;
if (parent.$parent.$ready) {
parent.$parent.debouncedReRender();
}
} else {
if (parent.$ready) {
parent.debouncedReRender();
}
}
}
};

View File

@ -1,254 +0,0 @@
<template>
<div><slot></slot></div>
</template>
<script type="text/babel">
import ElCheckbox from 'packages/checkbox/index.js';
import ElTag from 'packages/tag/index.js';
let columnIdSeed = 1;
const defaults = {
default: {
direction: ''
},
selection: {
width: 48,
minWidth: 48,
realWidth: 48,
direction: ''
},
index: {
width: 48,
minWidth: 48,
realWidth: 48,
direction: ''
},
filter: {
headerTemplate: 'filter header',
direction: ''
}
};
const forced = {
selection: {
// TODO :value.sync="$parent.$parent.selection"
headerTemplate: '<div><el-checkbox @click="toggleAllSelection($event)" :value.sync="allSelected"></el-checkbox></div>',
template: '<el-checkbox :value.sync="row.$selected"></el-checkbox>',
sortable: false,
resizable: false
},
index: {
headerTemplate: '<div>#</div>',
template: '{{ $parent.$index + 1 }}',
sortable: false
},
filter: {
headerTemplate: '<div>#</div>',
template: '<el-tag type="primary" style="height: 16px; line-height: 16px; min-width: 40px; text-align: center">{{ row[column.property] }}</el-tag>',
resizable: false
}
};
const getDefaultColumn = function(type, options) {
const column = {};
Object.assign(column, defaults[type || 'default']);
for (let name in options) {
if (options.hasOwnProperty(name)) {
const value = options[name];
if (typeof value !== 'undefined') {
column[name] = value;
}
}
}
return column;
};
import Vue from 'vue';
export default {
name: 'el-table-column',
props: {
type: {
type: String,
default: 'default'
},
label: String,
property: String,
width: {},
minWidth: {},
template: String,
sortable: {
type: Boolean,
default: false
},
resizable: {
type: Boolean,
default: true
},
formatter: Function
},
data() {
return {
isChildColumn: false,
columns: []
};
},
components: {
ElCheckbox,
ElTag
},
beforeCompile() {
let columnId = this.columnId = (this.$parent.gridId || (this.$parent.columnId + '_')) + 'column_' + columnIdSeed++;
let parent = this.$parent;
if (!parent.gridId) {
this.isChildColumn = true;
}
let type = this.type;
let width = this.width;
if (width !== undefined) {
width = parseInt(width, 10);
if (isNaN(width)) {
width = null;
}
}
let minWidth = this.minWidth;
if (minWidth !== undefined) {
minWidth = parseInt(minWidth, 10);
if (isNaN(minWidth)) {
minWidth = 80;
}
} else {
minWidth = 80;
}
let options = this.$options;
let tagName = options.el.tagName.toLowerCase();
let isColumnGroup = false;
let template = this.template;
if (options._content) {
let content = options._content.innerHTML;
if (content.indexOf(`</${tagName}>`) === -1) {
options._content = null;
template = content;
} else {
template = null;
isColumnGroup = true;
}
}
let property = this.property;
if ((!template || /^\s*$/.test(template)) && property) {
template = `{{ $getPropertyText(row, '${property}', '${columnId}') }}`;
}
let column = getDefaultColumn(type, {
id: columnId,
label: this.label,
property: this.property,
type,
template,
minWidth,
width,
isColumnGroup,
realWidth: width || minWidth,
sortable: this.sortable,
resizable: this.resizable,
formatter: this.formatter
});
Object.assign(column, forced[type] || {});
if (column.headerTemplate) {
Vue.partial('headerTemplate:' + column.id, column.headerTemplate);
}
if (column.template) {
// Vue.partial('template:' + column.id, `<div class="cell">${column.template}</div>`);
Vue.partial('template:' + column.id, `<el-tooltip @created="handleCreate" :effect="effect" placement="top" :disabled="tooltipDisabled"><div class="cell">${column.template}</div><span slot="content">${column.template}</span></el-tooltip>`);
}
this.columnConfig = column;
},
detached() {
if (!this.$parent) {
return;
}
let columns = this.$parent.columns;
if (columns) {
let columnId = this.columnId;
for (let i = 0, j = columns.length; i < j; i++) {
let column = columns[i];
if (column.id === columnId) {
columns.splice(i, 1);
break;
}
}
}
if (this.isChildColumn) {
if (this.$parent.$parent.$ready) {
this.$parent.$parent.debouncedReRender();
}
} else {
if (this.$parent.$ready) {
this.$parent.debouncedReRender();
}
}
},
watch: {
label(newVal) {
if (this.columnConfig) {
this.columnConfig.label = newVal;
}
},
property(newVal) {
if (this.columnConfig) {
this.columnConfig.property = newVal;
}
}
},
ready() {
let parent = this.$parent;
let columnConfig = this.columnConfig;
let columnIndex;
if (!this.isChildColumn) {
columnIndex = [].indexOf.call(parent.$els.hiddenColumns.children, this.$el);
} else {
columnIndex = [].indexOf.call(parent.$el.children, this.$el);
}
parent.columns.splice(columnIndex, 0, columnConfig);
if (this.isChildColumn) {
parent.columnConfig.columns = parent.columns;
if (parent.$parent.$ready) {
parent.$parent.debouncedReRender();
}
} else {
if (parent.$ready) {
parent.debouncedReRender();
}
}
}
};
</script>

View File

@ -0,0 +1,232 @@
import ElCheckbox from 'packages/checkbox/index.js';
import ElTag from 'packages/tag/index.js';
export default {
name: 'el-table-header',
render(h) {
return (
<table
class="el-table__header"
cellspacing="0"
cellpadding="0"
border="0">
{
this._l(this.columns, column =>
<colgroup
name={ column.id }
width={ column.realWidth || column.width }
/>).concat(
<thead>
<tr>
{
this._l(this.columns, column =>
<th
on-mousemove={ ($event) => this.handleMouseMove($event, column) }
on-mouseout={ this.handleMouseOut }
on-mousedown={ ($event) => this.handleMouseDown($event, column) }
on-click={ ($event) => this.handleHeaderClick($event, column) }
class={ [column.id, column.direction] }>
{
[
column.headerTemplate
? column.headerTemplate.call(this._renderProxy, h)
: <div>{ column.label }</div>,
column.sortable
? <div class="caret-wrapper">
<i class="sort-caret ascending"></i>
<i class="sort-caret descending"></i>
</div>
: ''
]
}
</th>
).concat(<th
class="gutter"
style={{
width: (this.$parent.showVScrollBar
? this.$parent.gutterWidth
: 0
) + 'px'
}}>&nbsp;</th>)
}
</tr>
</thead>
)
}
</table>
);
},
props: {
columns: {},
fixed: Boolean,
allSelected: {
default: Boolean
},
border: Boolean
},
components: {
ElCheckbox,
ElTag
},
methods: {
toggleAllSelection($event) {
this.$parent.toggleAllSelection($event);
},
handleMouseDown(event, column) {
if (this.draggingColumn && this.border) {
this.dragging = true;
this.$parent.resizeProxyVisible = true;
const gridEl = this.$parent.$el;
const gridLeft = gridEl.getBoundingClientRect().left;
const columnEl = this.$el.querySelector(`th.${column.id}`);
const columnRect = columnEl.getBoundingClientRect();
const minLeft = columnRect.left - gridLeft + 30;
columnEl.classList.add('noclick');
this.dragState = {
startMouseLeft: event.clientX,
startLeft: columnRect.right - gridLeft,
startColumnLeft: columnRect.left - gridLeft,
gridLeft: gridLeft
};
const resizeProxy = this.$parent.$refs.resizeProxy;
resizeProxy.style.left = this.dragState.startLeft + 'px';
document.onselectstart = function() { return false; };
document.ondragstart = function() { return false; };
const mousemove = (event) => {
const deltaLeft = event.clientX - this.dragState.startMouseLeft;
const proxyLeft = this.dragState.startLeft + deltaLeft;
resizeProxy.style.left = Math.max(minLeft, proxyLeft) + 'px';
};
const mouseup = () => {
if (this.dragging) {
const finalLeft = parseInt(resizeProxy.style.left, 10);
const columnWidth = finalLeft - this.dragState.startColumnLeft;
column.width = column.realWidth = columnWidth;
this.$nextTick(() => {
this.$parent.$calcColumns();
});
document.body.style.cursor = '';
this.dragging = false;
this.draggingColumn = null;
this.dragState = {};
this.$parent.resizeProxyVisible = false;
}
document.removeEventListener('mousemove', mousemove);
document.removeEventListener('mouseup', mouseup);
document.onselectstart = null;
document.ondragstart = null;
setTimeout(function() {
columnEl.classList.remove('noclick');
}, 0);
};
document.addEventListener('mousemove', mousemove);
document.addEventListener('mouseup', mouseup);
}
},
handleMouseMove(event, column) {
const target = event.target;
if (!column || !column.resizable) return;
if (!this.dragging && this.border) {
let rect = target.getBoundingClientRect();
if (rect.width > 12 && rect.right - event.pageX < 8) {
document.body.style.cursor = 'col-resize';
this.draggingColumn = column;
} else if (!this.dragging) {
document.body.style.cursor = '';
this.draggingColumn = null;
if (column.sortable) document.body.style.cursor = 'pointer';
}
}
},
handleMouseOut() {
document.body.style.cursor = '';
},
handleHeaderClick(event, column) {
let target = event.target;
while (target && target.tagName !== 'TH') {
target = target.parentNode;
}
if (target && target.tagName === 'TH') {
if (target.classList.contains('noclick')) {
target.classList.remove('noclick');
return;
}
}
if (!column.sortable) return;
const grid = this.$parent;
if (grid.sortingColumn !== column) {
if (grid.sortingColumn) {
grid.sortingColumn.direction = '';
}
grid.sortingColumn = column;
grid.sortingProperty = column.property;
}
if (!column.direction) {
column.direction = 'ascending';
} else if (column.direction === 'ascending') {
column.direction = 'descending';
} else {
column.direction = '';
grid.sortingColumn = null;
grid.sortingProperty = null;
}
grid.sortingDirection = column.direction === 'descending' ? -1 : 1;
},
$setVisibleFilter(property) {
if (this.visibleFilter) {
this.visibleFilter = null;
} else {
this.visibleFilter = property;
}
}
},
watch: {
visibleFilter(val) {
this.$parent.visibleFilter = val;
}
},
data() {
return {
draggingColumn: null,
dragging: false,
dragState: {},
columnsMap: null,
visibleFilter: null
};
}
};

View File

@ -1,198 +0,0 @@
<template>
<table class="el-table__header" cellspacing="0" cellpadding="0" border="0">
<colgroup v-for="column in columns" :name="column.id" :width="column.realWidth || column.width"></colgroup>
<thead>
<tr>
<th v-for="column in columns"
@mousemove="handleMouseMove($event, column)"
@mouseout="handleMouseOut"
@mousedown="handleMouseDown($event, column)"
@click="handleHeaderClick($event, column)"
class="{{ column.id }} {{column.direction}}">
<partial v-if="column.headerTemplate" :name="'headerTemplate:' + column.id"></partial>
<partial v-else name="default"></partial><div class="caret-wrapper" v-if="column.sortable"><i class="sort-caret ascending"></i><i class="sort-caret descending"></i></div>
</th>
<th class="gutter" :style="{ width: ($parent.showVScrollBar ? $parent.gutterWidth : 0) + 'px' }">&nbsp;</th>
</tr>
</thead>
</table>
</template>
<script type="text/babel">
import Vue from 'vue';
export default {
name: 'el-table-header',
props: {
columns: {},
fixed: Boolean,
allSelected: {
default: Boolean
},
border: Boolean
},
partials: {
default: '<div>{{column.label}}</div>'
},
methods: {
toggleAllSelection($event) {
this.$parent.toggleAllSelection($event);
},
handleMouseDown(event, column) {
if (this.draggingColumn && this.border) {
this.dragging = true;
this.$parent.resizeProxyVisible = true;
const gridEl = this.$parent.$el;
const gridLeft = gridEl.getBoundingClientRect().left;
const columnEl = this.$el.querySelector(`th.${column.id}`);
const columnRect = columnEl.getBoundingClientRect();
const minLeft = columnRect.left - gridLeft + 30;
columnEl.classList.add('noclick');
this.dragState = {
startMouseLeft: event.clientX,
startLeft: columnRect.right - gridLeft,
startColumnLeft: columnRect.left - gridLeft,
gridLeft: gridLeft
};
const resizeProxy = this.$parent.$els.resizeProxy;
resizeProxy.style.left = this.dragState.startLeft + 'px';
document.onselectstart = function() { return false; };
document.ondragstart = function() { return false; };
const mousemove = (event) => {
const deltaLeft = event.clientX - this.dragState.startMouseLeft;
const proxyLeft = this.dragState.startLeft + deltaLeft;
resizeProxy.style.left = Math.max(minLeft, proxyLeft) + 'px';
};
const mouseup = () => {
if (this.dragging) {
const finalLeft = parseInt(resizeProxy.style.left, 10);
const columnWidth = finalLeft - this.dragState.startColumnLeft;
column.width = column.realWidth = columnWidth;
Vue.nextTick(() => {
this.$parent.$calcColumns();
});
document.body.style.cursor = '';
this.dragging = false;
this.draggingColumn = null;
this.dragState = {};
this.$parent.resizeProxyVisible = false;
}
document.removeEventListener('mousemove', mousemove);
document.removeEventListener('mouseup', mouseup);
document.onselectstart = null;
document.ondragstart = null;
setTimeout(function() {
columnEl.classList.remove('noclick');
}, 0);
};
document.addEventListener('mousemove', mousemove);
document.addEventListener('mouseup', mouseup);
}
},
handleMouseMove(event, column) {
const target = event.target;
if (!column || !column.resizable) return;
if (!this.dragging && this.border) {
let rect = target.getBoundingClientRect();
if (rect.width > 12 && rect.right - event.pageX < 8) {
document.body.style.cursor = 'col-resize';
this.draggingColumn = column;
} else if (!this.dragging) {
document.body.style.cursor = '';
this.draggingColumn = null;
if (column.sortable) document.body.style.cursor = 'pointer';
}
}
},
handleMouseOut() {
document.body.style.cursor = '';
},
handleHeaderClick(event, column) {
let target = event.target;
while (target && target.tagName !== 'TH') {
target = target.parentNode;
}
if (target && target.tagName === 'TH') {
if (target.classList.contains('noclick')) {
target.classList.remove('noclick');
return;
}
}
if (!column.sortable) return;
const grid = this.$parent;
if (grid.sortingColumn !== column) {
if (grid.sortingColumn) {
grid.sortingColumn.direction = '';
}
grid.sortingColumn = column;
grid.sortingProperty = column.property;
}
if (!column.direction) {
column.direction = 'ascending';
} else if (column.direction === 'ascending') {
column.direction = 'descending';
} else {
column.direction = '';
grid.sortingColumn = null;
grid.sortingProperty = null;
}
grid.sortingDirection = column.direction === 'descending' ? -1 : 1;
},
$setVisibleFilter(property) {
if (this.visibleFilter) {
this.visibleFilter = null;
} else {
this.visibleFilter = property;
}
}
},
watch: {
visibleFilter(val) {
this.$parent.visibleFilter = val;
}
},
data() {
return {
draggingColumn: null,
dragging: false,
dragState: {},
columnsMap: null,
visibleFilter: null
};
}
};
</script>

View File

@ -1,36 +1,36 @@
<template>
<div class="el-table" :class="{ 'el-table--fit': fit, 'el-table--striped': stripe, 'el-table--border': border }" @mouseleave="handleMouseLeave($event)">
<div class="hidden-columns" v-el:hidden-columns><slot></slot></div>
<div class="hidden-columns" ref="hiddenColumns"><slot></slot></div>
<div class="el-table__header-wrapper">
<table-header :columns="columns" :all-selected.sync="allSelected" :selection.sync="selection" :style="{ width: bodyWidth ? bodyWidth + 'px' : '' }" :border="border"></table-header>
<table-header :columns="columns" :all-selected="allSelected" @allselectedchange="handleAllSelectedChange" :selection="selection" :style="{ width: bodyWidth ? bodyWidth + 'px' : '' }" :border="border"></table-header>
</div>
<div class="el-table__body-wrapper">
<table-body :columns="columns" :selection.sync="selection" :data="data | orderBy sortingProperty sortingDirection" :style="{ width: bodyWidth ? bodyWidth - (showVScrollBar ? gutterWidth : 0 ) + 'px' : '' }"></table-body>
<table-body :columns="columns" :selection="selection" :data="filterData" :style="{ width: bodyWidth ? bodyWidth - (showVScrollBar ? gutterWidth : 0 ) + 'px' : '' }"></table-body>
</div>
<div class="el-table__fixed" :style="{ width: fixedBodyWidth ? fixedBodyWidth + 'px' : '' }" v-el:fixed>
<div class="el-table__fixed" :style="{ width: fixedBodyWidth ? fixedBodyWidth + 'px' : '' }" ref="fixed">
<div class="el-table__fixed-header-wrapper" v-if="fixedColumnCount > 0">
<table-header :columns="fixedColumns" :all-selected.sync="allSelected" :selection.sync="selection" :style="{ width: fixedBodyWidth ? fixedBodyWidth + 'px' : '' }" :border="border"></table-header>
<table-header :columns="fixedColumns" :all-selected="allSelected" @allselectedchange="handleAllSelectedChange" :selection="selection" :style="{ width: fixedBodyWidth ? fixedBodyWidth + 'px' : '' }" :border="border"></table-header>
</div>
<div class="el-table__fixed-body-wrapper" v-if="fixedColumnCount > 0" :style="{ top: headerHeight + 'px' }">
<table-body :columns="fixedColumns" fixed :selection.sync="selection" :data="data | orderBy sortingProperty sortingDirection" :style="{ width: fixedBodyWidth ? fixedBodyWidth + 'px' : '' }"></table-body>
<table-body :columns="fixedColumns" fixed :selection="selection" :data="filterData" :style="{ width: fixedBodyWidth ? fixedBodyWidth + 'px' : '' }"></table-body>
</div>
</div>
<div class="el-table__column-resize-proxy" v-el:resize-proxy v-show="resizeProxyVisible"></div>
<div class="el-table__column-resize-proxy" ref="resizeProxy" v-show="resizeProxyVisible"></div>
<slot name="bottom"></slot>
</div>
</template>
<script type="text/babel">
import Vue from 'vue';
import throttle from 'throttle-debounce/throttle';
import debounce from 'throttle-debounce/debounce';
import { getScrollBarWidth } from './util';
import { getScrollBarWidth, orderBy } from './util';
import objectAssign from 'object-assign';
let gridIdSeed = 1;
let GUTTER_WIDTH;
import TableBody from './table-body.vue';
import TableHeader from './table-header.vue';
import TableBody from './table-body';
import TableHeader from './table-header';
export default {
name: 'el-table',
@ -84,24 +84,16 @@
}
},
events: {
onresize() {
Vue.nextTick(() => {
this.$calcColumns();
});
}
},
partials: {
default: '<div>{{column.label}}</div>'
},
components: {
TableHeader,
TableBody
},
methods: {
handleAllSelectedChange(val) {
this.allSelected = val;
},
doOnDataChange(data) {
data = data || [];
@ -111,7 +103,7 @@
if (!this.allowNoSelection) {
this.selected = data[0];
if (this.selected !== oldSelection) {
this.$emit('selection-change', this.selected);
this.$emit('selectionchange', this.selected);
}
}
} else if (data.indexOf(oldSelection) === -1) {
@ -121,7 +113,7 @@
this.selected = null;
}
if (this.selected !== oldSelection) {
this.$emit('selection-change', this.selected);
this.$emit('selectionchange', this.selected);
}
}
}
@ -129,7 +121,7 @@
toggleAllSelection() {
setTimeout(() => {
this.data.forEach(item => {
this.tableData.forEach(item => {
item.$selected = this.allSelected;
});
}, 0);
@ -241,7 +233,7 @@
this.fixedBodyWidth = fixedBodyWidth;
}
Vue.nextTick(() => {
this.$nextTick(() => {
this.headerHeight = this.$el.querySelector('.el-table__header-wrapper').offsetHeight;
});
},
@ -258,7 +250,7 @@
gridWrapper.style.height = bodyHeight + 'px';
this.$el.style.height = height + 'px';
this.$els.fixed.style.height = height + 'px';
this.$refs.fixed.style.height = height + 'px';
const fixedBodyWrapper = this.$el.querySelector('.el-table__fixed-body-wrapper');
if (fixedBodyWrapper) {
@ -276,7 +268,7 @@
},
updateScrollInfo() {
Vue.nextTick(() => {
this.$nextTick(() => {
if (this.$el) {
let gridBodyWrapper = this.$el.querySelector('.el-table__body-wrapper');
let gridBody = this.$el.querySelector('.el-table__body-wrapper .el-table__body');
@ -310,7 +302,7 @@
window.addEventListener('resize', this.windowResizeListener);
}
Vue.nextTick(() => {
this.$nextTick(() => {
if (this.height) {
this.$calcHeight(this.height);
}
@ -319,6 +311,7 @@
},
created() {
this.tableData = this.data;
this.gridId = 'grid_' + gridIdSeed + '_';
if (GUTTER_WIDTH === undefined) {
@ -334,10 +327,8 @@
computed: {
selection() {
if (this.selectionMode === 'multiple') {
const data = this.data || [];
return data.filter(function(item) {
return item.$selected === true;
});
const data = this.tableData || [];
return data.filter(item => item.$selected === true);
} else if (this.selectionMode === 'single') {
return this.selected;
} else {
@ -351,6 +342,10 @@
return columns.filter(function(item, index) {
return index < fixedColumnCount;
});
},
filterData() {
return orderBy(this.tableData, this.sortingProperty, this.sortingDirection);
}
},
@ -360,9 +355,9 @@
},
selection(val) {
this.$emit('selection-change', val);
this.$emit('selectionchange', val);
if (this.selectionMode === 'multiple') {
this.allSelected = val.length === this.data.length;
this.allSelected = val.length === this.tableData.length;
}
},
@ -374,26 +369,12 @@
this.$calcHeight(value);
},
data(newVal) {
tableData(newVal) {
this.doOnDataChange(newVal);
this.updateScrollInfo();
}
},
beforeCompile() {
const styleNode = document.createElement('style');
styleNode.type = 'text/css';
styleNode.rel = 'stylesheet';
styleNode.title = 'Grid Column Style';
document.getElementsByTagName('head')[0].appendChild(styleNode);
this.styleNode = styleNode;
if (this.data && this.selectionMode === 'multiple') {
this.data = this.data.map(item => Object.assign({ '$selected': false }, item));
}
},
destroyed() {
if (this.styleNode) {
this.styleNode.parentNode.removeChild(this.styleNode);
@ -404,23 +385,36 @@
}
},
ready() {
mounted() {
const styleNode = document.createElement('style');
styleNode.type = 'text/css';
styleNode.rel = 'stylesheet';
styleNode.title = 'Grid Column Style';
document.getElementsByTagName('head')[0].appendChild(styleNode);
this.styleNode = styleNode;
if (this.tableData && this.selectionMode === 'multiple') {
this.tableData = this.tableData.map(item => objectAssign({ '$selected': false }, item));
}
this.doRender();
this.$ready = true;
if (this.data) {
this.doOnDataChange(this.data);
if (this.tableData) {
this.doOnDataChange(this.tableData);
}
this.updateScrollInfo();
if (this.fixedColumnCount > 0) {
this.$nextTick(() => {
this.$els.fixed.style.height = this.$el.clientHeight + 'px';
this.$refs.fixed.style.height = this.$el.clientHeight + 'px';
});
}
},
data() {
return {
tableData: [],
showHScrollBar: false,
showVScrollBar: false,
hoverRowIndex: null,

View File

@ -77,11 +77,5 @@ export const orderBy = function(array, sortKey, reverse) {
};
export const getChild = function(event) {
let cell = event.target;
while (cell.children.length) {
cell = cell.children[0];
}
return cell;
return event.target.querySelector('.cell');
};