该组件支持三种渲染形态,因为有些简单的表格没必要去做太复杂的处理。
支持自适应页面宽度和可以设置固定宽度,理论上是所有浏览器都支持,但是因为我引用了一些工具类,处理数组的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; //}); });