Popover menu: Refactor to remove the necessity of coupling JS and PHP code to instantiate it correctly

This commit is contained in:
Molkobain
2021-03-22 13:21:31 +01:00
parent e8656e8504
commit 65ed5b3fce
14 changed files with 409 additions and 137 deletions

View File

@@ -40,19 +40,20 @@ $(function()
_initializePopoverMenu: function()
{
var me = this;
// Important: For now, the popover menu is manually instantiated even though the PHP NewsroomMenu class inherits PopoverMenu because the jQuery widget doesn't. We might refactor this in the future.
$(me.element).popover_menu({'toggler': this.js_selectors.menu_toggler});
$(this.js_selectors.menu_toggler).on('click', function(oEvent) {
$(this.js_selectors.menu_toggler).on('click', function (oEvent) {
var oEventTarget = $(oEvent.target);
var aEventTargetPos = oEventTarget.position();
var aEventTargetOffset = oEventTarget.offset();
$iHeight = Math.abs(aEventTargetOffset.top - 100);
$iHeight = Math.abs(aEventTargetOffset.top-100);
$(me.element).css({
'max-height': $iHeight + 'px',
'top': (aEventTargetPos.top + parseInt(oEventTarget.css('marginTop'), 10) - Math.min($(me.element).height(), $iHeight)) + 'px',
'left': (aEventTargetPos.left + parseInt(oEventTarget.css('marginLeft'), 10) + oEventTarget.width()) + 'px',
'max-height': $iHeight+'px',
'top': (aEventTargetPos.top+parseInt(oEventTarget.css('marginTop'), 10)-Math.min($(me.element).height(), $iHeight))+'px',
'left': (aEventTargetPos.left+parseInt(oEventTarget.css('marginLeft'), 10)+oEventTarget.width())+'px',
});
$(me.element).popover_menu("togglePopup");
});
this.element.addClass(this.css_classes.newsroom_menu);
$(this.js_selectors.menu_toggler).addClass('ibo-is-loaded');
@@ -214,7 +215,7 @@ $(function()
},
_buildNoMessageItem: function()
{
return '<div class="ibo-popover-menu--item ibo-popover-menu--item--no-message">' + Dict.S(this.options.labels.no_notification) +
return '<div class="ibo-popover-menu--item ibo-popover-menu--item--no-message">' + Dict.S(this.options.labels.no_notification) +
'<div class="ibo-popover-menu--item--no-message--image ibo-svg-illustration--container">' + this.options.no_message_icon + '</div></div>';
},
_buildSingleShowAllMessagesItem: function()
@@ -269,10 +270,10 @@ $(function()
sMessageSection += sNoMessageItem;
}
sMessageSection += '<hr class="ibo-popover-menu--item ibo-popover-menu--separator"></div>';
if (this.options.providers.length == 1)
{
var SingleShowAllMessagesItem = this._buildSingleShowAllMessagesItem();
var SingleShowAllMessagesItem = this._buildSingleShowAllMessagesItem();
sShowAllMessagesSection += SingleShowAllMessagesItem;
sShowAllMessagesSection += '</div>'
}
@@ -303,19 +304,14 @@ $(function()
// Add class to show there is no messages
$(this.js_selectors.menu_toggler).addClass(this.css_classes.empty);
}
if (this.options.providers.length != 1)
{
var oElem = $('[data-role="ibo-navigation-menu--notifications-show-all-multiple"]~[data-role="ibo-popover-menu"]');
oElem.popover_menu({'toggler': '[data-role="ibo-navigation-menu--notifications-show-all-multiple"]'});
$('[data-role="ibo-navigation-menu--notifications-show-all-multiple"]').on('click', function(oEvent){
var oEventTarget = $(oEvent.target);
var aEventTargetPos = oEventTarget.position();
oElem.css({
'left': (aEventTargetPos.left + parseInt(oEventTarget.css('marginLeft'), 10) + oEventTarget.width()) + 'px'
});
oElem.popover_menu("togglePopup");
if (this.options.providers.length != 1) {
var oElem = $('[data-role="ibo-navigation-menu--notifications-show-all-multiple"]~[data-role="ibo-popover-menu"]');
oElem.popover_menu({
'toggler': '[data-role="ibo-navigation-menu--notifications-show-all-multiple"]',
'position': {
'horizontal': "(oTargetPos.left+parseInt(oTargetElem.css('marginLeft'), 10)+(oTargetElem.outerWidth() / 2)-(oElem.outerWidth() / 2))+'px'",
},
});
}

View File

@@ -26,7 +26,26 @@ $(function()
// default options
options:
{
// Valid JS selector of the DOM element toggling the menu on click
toggler: '',
// Container element of the menu. Can be either 'parent' (default, better performance) or 'body' (use it if the menu gets cut by the hidden overflow of its parent).
container: 'parent',
// Position of the menu, relative to a DOM target element. Default target is 'toggler', but any valid JS selector is also accepted
position: {
// DOM element used to compute the menu relative position from. Value be 'toggler' to use the 'toggler' option or any valid JS selector.
target: 'toggler',
// Relative vertical position of the menu from the target. Value can be 'below' or 'above' for the menu to be strictly below/above the target,
// or a JS expression to be evaluated that must return pixels (eg. (oTargetPos.top + oTarget.outerHeight(true)) + 'px')
vertical: 'below',
// Relative horizontal position of the menu from the target. Value can be 'align_inner_left' or 'align_inner_right' for the menu to be aligned with the target border,
// or a JS expression to be evaluated that must return pixels (eg. (oTargetPos.left + oTarget.outerWidth(true) - popover.width()) + 'px')
// JS vars that can be used in the expression:
// - oElem
// - oTargetElem
// - oTargetPos
horizontal: 'align_inner_right',
},
add_visual_hint_to_toggler: false
},
css_classes:
{
@@ -41,12 +60,22 @@ $(function()
// the constructor
_create: function () {
this._bindEvents();
this._closePopup();
// Consistency checks
// - When target position set to 'toggler', ensure that a toggler is indeed set
if (('toggler' === this.options.position.target) && (false === this._hasToggler())) {
CombodoJSConsole.Error('Could not instantiate menu as the position target is set to "toggler" but no toggler set');
}
// Build markup
if (true === this.options.add_visual_hint_to_toggler) {
this._addVisualHintToToggler();
}
if ('body' === this.options.container) {
this.element.appendTo($('body'));
}
this._bindEvents();
this._closePopup();
},
// events bound via _bind are removed automatically
// revert other modifications here
@@ -56,6 +85,27 @@ $(function()
const me = this;
const oBodyElem = $('body');
// Toggler
if (true === this._hasToggler()) {
oBodyElem.find(this.options.toggler).on('click', function (oEvent) {
me._onTogglerClick(oEvent);
});
}
// Force menu to close on scroll when it is positioned on the body, otherwise it will not follow it's target and it will look buggy.
// Also, we decided not to update to position during scroll for to avoid performance drop.
if ('body' === this.options.container) {
// Important: This event is not bind using jQuery but the native method so we can set the "passive" option to minimize performance drops
// as the 'scroll' event is extremely CPU consuming.
// TODO 3.0.0: Make it work, event seems not to be triggered on user scroll
// window.addEventListener('scroll', function () {
// me._onBodyScroll();
// }, {
// passive: true
// })
}
// Menu items
this.element.find(this.js_selectors.item).on('click', function (oEvent) {
me._closePopup();
});
@@ -67,6 +117,61 @@ $(function()
},
// Events callbacks
_onTogglerClick: function (oEvent) {
// Avoid anchor / link default behavior
oEvent.preventDefault();
// Only recompute position when the menu is closed and about to be opened
if (false === this._isOpened()) {
const oTargetElem = ('toggler' === this.options.position.target) ? $(this.options.toggler) : $(this.options.position.target);
const oTargetPos = ('parent' === this.options.container) ? oTargetElem.position() : oTargetElem.offset();
let oNextCSSPosition = {
'z-index': 1,
};
const sVerticalPosExp = this.options.position.vertical;
const sHorizontalPosExp = this.options.position.horizontal;
// Position referential
if ('body' === this.options.container) {
oNextCSSPosition['position'] = 'fixed';
oNextCSSPosition['z-index'] = 30; // 30 to be above #ibo-page-container (10) and #ibo-navigation-menu (20)
}
// Vertical
if ('below' === sVerticalPosExp) {
oNextCSSPosition['top'] = (oTargetPos.top+oTargetElem.outerHeight())+'px';
} else if ('above' === sVerticalPosExp) {
oNextCSSPosition['top'] = (oTargetPos.top-this.element.outerHeight())+'px';
} else {
let oTmpFunction = eval('(oElem, oTargetElem, oTargetPos) => '+sVerticalPosExp);
oNextCSSPosition['top'] = oTmpFunction(this.element, oTargetElem, oTargetPos);
}
// Horizontal
if ('align_inner_left' === sHorizontalPosExp) {
oNextCSSPosition['left'] = (oTargetPos.left)+'px';
} else if ('align_outer_left' === sHorizontalPosExp) {
oNextCSSPosition['left'] = (oTargetPos.left-this.element.width())+'px';
} else if ('align_inner_right' === sHorizontalPosExp) {
oNextCSSPosition['left'] = (oTargetPos.left+oTargetElem.outerWidth(true)-this.element.width())+'px';
} else if ('align_outer_right' === sHorizontalPosExp) {
oNextCSSPosition['left'] = (oTargetPos.left+oTargetElem.outerWidth(true))+'px';
} else {
let oTmpFunction = eval('(oElem, oTargetElem, oTargetPos) => '+sHorizontalPosExp);
oNextCSSPosition['left'] = oTmpFunction(this.element, oTargetElem, oTargetPos);
}
this.element.css(oNextCSSPosition);
}
this.togglePopup();
},
_onBodyScroll: function () {
if (true === this._isOpened()) {
this._closePopup();
}
},
_onBodyClick: function (oEvent) {
if ($(oEvent.target.closest(this.js_selectors.menu)).length === 0 && $(oEvent.target.closest(this.options.toggler)).length === 0) {
this._closePopup();
@@ -74,6 +179,21 @@ $(function()
},
// Methods
/**
* @return {boolean} True if there is a toggler selector for the popover menu
* @private
*/
_hasToggler: function () {
if (('' === this.options.toggler) || (null === this.options.toggler)) {
return false;
}
if ($(this.options.toggler).length === 0) {
return false;
}
return true;
},
/**
* Add a visual hint (caret) on the toggler
*
@@ -81,40 +201,59 @@ $(function()
* @private
*/
_addVisualHintToToggler: function () {
if ('' === this.options.toggler) {
if (false === this._hasToggler()) {
return false;
}
const oTogglerElem = $(this.options.toggler);
if (oTogglerElem.length === 0) {
return false;
}
oTogglerElem.append($(`<span class="ibo-popover-menu--toggler-visual-hint"><span class="fas fa-caret-down"></span></span>`));
$(this.options.toggler).append($(`<span class="ibo-popover-menu--toggler-visual-hint"><span class="fas fa-caret-down"></span></span>`));
return true;
},
/**
* @return {boolean} True if the menu is currently opened
* @private
*/
_isOpened: function () {
return this.element.hasClass(this.css_classes.opened);
},
/**
* Open the menu
* @return {void}
* @private
*/
_openPopup: function () {
this.element.addClass(this.css_classes.opened);
},
/**
* Close the menu
* @return {void}
* @private
*/
_closePopup: function () {
this.element.removeClass(this.css_classes.opened);
},
/**
* @api
* @return {void}
*/
openPopup: function () {
this._openPopup();
},
closePopup: function()
{
/**
* @api
* @return {void}
*/
closePopup: function () {
this._closePopup();
},
togglePopup: function()
{
if(this.element.hasClass(this.css_classes.opened))
{
/**
* @api
* @return {void}
*/
togglePopup: function () {
if (this.element.hasClass(this.css_classes.opened)) {
this._closePopup();
}
else
{
} else {
this._openPopup();
}
},

View File

@@ -134,9 +134,6 @@ $(function()
this._ReformatDateTimes();
this._PrepareEntriesSubmitConfirmationDialog();
// TODO 3.0.0: Modify PopoverMenu so we can pass it the ID of the block triggering the open/close
//$(this.element).find(this.js_selectors.send_choices_picker).popover_menu({toggler: this.js_selectors.send_button});
this.element.trigger('ready.activity_panel.itop');
},
// events bound via _bind are removed automatically

View File

@@ -100,11 +100,6 @@ $(function()
this.element.find(this.js_selectors.menu_filter_hint_close).on('click', function (oEvent) {
me._onFilterHintCloseClick(oEvent);
});
// User info
this.element.find(this.js_selectors.user_menu_toggler).on('click', function (oEvent) {
me._onUserMenuTogglerClick(oEvent);
});
},
// Events callbacks
@@ -195,20 +190,6 @@ $(function()
SetUserPreference('navigation_menu.show_filter_hint', false, true);
},
_onUserMenuTogglerClick: function(oEvent)
{
// Avoid anchor glitch
oEvent.preventDefault();
var oEventTarget = $(oEvent.target);
var aEventTargetPos = oEventTarget.position();
$(this.js_selectors.user_menu_container).css({
'top': (aEventTargetPos.top + parseInt(oEventTarget.css('marginTop'), 10) - $(this.js_selectors.user_menu).height()) + 'px',
'left': (aEventTargetPos.left + parseInt(oEventTarget.css('marginLeft'), 10) + oEventTarget.width()) + 'px'
});
$(this.js_selectors.user_menu).popover_menu('togglePopup');
},
// Methods
_checkIfClickShouldCloseDrawer: function(oEvent)
{