fineui是帆软报表和BI产品线所使用的前端框架。
 
 
 

820 lines
29 KiB

import { isNull, isFunction, each, stripEL, keys, isArray, contains, isKey, isOdd, isWidget, isNotNull, has } from "../2.base";
import { Widget } from "../4.widget";
import { _lazyCreateWidget, Providers } from "../5.inject";
import { shortcut } from "../decorator";
import { pixFormat, Events } from "../constant";
/**
* 布局容器类
*
* @cfg {JSON} options 配置属性
* @cfg {Boolean} [options.scrollable=false] 子组件超出容器边界之后是否会出现滚动条
* @cfg {Boolean} [options.scrollx=false] 子组件超出容器边界之后是否会出现横向滚动条
* @cfg {Boolean} [options.scrolly=false] 子组件超出容器边界之后是否会出现纵向滚动条
*/
@shortcut()
export class Layout extends Widget {
static xtype = "bi.layout";
props() {
return {
scrollable: null, // true, false, null
scrollx: false, // true, false
scrolly: false, // true, false
items: [],
innerHgap: 0,
innerVgap: 0,
};
}
render() {
const o = this.options;
this._init4Margin();
this._init4Scroll();
if (isFunction(o.columnSize)) {
const columnSizeFn = o.columnSize;
o.columnSize = this.__watch(columnSizeFn, (context, newValue) => {
o.columnSize = newValue;
this.resize();
});
}
if (isFunction(o.rowSize)) {
const rowSizeFn = o.rowSize;
o.rowSize = this.__watch(rowSizeFn, (context, newValue) => {
o.rowSize = newValue;
this.resize();
});
}
}
_init4Margin() {
if (this.options.top) {
this.element.css("top", pixFormat(this.options.top));
}
if (this.options.left) {
this.element.css("left", pixFormat(this.options.left));
}
if (this.options.bottom) {
this.element.css("bottom", pixFormat(this.options.bottom));
}
if (this.options.right) {
this.element.css("right", pixFormat(this.options.right));
}
}
_init4Scroll() {
switch (this.options.scrollable) {
case true:
case "xy":
this.element.css("overflow", "auto");
return;
case false:
this.element.css("overflow", "hidden");
return;
case "x":
this.element.css({
"overflow-x": "auto",
"overflow-y": "hidden",
});
return;
case "y":
this.element.css({
"overflow-x": "hidden",
"overflow-y": "auto",
});
return;
default :
break;
}
if (this.options.scrollx) {
this.element.css({
"overflow-x": "auto",
"overflow-y": "hidden",
});
return;
}
if (this.options.scrolly) {
this.element.css({
"overflow-x": "hidden",
"overflow-y": "auto",
});
}
}
appendFragment(frag) {
this.element.append(frag);
}
_mountChildren() {
const frag = Widget._renderEngine.createFragment();
let hasChild = false;
for (const key in this._children) {
const child = this._children[key];
if (child.element !== this.element) {
frag.appendChild(child.element[0]);
hasChild = true;
}
}
if (hasChild === true) {
this.appendFragment(frag);
}
}
_getChildName(index) {
return `${index}`;
}
_addElement(i, item, context, widget) {
let w;
if (widget) {
return widget;
}
if (!this.hasWidget(this._getChildName(i))) {
w = _lazyCreateWidget(item, context);
w.on(Events.DESTROY, () => {
each(this._children, (name, child) => {
if (child === w) {
delete this._children[name];
this.removeItemAt(name | 0);
}
});
});
this.addWidget(this._getChildName(i), w);
} else {
w = this.getWidgetByName(this._getChildName(i));
}
return w;
}
_newElement(i, item, context) {
const w = _lazyCreateWidget(item, context);
w.on(Events.DESTROY, () => {
each(this._children, (name, child) => {
if (child === w) {
delete this._children[name];
this.removeItemAt(name | 0);
}
});
});
return this._addElement(i, item, context, w);
}
_getOptions(item) {
if (item instanceof Widget) {
item = item.options;
}
item = stripEL(item);
if (item instanceof Widget) {
item = item.options;
}
return item;
}
_compare(item1, item2) {
// 不比较函数
const eq = (a, b, aStack, bStack) => {
if (a === b) {
return a !== 0 || 1 / a === 1 / b;
}
if (isNull(a) || isNull(b)) {
return a === b;
}
const className = Object.prototype.toString.call(a);
switch (className) {
case "[object RegExp]":
case "[object String]":
return `${a}` === `${b}`;
case "[object Number]":
if (+a !== +a) {
return +b !== +b;
}
return +a === 0 ? 1 / +a === 1 / b : +a === +b;
case "[object Date]":
case "[object Boolean]":
return +a === +b;
default:
}
const areArrays = className === "[object Array]";
if (!areArrays) {
if (isFunction(a) && isFunction(b)) {
return true;
}
a = this._getOptions(a);
b = this._getOptions(b);
}
aStack = aStack || [];
bStack = bStack || [];
let length = aStack.length;
while (length--) {
if (aStack[length] === a) {
return bStack[length] === b;
}
}
aStack.push(a);
bStack.push(b);
if (areArrays) {
length = a.length;
if (length !== b.length) {
return false;
}
while (length--) {
if (!eq(a[length], b[length], aStack, bStack)) {
return false;
}
}
} else {
const aKeys = keys(a);
let key;
length = aKeys.length;
if (keys(b).length !== length) {
return false;
}
while (length--) {
key = aKeys[length];
if (!(has(b, key) && eq(a[key], b[key], aStack, bStack))) {
return false;
}
}
}
aStack.pop();
bStack.pop();
return true;
};
return eq(item1, item2);
}
_getWrapper() {
return this.element;
}
// 不依赖于this.options.items进行更新
_updateItemAt(oldIndex, newIndex, item) {
const del = this._children[this._getChildName(oldIndex)];
const w = this._newElement(newIndex, item);
// 需要有个地方临时存一下新建的组件,否则如果直接使用newIndex的话,newIndex位置的元素可能会被用到
this._children[`${this._getChildName(newIndex)}-temp`] = w;
const nextSibling = del.element.next();
if (nextSibling.length > 0) {
Widget._renderEngine.createElement(nextSibling).before(w.element);
} else {
w.element.appendTo(this._getWrapper());
}
del._destroy();
w._mount();
return true;
}
_addItemAt(index, item) {
for (let i = this.options.items.length; i > index; i--) {
this._children[this._getChildName(i)] = this._children[this._getChildName(i - 1)];
}
delete this._children[this._getChildName(index)];
this.options.items.splice(index, 0, item);
}
_removeItemAt(index) {
for (let i = index; i < this.options.items.length - 1; i++) {
this._children[this._getChildName(i)] = this._children[this._getChildName(i + 1)];
}
delete this._children[this._getChildName(this.options.items.length - 1)];
this.options.items.splice(index, 1);
}
_clearGap(w) {
w.element.css({
"margin-top": "",
"margin-bottom": "",
"margin-left": "",
"margin-right": "",
});
}
_optimiseGap(gap) {
return (gap > 0 && gap < 1) ? `${(gap * 100).toFixed(1)}%` : pixFormat(gap);
}
_optimiseItemLgap(item) {
if (Providers.getProvider("bi.provider.system").getLayoutOptimize()) {
return ((!item.type && item.el) ? ((item._lgap || 0) + (item.lgap || 0)) : item._lgap) || 0;
}
return (item._lgap || 0) + (item.lgap || 0);
}
_optimiseItemRgap(item) {
if (Providers.getProvider("bi.provider.system").getLayoutOptimize()) {
return ((!item.type && item.el) ? ((item._rgap || 0) + (item.rgap || 0)) : item._rgap) || 0;
}
return (item._rgap || 0) + (item.rgap || 0);
}
_optimiseItemTgap(item) {
if (Providers.getProvider("bi.provider.system").getLayoutOptimize()) {
return ((!item.type && item.el) ? ((item._tgap || 0) + (item.tgap || 0)) : item._tgap) || 0;
}
return (item._tgap || 0) + (item.tgap || 0);
}
_optimiseItemBgap(item) {
if (Providers.getProvider("bi.provider.system").getLayoutOptimize()) {
return ((!item.type && item.el) ? ((item._bgap || 0) + (item.bgap || 0)) : item._bgap) || 0;
}
return (item._bgap || 0) + (item.bgap || 0);
}
_optimiseItemHgap(item) {
if (Providers.getProvider("bi.provider.system").getLayoutOptimize()) {
return ((!item.type && item.el) ? ((item._hgap || 0) + (item.hgap || 0)) : item._hgap) || 0;
}
return (item._hgap || 0) + (item.hgap || 0);
}
_optimiseItemVgap(item) {
if (Providers.getProvider("bi.provider.system").getLayoutOptimize()) {
return ((!item.type && item.el) ? ((item._vgap || 0) + (item.vgap || 0)) : item._vgap) || 0;
}
return (item._vgap || 0) + (item.vgap || 0);
}
_handleGap(w, item, hIndex, vIndex) {
const o = this.options;
let innerLgap, innerRgap, innerTgap, innerBgap;
if (isNull(vIndex)) {
innerTgap = innerBgap = o.innerVgap;
innerLgap = hIndex === 0 ? o.innerHgap : 0;
innerRgap = hIndex === o.items.length - 1 ? o.innerHgap : 0;
} else {
innerLgap = innerRgap = o.innerHgap;
innerTgap = vIndex === 0 ? o.innerVgap : 0;
innerBgap = vIndex === o.items.length - 1 ? o.innerVgap : 0;
}
if (o.vgap + o.tgap + innerTgap + this._optimiseItemTgap(item) + this._optimiseItemVgap(item) !== 0) {
const top = ((isNull(vIndex) || vIndex === 0) ? o.vgap : 0) + o.tgap + innerTgap + this._optimiseItemTgap(item) + this._optimiseItemVgap(item);
w.element.css({
"margin-top": this._optimiseGap(top),
});
}
if (o.hgap + o.lgap + innerLgap + this._optimiseItemLgap(item) + this._optimiseItemHgap(item) !== 0) {
const left = ((isNull(hIndex) || hIndex === 0) ? o.hgap : 0) + o.lgap + innerLgap + this._optimiseItemLgap(item) + this._optimiseItemHgap(item);
w.element.css({
"margin-left": this._optimiseGap(left),
});
}
if (o.hgap + o.rgap + innerRgap + this._optimiseItemRgap(item) + this._optimiseItemHgap(item) !== 0) {
const right = o.hgap + o.rgap + innerRgap + this._optimiseItemRgap(item) + this._optimiseItemHgap(item);
w.element.css({
"margin-right": this._optimiseGap(right),
});
}
if (o.vgap + o.bgap + innerBgap + this._optimiseItemBgap(item) + this._optimiseItemVgap(item) !== 0) {
const bottom = o.vgap + o.bgap + innerBgap + this._optimiseItemBgap(item) + this._optimiseItemVgap(item);
w.element.css({
"margin-bottom": this._optimiseGap(bottom),
});
}
}
// 横向换纵向
_handleReverseGap(w, item, index) {
const o = this.options;
const innerLgap = o.innerHgap;
const innerRgap = o.innerHgap;
const innerTgap = index === 0 ? o.innerVgap : 0;
const innerBgap = index === o.items.length - 1 ? o.innerVgap : 0;
if (o.vgap + o.tgap + innerTgap + this._optimiseItemTgap(item) + this._optimiseItemVgap(item) !== 0) {
const top = (index === 0 ? o.vgap : 0) + (index === 0 ? o.tgap : 0) + innerTgap + this._optimiseItemTgap(item) + this._optimiseItemVgap(item);
w.element.css({
"margin-top": this._optimiseGap(top),
});
}
if (o.hgap + o.lgap + innerLgap + this._optimiseItemLgap(item) + this._optimiseItemHgap(item) !== 0) {
const left = o.hgap + o.lgap + innerLgap + this._optimiseItemLgap(item) + this._optimiseItemHgap(item);
w.element.css({
"margin-left": this._optimiseGap(left),
});
}
if (o.hgap + o.rgap + innerRgap + this._optimiseItemRgap(item) + this._optimiseItemHgap(item) !== 0) {
const right = o.hgap + o.rgap + innerRgap + this._optimiseItemRgap(item) + this._optimiseItemHgap(item);
w.element.css({
"margin-right": this._optimiseGap(right),
});
}
// 这里的代码是关键
if (o.vgap + o.hgap + o.bgap + innerBgap + this._optimiseItemBgap(item) + this._optimiseItemVgap(item) !== 0) {
const bottom = (index === o.items.length - 1 ? o.vgap : o.hgap) + (index === o.items.length - 1 ? o.bgap : 0) + innerBgap + this._optimiseItemBgap(item) + this._optimiseItemVgap(item);
w.element.css({
"margin-bottom": this._optimiseGap(bottom),
});
}
}
/**
* 添加一个子组件到容器中
* @param {JSON/Widget} item 子组件
*/
addItem(item) {
return this.addItemAt(this.options.items.length, item);
}
prependItem(item) {
return this.addItemAt(0, item);
}
addItemAt(index, item) {
if (index < 0 || index > this.options.items.length) {
return;
}
this._addItemAt(index, item);
const w = this._addElement(index, item);
// addItemAt 还是用之前的找上个兄弟节点向后插入的方式
if (index > 0) {
this._children[this._getChildName(index - 1)].element.after(w.element);
} else {
w.element.prependTo(this._getWrapper());
}
w._mount();
return w;
}
removeItemAt(indexes) {
indexes = isArray(indexes) ? indexes : [indexes];
const deleted = [];
const newItems = [], newChildren = {};
for (let i = 0, len = this.options.items.length; i < len; i++) {
const child = this._children[this._getChildName(i)];
if (contains(indexes, i)) {
child && deleted.push(child);
} else {
newChildren[this._getChildName(newItems.length)] = child;
newItems.push(this.options.items[i]);
}
}
this.options.items = newItems;
this._children = newChildren;
each(deleted, (i, c) => {
c._destroy();
});
}
shouldUpdateItem(index, item) {
const child = this._children[this._getChildName(index)];
if (!child || !child.shouldUpdate) {
return null;
}
return child.shouldUpdate(this._getOptions(item));
}
addItems(items, context) {
const o = this.options;
const fragment = Widget._renderEngine.createFragment();
const added = [];
each(items, (i, item) => {
const w = this._addElement(o.items.length, item, context);
this._children[this._getChildName(o.items.length)] = w;
o.items.push(item);
added.push(w);
fragment.appendChild(w.element[0]);
});
if (this._isMounted) {
this._getWrapper().append(fragment);
each(added, (i, w) => {
w._mount();
});
}
}
prependItems(items, context) {
items = items || [];
const fragment = Widget._renderEngine.createFragment();
const added = [];
for (let i = items.length - 1; i >= 0; i--) {
this._addItemAt(0, items[i]);
const w = this._addElement(0, items[i], context);
this._children[this._getChildName(0)] = w;
this.options.items.unshift(items[i]);
added.push(w);
fragment.appendChild(w.element[0]);
}
if (this._isMounted) {
this._getWrapper().prepend(fragment);
each(added, (i, w) => {
w._mount();
});
}
}
getValue() {
let value = [], child;
each(this.options.items, i => {
child = this._children[this._getChildName(i)];
if (child) {
let v = child.getValue();
v = isArray(v) ? v : [v];
value = value.concat(v);
}
});
return value;
}
setValue(v) {
let child;
each(this.options.items, i => {
child = this._children[this._getChildName(i)];
child && child.setValue(v);
});
}
setText(v) {
let child;
each(this.options.items, i => {
child = this._children[this._getChildName(i)];
child && child.setText(v);
});
}
patchItem(oldVnode, vnode, oldIndex, newIndex) {
const shouldUpdate = this.shouldUpdateItem(oldIndex, vnode);
const child = this._children[this._getChildName(oldIndex)];
if (shouldUpdate) {
this._children[`${this._getChildName(newIndex)}-temp`] = child;
return child._update(this._getOptions(vnode), shouldUpdate);
}
if (shouldUpdate === null && !this._compare(oldVnode, vnode)) {
// if (child.update) {
// return child.update(this._getOptions(vnode));
// }
return this._updateItemAt(oldIndex, newIndex, vnode);
}
}
updateChildren(oldCh, newCh, context) {
let oldStartIdx = 0, newStartIdx = 0;
let oldEndIdx = oldCh.length - 1;
let oldStartVnode = oldCh[0];
let oldEndVnode = oldCh[oldEndIdx];
let newEndIdx = newCh.length - 1;
let newStartVnode = newCh[0];
let newEndVnode = newCh[newEndIdx];
let before;
let updated;
const children = {};
const sameVnode = (vnode1, vnode2, oldIndex, newIndex) => {
vnode1 = this._getOptions(vnode1);
vnode2 = this._getOptions(vnode2);
if (isKey(vnode1.key)) {
return vnode1.key === vnode2.key;
}
if (oldIndex >= 0) {
return oldIndex === newIndex;
}
};
const addNode = (vnode, index, context) => {
const opt = this._getOptions(vnode);
const key = isNull(opt.key) ? index : opt.key;
children[key] = this._newElement(index, vnode, context);
return children[key];
};
const addVnodes = (before, vnodes, startIdx, endIdx, context) => {
for (; startIdx <= endIdx; ++startIdx) {
const node = addNode(vnodes[startIdx], startIdx, context);
insertBefore(node, before, false, startIdx);
}
};
const removeVnodes = (vnodes, startIdx, endIdx) => {
for (; startIdx <= endIdx; ++startIdx) {
const ch = vnodes[startIdx];
if (isNotNull(ch)) {
const node = this._getOptions(ch);
const key = isNull(node.key) ? startIdx : node.key;
children[key]._destroy();
}
}
};
const insertBefore = (insert, before, isNext, index) => {
insert = this._getOptions(insert);
before = before && this._getOptions(before);
const insertKey = isKey(insert.key) ? insert.key : index;
if (before && children[before.key]) {
const beforeKey = isKey(before.key) ? before.key : index;
let next;
if (isNext) {
next = children[beforeKey].element.next();
} else {
next = children[beforeKey].element;
}
if (next.length > 0) {
next.before(children[insertKey].element);
} else {
this._getWrapper().append(children[insertKey].element);
}
} else {
this._getWrapper().append(children[insertKey].element);
}
};
const findOldVnode = (vnodes, vNode, beginIdx, endIdx) => {
let i, found, findIndex;
for (i = beginIdx; i <= endIdx; ++i) {
if (vnodes[i] && sameVnode(vnodes[i], vNode)) {
found = vnodes[i];
findIndex = i;
}
}
return [found, findIndex];
};
each(oldCh, (i, child) => {
child = this._getOptions(child);
const key = isNull(child.key) ? i : child.key;
if (isKey(key)) {
children[key] = this._children[this._getChildName(i)];
}
});
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (isNull(oldStartVnode)) {
oldStartVnode = oldCh[++oldStartIdx];
} else if (isNull(oldEndVnode)) {
oldEndVnode = oldCh[--oldEndIdx];
} else if (sameVnode(oldStartVnode, newStartVnode, oldStartIdx, newStartIdx)) {
const willUpdate = this.patchItem(oldStartVnode, newStartVnode, oldStartIdx, newStartIdx);
updated = willUpdate || updated;
children[isNull(oldStartVnode.key) ? oldStartIdx : oldStartVnode.key] = willUpdate ? this._children[`${this._getChildName(newStartIdx)}-temp`] : this._children[this._getChildName(oldStartIdx)];
oldStartVnode = oldCh[++oldStartIdx];
newStartVnode = newCh[++newStartIdx];
} else if (sameVnode(oldEndVnode, newEndVnode, oldEndIdx, newEndIdx)) {
const willUpdate = this.patchItem(oldEndVnode, newEndVnode, oldEndIdx, newEndIdx);
updated = willUpdate || updated;
children[isNull(oldEndVnode.key) ? oldEndIdx : oldEndVnode.key] = willUpdate ? this._children[`${this._getChildName(newEndIdx)}-temp`] : this._children[this._getChildName(oldEndIdx)];
oldEndVnode = oldCh[--oldEndIdx];
newEndVnode = newCh[--newEndIdx];
} else if (sameVnode(oldStartVnode, newEndVnode)) {
const willUpdate = this.patchItem(oldStartVnode, newEndVnode, oldStartIdx, newStartIdx);
updated = willUpdate || updated;
children[isNull(oldStartVnode.key) ? oldStartIdx : oldStartVnode.key] = willUpdate ? this._children[`${this._getChildName(newStartIdx)}-temp`] : this._children[this._getChildName(oldStartIdx)];
insertBefore(oldStartVnode, oldEndVnode, true);
oldStartVnode = oldCh[++oldStartIdx];
newEndVnode = newCh[--newEndIdx];
} else if (sameVnode(oldEndVnode, newStartVnode)) {
const willUpdate = this.patchItem(oldEndVnode, newStartVnode, oldEndIdx, newEndIdx);
updated = willUpdate || updated;
children[isNull(oldEndVnode) ? oldEndIdx : oldEndVnode.key] = willUpdate ? this._children[`${this._getChildName(newEndIdx)}-temp`] : this._children[this._getChildName(oldEndIdx)];
insertBefore(oldEndVnode, oldStartVnode);
oldEndVnode = oldCh[--oldEndIdx];
newStartVnode = newCh[++newStartIdx];
} else {
const sameOldVnode = findOldVnode(oldCh, newStartVnode, oldStartIdx, oldEndIdx);
if (isNull(sameOldVnode[0])) { // 不存在就把新的放到左边
const node = addNode(newStartVnode, newStartIdx, context);
insertBefore(node, oldStartVnode);
} else { // 如果新节点在旧节点区间中存在就复用一下
const sameOldIndex = sameOldVnode[1];
const willUpdate = this.patchItem(sameOldVnode[0], newStartVnode, sameOldIndex, newStartIdx);
updated = willUpdate || updated;
children[isNull(sameOldVnode[0].key) ? newStartIdx : sameOldVnode[0].key] = willUpdate ? this._children[`${this._getChildName(newStartIdx)}-temp`] : this._children[this._getChildName(sameOldIndex)];
oldCh[sameOldIndex] = undefined;
insertBefore(sameOldVnode[0], oldStartVnode);
}
newStartVnode = newCh[++newStartIdx];
}
}
if (oldStartIdx > oldEndIdx) {
before = isNull(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1];
addVnodes(before, newCh, newStartIdx, newEndIdx, context);
} else if (newStartIdx > newEndIdx) {
removeVnodes(oldCh, oldStartIdx, oldEndIdx);
}
this._children = {};
each(newCh, (i, child) => {
const node = this._getOptions(child);
const key = isNull(node.key) ? i : node.key;
children[key]._setParent && children[key]._setParent(this);
children[key]._mount();
this._children[this._getChildName(i)] = children[key];
});
return updated;
}
forceUpdate(opt) {
if (this._isMounted) {
each(this._children, (i, c) => {
c.destroy();
});
this._children = {};
this._isMounted = false;
}
this.options.items = opt.items;
this.stroke(opt.items);
this._mount();
}
update(opt) {
const o = this.options;
const items = opt.items || [];
const context = opt.context;
const oldItems = o.items;
this.options.items = items;
return this.updateChildren(oldItems, items, context);
}
stroke(items, options) {
options = options || {};
each(items, (i, item) => {
item && this._addElement(i, item, options.context);
});
}
getRowColumnCls(rowIndex, colIndex, lastRowIndex, lastColIndex) {
let cls = "";
if (rowIndex === 0) {
cls += " first-row";
} else if (rowIndex === lastRowIndex) {
cls += " last-row";
}
if (colIndex === 0) {
cls += " first-col";
} else if (colIndex === lastColIndex) {
cls += " last-col";
}
isOdd(rowIndex + 1) ? (cls += " odd-row") : (cls += " even-row");
isOdd(colIndex + 1) ? (cls += " odd-col") : (cls += " even-col");
cls += " center-element";
return cls;
}
removeWidget(nameOrWidget) {
let removeIndex;
if (isWidget(nameOrWidget)) {
each(this._children, (name, child) => {
if (child === nameOrWidget) {
removeIndex = name;
}
});
} else {
removeIndex = nameOrWidget;
}
if (removeIndex) {
this._removeItemAt(removeIndex | 0);
}
}
empty() {
super.empty(...arguments);
this.options.items = [];
}
destroy() {
super.destroy(...arguments);
this.options.items = [];
}
populate(items, options) {
items = items || [];
options = options || {};
if (this._isMounted) {
this.update({
items,
context: options.context,
});
return;
}
this.options.items = items;
this.stroke(items, options);
}
resize() {
this.stroke(this.options.items);
}
}