/** * GridView * * Created by GUY on 2016/1/11. * @class BI.GridView * @extends BI.Widget */ import { Widget, shortcut } from "../../core"; @shortcut() export default class GridView extends Widget { _defaultConfig() { return BI.extend(super._defaultConfig(arguments), { baseCls: "bi-grid-view", // width: 400, //必设 // height: 300, //必设 scrollable: true, scrollx: false, scrolly: false, overflowX: true, overflowY: true, el: { type: "bi.vertical" }, overscanColumnCount: 0, overscanRowCount: 0, rowHeightGetter: BI.emptyFn, // number类型或function类型 columnWidthGetter: BI.emptyFn, // number类型或function类型 // estimatedColumnSize: 100, //columnWidthGetter为function时必设 // estimatedRowSize: 30, //rowHeightGetter为function时必设 scrollLeft: 0, scrollTop: 0, items: [], itemFormatter: (item, row, col) => { return item; }, }); } static xtype = "bi.grid_view"; static EVENT_SCROLL = "EVENT_SCROLL"; render() { const o = this.options; const { overflowX, overflowY, el } = this.options; this.renderedCells = []; this.renderedKeys = []; this.renderRange = {}; this._scrollLock = false; this._debounceRelease = BI.debounce(() => { this._scrollLock = false; }, 1000 / 60); this.container = BI._lazyCreateWidget({ type: "bi.absolute", }); this.element.scroll(() => { if (this._scrollLock === true) { return; } o.scrollLeft = this.element.scrollLeft(); o.scrollTop = this.element.scrollTop(); this._calculateChildrenToRender(); this.fireEvent(GridView.EVENT_SCROLL, { scrollLeft: o.scrollLeft, scrollTop: o.scrollTop, }); }); // 兼容一下 let scrollable = o.scrollable, scrollx = o.scrollx, scrolly = o.scrolly; if (overflowX === false) { if (overflowY === false) { scrollable = false; } else { scrollable = "y"; } } else { if (overflowY === false) { scrollable = "x"; } } BI._lazyCreateWidget(el, { type: "bi.vertical", element: this, scrollable: scrollable, scrolly: scrolly, scrollx: scrollx, items: [this.container], }); o.items = BI.isFunction(o.items) ? this.__watch(o.items, (context, newValue) => { this.populate(newValue); }) : o.items; if (o.items.length > 0) { this._calculateSizeAndPositionData(); this._populate(); } } // mounted之后绑定事件 mounted() { const { scrollLeft, scrollTop } = this.options; if (scrollLeft !== 0 || scrollTop !== 0) { this.element.scrollTop(scrollTop); this.element.scrollLeft(scrollLeft); } } destroyed() { BI.each(this.renderedCells, (i, cell) => { cell.el._destroy(); }) } _calculateSizeAndPositionData() { const { columnCount, items, rowCount, columnWidthGetter, estimatedColumnSize, rowHeightGetter, estimatedRowSize } = this.options; this.rowCount = 0; this.columnCount = 0; if (BI.isNumber(columnCount)) { this.columnCount = columnCount; } else if (items.length > 0) { this.columnCount = items[0].length; } if (BI.isNumber(rowCount)) { this.rowCount = rowCount; } else { this.rowCount = items.length; } this._columnSizeAndPositionManager = new BI.ScalingCellSizeAndPositionManager(this.columnCount, columnWidthGetter, estimatedColumnSize); this._rowSizeAndPositionManager = new BI.ScalingCellSizeAndPositionManager(this.rowCount, rowHeightGetter, estimatedRowSize); } _getOverscanIndices(cellCount, overscanCellsCount, startIndex, stopIndex) { return { overscanStartIndex: Math.max(0, startIndex - overscanCellsCount), overscanStopIndex: Math.min(cellCount - 1, stopIndex + overscanCellsCount), }; } _calculateChildrenToRender() { const o = this.options; const { itemFormatter, items } = this.options; const width = o.width, height = o.height, scrollLeft = BI.clamp(o.scrollLeft, 0, this._getMaxScrollLeft()), scrollTop = BI.clamp(o.scrollTop, 0, this._getMaxScrollTop()), overscanColumnCount = o.overscanColumnCount, overscanRowCount = o.overscanRowCount; if (height > 0 && width > 0) { const visibleColumnIndices = this._columnSizeAndPositionManager.getVisibleCellRange(width, scrollLeft); const visibleRowIndices = this._rowSizeAndPositionManager.getVisibleCellRange(height, scrollTop); const renderedCells = [], renderedKeys = {}, renderedWidgets = {}; let minX = this._getMaxScrollLeft(), minY = this._getMaxScrollTop(), maxX = 0, maxY = 0; // 没有可见的单元格就干掉所有渲染过的 if (!BI.isEmpty(visibleColumnIndices) && !BI.isEmpty(visibleRowIndices)) { const horizontalOffsetAdjustment = this._columnSizeAndPositionManager.getOffsetAdjustment(width, scrollLeft); const verticalOffsetAdjustment = this._rowSizeAndPositionManager.getOffsetAdjustment(height, scrollTop); this._renderedColumnStartIndex = visibleColumnIndices.start; this._renderedColumnStopIndex = visibleColumnIndices.stop; this._renderedRowStartIndex = visibleRowIndices.start; this._renderedRowStopIndex = visibleRowIndices.stop; const overscanColumnIndices = this._getOverscanIndices(this.columnCount, overscanColumnCount, this._renderedColumnStartIndex, this._renderedColumnStopIndex); const overscanRowIndices = this._getOverscanIndices(this.rowCount, overscanRowCount, this._renderedRowStartIndex, this._renderedRowStopIndex); const columnStartIndex = overscanColumnIndices.overscanStartIndex; const columnStopIndex = overscanColumnIndices.overscanStopIndex; const rowStartIndex = overscanRowIndices.overscanStartIndex; const rowStopIndex = overscanRowIndices.overscanStopIndex; // 算区间size const minRowDatum = this._rowSizeAndPositionManager.getSizeAndPositionOfCell(rowStartIndex); const minColumnDatum = this._columnSizeAndPositionManager.getSizeAndPositionOfCell(columnStartIndex); const maxRowDatum = this._rowSizeAndPositionManager.getSizeAndPositionOfCell(rowStopIndex); const maxColumnDatum = this._columnSizeAndPositionManager.getSizeAndPositionOfCell(columnStopIndex); const top = minRowDatum.offset + verticalOffsetAdjustment; const left = minColumnDatum.offset + horizontalOffsetAdjustment; const bottom = maxRowDatum.offset + verticalOffsetAdjustment + maxRowDatum.size; const right = maxColumnDatum.offset + horizontalOffsetAdjustment + maxColumnDatum.size; // 如果滚动的区间并没有超出渲染的范围 if (top >= this.renderRange.minY && bottom <= this.renderRange.maxY && left >= this.renderRange.minX && right <= this.renderRange.maxX) { return; } let count = 0; for (let rowIndex = rowStartIndex; rowIndex <= rowStopIndex; rowIndex++) { const rowDatum = this._rowSizeAndPositionManager.getSizeAndPositionOfCell(rowIndex); for (let columnIndex = columnStartIndex; columnIndex <= columnStopIndex; columnIndex++) { const key = rowIndex + "-" + columnIndex; const columnDatum = this._columnSizeAndPositionManager.getSizeAndPositionOfCell(columnIndex); const index = this.renderedKeys[key] && this.renderedKeys[key][2]; let child; if (index >= 0) { this.renderedCells[index].el.setWidth(columnDatum.size); this.renderedCells[index].el.setHeight(rowDatum.size); // 这里只使用px this.renderedCells[index].el.element.css("left", columnDatum.offset + horizontalOffsetAdjustment + "px"); this.renderedCells[index].el.element.css("top", rowDatum.offset + verticalOffsetAdjustment + "px"); child = this.renderedCells[index].el; renderedCells.push(this.renderedCells[index]); } else { const item = itemFormatter(items[rowIndex][columnIndex], rowIndex, columnIndex); child = BI._lazyCreateWidget(BI.extend({ type: "bi.label", width: columnDatum.size, height: rowDatum.size, }, item, { cls: (item.cls || "") + " grid-cell" + (rowIndex === 0 ? " first-row" : "") + (columnIndex === 0 ? " first-col" : ""), _rowIndex: rowIndex, _columnIndex: columnIndex, _left: columnDatum.offset + horizontalOffsetAdjustment, _top: rowDatum.offset + verticalOffsetAdjustment, }), this); renderedCells.push({ el: child, left: columnDatum.offset + horizontalOffsetAdjustment + "px", top: rowDatum.offset + verticalOffsetAdjustment + "px", _left: columnDatum.offset + horizontalOffsetAdjustment, _top: rowDatum.offset + verticalOffsetAdjustment, // _width: columnDatum.size, // _height: rowDatum.size }); } minX = Math.min(minX, columnDatum.offset + horizontalOffsetAdjustment); maxX = Math.max(maxX, columnDatum.offset + horizontalOffsetAdjustment + columnDatum.size); minY = Math.min(minY, rowDatum.offset + verticalOffsetAdjustment); maxY = Math.max(maxY, rowDatum.offset + verticalOffsetAdjustment + rowDatum.size); renderedKeys[key] = [rowIndex, columnIndex, count]; renderedWidgets[count] = child; count++; } } } // 已存在的, 需要添加的和需要删除的 const existSet = {}, addSet = {}, deleteArray = []; BI.each(renderedKeys, (i, key) => { if (this.renderedKeys[i]) { existSet[i] = key; } else { addSet[i] = key; } }); BI.each(this.renderedKeys, (i, key) => { if (existSet[i]) { return; } if (addSet[i]) { return; } deleteArray.push(key[2]); }); BI.each(deleteArray, (i, index) => { // 性能优化,不调用destroy方法防止触发destroy事件 this.renderedCells[index].el._destroy(); }); const addedItems = []; BI.each(addSet, (index, key) => { addedItems.push(renderedCells[key[2]]); }); // 与listview一样, 给上下文 this.container.addItems(addedItems, this); // 拦截父子级关系 this.container._children = renderedWidgets; this.container.attr("items", renderedCells); this.renderedCells = renderedCells; this.renderedKeys = renderedKeys; this.renderRange = { minX: minX, minY: minY, maxX: maxX, maxY: maxY }; } } _isOverflowX() { const { scrollable, scrollx, overflowX } = this.options; // 兼容一下 if (overflowX === false) { return false; } if (scrollx) { return true; } if (scrollable === true || scrollable === "xy" || scrollable === "x") { return true; } return false; } _isOverflowY() { const { scrollable, scrolly, overflowX } = this.options; // 兼容一下 // var scrollable = o.scrollable, scrolly = o.scrolly; if (overflowX === false) { return false; } if (scrolly) { return true; } if (scrollable === true || scrollable === "xy" || scrollable === "y") { return true; } return false; } _getMaxScrollLeft() { return Math.max(0, this._getContainerWidth() - this.options.width + (this._isOverflowX() ? BI.DOM.getScrollWidth() : 0)); } _getMaxScrollTop() { return Math.max(0, this._getContainerHeight() - this.options.height + (this._isOverflowY() ? BI.DOM.getScrollWidth() : 0)); } _getContainerWidth() { return this.columnCount * this.options.estimatedColumnSize; } _getContainerHeight() { return this.rowCount * this.options.estimatedRowSize; } _populate(items) { const { scrollTop, scrollLeft } = this.options; this._reRange(); if (items && items !== this.options.items) { this.options.items = items; this._calculateSizeAndPositionData(); } this.container.setWidth(this._getContainerWidth()); this.container.setHeight(this._getContainerHeight()); // 元素未挂载时不能设置scrollTop this._debounceRelease(); try { this.element.scrollTop(scrollTop); this.element.scrollLeft(scrollLeft); } catch (e) { } this._calculateChildrenToRender(); } setScrollLeft(scrollLeft) { if (this.options.scrollLeft === scrollLeft) { return; } this._scrollLock = true; this.options.scrollLeft = BI.clamp(scrollLeft || 0, 0, this._getMaxScrollLeft()); this._debounceRelease(); this.element.scrollLeft(this.options.scrollLeft); this._calculateChildrenToRender(); } setScrollTop(scrollTop) { if (this.options.scrollTop === scrollTop) { return; } this._scrollLock = true; this.options.scrollTop = BI.clamp(scrollTop || 0, 0, this._getMaxScrollTop()); this._debounceRelease(); this.element.scrollTop(this.options.scrollTop); this._calculateChildrenToRender(); } setColumnCount(columnCount) { this.options.columnCount = columnCount; } setRowCount(rowCount) { this.options.rowCount = rowCount; } setOverflowX(b) { if (this.options.overflowX !== !!b) { this.options.overflowX = !!b; BI.nextTick(() => { this.element.css({ overflowX: b ? "auto" : "hidden" }); }); } } setOverflowY(b) { if (this.options.overflowY !== !!b) { this.options.overflowY = !!b; BI.nextTick(() => { this.element.css({ overflowY: b ? "auto" : "hidden" }); }); } } getScrollLeft() { return this.options.scrollLeft; } getScrollTop() { return this.options.scrollTop; } getMaxScrollLeft() { return this._getMaxScrollLeft(); } getMaxScrollTop() { return this._getMaxScrollTop(); } setEstimatedColumnSize(width) { this.options.estimatedColumnSize = width; } setEstimatedRowSize(height) { this.options.estimatedRowSize = height; } // 重新计算children _reRange() { this.renderRange = {}; } _clearChildren() { this.container._children = {}; this.container.attr("items", []); } restore() { BI.each(this.renderedCells, (i, cell) => { cell.el._destroy(); }); this._clearChildren(); this.renderedCells = []; this.renderedKeys = []; this.renderRange = {}; this._scrollLock = false; } populate(items) { if (items && items !== this.options.items) { this.restore(); } this._populate(items); } }