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
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 = {}; |
|
};
|
|
|