META-INF.resources.primefaces.overlaypanel.overlaypanel.js Maven / Gradle / Ivy
/**
* __PrimeFaces OverlayPanel Widget__
*
* OverlayPanel is a generic panel component that can be displayed on top of other content.
*
* @typedef PrimeFaces.widget.OverlayPanel.OnShowCallback Callback that is invoked when the panel is shown. The overlay
* panel widget instance ins passed as the this context.
* @this {PrimeFaces.widget.OverlayPanel} PrimeFaces.widget.OverlayPanel.OnShowCallback
*
* @typedef PrimeFaces.widget.OverlayPanel.OnHideCallback Callback that is invoked when the panel is hidden. The data table
* widget instance ins passed as the this context.
* @this {PrimeFaces.widget.OverlayPanel} PrimeFaces.widget.OverlayPanel.OnHideCallback
*
* @prop {JQuery} closerIcon The DOM element for the icon that closes the overlay panel.
* @prop {JQuery} content The DOM element for the content of the overlay panel.
* @prop {PrimeFaces.UnbindCallback} [hideOverlayHandler] Unbind callback for the hide overlay handler.
* @prop {boolean} loaded When dynamic loading is enabled, whether the content was already loaded.
* @prop {PrimeFaces.UnbindCallback} [resizeHandler] Unbind callback for the resize handler.
* @prop {PrimeFaces.UnbindCallback} [scrollHandler] Unbind callback for the scroll handler.
* @prop {number} showTimeout The set-timeout timer ID of the timer used for showing the overlay panel.
* @prop {JQuery} target The DOM element for the target component that triggers this overlay panel.
* @prop {JQuery} targetElement The DOM element for the resolved target component that triggers this overlay panel.
* @prop {number} targetZindex The z-index of the target component that triggers this overlay panel.
* @prop {PrimeFaces.CssTransitionHandler | null} [transition] Handler for CSS transitions used by this widget.
* @prop {boolean} allowHide Variable used to control whether the overlay is being hovered in autoHide mode
*
* @interface {PrimeFaces.widget.OverlayPanelCfg} cfg The configuration for the {@link OverlayPanel| OverlayPanel widget}.
* You can access this configuration via {@link PrimeFaces.widget.BaseWidget.cfg|BaseWidget.cfg}. Please note that this
* configuration is usually meant to be read-only and should not be modified.
* @extends {PrimeFaces.widget.DynamicOverlayWidgetCfg} cfg
*
* @prop {string} cfg.appendTo Appends the overlayPanel to the given search expression.
* @prop {string} cfg.autoHide Whether to hide overlay when hovering over overlay content when using custom show/hide.
* @prop {string} cfg.at Position of the target relative to the panel.
* @prop {boolean} cfg.dynamic `true` to load the content via AJAX when the overlay panel is opened, `false` to load
* the content immediately.
* @prop {boolean} cfg.cache Only relevant for dynamic="true": Defines if activating the panel should load the contents from server again. For cache="true" (default) the panel content is only loaded once.
* @prop {string} cfg.hideEvent Event on target to hide the panel.
* @prop {string} cfg.collision When the positioned element overflows the window in some direction, move it to an
* alternative position. Similar to my and at, this accepts a single value or a pair for horizontal/vertical, e.g.,
* `flip`, `fit`, `fit flip`, `fit none`.
* @prop {boolean} cfg.dismissable When set `true`, clicking outside of the panel hides the overlay.
* @prop {boolean} cfg.modal Specifies whether the document should be shielded with a partially transparent mask to
* require the user to close the panel before being able to activate any elements in the document.
* @prop {string} cfg.my Position of the panel relative to the target.
* @prop {PrimeFaces.widget.OverlayPanel.OnHideCallback} cfg.onHide Client side callback to execute when the panel is
* shown.
* @prop {PrimeFaces.widget.OverlayPanel.OnShowCallback} cfg.onShow Client side callback to execute when the panel is
* hidden.
* @prop {boolean} cfg.showCloseIcon Displays a close icon to hide the overlay, default is `false`.
* @prop {number} cfg.showDelay Delay in milliseconds applied when the overlay panel is shown.
* @prop {string} cfg.showEvent Event on target to hide the panel.
* @prop {string} cfg.target Search expression for target component to display panel next to.
*/
PrimeFaces.widget.OverlayPanel = PrimeFaces.widget.DynamicOverlayWidget.extend({
/**
* @override
* @inheritdoc
* @param {PrimeFaces.PartialWidgetCfg} cfg
*/
init: function(cfg) {
if (cfg.target) {
this.target = PrimeFaces.expressions.SearchExpressionFacade.resolveComponentsAsSelector(cfg.target);
if (this.target.hasClass('ui-splitbutton')) {
this.target = this.target.find('.ui-splitbutton-menubutton');
}
}
this._super(cfg, null, null, this.target);
this.content = this.jq.children('div.ui-overlaypanel-content');
//configuration
this.cfg.my = this.cfg.my || 'left top';
this.cfg.at = this.cfg.at || 'left bottom';
this.cfg.collision = this.cfg.collision || 'flip';
this.cfg.showEvent = this.cfg.showEvent || 'click.ui-overlaypanel';
this.cfg.hideEvent = this.cfg.hideEvent || 'click.ui-overlaypanel';
this.cfg.dismissable = (this.cfg.dismissable === false) ? false : true;
this.cfg.showDelay = PrimeFaces.utils.defaultNumeric(this.cfg.showDelay, 0);
this.cfg.autoHide = (this.cfg.autoHide === undefined) ? true : this.cfg.autoHide;
this.cfg.cache = this.cfg.cache === false ? false : true;
this.allowHide = true;
if (this.cfg.showCloseIcon) {
this.closerIcon = $('')
.attr('aria-label', PrimeFaces.getAriaLabel('overlaypanel.CLOSE')).appendTo(this.jq);
}
this.bindCommonEvents();
if (this.target) {
this.bindTargetEvents();
// set aria attributes
this.target.attr({
'aria-expanded': false,
'aria-controls': this.id
});
}
this.transition = PrimeFaces.utils.registerCSSTransition(this.jq, 'ui-connected-overlay');
},
/**
* @override
* @inheritdoc
* @param {PrimeFaces.PartialWidgetCfg} cfg
*/
refresh: function(cfg) {
this._cleanup();
this._super(cfg);
},
/**
* @override
* @inheritdoc
*/
destroy: function() {
this._super();
this._cleanup();
},
/**
* Clean up this widget and remove elements from DOM.
* @private
*/
_cleanup: function() {
// fix #4307
this.loaded = false;
// see #setupDialogSupport
if (!this.cfg.appendTo) {
PrimeFaces.utils.removeDynamicOverlay(this, this.jq, this.id, $(document.body));
}
this.jq.remove();
},
/**
* Sets up the event listeners for the target component that triggers this overlay panel.
* @private
*/
bindTargetEvents: function() {
var $this = this;
//mark target and descandants of target as a trigger for a primefaces overlay
this.target.data('primefaces-overlay-target', this.id).find('*').data('primefaces-overlay-target', this.id);
//show and hide events for target
if (this.cfg.showEvent === this.cfg.hideEvent) {
var event = this.cfg.showEvent;
this.target.on(event, function(e) {
$this.toggle();
});
}
else {
var showEvent = this.cfg.showEvent + '.ui-overlaypanel',
hideEvent = this.cfg.hideEvent + '.ui-overlaypanel';
this.target.off(showEvent + ' ' + hideEvent).on(showEvent, function(e) {
if (!$this.isVisible()) {
$this.show();
if (showEvent === 'contextmenu.ui-overlaypanel') {
e.preventDefault();
}
}
})
.on(hideEvent, function(e) {
clearTimeout($this.showTimeout);
if ($this.isVisible()) {
// GitHub #8546
if (!$this.isAutoHide() && $(e.relatedTarget).is('div.ui-overlaypanel-content')) {
$this.allowHide = false;
return;
}
$this.hide();
}
});
}
$this.target.off('keydown.ui-overlaypanel keyup.ui-overlaypanel')
.on('keydown.ui-overlaypanel', PrimeFaces.utils.blockEnterKey)
.on('keyup.ui-overlaypanel', function(e) {
if (e.key === 'Enter') {
$this.toggle();
e.preventDefault();
}
});
this.bindAutoHide();
},
/**
* Sets up mouse listeners if autoHide is disabled to keep the overlay open if overlay has focus.
* @private
*/
bindAutoHide: function() {
if (this.isAutoHide()) {
return;
}
var $this = this;
this.jq.off("mouseenter.tooltip mouseleave.tooltip")
.on("mouseenter.tooltip", function(e) {
$this.allowHide = false;
})
.on("mouseleave.tooltip", function(e) {
if ($(e.relatedTarget).is($this.target)) {
return;
}
$this.allowHide = true;
$this.hide();
});
},
/**
* Sets up some common event listeners always required by this widget.
* @private
*/
bindCommonEvents: function() {
var $this = this;
if (this.cfg.showCloseIcon) {
this.closerIcon.on('mouseover.ui-overlaypanel', function() {
$(this).addClass('ui-state-hover');
})
.on('mouseout.ui-overlaypanel', function() {
$(this).removeClass('ui-state-hover');
})
.on('click.ui-overlaypanel', function(e) {
$this.hide();
e.preventDefault();
})
.on('focus.ui-overlaypanel', function() {
$(this).addClass('ui-state-focus');
})
.on('blur.ui-overlaypanel', function() {
$(this).removeClass('ui-state-focus');
});
}
},
/**
* Sets up all panel event listeners
* @private
*/
bindPanelEvents: function() {
var $this = this;
//hide overlay when mousedown is at outside of overlay
if (this.cfg.dismissable && !this.cfg.modal) {
// anything focused outside the overlay will close it
var eventNamespace = 'keyup.' + this.id + '_hide mousedown.' + this.id + '_hide';
this.hideOverlayHandler = PrimeFaces.utils.registerHideOverlayHandler(this, eventNamespace, this.jq,
function() { return $this.target; },
function(e, eventTarget) {
if (!($this.jq.is(eventTarget) || $this.jq.has(eventTarget).length > 0 || eventTarget.closest('.ui-input-overlay').length > 0)) {
$this.hide();
}
});
}
this.resizeHandler = PrimeFaces.utils.registerResizeHandler(this, 'resize.' + this.id + '_hide', this.jq, function() {
$this.handleViewportChange();
});
this.scrollHandler = PrimeFaces.utils.registerConnectedOverlayScrollHandler(this, 'scroll.' + this.id + '_hide', this.target, function() {
$this.handleViewportChange();
});
},
/**
* Fired when the browser viewport is resized or scrolled. In Mobile environment we don't want to hider the overlay
* we want to re-align it. This is because on some mobile browser the popup may force the browser to trigger a
* resize immediately and close the overlay. See GitHub #7075.
* @private
*/
handleViewportChange: function() {
if (PrimeFaces.env.mobile || PrimeFaces.hideOverlaysOnViewportChange === false) {
this.align(this.target);
} else {
this.hide();
}
},
/**
* Unbind all panel event listeners
* @private
*/
unbindPanelEvents: function() {
if (this.hideOverlayHandler) {
this.hideOverlayHandler.unbind();
}
if (this.resizeHandler) {
this.resizeHandler.unbind();
}
if (this.scrollHandler) {
this.scrollHandler.unbind();
}
},
/**
* Brings up the overlay panel if it is currently hidden, or hides it if it is currently displayed.
*/
toggle: function() {
if (!this.isVisible()) {
this.show();
}
else {
clearTimeout(this.showTimeout);
this.hide();
}
},
/**
* Brings up the overlay panel so that is displayed and visible.
* @param {string | JQuery} [target] ID or DOM element of the target component that triggers this overlay panel.
*/
show: function(target) {
if (this.isVisible()) {
return;
}
var thisPanel = this;
this.showTimeout = setTimeout(function() {
if (!thisPanel.loaded && thisPanel.cfg.dynamic) {
thisPanel.loadContents(target);
}
else {
thisPanel._show(target);
}
}, this.cfg.showDelay);
},
/**
* Makes the overlay panel visible.
* @private
* @param {string | JQuery} [target] ID or DOM element of the target component that triggers this overlay panel.
*/
_show: function(target) {
var $this = this;
if (this.transition) {
var showWithCSSTransition = function() {
$this.transition.show({
onEnter: function() {
$this.jq.css('z-index', PrimeFaces.nextZindex());
$this.align(target);
},
onEntered: function() {
$this.bindPanelEvents();
$this.postShow();
if ($this.cfg.modal) {
$this.enableModality();
}
}
});
};
var targetEl = this.getTarget(target);
if (this.isVisible() && this.targetElement && !this.targetElement.is(targetEl)) {
this.hide(function() {
showWithCSSTransition();
});
}
else {
showWithCSSTransition();
}
}
},
/**
* Get new target element using selector param.
* @private
* @param {string | JQuery} [target] ID or DOM element of the target component that triggers this overlay panel.
* @return {JQuery|null} DOM Element or null
*/
getTarget: function(target) {
if (target) {
if (typeof target === 'string') {
return $(document.getElementById(target));
}
else if (target instanceof $) {
return target;
}
}
else if (this.target) {
return this.target;
}
return null;
},
/**
* Aligns the overlay panel so that it is shown at the correct position.
* @private
* @param {string | JQuery} [target] ID or DOM element of the target component that triggers this overlay panel.
*/
align: function(target) {
var win = $(window),
allowedNegativeValuesByParentOffset = this.jq.offsetParent().offset();
this.targetElement = this.getTarget(target);
if (this.targetElement.hasClass('ui-splitbutton-menubutton')) {
this.targetElement = this.targetElement.parent();
}
if (this.targetElement) {
this.targetZindex = this.targetElement.zIndex();
}
this.jq.css({ 'left': '', 'top': '', 'transform-origin': 'center top' })
.position({
my: this.cfg.my
, at: this.cfg.at
, of: this.targetElement
, collision: this.cfg.collision
, using: function(pos, directions) {
if (pos.top < -allowedNegativeValuesByParentOffset.top) {
pos.top = -allowedNegativeValuesByParentOffset.top;
}
if (pos.left < -allowedNegativeValuesByParentOffset.left) {
pos.left = -allowedNegativeValuesByParentOffset.left;
}
$(this).css('transform-origin', 'center ' + directions.vertical).css(pos);
}
});
var widthOffset = this.jq.width() - this.content.width();
this.jq.css('max-width', win.width() - widthOffset + 'px');
},
/**
* Hides this overlay panel so that it is not displayed anymore.
* @param {() => void} [callback] Custom callback that is invoked after this overlay panel was closed.
*/
hide: function(callback) {
if (this.transition) {
var $this = this;
this.transition.hide({
onExit: function() {
$this.unbindPanelEvents();
},
onExited: function() {
if ($this.cfg.modal) {
$this.disableModality();
}
$this.postHide();
if (callback) {
callback();
}
}
});
}
},
/**
* Callback that is invoked after this overlay panel was opened.
* @private
*/
postShow: function() {
this.callBehavior('show');
PrimeFaces.invokeDeferredRenders(this.id);
if (this.cfg.onShow) {
this.cfg.onShow.call(this);
}
this.applyFocus();
if (this.target) {
this.target.attr('aria-expanded', true);
}
},
/**
* Callback that is invoked after this overlay panel was closed.
* @private
*/
postHide: function() {
this.callBehavior('hide');
if (this.cfg.onHide) {
this.cfg.onHide.call(this);
}
if (this.target) {
this.target.attr('aria-expanded', false);
}
},
/**
* Loads the contents of this overlay panel dynamically via AJAX, if dynamic loading is enabled.
* @private
* @param {string | JQuery} [target] ID or DOM element of the target component that triggers this overlay panel.
*/
loadContents: function(target) {
var $this = this,
options = {
source: this.id,
process: this.id,
update: this.id,
ignoreAutoUpdate: true,
params: [
{ name: this.id + '_contentLoad', value: true }
],
onsuccess: function(responseXML, status, xhr) {
PrimeFaces.ajax.Response.handle(responseXML, status, xhr, {
widget: $this,
handle: function(content) {
this.content.html(content);
this.loaded = this.cfg.cache;
}
});
return true;
},
oncomplete: function() {
$this._show(target);
}
};
if(this.hasBehavior('loadContent')) {
this.callBehavior('loadContent', options);
}
else {
PrimeFaces.ajax.Request.handle(options);
}
},
/**
* Checks whether this overlay panel is currently visible.
* @return {boolean} `true` if this overlay panel is currently displayed, or `false` otherwise.
*/
isVisible: function() {
return this.jq.is(':visible');
},
/**
* Applies focus to the first focusable element of the content in the panel.
*/
applyFocus: function() {
this.jq.find(':not(:submit):not(:button):input:visible:enabled:first').trigger('focus');
},
/**
* @override
* @inheritdoc
*/
enableModality: function() {
this._super();
if (this.targetElement) {
this.targetElement.css('z-index', String(this.jq.css('z-index')));
}
},
/**
* @override
* @inheritdoc
*/
disableModality: function() {
this._super();
if (this.targetElement) {
this.targetElement.css('z-index', String(this.targetZindex));
}
},
/**
* @override
* @inheritdoc
* @return {JQuery}
*/
getModalTabbables: function() {
var tabbables = this.jq.find(':tabbable');
if (this.targetElement && this.targetElement.is(':tabbable')) {
tabbables = tabbables.add(this.targetElement);
}
return tabbables;
},
/**
* Checks if the target has the autoHide property enabled or disabled to keep the overlay open.
* @return {boolean} Whether this overlay should be left showing or closed.
*/
isAutoHide: function() {
return this.jq.data('autohide') || this.cfg.autoHide;
}
});
© 2015 - 2024 Weber Informatics LLC | Privacy Policy