fineui是帆软报表和BI产品线所使用的前端框架。
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

488 lines
16 KiB

import {
VerticalLayout,
AbsoluteLayout,
Widget,
shortcut,
extend,
emptyFn,
debounce,
_lazyCreateWidget,
isFunction,
SectionManager,
isNull,
each,
clamp,
toArray,
invert,
min,
max,
nextTick,
DOM
} from "@/core";
import { Label } from "../single";
/**
* CollectionView
*
* Created by GUY on 2016/1/15.
* @class CollectionView
* @extends Widget
*/
@shortcut()
export class CollectionView extends Widget {
_defaultConfig() {
return extend(super._defaultConfig(...arguments), {
baseCls: "bi-collection",
// width: 400, //必设
// height: 300, //必设
scrollable: true,
scrollx: false,
scrolly: false,
overflowX: true,
overflowY: true,
el: {
type: VerticalLayout.xtype,
},
cellSizeAndPositionGetter: emptyFn,
horizontalOverscanSize: 0,
verticalOverscanSize: 0,
scrollLeft: 0,
scrollTop: 0,
items: [],
itemFormatter: (item, index) => item,
});
}
static EVENT_SCROLL = "EVENT_SCROLL";
static xtype = "bi.collection_view";
render() {
const o = this.options;
const { overflowX, overflowY, el } = this.options;
this.renderedCells = [];
this.renderedKeys = [];
this.renderRange = {};
this._scrollLock = false;
this._debounceRelease = debounce(() => {
this._scrollLock = false;
}, 1000 / 60);
this.container = _lazyCreateWidget({
type: AbsoluteLayout.xtype,
});
this.element.scroll(() => {
if (this._scrollLock === true) {
return;
}
o.scrollLeft = this.element.scrollLeft();
o.scrollTop = this.element.scrollTop();
this._calculateChildrenToRender();
this.fireEvent(CollectionView.EVENT_SCROLL, {
scrollLeft: o.scrollLeft,
scrollTop: o.scrollTop,
});
});
// 兼容一下
let scrollable = o.scrollable;
const scrollx = o.scrollx,
scrolly = o.scrolly;
if (overflowX === false) {
if (overflowY === false) {
scrollable = false;
} else {
scrollable = "y";
}
} else {
if (overflowY === false) {
scrollable = "x";
}
}
_lazyCreateWidget(el, {
type: VerticalLayout.xtype,
element: this,
scrollable,
scrolly,
scrollx,
items: [this.container],
});
o.items = 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);
}
}
_calculateSizeAndPositionData() {
const { items, cellSizeAndPositionGetter } = this.options;
const cellMetadata = [];
const sectionManager = new SectionManager();
let height = 0;
let width = 0;
for (let index = 0, len = items.length; index < len; index++) {
const cellMetadatum = cellSizeAndPositionGetter(index);
if (
isNull(cellMetadatum.height) ||
isNaN(cellMetadatum.height) ||
isNull(cellMetadatum.width) ||
isNaN(cellMetadatum.width) ||
isNull(cellMetadatum.x) ||
isNaN(cellMetadatum.x) ||
isNull(cellMetadatum.y) ||
isNaN(cellMetadatum.y)
) {
throw Error();
}
height = Math.max(height, cellMetadatum.y + cellMetadatum.height);
width = Math.max(width, cellMetadatum.x + cellMetadatum.width);
cellMetadatum.index = index;
cellMetadata[index] = cellMetadatum;
sectionManager.registerCell(cellMetadatum, index);
}
this._cellMetadata = cellMetadata;
this._sectionManager = sectionManager;
this._height = height;
this._width = width;
}
_cellRenderers(height, width, x, y) {
this._lastRenderedCellIndices = this._sectionManager.getCellIndices(height, width, x, y);
return this._cellGroupRenderer();
}
_cellGroupRenderer() {
const rendered = [];
each(this._lastRenderedCellIndices, (i, index) => {
const cellMetadata = this._sectionManager.getCellMetadata(index);
rendered.push(cellMetadata);
});
return rendered;
}
_calculateChildrenToRender() {
const o = this.options;
const { horizontalOverscanSize, verticalOverscanSize, width, height, itemFormatter, items } = this.options;
const scrollLeft = clamp(o.scrollLeft, 0, this._getMaxScrollLeft());
const scrollTop = clamp(o.scrollTop, 0, this._getMaxScrollTop());
const left = Math.max(0, scrollLeft - horizontalOverscanSize);
const top = Math.max(0, scrollTop - verticalOverscanSize);
const right = Math.min(this._width, scrollLeft + width + horizontalOverscanSize);
const bottom = Math.min(this._height, scrollTop + height + verticalOverscanSize);
if (right > 0 && bottom > 0) {
// 如果滚动的区间并没有超出渲染的范围
if (
top >= this.renderRange.minY &&
bottom <= this.renderRange.maxY &&
left >= this.renderRange.minX &&
right <= this.renderRange.maxX
) {
return;
}
const childrenToDisplay = this._cellRenderers(bottom - top, right - left, left, top);
const renderedCells = [],
renderedKeys = {},
renderedWidgets = {};
// 存储所有的left和top
let lefts = {},
tops = {};
for (let i = 0, len = childrenToDisplay.length; i < len; i++) {
const datum = childrenToDisplay[i];
lefts[datum.x] = datum.x;
lefts[datum.x + datum.width] = datum.x + datum.width;
tops[datum.y] = datum.y;
tops[datum.y + datum.height] = datum.y + datum.height;
}
lefts = toArray(lefts);
tops = toArray(tops);
const leftMap = invert(lefts);
const topMap = invert(tops);
// 存储上下左右四个边界
const leftBorder = {},
rightBorder = {},
topBorder = {},
bottomBorder = {};
function assertMinBorder(border, offset) {
if (isNull(border[offset])) {
border[offset] = Number.MAX_VALUE;
}
}
function assertMaxBorder(border, offset) {
if (isNull(border[offset])) {
border[offset] = 0;
}
}
for (let i = 0, len = childrenToDisplay.length; i < len; i++) {
const datum = childrenToDisplay[i];
const index = this.renderedKeys[datum.index] && this.renderedKeys[datum.index][1];
let child;
if (index >= 0) {
this.renderedCells[index].el.setWidth(datum.width);
this.renderedCells[index].el.setHeight(datum.height);
// 这里只使用px
this.renderedCells[index].el.element.css("left", `${datum.x}px`);
this.renderedCells[index].el.element.css("top", `${datum.y}px`);
renderedCells.push((child = this.renderedCells[index]));
} else {
const item = itemFormatter(items[datum.index], datum.index);
child = _lazyCreateWidget(
extend(
{
type: Label.xtype,
width: datum.width,
height: datum.height,
},
item,
{
cls: `${item.cls || ""} collection-cell${datum.y === 0 ? " first-row" : ""}${
datum.x === 0 ? " first-col" : ""
}`,
_left: datum.x,
_top: datum.y,
}
)
);
renderedCells.push({
el: child,
left: `${datum.x}px`,
top: `${datum.y}px`,
_left: datum.x,
_top: datum.y,
// _width: datum.width,
// _height: datum.height
});
}
const startTopIndex = topMap[datum.y] | 0;
const endTopIndex = topMap[datum.y + datum.height] | 0;
for (let k = startTopIndex; k <= endTopIndex; k++) {
const t = tops[k];
assertMinBorder(leftBorder, t);
assertMaxBorder(rightBorder, t);
leftBorder[t] = Math.min(leftBorder[t], datum.x);
rightBorder[t] = Math.max(rightBorder[t], datum.x + datum.width);
}
const startLeftIndex = leftMap[datum.x] | 0;
const endLeftIndex = leftMap[datum.x + datum.width] | 0;
for (let k = startLeftIndex; k <= endLeftIndex; k++) {
const l = lefts[k];
assertMinBorder(topBorder, l);
assertMaxBorder(bottomBorder, l);
topBorder[l] = Math.min(topBorder[l], datum.y);
bottomBorder[l] = Math.max(bottomBorder[l], datum.y + datum.height);
}
renderedKeys[datum.index] = [datum.index, i];
renderedWidgets[i] = child;
}
// 已存在的, 需要添加的和需要删除的
const existSet = {},
addSet = {},
deleteArray = [];
each(renderedKeys, (i, key) => {
if (this.renderedKeys[i]) {
existSet[i] = key;
} else {
addSet[i] = key;
}
});
each(this.renderedKeys, (i, key) => {
if (existSet[i]) {
return;
}
if (addSet[i]) {
return;
}
deleteArray.push(key[1]);
});
each(deleteArray, (i, index) => {
// 性能优化,不调用destroy方法防止触发destroy事件
this.renderedCells[index].el._destroy();
});
const addedItems = [];
each(addSet, (index, key) => {
addedItems.push(renderedCells[key[1]]);
});
this.container.addItems(addedItems);
// 拦截父子级关系
this.container._children = renderedWidgets;
this.container.attr("items", renderedCells);
this.renderedCells = renderedCells;
this.renderedKeys = renderedKeys;
// Todo 左右比较特殊
const minX = min(leftBorder);
const maxX = max(rightBorder);
const minY = max(topBorder);
const maxY = min(bottomBorder);
this.renderRange = { minX, minY, maxX, maxY };
}
}
_isOverflowX() {
const o = this.options;
const { overflowX } = this.options;
// 兼容一下
const scrollable = o.scrollable,
scrollx = o.scrollx;
if (overflowX === false) {
return false;
}
if (scrollx) {
return true;
}
if (scrollable === true || scrollable === "xy" || scrollable === "x") {
return true;
}
return false;
}
_isOverflowY() {
const o = this.options;
const { overflowX } = this.options;
// 兼容一下
const 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._width - this.options.width + (this._isOverflowX() ? DOM.getScrollWidth() : 0));
}
_getMaxScrollTop() {
return Math.max(0, this._height - this.options.height + (this._isOverflowY() ? DOM.getScrollWidth() : 0));
}
_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._width);
this.container.setHeight(this._height);
this._debounceRelease();
// 元素未挂载时不能设置scrollTop
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 = 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 = clamp(scrollTop || 0, 0, this._getMaxScrollTop());
this._debounceRelease();
this.element.scrollTop(this.options.scrollTop);
this._calculateChildrenToRender();
}
setOverflowX(b) {
if (this.options.overflowX !== !!b) {
this.options.overflowX = !!b;
nextTick(() => {
this.element.css({ overflowX: b ? "auto" : "hidden" });
});
}
}
setOverflowY(b) {
if (this.options.overflowY !== !!b) {
this.options.overflowY = !!b;
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();
}
// 重新计算children
_reRange() {
this.renderRange = {};
}
_clearChildren() {
this.container._children = {};
this.container.attr("items", []);
}
restore() {
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);
}
}