/** * @class BI.Bubble * @extends BI.Widget */ import { shortcut, Widget, Controller, extend, nextTick, createWidget, each, defer, debounce, delay, isNull, isFunction, contains, bind } from "../../core"; @shortcut() export default class Bubble extends Widget { static xtype = "bi.bubble"; 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-popper", attributes: { tabIndex: -1, }, trigger: "click", // click || hover || click-hover || "hover-click" || "" toggle: true, direction: "", placement: "bottom-start", // top-start/top/top-end/bottom-start/bottom/bottom-end/left-start/left/left-end/right-start/right/right-end logic: { dynamic: true, }, container: null, // popupview放置的容器,默认为this.element isDefaultInit: false, destroyWhenHide: false, hideWhenClickOutside: true, showArrow: true, hideWhenBlur: false, isNeedAdjustHeight: true, // 是否需要高度调整 isNeedAdjustWidth: true, stopEvent: false, stopPropagation: false, adjustLength: 0, // 调整的距离 adjustXOffset: 0, adjustYOffset: 0, hideChecker: BI.emptyFn, offsetStyle: "left", // left,right,center el: {}, popup: {}, comboClass: "bi-combo-popup", hoverClass: "bi-combo-hover", }); } 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.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(Bubble.EVENT_EXPAND); } if (type === BI.Events.COLLAPSE) { this.fireEvent(Controller.EVENT_CHANGE, type, value, obj, ...args); this.isViewVisible() && this.fireEvent(Bubble.EVENT_COLLAPSE); } if (type === BI.Events.CLICK) { this.fireEvent(Bubble.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()); } _toggle(e) { this._assertPopupViewRender(); if (this.popupView.isVisible()) { this._hideView(e); } else { if (this.isEnabled()) { this._popupView(e); } } } _initPullDownAction() { const { stopEvent, stopPropagation, toggle } = this.options; const evs = (this.options.trigger || "").split(","); const st = (e) => { if (stopEvent) { e.stopEvent(); } if (stopPropagation) { e.stopPropagation(); } } let enterPopup = false; const hide = (e) => { if (this.isEnabled() && this.isValid() && this.combo.isEnabled() && this.combo.isValid() && toggle === true) { this._hideView(e); this.fireEvent(Controller.EVENT_CHANGE, BI.Events.COLLAPSE, "", this.combo); this.fireEvent(Bubble.EVENT_COLLAPSE); } this.popupView && this.popupView.element.off("mouseenter." + this.getName()).off("mouseleave." + this.getName()); enterPopup = false; } each(evs, (i, ev) => { let debounced; switch (ev) { case "hover": this.element.on("mouseenter." + this.getName(), (e) => { if (this.isEnabled() && this.isValid() && this.combo.isEnabled() && this.combo.isValid()) { this._popupView(e); this.fireEvent(Controller.EVENT_CHANGE, BI.Events.EXPAND, "", this.combo); this.fireEvent(Bubble.EVENT_EXPAND); } }); this.element.on("mouseleave." + this.getName(), (e) => { if (this.popupView) { this.popupView.element.on("mouseenter." + this.getName(), (e) => { enterPopup = true; this.popupView.element.on("mouseleave." + this.getName(), (e) => { hide(e); }); this.popupView.element.off("mouseenter." + this.getName()); }); defer(() => { if (!enterPopup) { hide(e); } }, 50); } }); break; case "click": debounced = debounce((e) => { if (this.combo.element.__isMouseInBounds__(e)) { if (this.isEnabled() && this.isValid() && this.combo.isEnabled() && this.combo.isValid()) { // if (!o.toggle && this.isViewVisible()) { // return; // } toggle ? this._toggle(e) : this._popupView(e); if (this.isViewVisible()) { this.fireEvent(Controller.EVENT_CHANGE, BI.Events.EXPAND, "", this.combo); this.fireEvent(Bubble.EVENT_EXPAND); } else { this.fireEvent(Controller.EVENT_CHANGE, BI.Events.COLLAPSE, "", this.combo); this.fireEvent(Bubble.EVENT_COLLAPSE); } } } }, BI.EVENT_RESPONSE_TIME, { "leading": true, "trailing": false, }); this.element.off(ev + "." + this.getName()).on(ev + "." + this.getName(), (e) => { debounced(e); st(e); }); break; case "click-hover": debounced = debounce((e) => { if (this.combo.element.__isMouseInBounds__(e)) { if (this.isEnabled() && this.isValid() && this.combo.isEnabled() && this.combo.isValid()) { // if (this.isViewVisible()) { // return; // } this._popupView(e); if (this.isViewVisible()) { this.fireEvent(Controller.EVENT_CHANGE, BI.Events.EXPAND, "", this.combo); this.fireEvent(Bubble.EVENT_EXPAND); } } } }, BI.EVENT_RESPONSE_TIME, { "leading": true, "trailing": false, }); this.element.off("click." + this.getName()).on("click." + this.getName(), (e) => { debounced(e); st(e); }); this.element.on("mouseleave." + this.getName(), (e) => { if (this.popupView) { this.popupView.element.on("mouseenter." + this.getName(), (e) => { enterPopup = true; this.popupView.element.on("mouseleave." + this.getName(), (e) => { hide(e); }); this.popupView.element.off("mouseenter." + this.getName()); }); delay(() => { if (!enterPopup) { hide(e); } }, 50); } }); break; case "hover-click": this.element.on("mouseenter." + this.getName(), (e) => { if (this.isEnabled() && this.isValid() && this.combo.isEnabled() && this.combo.isValid()) { this._popupView(e); this.fireEvent(Controller.EVENT_CHANGE, BI.Events.EXPAND, "", this.combo); this.fireEvent(Bubble.EVENT_EXPAND); } }); break; default: break; } }); } _initCombo() { this.combo = createWidget(this.options.el, { value: this.options.value, }); } _assertPopupView() { const { showArrow, value } = this.options; if (isNull(this.popupView)) { this.popupView = createWidget(isFunction(this.options.popup) ? this.options.popup() : this.options.popup, { type: "bi.bubble_popup_view", 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); }); } } _assertPopupViewRender() { this._assertPopupView(); if (!this._rendered) { createWidget({ type: "bi.vertical", scrolly: false, element: isFunction(this.options.container) ? this.options.container() : (this.options.container || this), items: [ { el: this.popupView } ], }); this._rendered = true; } } _hideIf(e, skipTriggerChecker) { // if (this.element.__isMouseInBounds__(e) || (this.popupView && this.popupView.element.__isMouseInBounds__(e))) { // return; // } // BI-10290 公式combo双击公式内容会收起 if (e && ((skipTriggerChecker !== true && this.element.find(e.target).length > 0) || (this.popupView && this.popupView.element.find(e.target).length > 0) || e.target.className === "CodeMirror-cursor" || Widget._renderEngine.createElement(e.target).closest(".CodeMirror-hints").length > 0)) {// BI-9887 CodeMirror的公式弹框需要特殊处理下 const directions = this.options.direction.split(","); if (contains(directions, "innerLeft") || contains(directions, "innerRight")) { // popup可以出现在trigger内部的combo,滚动时不需要消失,而是调整位置 this.adjustWidth(); this.adjustHeight(); } return; } const isHide = this.options.hideChecker.apply(this, [e]); if (isHide === false) { return; } this._hideView(e); return true; } _hideView(e) { const { hideWhenBlur } = this.options; this.fireEvent(Bubble.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(); } if (this.popper) { this.popper.destroy(); this.popper = null; } this.element.removeClass(this.options.comboClass); 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(Bubble.EVENT_AFTER_HIDEVIEW); } _popupView(e) { const { adjustXOffset, showArrow, adjustYOffset, adjustLength, placement, hideWhenClickOutside, hideWhenBlur } = this.options; this._assertPopupViewRender(); this.fireEvent(Bubble.EVENT_BEFORE_POPUPVIEW); // popupVisible是为了获取其宽高, 放到可视范围之外以防止在IE下闪一下 // this.popupView.css({left: -999999999, top: -99999999}); this.popupView.visible(); this.adjustWidth(e); if (this.popper) { this.popper.destroy(); } const modifiers = [ { name: "offset", options: { offset: () => { return [adjustXOffset, (showArrow ? 12 : 0) + (adjustYOffset + adjustLength)]; }, }, } ]; if (this.options.showArrow) { modifiers.push({ name: "arrow", options: { padding: 4, element: this.popupView.arrow.element[0], }, }); } this.popper = BI.Popper.createPopper(this.combo.element[0], this.popupView.element[0], { placement, strategy: "fixed", modifiers, }); // this.adjustHeight(e); this.element.addClass(this.options.comboClass); hideWhenClickOutside && Widget._renderEngine.createElement(document).unbind("mousedown." + this.getName()); BI.EVENT_BLUR && hideWhenBlur && Widget._renderEngine.createElement(window).unbind("blur." + this.getName()); hideWhenClickOutside && Widget._renderEngine.createElement(document).bind("mousedown." + this.getName(), bind(this._hideIf, this)); BI.EVENT_BLUR && hideWhenBlur && Widget._renderEngine.createElement(window).bind("blur." + this.getName(), bind(this._hideIf, this)); this.fireEvent(Bubble.EVENT_AFTER_POPUPVIEW); } adjustWidth(e) { const { isNeedAdjustWidth } = this.options; if (!this.popupView) { return; } if (isNeedAdjustWidth === true) { this.resetListWidth(""); const width = this.popupView.element.outerWidth(); let maxW = this.element.outerWidth() || this.options.width; // BI-93885 最大列宽算法调整 if (maxW < 500) { if (width >= 500) { maxW = 500; } else if (width > maxW) { // 防止小数导致差那么一点 maxW = width + 1; } } // if (width > maxW + 80) { // maxW = maxW + 80; // } else if (width > maxW) { // maxW = width; // } this.resetListWidth(maxW < 100 ? 100 : maxW); } } adjustHeight() { } resetListHeight(h) { this._assertPopupView(); this.popupView.resetHeight && this.popupView.resetHeight(h); } resetListWidth(w) { this._assertPopupView(); this.popupView.resetWidth && this.popupView.resetWidth(w); } populate(items) { this._assertPopupView(); this.popupView.populate.apply(this.popupView, arguments); this.combo.populate && this.combo.populate.apply(this.combo, arguments); } _setEnable(arg) { super._setEnable(arguments); if (arg === true) { this.element.removeClass("base-disabled disabled"); } else if (arg === false) { this.element.addClass("base-disabled disabled"); } !arg && this.element.removeClass(this.options.hoverClass); !arg && this.isViewVisible() && this._hideView(); } setValue(v) { this.combo.setValue(v); if (isNull(this.popupView)) { this.options.popup.value = v; } else { this.popupView.setValue(v); } } getValue() { if (isNull(this.popupView)) { return this.options.popup.value; } else { return this.popupView.getValue(); } } isViewVisible() { return this.isEnabled() && this.combo.isEnabled() && !!this.popupView && this.popupView.isVisible(); } showView(e) { // 减少popup 调整宽高的次数 if (this.isEnabled() && this.combo.isEnabled() && !this.isViewVisible()) { this._popupView(e); } } hideView(e) { this._hideView(e); } getView() { return this.popupView; } getPopupPosition() { return this.position; } toggle() { this._toggle(); } destroyed() { Widget._renderEngine.createElement(document) .unbind("click." + this.getName()) .unbind("mousedown." + this.getName()) .unbind("mouseenter." + this.getName()) .unbind("mouseleave." + this.getName()); Widget._renderEngine.createElement(window) .unbind("blur." + this.getName()); this.popper && this.popper.destroy(); this.popper = null; this.popupView && this.popupView._destroy(); } }