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.
 
 
 

557 lines
20 KiB

import { PopupView } from "../layer";
import { Bubble } from "./bubble";
import {
shortcut,
Widget,
Controller,
extend,
createWidget,
nextTick,
bind,
isNotNull,
isNull,
isFunction,
each
} from "@/core";
import { Resizers } from "@/base/0.base";
/**
* @class BI.Combo
* @extends BI.Widget
*/
let needHideWhenAnotherComboOpen = {};
let currentOpenedCombos = {};
@shortcut()
export class Combo extends Bubble {
static xtype = "bi.combo";
static EVENT_TRIGGER_CHANGE = "EVENT_TRIGGER_CHANGE";
static EVENT_CHANGE = "EVENT_CHANGE";
static EVENT_EXPAND = "EVENT_EXPAND";
static EVENT_COLLAPSE = "EVENT_COLLAPSE";
static EVENT_AFTER_INIT = "EVENT_AFTER_INIT";
static EVENT_BEFORE_POPUPVIEW = "EVENT_BEFORE_POPUPVIEW";
static EVENT_AFTER_POPUPVIEW = "EVENT_AFTER_POPUPVIEW";
static EVENT_BEFORE_HIDEVIEW = "EVENT_BEFORE_HIDEVIEW";
static EVENT_AFTER_HIDEVIEW = "EVENT_AFTER_HIDEVIEW";
_defaultConfig() {
const conf = super._defaultConfig(...arguments);
return extend(conf, {
baseCls: `${conf.baseCls || ""} bi-combo${BI.isIE() ? " hack" : ""}`,
attributes: {
tabIndex: -1,
},
trigger: "click", // click || hover || click-hover || ""
toggle: true,
direction: "bottom", // top||bottom||left||right||top,left||top,right||bottom,left||bottom,right||right,innerRight||right,innerLeft||innerRight||innerLeft
logic: {
dynamic: true,
},
container: null, // popupview放置的容器,默认为this.element
isDefaultInit: false,
destroyWhenHide: false,
hideWhenBlur: true,
hideWhenAnotherComboOpen: false,
hideWhenClickOutside: true,
showArrow: false,
isNeedAdjustHeight: true, // 是否需要高度调整
isNeedAdjustWidth: true,
stopEvent: false,
stopPropagation: false,
adjustLength: 0, // 调整的距离
adjustXOffset: 0,
adjustYOffset: 0,
supportCSSTransform: true,
hideChecker: BI.emptyFn,
offsetStyle: "", // "",center,middle
el: {},
popup: {},
comboClass: "bi-combo-popup",
hoverClass: "bi-combo-hover",
belowMouse: false,
});
}
render() {
const { hoverClass, logic, isDefaultInit } = this.options;
this._initCombo();
// 延迟绑定事件,这样可以将自己绑定的事情优先执行
nextTick(() => {
!this.isDestroyed() && this._initPullDownAction();
});
this.combo.on(Controller.EVENT_CHANGE, (type, value, obj, ...args) => {
if (this.isEnabled() && this.isValid()) {
if (type === BI.Events.TOGGLE) {
this._toggle();
}
if (type === BI.Events.EXPAND) {
this._popupView();
}
if (type === BI.Events.COLLAPSE) {
this._hideView();
}
if (type === BI.Events.EXPAND) {
this.fireEvent(Controller.EVENT_CHANGE, type, value, obj, ...args);
this.fireEvent(Combo.EVENT_EXPAND);
}
if (type === BI.Events.COLLAPSE) {
this.fireEvent(Controller.EVENT_CHANGE, type, value, obj, ...args);
this.isViewVisible() && this.fireEvent(Combo.EVENT_COLLAPSE);
}
if (type === BI.Events.CLICK) {
this.fireEvent(Combo.EVENT_TRIGGER_CHANGE, obj);
}
}
});
this.element.on(`mouseenter.${this.getName()}`, e => {
if (this.isEnabled() && this.isValid() && this.combo.isEnabled() && this.combo.isValid()) {
this.element.addClass(hoverClass);
}
});
this.element.on(`mouseleave.${this.getName()}`, e => {
if (this.isEnabled() && this.isValid() && this.combo.isEnabled() && this.combo.isValid()) {
this.element.removeClass(hoverClass);
}
});
createWidget(
extend(
{
element: this,
scrolly: false,
},
BI.LogicFactory.createLogic(
"vertical",
extend(logic, {
items: [{ el: this.combo }],
})
)
)
);
isDefaultInit && this._assertPopupView();
Resizers.add(
this.getName(),
bind(e => {
// 如果resize对象是combo的子元素,则不应该收起,或交由hideChecker去处理
if (this.isViewVisible()) {
isNotNull(e) ? this._hideIf(e) : this._hideView();
}
}, this)
);
}
_assertPopupView() {
const { showArrow, value, hideWhenClickOutside, hideWhenBlur } = this.options;
if (isNull(this.popupView)) {
this.popupView = createWidget(
isFunction(this.options.popup) ? this.options.popup() : this.options.popup,
{
type: PopupView.xtype,
showArrow,
value,
},
this
);
this.popupView.on(Controller.EVENT_CHANGE, (type, value, obj, ...args) => {
if (type === BI.Events.CLICK) {
this.combo.setValue(this.getValue());
this.fireEvent(Bubble.EVENT_CHANGE, value, obj);
}
this.fireEvent(Controller.EVENT_CHANGE, type, value, obj, ...args);
});
this.popupView.setVisible(false);
nextTick(() => {
this.fireEvent(Bubble.EVENT_AFTER_INIT);
});
}
}
_hideView(e) {
const { hideWhenClickOutside, hideWhenBlur } = this.options;
this.fireEvent(Combo.EVENT_BEFORE_HIDEVIEW);
if (this.options.destroyWhenHide === true) {
this.popupView && this.popupView.destroy();
this.popupView = null;
this._rendered = false;
} else {
this.popupView && this.popupView.invisible();
}
if (!e || !this.combo.element.__isMouseInBounds__(e)) {
this.element.removeClass(this.options.hoverClass);
// 应对bi-focus-shadow在收起时不失焦
this.element.blur();
}
this.element.removeClass(this.options.comboClass);
delete needHideWhenAnotherComboOpen[this.getName()];
delete currentOpenedCombos[this.getName()];
hideWhenClickOutside &&
Widget._renderEngine
.createElement(document)
.unbind(`mousedown.${this.getName()}`)
.unbind(`mousewheel.${this.getName()}`);
BI.EVENT_BLUR && hideWhenBlur && Widget._renderEngine.createElement(window).unbind(`blur.${this.getName()}`);
this.fireEvent(Combo.EVENT_AFTER_HIDEVIEW, e);
}
_popupView(e) {
const { hideWhenClickOutside, hideWhenBlur } = this.options;
this._assertPopupViewRender();
this.fireEvent(Combo.EVENT_BEFORE_POPUPVIEW);
// popupVisible是为了获取其宽高, 放到可视范围之外以防止在IE下闪一下
this.popupView.css({ left: -99999, top: -99999 });
this.popupView.visible();
each(needHideWhenAnotherComboOpen, (i, combo) => {
if (i !== this.getName()) {
if (combo && combo._hideIf(e, true) === true) {
delete needHideWhenAnotherComboOpen[i];
}
}
});
currentOpenedCombos[this.getName()] = this;
this.options.hideWhenAnotherComboOpen && (needHideWhenAnotherComboOpen[this.getName()] = this);
this.adjustWidth(e);
this.adjustHeight(e);
this.element.addClass(this.options.comboClass);
hideWhenClickOutside &&
Widget._renderEngine
.createElement(document)
.unbind(`mousedown.${this.getName()}`)
.unbind(`mousewheel.${this.getName()}`);
hideWhenClickOutside && Widget._renderEngine.createElement(document).unbind(`mousewheel.${this.getName()}`);
BI.EVENT_BLUR && hideWhenBlur && Widget._renderEngine.createElement(window).unbind(`blur.${this.getName()}`);
hideWhenClickOutside &&
Widget._renderEngine.createElement(document).bind(`mousewheel.${this.getName()}`, bind(this._hideIf, this));
hideWhenClickOutside &&
Widget._renderEngine
.createElement(document)
.bind(`mousedown.${this.getName()}`, bind(this._hideIf, this))
.bind(`mousewheel.${this.getName()}`, bind(this._hideIf, this));
BI.EVENT_BLUR &&
hideWhenBlur &&
Widget._renderEngine.createElement(window).bind(`blur.${this.getName()}`, bind(this._hideIf, this));
this.fireEvent(Combo.EVENT_AFTER_POPUPVIEW);
}
adjustHeight(e) {
const {
belowMouse,
supportCSSTransform,
container,
direction,
adjustXOffset,
adjustYOffset,
adjustLength,
showArrow,
isNeedAdjustHeight,
offsetStyle,
} = this.options;
let p = {};
if (!this.popupView) {
return;
}
const isVisible = this.popupView.isVisible();
this.popupView.visible();
const combo =
belowMouse && isNotNull(e)
? {
element: {
0: e.target,
offset: () => {
return {
left: e.pageX,
top: e.pageY,
};
},
bounds: () => {
// offset为其相对于父定位元素的偏移
return {
x: e.offsetX,
y: e.offsetY,
width: 0,
height: 24,
};
},
outerWidth: () => 0,
outerHeight: () => 24,
},
}
: this.combo;
const positionRelativeElement = supportCSSTransform
? BI.DOM.getPositionRelativeContainingBlock(
isNull(container)
? this.element[0]
: Widget._renderEngine.createElement(isFunction(container) ? container() : container)[0]
)
: null;
const TRIANGLE_LENGTH = 12;
switch (direction) {
case "bottom":
case "bottom,right":
p = BI.DOM.getComboPosition(
combo,
this.popupView,
adjustXOffset,
adjustYOffset + adjustLength + (showArrow ? TRIANGLE_LENGTH : 0),
isNeedAdjustHeight,
["bottom", "top", "right", "left"],
offsetStyle,
positionRelativeElement
);
break;
case "top":
case "top,right":
p = BI.DOM.getComboPosition(
combo,
this.popupView,
adjustXOffset,
adjustYOffset + adjustLength + (showArrow ? TRIANGLE_LENGTH : 0),
isNeedAdjustHeight,
["top", "bottom", "right", "left"],
offsetStyle,
positionRelativeElement
);
break;
case "left":
case "left,bottom":
p = BI.DOM.getComboPosition(
combo,
this.popupView,
adjustXOffset + adjustLength + (showArrow ? TRIANGLE_LENGTH : 0),
adjustYOffset,
isNeedAdjustHeight,
["left", "right", "bottom", "top"],
offsetStyle,
positionRelativeElement
);
break;
case "right":
case "right,bottom":
p = BI.DOM.getComboPosition(
combo,
this.popupView,
adjustXOffset + adjustLength + (showArrow ? TRIANGLE_LENGTH : 0),
adjustYOffset,
isNeedAdjustHeight,
["right", "left", "bottom", "top"],
offsetStyle,
positionRelativeElement
);
break;
case "top,left":
p = BI.DOM.getComboPosition(
combo,
this.popupView,
adjustXOffset,
adjustYOffset + adjustLength + (showArrow ? TRIANGLE_LENGTH : 0),
isNeedAdjustHeight,
["top", "bottom", "left", "right"],
offsetStyle,
positionRelativeElement
);
break;
case "bottom,left":
p = BI.DOM.getComboPosition(
combo,
this.popupView,
adjustXOffset,
adjustYOffset + adjustLength + (showArrow ? TRIANGLE_LENGTH : 0),
isNeedAdjustHeight,
["bottom", "top", "left", "right"],
offsetStyle,
positionRelativeElement
);
break;
case "left,top":
p = BI.DOM.getComboPosition(
combo,
this.popupView,
adjustXOffset + adjustLength + (showArrow ? TRIANGLE_LENGTH : 0),
adjustYOffset,
isNeedAdjustHeight,
["left", "right", "top", "bottom"],
offsetStyle,
positionRelativeElement
);
break;
case "right,top":
p = BI.DOM.getComboPosition(
combo,
this.popupView,
adjustXOffset + adjustLength + (showArrow ? TRIANGLE_LENGTH : 0),
adjustYOffset,
isNeedAdjustHeight,
["right", "left", "top", "bottom"],
offsetStyle,
positionRelativeElement
);
break;
case "right,innerRight":
p = BI.DOM.getComboPosition(
combo,
this.popupView,
adjustXOffset + adjustLength + (showArrow ? TRIANGLE_LENGTH : 0),
adjustYOffset,
isNeedAdjustHeight,
["right", "left", "innerRight", "innerLeft", "bottom", "top"],
offsetStyle,
positionRelativeElement
);
break;
case "right,innerLeft":
p = BI.DOM.getComboPosition(
combo,
this.popupView,
adjustXOffset + adjustLength + (showArrow ? TRIANGLE_LENGTH : 0),
adjustYOffset,
isNeedAdjustHeight,
["right", "left", "innerLeft", "innerRight", "bottom", "top"],
offsetStyle,
positionRelativeElement
);
break;
case "innerRight":
p = BI.DOM.getComboPosition(
combo,
this.popupView,
adjustXOffset + adjustLength + (showArrow ? TRIANGLE_LENGTH : 0),
adjustYOffset,
isNeedAdjustHeight,
["innerRight", "innerLeft", "right", "left", "bottom", "top"],
offsetStyle,
positionRelativeElement
);
break;
case "innerLeft":
p = BI.DOM.getComboPosition(
combo,
this.popupView,
adjustXOffset + adjustLength + (showArrow ? TRIANGLE_LENGTH : 0),
adjustYOffset,
isNeedAdjustHeight,
["innerLeft", "innerRight", "left", "right", "bottom", "top"],
offsetStyle,
positionRelativeElement
);
break;
case "top,custom":
case "custom,top":
p = BI.DOM.getTopAdaptPosition(
combo,
this.popupView,
adjustYOffset + adjustLength + (showArrow ? TRIANGLE_LENGTH : 0),
isNeedAdjustHeight
);
p.dir = "top";
break;
case "custom,bottom":
case "bottom,custom":
p = BI.DOM.getBottomAdaptPosition(
combo,
this.popupView,
adjustYOffset + adjustLength + (showArrow ? TRIANGLE_LENGTH : 0),
isNeedAdjustHeight
);
p.dir = "bottom";
break;
case "left,custom":
case "custom,left":
p = BI.DOM.getLeftAdaptPosition(
combo,
this.popupView,
adjustXOffset + adjustLength + (showArrow ? TRIANGLE_LENGTH : 0)
);
delete p.top;
delete p.adaptHeight;
p.dir = "left";
break;
case "custom,right":
case "right,custom":
p = BI.DOM.getRightAdaptPosition(
combo,
this.popupView,
adjustXOffset + adjustLength + (showArrow ? TRIANGLE_LENGTH : 0)
);
delete p.top;
delete p.adaptHeight;
p.dir = "right";
break;
default:
break;
}
if ("adaptHeight" in p) {
this.resetListHeight(p.adaptHeight);
}
const width = this.combo.element.outerWidth();
const height = this.combo.element.outerHeight();
this.popupView.setDirection &&
this.popupView.setDirection(p.dir, {
width,
height,
offsetStyle,
adjustXOffset,
adjustYOffset,
offset: this.combo.element.offset(),
});
if (supportCSSTransform) {
const positonedRect = positionRelativeElement.getBoundingClientRect();
const scaleX = positonedRect.width / positionRelativeElement.offsetWidth;
const scaleY = positonedRect.height / positionRelativeElement.offsetHeight;
p.top && (p.top = p.top / scaleY);
p.left && (p.left = p.left / scaleX);
}
if ("left" in p) {
this.popupView.element.css({
left: p.left,
});
}
if ("top" in p) {
this.popupView.element.css({
top: p.top,
});
}
this.position = p;
this.popupView.setVisible(isVisible);
}
destroyed() {
Widget._renderEngine
.createElement(document)
.unbind(`click.${this.getName()}`)
.unbind(`mousedown.${this.getName()}`)
.unbind(`mousewheel.${this.getName()}`)
.unbind(`mouseenter.${this.getName()}`)
.unbind(`mouseleave.${this.getName()}`);
Widget._renderEngine.createElement(window).unbind(`blur.${this.getName()}`);
Resizers.remove(this.getName());
this.popupView && this.popupView._destroy();
delete needHideWhenAnotherComboOpen[this.getName()];
delete currentOpenedCombos[this.getName()];
}
}
Combo.closeAll = () => {
each(currentOpenedCombos, (i, combo) => {
if (combo) {
combo.hideView();
}
});
currentOpenedCombos = {};
needHideWhenAnotherComboOpen = {};
};