devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
689 lines (584 loc) • 22.2 kB
JavaScript
"use strict";
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
var $ = require("../core/renderer"),
windowUtils = require("../core/utils/window"),
window = windowUtils.getWindow(),
domAdapter = require("../core/dom_adapter"),
eventsEngine = require("../events/core/events_engine"),
registerComponent = require("../core/component_registrator"),
stringUtils = require("../core/utils/string"),
extend = require("../core/utils/extend").extend,
translator = require("../animation/translator"),
positionUtils = require("../animation/position"),
noop = require("../core/utils/common").noop,
typeUtils = require("../core/utils/type"),
mathUtils = require("../core/utils/math"),
eventUtils = require("../events/utils"),
Popup = require("./popup");
var POPOVER_CLASS = "dx-popover",
POPOVER_WRAPPER_CLASS = "dx-popover-wrapper",
POPOVER_ARROW_CLASS = "dx-popover-arrow",
POPOVER_WITHOUT_TITLE_CLASS = "dx-popover-without-title",
POSITION_FLIP_MAP = {
"left": "right",
"top": "bottom",
"right": "left",
"bottom": "top",
"center": "center"
},
WEIGHT_OF_SIDES = {
"left": -1,
"top": -1,
"center": 0,
"right": 1,
"bottom": 1
},
POSITION_ALIASES = {
// NOTE: public API
"top": { my: "bottom center", at: "top center", collision: "fit flip" },
"bottom": { my: "top center", at: "bottom center", collision: "fit flip" },
"right": { my: "left center", at: "right center", collision: "flip fit" },
"left": { my: "right center", at: "left center", collision: "flip fit" }
},
SIDE_BORDER_WIDTH_STYLES = {
"left": "borderLeftWidth",
"top": "borderTopWidth",
"right": "borderRightWidth",
"bottom": "borderBottomWidth"
},
getEventName = function getEventName(that, optionName) {
var optionValue = that.option(optionName);
return getEventNameByOption(optionValue);
},
getEventNameByOption = function getEventNameByOption(optionValue) {
return typeUtils.isObject(optionValue) ? optionValue.name : optionValue;
},
getEventDelay = function getEventDelay(that, optionName) {
var optionValue = that.option(optionName);
return typeUtils.isObject(optionValue) && optionValue.delay;
},
attachEvent = function attachEvent(that, name) {
var delay,
action,
handler,
eventName,
target = that.option("target"),
event = getEventName(that, name + "Event");
if (!event || that.option("disabled")) {
return;
}
eventName = eventUtils.addNamespace(event, that.NAME);
action = that._createAction(function () {
delay = getEventDelay(that, name + "Event");
this._clearEventTimeout(name === "hide");
if (delay) {
this._timeouts[name] = setTimeout(function () {
that[name]();
}, delay);
} else {
that[name]();
}
}.bind(that), { validatingTargetName: "target" });
handler = function handler(e) {
action({ event: e, target: $(e.currentTarget) });
};
if (target.jquery || target.nodeType || typeUtils.isWindow(target)) {
that["_" + name + "EventHandler"] = undefined;
eventsEngine.on(target, eventName, handler);
} else {
that["_" + name + "EventHandler"] = handler;
eventsEngine.on(domAdapter.getDocument(), eventName, target, handler);
}
},
detachEvent = function detachEvent(that, target, name, event) {
var eventName = event || getEventName(that, name + "Event");
if (!eventName) {
return;
}
eventName = eventUtils.addNamespace(eventName, that.NAME);
if (that["_" + name + "EventHandler"]) {
eventsEngine.off(domAdapter.getDocument(), eventName, target, that["_" + name + "EventHandler"]);
} else {
eventsEngine.off($(target), eventName);
}
};
/**
* @name dxPopover
* @publicName dxPopover
* @inherits dxPopup
* @module ui/popover
* @export default
*/
var Popover = Popup.inherit({
_getDefaultOptions: function _getDefaultOptions() {
return extend(this.callBase(), {
/**
* @name dxPopoverOptions.target
* @publicName target
* @type string|Node|jQuery
*/
target: window,
/**
* @name dxPopoverOptions.shading
* @type boolean
* @publicName shading
* @default false
* @inheritdoc
*/
shading: false,
/**
* @name dxPopoverOptions.position
* @publicName position
* @type Enums.Position|positionConfig
* @default 'bottom'
*/
position: 'bottom',
/**
* @name dxPopoverOptions.closeOnOutsideClick
* @publicName closeOnOutsideClick
* @default true
* @inheritdoc
*/
closeOnOutsideClick: true,
/**
* @name dxPopoverOptions.animation
* @publicName animation
* @type object
* @default { show: { type: "fade", from: 0, to: 1 }, hide: { type: "fade", to: 0 } }
*/
animation: {
/**
* @name dxPopoverOptions.animation.show
* @publicName show
* @type animationConfig
* @default { type: "fade", from: 0, to: 1 }
*/
show: {
type: "fade",
from: 0,
to: 1
},
/**
* @name dxPopoverOptions.animation.hide
* @publicName hide
* @type animationConfig
* @default { type: "fade", to: 0 }
*/
hide: {
type: "fade",
to: 0
}
},
/**
* @name dxPopoverOptions.showtitle
* @publicName showTitle
* @type boolean
* @default false
*/
showTitle: false,
/**
* @name dxPopoverOptions.width
* @publicName width
* @type number|string|function
* @default "auto"
* @type_function_return number|string
*/
width: "auto",
/**
* @name dxPopoverOptions.height
* @publicName height
* @type number|string|function
* @default "auto"
* @type_function_return number|string
*/
height: "auto",
/**
* @name dxPopoverOptions.dragEnabled
* @publicName dragEnabled
* @hidden
* @inheritdoc
*/
dragEnabled: false,
/**
* @name dxPopoverOptions.resizeEnabled
* @publicName resizeEnabled
* @hidden
* @inheritdoc
*/
resizeEnabled: false,
/**
* @name dxPopoverOptions.onResizeStart
* @publicName onResizeStart
* @extends Action
* @action
* @hidden
* @inheritdoc
*/
/**
* @name dxPopoverOptions.onResize
* @publicName onResize
* @extends Action
* @action
* @hidden
* @inheritdoc
*/
/**
* @name dxPopoverOptions.onResizeEnd
* @publicName onResizeEnd
* @extends Action
* @action
* @hidden
* @inheritdoc
*/
/**
* @name dxPopoverOptions.fullScreen
* @publicName fullScreen
* @hidden
* @inheritdoc
*/
/**
* @name dxPopoverOptions.showEvent
* @publicName showEvent
* @type Object|string
* @default undefined
*/
/**
* @name dxPopoverOptions.showEvent.name
* @publicName name
* @type string
* @default undefined
*/
/**
* @name dxPopoverOptions.showEvent.delay
* @publicName delay
* @type number
* @default undefined
*/
/**
* @name dxPopoverOptions.hideEvent
* @publicName hideEvent
* @type Object|string
* @default undefined
*/
/**
* @name dxPopoverOptions.hideEvent.name
* @publicName name
* @type string
* @default undefined
*/
/**
* @name dxPopoverOptions.hideEvent.delay
* @publicName delay
* @type number
* @default undefined
*/
fullScreen: false,
closeOnTargetScroll: true,
arrowPosition: "",
arrowOffset: 0,
boundaryOffset: { h: 10, v: 10
/**
* @name dxPopoverOptions.focusStateEnabled
* @publicName focusStateEnabled
* @hidden
* @inheritdoc
*/
/**
* @name dxPopoverOptions.accessKey
* @publicName accessKey
* @hidden
* @inheritdoc
*/
/**
* @name dxPopoverOptions.tabIndex
* @publicName tabIndex
* @hidden
* @inheritdoc
*/
} });
},
_defaultOptionsRules: function _defaultOptionsRules() {
return [{
device: { platform: "ios" },
options: {
arrowPosition: {
boundaryOffset: { h: 20, v: -10 },
collision: "fit"
}
}
}, {
device: function device() {
return !windowUtils.hasWindow();
},
options: {
animation: null
}
}];
},
_init: function _init() {
this.callBase();
this._renderArrow();
this._timeouts = {};
this.$element().addClass(POPOVER_CLASS);
this._wrapper().addClass(POPOVER_WRAPPER_CLASS);
},
_render: function _render() {
this.callBase.apply(this, arguments);
this._detachEvents(this.option("target"));
this._attachEvents();
},
_detachEvents: function _detachEvents(target) {
detachEvent(this, target, "show");
detachEvent(this, target, "hide");
},
_attachEvents: function _attachEvents() {
attachEvent(this, "show");
attachEvent(this, "hide");
},
_renderArrow: function _renderArrow() {
this._$arrow = $("<div>").addClass(POPOVER_ARROW_CLASS).prependTo(this.overlayContent());
},
_documentDownHandler: function _documentDownHandler(e) {
if (this._isOutsideClick(e)) {
return this.callBase(e);
}
return true;
},
_isOutsideClick: function _isOutsideClick(e) {
return !$(e.target).closest(this.option("target")).length;
},
_animate: function _animate(animation) {
if (animation && animation.to && _typeof(animation.to) === "object") {
extend(animation.to, {
position: this._getContainerPosition()
});
}
this.callBase.apply(this, arguments);
},
_stopAnimation: function _stopAnimation() {
this.callBase.apply(this, arguments);
},
_renderTitle: function _renderTitle() {
this._wrapper().toggleClass(POPOVER_WITHOUT_TITLE_CLASS, !this.option("showTitle"));
this.callBase();
},
_renderPosition: function _renderPosition() {
this.callBase();
this._renderOverlayPosition();
},
_renderOverlayBoundaryOffset: noop,
_renderOverlayPosition: function _renderOverlayPosition() {
this._resetOverlayPosition();
this._updateContentSize();
var contentPosition = this._getContainerPosition();
var resultLocation = positionUtils.setup(this._$content, contentPosition);
var positionSide = this._getSideByLocation(resultLocation);
this._togglePositionClass("dx-position-" + positionSide);
this._toggleFlippedClass(resultLocation.h.flip, resultLocation.v.flip);
this._renderArrowPosition(positionSide);
},
_resetOverlayPosition: function _resetOverlayPosition() {
this._setContentHeight(true);
this._togglePositionClass("dx-position-" + this._positionSide);
translator.move(this._$content, { left: 0, top: 0 });
this._$arrow.css({
top: 'auto', right: 'auto', bottom: 'auto', left: 'auto'
});
},
_updateContentSize: function _updateContentSize() {
if (!this._$popupContent) {
return;
}
var containerLocation = positionUtils.calculate(this._$content, this._getContainerPosition());
if (containerLocation.h.oversize > 0 && this._isHorizontalSide() && !containerLocation.h.fit) {
var newContainerWidth = this._$content.width() - containerLocation.h.oversize;
this._$content.width(newContainerWidth);
}
if (containerLocation.v.oversize > 0 && this._isVerticalSide() && !containerLocation.v.fit) {
var newOverlayContentHeight = this._$content.height() - containerLocation.v.oversize,
newPopupContentHeight = this._$popupContent.height() - containerLocation.v.oversize;
this._$content.height(newOverlayContentHeight);
this._$popupContent.height(newPopupContentHeight);
}
},
_getContainerPosition: function _getContainerPosition() {
var offset = stringUtils.pairToObject(this._position.offset || "");
var hOffset = offset.h;
var vOffset = offset.v;
var isPopoverInside = this._isPopoverInside();
var sign = (isPopoverInside ? -1 : 1) * WEIGHT_OF_SIDES[this._positionSide];
var arrowSizeCorrection = this._getContentBorderWidth(this._positionSide);
if (this._isVerticalSide()) {
vOffset += sign * (this._$arrow.height() - arrowSizeCorrection);
} else if (this._isHorizontalSide()) {
hOffset += sign * (this._$arrow.width() - arrowSizeCorrection);
}
return extend({}, this._position, { offset: hOffset + " " + vOffset });
},
_getContentBorderWidth: function _getContentBorderWidth(side) {
var borderWidth = this._$content.css(SIDE_BORDER_WIDTH_STYLES[side]);
return parseInt(borderWidth) || 0;
},
_getSideByLocation: function _getSideByLocation(location) {
var isFlippedByVertical = location.v.flip;
var isFlippedByHorizontal = location.h.flip;
return this._isVerticalSide() && isFlippedByVertical || this._isHorizontalSide() && isFlippedByHorizontal || this._isPopoverInside() ? POSITION_FLIP_MAP[this._positionSide] : this._positionSide;
},
_togglePositionClass: function _togglePositionClass(positionClass) {
this._$wrapper.removeClass("dx-position-left dx-position-right dx-position-top dx-position-bottom").addClass(positionClass);
},
_toggleFlippedClass: function _toggleFlippedClass(isFlippedHorizontal, isFlippedVertical) {
this._$wrapper.toggleClass("dx-popover-flipped-horizontal", isFlippedHorizontal).toggleClass("dx-popover-flipped-vertical", isFlippedVertical);
},
_renderArrowPosition: function _renderArrowPosition(side) {
this._$arrow.css(POSITION_FLIP_MAP[side], -(this._isVerticalSide(side) ? this._$arrow.height() : this._$arrow.width()));
var axis = this._isVerticalSide(side) ? "left" : "top";
var sizeProperty = this._isVerticalSide(side) ? "outerWidth" : "outerHeight";
var $target = $(this._position.of);
var targetOffset = positionUtils.offset($target) || { top: 0, left: 0 };
var contentOffset = positionUtils.offset(this._$content);
var arrowSize = this._$arrow[sizeProperty]();
var contentLocation = contentOffset[axis];
var contentSize = this._$content[sizeProperty]();
var targetLocation = targetOffset[axis];
var targetSize = $target.get(0).preventDefault ? 0 : $target[sizeProperty]();
var min = Math.max(contentLocation, targetLocation);
var max = Math.min(contentLocation + contentSize, targetLocation + targetSize);
var arrowLocation;
if (this.option("arrowPosition") === "start") {
arrowLocation = min - contentLocation;
} else if (this.option("arrowPosition") === "end") {
arrowLocation = max - contentLocation - arrowSize;
} else {
arrowLocation = (min + max) / 2 - contentLocation - arrowSize / 2;
}
var borderWidth = this._getContentBorderWidth(side);
var finalArrowLocation = mathUtils.fitIntoRange(arrowLocation - borderWidth + this.option("arrowOffset"), borderWidth, contentSize - arrowSize - borderWidth * 2);
this._$arrow.css(axis, finalArrowLocation);
},
_isPopoverInside: function _isPopoverInside() {
var position = this._transformStringPosition(this.option("position"), POSITION_ALIASES);
var my = positionUtils.setup.normalizeAlign(position.my);
var at = positionUtils.setup.normalizeAlign(position.at);
return my.h === at.h && my.v === at.v;
},
_setContentHeight: function _setContentHeight(fullUpdate) {
if (fullUpdate) {
this.callBase();
}
},
_renderShadingPosition: function _renderShadingPosition() {
if (this.option("shading")) {
this._$wrapper.css({ top: 0, left: 0 });
}
},
_renderShadingDimensions: function _renderShadingDimensions() {
if (this.option("shading")) {
this._$wrapper.css({
width: "100%",
height: "100%"
});
}
},
_normalizePosition: function _normalizePosition() {
var position = extend({}, this._transformStringPosition(this.option("position"), POSITION_ALIASES));
if (!position.of) {
position.of = this.option("target");
}
if (!position.collision) {
position.collision = "flip";
}
if (!position.boundaryOffset) {
position.boundaryOffset = this.option("boundaryOffset");
}
this._positionSide = this._getDisplaySide(position);
this._position = position;
},
_getDisplaySide: function _getDisplaySide(position) {
var my = positionUtils.setup.normalizeAlign(position.my),
at = positionUtils.setup.normalizeAlign(position.at);
var weightSign = WEIGHT_OF_SIDES[my.h] === WEIGHT_OF_SIDES[at.h] && WEIGHT_OF_SIDES[my.v] === WEIGHT_OF_SIDES[at.v] ? -1 : 1,
horizontalWeight = Math.abs(WEIGHT_OF_SIDES[my.h] - weightSign * WEIGHT_OF_SIDES[at.h]),
verticalWeight = Math.abs(WEIGHT_OF_SIDES[my.v] - weightSign * WEIGHT_OF_SIDES[at.v]);
return horizontalWeight > verticalWeight ? at.h : at.v;
},
_isVerticalSide: function _isVerticalSide(side) {
side = side || this._positionSide;
return side === "top" || side === "bottom";
},
_isHorizontalSide: function _isHorizontalSide(side) {
side = side || this._positionSide;
return side === "left" || side === "right";
},
_clearEventTimeout: function _clearEventTimeout(visibility) {
clearTimeout(this._timeouts[visibility ? "show" : "hide"]);
},
_clean: function _clean() {
this._detachEvents(this.option("target"));
this.callBase.apply(this, arguments);
},
_optionChanged: function _optionChanged(args) {
switch (args.name) {
case "showTitle":
case "title":
case "titleTemplate":
this.callBase(args);
this._renderGeometry();
break;
case "boundaryOffset":
case "arrowPosition":
case "arrowOffset":
this._renderGeometry();
break;
case "fullScreen":
if (args.value) {
this.option("fullScreen", false);
}
break;
case "target":
args.previousValue && this._detachEvents(args.previousValue);
this.callBase(args);
break;
case "showEvent":
case "hideEvent":
var name = args.name.substring(0, 4),
event = getEventNameByOption(args.previousValue);
this.hide();
detachEvent(this, this.option("target"), name, event);
attachEvent(this, name);
break;
case "visible":
this._clearEventTimeout(args.value);
this.callBase(args);
break;
default:
this.callBase(args);
}
},
/**
* @name dxPopoverMethods.show
* @publicName show(target)
* @param1 target:string|Node|jQuery
* @return Promise<void>
*/
show: function show(target) {
if (target) {
this.option("target", target);
}
return this.callBase();
}
/**
* @name dxPopoverMethods.registerKeyHandler
* @publicName registerKeyHandler(key, handler)
* @hidden
* @inheritdoc
*/
/**
* @name dxPopoverMethods.focus
* @publicName focus()
* @hidden
* @inheritdoc
*/
});
registerComponent("dxPopover", Popover);
module.exports = Popover;