该组件支持三种渲染形态,因为有些简单的表格没必要去做太复杂的处理。
支持自适应页面宽度和可以设置固定宽度,理论上是所有浏览器都支持,但是因为我引用了一些工具类,处理数组的filter、map 所以向前兼容可以自己实现这些方法
第一种:不需要固定头和列,纯普通表格。
第二种:只需要固定头,滚动内容区域
第三种:需要固定列。
本身想采用虚拟DOM来常渲染,因为这种组件对性能消耗挺大,后面想了一下本来就是来提供大家一个思路的例子没必要写的太复杂,现在的话写的还算比较简单只有几百行,如果代码量很多对于一个想研究这类组件的同学可能压力很多。
require(['yg'], function () {
(function () {
function FTable(columns)
{
this.columns = columns;
this.unionColumns = concatColumn(columns);
this.colgroup = null;
this.thead = null;
this.tbody = null;
this.wrapper = $('<div>');
this.element = $('<table class="fx-table">').appendTo(this.wrapper);
this.initColgroup();
}
FTable.prototype.getFixedColumns=function()
{
return Yg._.filter(this.unionColumns, { fixed: true })
}
FTable.prototype.forColumnWidth = function (viewWidth,callback) {
var unionColumns = this.unionColumns, count = unionColumns.length, totalWidth = 0, surplusWidth, columnWidth;
function getValue(value) {
value = parseInt(value);
return String(value).indexOf('%') != -1 ? value / 100 * viewWidth : value;
}
for (var i = 0; i < count; i++) {
if (callback(unionColumns[i])===true) {
totalWidth += getValue(unionColumns[i].width);
}
}
return totalWidth;
}
FTable.prototype.getColumnWidth = function (viewWidth) {
return Math.max(this.forColumnWidth(viewWidth, function (c) { return Yg._.has(c, 'width');}), viewWidth)
}
FTable.prototype.getFixedColumnWidth = function (viewWidth) {
return this.forColumnWidth(viewWidth, function (c) { return Yg._.has(c, 'width')&&c.fixed; });
}
FTable.prototype.getHeight=function()
{
return this.element.outerHeight();
}
FTable.prototype.getWidth = function () {
return this.element.outerWidth();
}
FTable.prototype.setWidth = function (width)
{
this.element.width(width);
}
FTable.prototype.setHeight = function (height) {
this.element.height(height);
}
FTable.prototype.setOuterWidth = function (width) {
this.wrapper.width(width);
}
FTable.prototype.setOuterHeight = function (height) {
this.wrapper.height(height);
}
FTable.prototype.setStyle=function(styles)
{
this.element.css(styles);
}
FTable.prototype.clone=function()
{
return new FTable(this.columns);
}
FTable.prototype.initColgroup=function()
{
this.colgroup = new Colgroup(this.unionColumns);
}
FTable.prototype.setHead = function (thead)
{
this.thead = thead;
}
FTable.prototype.initHead = function (visibleNoFixed)
{
this.thead = new FTHead(this.columns, visibleNoFixed);
}
FTable.prototype.initBody = function (visibleNoFixed) {
this.tbody = new FTBody(this.unionColumns, visibleNoFixed);
}
FTable.prototype.renderData=function(data)
{
this.tbody.renderData(data);
this.trigger('onRenderData',data);
}
FTable.prototype.render=function(parent)
{
this.colgroup.render(this.element);
this.thead && this.thead.render(this.element);
this.tbody && this.tbody.render(this.element);
parent.append(this.wrapper);
}
Yg.extend(FTable.prototype, Yg.Events);
function Colgroup(columns)
{
this.element = $('<colgroup>');
Yg.each(columns, Yg.bind(this.addCol, this));
}
Colgroup.prototype.addCol = function (column) {
var col = $('<col>');
if (column.hasOwnProperty('width')) {
col.css({ width: column.width });
}
col.appendTo(this.element);
}
Colgroup.prototype.render=function(parent)
{
parent.append(this.element);
}
function FTHead(columns, visibleNoFixed)
{
this.visibleNoFixed = visibleNoFixed;
this.element = $('<thead>');
this.addRow(columns);
}
FTHead.prototype.rowDepth = function (columns, len)
{
var childColumns = [];
for (var i = 0, length = columns.length; i < length; i++) {
if (Yg._.has(columns[i], 'columns') && columns[i].columns.length > 0)
{
childColumns = childColumns.concat(columns[i].columns);
}
}
if(childColumns.length>0)
{
return this.rowDepth(childColumns,len+1);
}
return len;
}
FTHead.prototype.colDepth = function (columns, len) {
var childColumns = [], length = columns.length;
for (var i = 0; i < length; i++) {
if (Yg._.has(columns[i], 'columns') && columns[i].columns.length > 0) {
childColumns = childColumns.concat(columns[i].columns);
}
}
if (childColumns.length > 0) {
return this.colDepth(childColumns, len +length-1);
}
return len + length;
}
FTHead.prototype.addRow = function (columns) {
var tr = $('<tr>'), childColumns = [], that = this, rowSpan = this.rowDepth(columns,1);
Yg.each(columns, function (column) {
that.addCell(tr, column, rowSpan);
if (Yg._.has(column, 'columns'))
{
childColumns = childColumns.concat(column.columns);
}
});
this.element.append(tr);
if(childColumns.length>0)
{
this.addRow(childColumns);
}
}
FTHead.prototype.addCell = function (parent, column, rowSpan) {
var cell = $('<th>'), crowSpan = 0, attrs = {};
if (Yg._.has(column, "headFormat"))
{
var template = Yg._.isFunction(column.headFormat) ? column.headFormat : Yg._.template(column.headFormat)
cell.html(template(column));
} else {
cell.text(column.title);
}
if (Yg._.has(column, "headAttrs")) {
attrs = Yg.extend(attrs, column.headAttrs);
}
if (Yg._.has(column, 'columns') && column.columns.length>0)
{
attrs.colspan = this.colDepth(column.columns, 0);
crowSpan = this.rowDepth(column.columns, 1)
}
if (this.visibleNoFixed && !column.fixed) {
cell.addClass('fx-hidden');
}
rowSpan -= crowSpan;
if (rowSpan > 1)
{
attrs.rowspan = rowSpan;
}
cell.attr(attrs);
parent.append(cell);
}
FTHead.prototype.render = function (parent)
{
parent.append(this.element);
}
function concatColumn(unionColumns)
{
var column, columns=[];
for (var i = 0, len = unionColumns.length; i < len; i++) {
column = unionColumns[i];
if (Yg._.has(column, 'columns')) {
Yg.each(column.columns,function(c){
c.fixed=column.field;
});
columns.push.apply(columns,concatColumn(column.columns));
} else {
columns.push(column);
}
}
return columns;
}
function FTBody(columns, visibleNoFixed)
{
Yg.bindAll(this, 'addRow');
this.visibleNoFixed = visibleNoFixed;
this.columns = columns;
this.element = $('<tbody>');
}
FTBody.prototype.addRow = function (rowData) {
var that=this, tr = $('<tr>'), columns=this.columns;
Yg.each(columns, function (column) {
that.addCell(tr, column,rowData);
});
this.element.append(tr);
}
FTBody.prototype.addCell = function (parent, column, rowData) {
var cell = $('<td>'), attrs = {};
if (Yg._.has(column, "format")) {
var template = Yg._.isFunction(column.format) ? column.format : Yg._.template(column.format)
cell.html(template(rowData, column));
} else {
cell.text(rowData[column.field]);
}
if (Yg._.has(column, "attrs")) {
attrs = Yg.extend(attrs, column.attrs);
}
if (this.visibleNoFixed &&!column.fixed)
{
cell.addClass('fx-hidden');
}
cell.attr(attrs);
if (Yg._.has(column, "rowSpan")) {
var rowSpan = column.rowSpan(rowData, column),isNumber=typeof rowSpan=="number";
if (isNumber && rowSpan > 1)
{
cell.attr('rowspan', rowSpan);
} else if (isNumber && rowSpan < 0) {
return;
}
}
parent.append(cell);
}
FTBody.prototype.renderData = function (data) {
data = data || [];
this.element.empty();
Yg.each(data, this.addRow);
}
FTBody.prototype.render = function (parent)
{
parent.append(this.element);
}
function getStyleValue(value,defaultValue)
{
return value != 'auto' && value != null ?parseInt(value):defaultValue;
}
var defaultOptions = {
columns: [],
dataSource: null,
autobind: true,
width: 'auto',
height: 'auto'
};
var Grid = function (element,options) {
this.options = $.extend({}, defaultOptions, options);
this.container = $(element).addClass('fx-table-container');
this.contentTable = null;
this.wrapper = $('<div class="fx-table-wrapper"><div class="fx-head"></div><div class="fx-body"></div></div>').appendTo(this.container);
this.initViewSize();
this.init();
}
Grid.prototype.initViewSize = function () {
var viewWidth = this.container.width(), options = this.options;
this._width = getStyleValue(options.width, 0);
this._height = getStyleValue(options.height, 0);
this.isScroll = this._width > 0 || this._height > 0;
this.viewWidth = viewWidth;
if (this._width > 0) {
this.container.css({
width: this._width
});
}
}
Grid.prototype.init=function()
{
var options = this.options;
Yg.bindAll(this, '_successHandler','_failHandler');
this.dataSource = Yg.parseDataSource(this.options.dataSource);
this.dataSource.then(this._successHandler, this._failHandler);
this.render();
if (options.autobind) {
this.dataSource.read();
}
}
Grid.prototype._successHandler=function(data)
{
this.contentTable.renderData(data);
}
Grid.prototype._failHandler = function () {
}
Grid.prototype.initTable= function () {
this.contentTable = new FTable(this.options.columns);
this.isFixedColumn = this.contentTable.getFixedColumns().length > 0;
}
Grid.prototype.initBody = function () {
}
Grid.prototype._setHeadStyle=function(element)
{
element.addClass('fx-head-table fx-hidden-scroll');
}
Grid.prototype._setBodyStyle = function (element) {
element.addClass('fx-body-table fx-body-scroll');
}
Grid.prototype._setFixedHeadStyle = function (element) {
element.addClass('fx-head-table fx-head-fixed fx-hidden-scroll');
}
Grid.prototype._setFixedBodyStyle = function (element) {
element.addClass('fx-body-table fx-hidden-scroll fx-body-fixed');
}
Grid.prototype.initFixedTable=function()
{
var that = this, options = that.options,
viewWidth=that.viewWidth,
width = that._width,
height = that._height,
contentTable = that.contentTable, isFixedColumn = that.isFixedColumn,
elFxHead = that.wrapper.children('.fx-head'),
elFxBody = that.wrapper.children('.fx-body'),
fHead = contentTable.clone(),
fixedBody, fixedHead, isScrollY = false, isScrollX=false;
fHead.wrapper.addClass('fx-head-table fx-hidden-scroll');
contentTable.wrapper.addClass('fx-body-table fx-body-scroll');
//获取表格宽度
var tableWidth = contentTable.getColumnWidth(width);
var fixedWidth = contentTable.getFixedColumnWidth(width);
function setTableLayout()
{
if (width > 0) {
// 设置固定宽度
fHead.setWidth(tableWidth);
fHead.setOuterWidth(width);
contentTable.setWidth(tableWidth);
contentTable.setOuterWidth(width);
}
if (height > 0&&width>0 && isScrollY)
{
fHead.setOuterWidth(width-17);
contentTable.setOuterHeight(height);
} else if (height > 0 && isScrollY) {
fHead.wrapper.css("marginRight",'17px');
contentTable.setOuterHeight(height);
}
if(isFixedColumn)
{
fixedHead.setOuterWidth(fixedWidth);
fixedHead.setWidth(tableWidth);
fixedBody.setOuterWidth(fixedWidth);
fixedBody.setWidth(tableWidth);
contentTable.wrapper.css({
width: (width - fixedWidth) + 'px',
marginLeft: fixedWidth+'px'
});
contentTable.element.css({
marginLeft: (-fixedWidth) + 'px'
});
}
if (isFixedColumn && height > 0 && isScrollX) {
fixedBody.setOuterHeight(height-17);
}
}
if (isFixedColumn) {
fixedHead = contentTable.clone();
fixedHead.initHead(true);
fixedBody = contentTable.clone();
fixedBody.initBody(true);
fixedHead.render(elFxHead);
fixedBody.render(elFxBody);
fixedHead.wrapper.addClass('fx-head-table fx-head-fixed fx-hidden-scroll');
fixedBody.wrapper.addClass('fx-body-table fx-hidden-scroll fx-body-fixed');
}
// setTableLayout();
// 初始化
contentTable.initBody();
fHead.initHead();
// 渲染
fHead.render(elFxHead);
contentTable.render(elFxBody);
contentTable.on('onRenderData', function (data) {
if (isFixedColumn)
{
fixedBody.renderData(data);
}
// 设置 布局
isScrollX = contentTable.wrapper[0].scrollWidth > width
isScrollY = contentTable.wrapper[0].scrollHeight > height
setTableLayout();
});
contentTable.wrapper.on('scroll', function () {
fHead&&(fHead.wrapper[0].scrollLeft = this.scrollLeft);
fixedBody && (fixedBody.wrapper[0].scrollTop = this.scrollTop);
});
}
Grid.prototype.render=function()
{
var that = this;
this.initTable();
if (this.isFixedColumn || this.isScroll) {
this.initFixedTable();
} else {
this.contentTable.initHead();
this.contentTable.initBody();
this.contentTable.render({
append: function (child) {
that.wrapper.html(child);
}
});
}
}
new Grid('#grid', {
dataSource: {
data: [{
name: "李三",
rowspan:3,
age: 43,
work: '北京',
work2: '上海'
}, {
name: "",
rowspan:-1,
age: 43,
work: '北京',
work2: '上海'
}, {
name: "",
rowspan:-1,
age: 43,
work: '北京',
work2: '上海'
}, {
name: "李四",
age: 43,
work: '北京',
work2: '上海'
}, {
name: "李三",
age: 43,
work: '北京',
work2: '上海'
}, {
name: "李四",
age: 43,
work: '北京',
work2: '上海'
}, {
name: "李三",
age: 43,
work: '北京',
work2: '上海'
}, {
name: "李四",
age: 43,
work: '北京',
work2: '上海'
}, {
name: "李三",
age: 43,
work: '北京',
work2: '上海'
}, {
name: "李四",
age: 43,
work: '北京',
work2: '上海'
}, {
name: "李三",
age: 43,
work: '北京',
work2: '上海'
}, {
name: "李四",
age: 43,
work: '北京',
work2: '上海'
}]
},
width:700,
height:150,
columns: [
{
title: '姓名',
field: 'name',
fixed: true,
width: '200px',
headAttrs: {
style: 'text-align:center'
},
rowSpan:function(row,c)
{
return row.rowspan;
}
},
{
title: '年龄',
field: 'age',
width: 100,
// fixed: ,
headAttrs: {
style: 'text-align:center'
}
}
,
{
title: '工作经历',
headAttrs: {
style: 'text-align:center'
},
columns: [
{
title: '工作经历2',
headAttrs: {
style: 'text-align:center'
},
columns: [
{
title: '工作经历2',
field: 'work',
width: 100,
}, {
title: '工作经历5',
field: 'work2',
width: 200,
}]
}, {
title: '工作经历5',
field: 'work2',
width: 300,
}
]
},
{
title: '呵呵',
field: 'age',
headAttrs: {
style: 'text-align:center'
},
width: 100,
}
]
});
}());
//$('#scrollBody').on('scroll', function () {
// $('#scrollHead')[0].scrollLeft = this.scrollLeft;
// $('#scrollBodyY')[0].scrollTop = this.scrollTop;
//});
});