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.
430 lines
15 KiB
430 lines
15 KiB
/** |
|
* CollectionView |
|
* |
|
* Created by GUY on 2016/1/15. |
|
* @class BI.CollectionView |
|
* @extends BI.Widget |
|
*/ |
|
BI.CollectionView = BI.inherit(BI.Widget, { |
|
_defaultConfig: function () { |
|
return BI.extend(BI.CollectionView.superclass._defaultConfig.apply(this, arguments), { |
|
baseCls: "bi-collection", |
|
// width: 400, //必设 |
|
// height: 300, //必设 |
|
scrollable: true, |
|
scrollx: false, |
|
scrolly: false, |
|
overflowX: true, |
|
overflowY: true, |
|
el: { |
|
type: "bi.vertical" |
|
}, |
|
cellSizeAndPositionGetter: BI.emptyFn, |
|
horizontalOverscanSize: 0, |
|
verticalOverscanSize: 0, |
|
scrollLeft: 0, |
|
scrollTop: 0, |
|
items: [], |
|
itemFormatter: function (item, index) { |
|
return item; |
|
}, |
|
}); |
|
}, |
|
|
|
render: function () { |
|
var self = this, o = this.options; |
|
this.renderedCells = []; |
|
this.renderedKeys = []; |
|
this.renderRange = {}; |
|
this._scrollLock = false; |
|
this._debounceRelease = BI.debounce(function () { |
|
self._scrollLock = false; |
|
}, 1000 / 60); |
|
this.container = BI._lazyCreateWidget({ |
|
type: "bi.absolute", |
|
}); |
|
this.element.scroll(function () { |
|
if (self._scrollLock === true) { |
|
return; |
|
} |
|
o.scrollLeft = self.element.scrollLeft(); |
|
o.scrollTop = self.element.scrollTop(); |
|
self._calculateChildrenToRender(); |
|
self.fireEvent(BI.CollectionView.EVENT_SCROLL, { |
|
scrollLeft: o.scrollLeft, |
|
scrollTop: o.scrollTop, |
|
}); |
|
}); |
|
// 兼容一下 |
|
var scrollable = o.scrollable, scrollx = o.scrollx, scrolly = o.scrolly; |
|
if (o.overflowX === false) { |
|
if (o.overflowY === false) { |
|
scrollable = false; |
|
} else { |
|
scrollable = "y"; |
|
} |
|
} else { |
|
if (o.overflowY === false) { |
|
scrollable = "x"; |
|
} |
|
} |
|
BI._lazyCreateWidget(o.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, function (context, newValue) { |
|
self.populate(newValue); |
|
}) : o.items; |
|
if (o.items.length > 0) { |
|
this._calculateSizeAndPositionData(); |
|
this._populate(); |
|
} |
|
}, |
|
|
|
// mounted之后绑定事件 |
|
mounted: function () { |
|
var o = this.options; |
|
if (o.scrollLeft !== 0 || o.scrollTop !== 0) { |
|
this.element.scrollTop(o.scrollTop); |
|
this.element.scrollLeft(o.scrollLeft); |
|
} |
|
}, |
|
|
|
_calculateSizeAndPositionData: function () { |
|
var o = this.options; |
|
var cellMetadata = []; |
|
var sectionManager = new BI.SectionManager(); |
|
var height = 0; |
|
var width = 0; |
|
|
|
for (var index = 0, len = o.items.length; index < len; index++) { |
|
var cellMetadatum = o.cellSizeAndPositionGetter(index); |
|
|
|
if (BI.isNull(cellMetadatum.height) || isNaN(cellMetadatum.height) || |
|
BI.isNull(cellMetadatum.width) || isNaN(cellMetadatum.width) || |
|
BI.isNull(cellMetadatum.x) || isNaN(cellMetadatum.x) || |
|
BI.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: function (height, width, x, y) { |
|
this._lastRenderedCellIndices = this._sectionManager.getCellIndices(height, width, x, y); |
|
|
|
return this._cellGroupRenderer(); |
|
}, |
|
|
|
_cellGroupRenderer: function () { |
|
var self = this; |
|
var rendered = []; |
|
BI.each(this._lastRenderedCellIndices, function (i, index) { |
|
var cellMetadata = self._sectionManager.getCellMetadata(index); |
|
rendered.push(cellMetadata); |
|
}); |
|
|
|
return rendered; |
|
}, |
|
|
|
_calculateChildrenToRender: function () { |
|
var self = this, o = this.options; |
|
var scrollLeft = BI.clamp(o.scrollLeft, 0, this._getMaxScrollLeft()); |
|
var scrollTop = BI.clamp(o.scrollTop, 0, this._getMaxScrollTop()); |
|
var left = Math.max(0, scrollLeft - o.horizontalOverscanSize); |
|
var top = Math.max(0, scrollTop - o.verticalOverscanSize); |
|
var right = Math.min(this._width, scrollLeft + o.width + o.horizontalOverscanSize); |
|
var bottom = Math.min(this._height, scrollTop + o.height + o.verticalOverscanSize); |
|
if (right > 0 && bottom > 0) { |
|
// 如果滚动的区间并没有超出渲染的范围 |
|
if (top >= this.renderRange.minY && bottom <= this.renderRange.maxY && left >= this.renderRange.minX && right <= this.renderRange.maxX) { |
|
return; |
|
} |
|
var childrenToDisplay = this._cellRenderers(bottom - top, right - left, left, top); |
|
var renderedCells = [], renderedKeys = {}, renderedWidgets = {}; |
|
// 存储所有的left和top |
|
var lefts = {}, tops = {}; |
|
for (var i = 0, len = childrenToDisplay.length; i < len; i++) { |
|
var 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 = BI.toArray(lefts); |
|
tops = BI.toArray(tops); |
|
var leftMap = BI.invert(lefts); |
|
var topMap = BI.invert(tops); |
|
// 存储上下左右四个边界 |
|
var leftBorder = {}, rightBorder = {}, topBorder = {}, bottomBorder = {}; |
|
function assertMinBorder(border, offset) { |
|
if (BI.isNull(border[offset])) { |
|
border[offset] = Number.MAX_VALUE; |
|
} |
|
} |
|
function assertMaxBorder(border, offset) { |
|
if (BI.isNull(border[offset])) { |
|
border[offset] = 0; |
|
} |
|
} |
|
for (var i = 0, len = childrenToDisplay.length; i < len; i++) { |
|
var datum = childrenToDisplay[i]; |
|
var index = this.renderedKeys[datum.index] && this.renderedKeys[datum.index][1]; |
|
var 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 { |
|
var item = o.itemFormatter(o.items[datum.index], datum.index); |
|
child = BI._lazyCreateWidget(BI.extend({ |
|
type: "bi.label", |
|
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 |
|
}); |
|
} |
|
var startTopIndex = topMap[datum.y] | 0; |
|
var endTopIndex = topMap[datum.y + datum.height] | 0; |
|
for (var k = startTopIndex; k <= endTopIndex; k++) { |
|
var 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); |
|
} |
|
var startLeftIndex = leftMap[datum.x] | 0; |
|
var endLeftIndex = leftMap[datum.x + datum.width] | 0; |
|
for (var k = startLeftIndex; k <= endLeftIndex; k++) { |
|
var 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; |
|
} |
|
// 已存在的, 需要添加的和需要删除的 |
|
var existSet = {}, addSet = {}, deleteArray = []; |
|
BI.each(renderedKeys, function (i, key) { |
|
if (self.renderedKeys[i]) { |
|
existSet[i] = key; |
|
} else { |
|
addSet[i] = key; |
|
} |
|
}); |
|
BI.each(this.renderedKeys, function (i, key) { |
|
if (existSet[i]) { |
|
return; |
|
} |
|
if (addSet[i]) { |
|
return; |
|
} |
|
deleteArray.push(key[1]); |
|
}); |
|
BI.each(deleteArray, function (i, index) { |
|
// 性能优化,不调用destroy方法防止触发destroy事件 |
|
self.renderedCells[index].el._destroy(); |
|
}); |
|
var addedItems = []; |
|
BI.each(addSet, function (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 左右比较特殊 |
|
var minX = BI.min(leftBorder); |
|
var maxX = BI.max(rightBorder); |
|
|
|
var minY = BI.max(topBorder); |
|
var maxY = BI.min(bottomBorder); |
|
|
|
this.renderRange = { minX: minX, minY: minY, maxX: maxX, maxY: maxY }; |
|
} |
|
}, |
|
|
|
_isOverflowX: function () { |
|
var o = this.options; |
|
// 兼容一下 |
|
var scrollable = o.scrollable, scrollx = o.scrollx; |
|
if (o.overflowX === false) { |
|
return false; |
|
} |
|
if (scrollx) { |
|
return true; |
|
} |
|
if (scrollable === true || scrollable === "xy" || scrollable === "x") { |
|
return true; |
|
} |
|
return false; |
|
}, |
|
|
|
_isOverflowY: function () { |
|
var o = this.options; |
|
// 兼容一下 |
|
var scrollable = o.scrollable, scrolly = o.scrolly; |
|
if (o.overflowY === false) { |
|
return false; |
|
} |
|
if (scrolly) { |
|
return true; |
|
} |
|
if (scrollable === true || scrollable === "xy" || scrollable === "y") { |
|
return true; |
|
} |
|
return false; |
|
}, |
|
|
|
_getMaxScrollLeft: function () { |
|
return Math.max(0, this._width - this.options.width + (this._isOverflowX() ? BI.DOM.getScrollWidth() : 0)); |
|
}, |
|
|
|
_getMaxScrollTop: function () { |
|
return Math.max(0, this._height - this.options.height + (this._isOverflowY() ? BI.DOM.getScrollWidth() : 0)); |
|
}, |
|
|
|
_populate: function (items) { |
|
var o = 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(o.scrollTop); |
|
this.element.scrollLeft(o.scrollLeft); |
|
} catch (e) { |
|
} |
|
this._calculateChildrenToRender(); |
|
}, |
|
|
|
setScrollLeft: function (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: function (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(); |
|
}, |
|
|
|
setOverflowX: function (b) { |
|
var self = this; |
|
if (this.options.overflowX !== !!b) { |
|
this.options.overflowX = !!b; |
|
BI.nextTick(function () { |
|
self.element.css({ overflowX: b ? "auto" : "hidden" }); |
|
}); |
|
} |
|
}, |
|
|
|
setOverflowY: function (b) { |
|
var self = this; |
|
if (this.options.overflowY !== !!b) { |
|
this.options.overflowY = !!b; |
|
BI.nextTick(function () { |
|
self.element.css({ overflowY: b ? "auto" : "hidden" }); |
|
}); |
|
} |
|
}, |
|
|
|
getScrollLeft: function () { |
|
return this.options.scrollLeft; |
|
}, |
|
|
|
getScrollTop: function () { |
|
return this.options.scrollTop; |
|
}, |
|
|
|
getMaxScrollLeft: function () { |
|
return this._getMaxScrollLeft(); |
|
}, |
|
|
|
getMaxScrollTop: function () { |
|
return this._getMaxScrollTop(); |
|
}, |
|
|
|
// 重新计算children |
|
_reRange: function () { |
|
this.renderRange = {}; |
|
}, |
|
|
|
_clearChildren: function () { |
|
this.container._children = {}; |
|
this.container.attr("items", []); |
|
}, |
|
|
|
restore: function () { |
|
BI.each(this.renderedCells, function (i, cell) { |
|
cell.el._destroy(); |
|
}); |
|
this._clearChildren(); |
|
this.renderedCells = []; |
|
this.renderedKeys = []; |
|
this.renderRange = {}; |
|
this._scrollLock = false; |
|
}, |
|
|
|
populate: function (items) { |
|
if (items && items !== this.options.items) { |
|
this.restore(); |
|
} |
|
this._populate(items); |
|
}, |
|
}); |
|
BI.CollectionView.EVENT_SCROLL = "EVENT_SCROLL"; |
|
BI.shortcut("bi.collection_view", BI.CollectionView);
|
|
|