From 084f3ec52becd90d5e73528b241a1dd760885824 Mon Sep 17 00:00:00 2001 From: Molkobain Date: Mon, 28 Nov 2022 15:57:25 +0100 Subject: [PATCH] =?UTF-8?q?N=C2=B05655=20-=20Continue=20work=20on=20Combod?= =?UTF-8?q?oModal=20Part=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../portal/public/js/toolbox.js | 106 +++++------- js/pages/backoffice/toolbox.js | 137 ++++++++++++++- js/utils.js | 161 ++++++++++++++++-- .../backoffice/itopwebpage/layout.html.twig | 12 ++ .../pages/backoffice/webpage/layout.html.twig | 11 +- 5 files changed, 343 insertions(+), 84 deletions(-) diff --git a/datamodels/2.x/itop-portal-base/portal/public/js/toolbox.js b/datamodels/2.x/itop-portal-base/portal/public/js/toolbox.js index 4abbda509..9714a3c79 100644 --- a/datamodels/2.x/itop-portal-base/portal/public/js/toolbox.js +++ b/datamodels/2.x/itop-portal-base/portal/public/js/toolbox.js @@ -39,6 +39,10 @@ const CombodoPortalToolbox = { * @deprecated 3.1.0 Use CombodoModal.OpenModal() instead */ OpenModal: function(oOptions) { + // Default value fallback for calls prior to 3.1.0 + if (oOptions.size === undefined) { + oOptions.size = 'lg'; + } return CombodoModal.OpenModal(oOptions); }, /** @@ -92,81 +96,39 @@ CombodoModal.CloseAllModals = function() { * @override * @inheritDoc */ -CombodoModal.OpenModal = function(oOptions) { - // Set default options - oOptions = $.extend( - true, - { - id: null, // ID of the created modal - attributes: {}, // HTML attributes - base_modal: { - usage: 'clone', // Either 'clone' or 'replace' - selector: '#modal-for-all' // Either a selector of the modal element used to base this one on or the modal element itself - }, - content: undefined, // Either a string, an object containing the endpoint / data or undefined to keep base modal content as-is - size: 'lg', // Either 'xs' / 'sm' / 'md' / 'lg' - }, - oOptions - ); +CombodoModal._GetDefaultBaseModalSelector = function() { + return '#modal-for-all'; +}; +/** + * @override + * @inheritDoc + */ +CombodoModal._InstantiateModal = function(oModalElem, oOptions) { + const me = this; - // Compute modal selector - let oSelectorElem = null; - switch(typeof oOptions.base_modal.selector) - { + // Resize to desired size + switch (typeof oOptions.size) { case 'string': - oSelectorElem = $(oOptions.base_modal.selector); + oModalElem.find('.modal-dialog') + .removeClass('modal-lg') + .addClass('modal-' + oOptions.size); break; case 'object': - oSelectorElem = oOptions.base_modal.selector; - break; - - default: - if (window.console && window.console.warn) - { - console.warn('Could not open modal dialog as the select option was malformed: ', oOptions.content); - } + CombodoJSConsole.Error('Could not open modal dialog as portal modals only support "xs", "sm", "md", "lg" sizes. Not specific width/height yet.'); return false; } - // Get modal element by either - let oModalElem = null; - // - Create a new modal from template - // Note : This could be better if we check for an existing modal first instead of always creating a new one - if (oOptions.base_modal.usage === 'clone') - { - oModalElem = oSelectorElem.clone(); - - // Force modal to have an HTML ID, otherwise it can lead to complications, especially with the portal_leave_handle.js - // See N°3469 - var sModalID = (oOptions.id !== null) ? oOptions.id : 'modal-with-generated-id-'+Date.now(); - oModalElem.attr('id', sModalID) - .appendTo('body'); - } - // - Get an existing modal in the DOM - else - { - oModalElem = oSelectorElem; - } - - // Set attributes - for(let sProp in oOptions.attributes) - { - oModalElem.attr(sProp, oOptions.attributes[sProp]); - } - - // Resize to small modal - oModalElem.find('.modal-dialog') - .removeClass('modal-lg') - .addClass('modal-' + oOptions.size); - // Load content switch (typeof oOptions.content) { case 'string': oModalElem.find('.modal-content').html(oOptions.content); - //Manually triggers bootstrap event in order to keep listeners working + // Internal callbacks + this._OnContentLoaded(oModalElem, oOptions.callbackOnContentLoaded); + + // Manually triggers bootstrap event in order to keep listeners working oModalElem.trigger('loaded.bs.modal'); break; @@ -185,6 +147,9 @@ CombodoModal.OpenModal = function(oOptions) { oModalElem.modal('hide'); } + // Internal callbacks + me._OnContentLoaded(oModalElem, oOptions.callbackOnContentLoaded); + //Manually triggers bootstrap event in order to keep listeners working oModalElem.trigger('loaded.bs.modal'); } @@ -196,14 +161,21 @@ CombodoModal.OpenModal = function(oOptions) { break; default: - if (window.console && window.console.warn) - { - console.warn('Could not open modal dialog as the content option was malformed: ', oOptions.content); - } + CombodoJSConsole.Warn('Could not open modal dialog as the content option was malformed: ' + oOptions.content); + return false; } // Show modal - oModalElem.modal('show'); + if (oOptions.auto_open) { + oModalElem.modal('show'); + } - return oModalElem; + return true; +}; +/** + * @override + * @inheritDoc + */ +CombodoModal._CenterModalInViewport = function (oModalElem) { + // No need to override, modal centers itself automatically }; \ No newline at end of file diff --git a/js/pages/backoffice/toolbox.js b/js/pages/backoffice/toolbox.js index b303c3d4c..8f72a08f2 100644 --- a/js/pages/backoffice/toolbox.js +++ b/js/pages/backoffice/toolbox.js @@ -186,10 +186,141 @@ CombodoModal.CloseAllModals = function() { * @override * @inheritDoc */ -CombodoModal.OpenModal = function(oOptions) { - // TODO: Implement +CombodoModal._GetDefaultBaseModalSelector = function() { + return '#ibo-modal-template'; +}; +/** + * @override + * @inheritDoc + */ +CombodoModal._InstantiateModal = function(oModalElem, oOptions) { + const me = this; - return null; + // Default options of the jQuery Dialog widget + let oJQueryOptions = { + width: 'auto', + height: 'auto', + modal: oOptions.extra_options.modal ?? true, + autoOpen: oOptions.auto_open, + }; + + // Resize to desired size + switch (typeof oOptions.size) { + case 'string': + switch (oOptions.size) { + case 'xs': + oJQueryOptions.width = Math.min(window.innerWidth * 0.2, '200px'); + oJQueryOptions.height = Math.min(window.innerHeight * 0.2, '150px'); + break; + + case 'sm': + oJQueryOptions.width = Math.min(window.innerWidth * 0.6, '800px'); + oJQueryOptions.height = Math.min(window.innerHeight * 0.6, '400px'); + break; + + case 'md': + oJQueryOptions.width = Math.min(window.innerWidth * 0.75, '1200px'); + oJQueryOptions.height = Math.min(window.innerHeight * 0.75, '600px'); + break; + + case 'lg': + oJQueryOptions.width = Math.min(window.innerWidth * 0.9, '1800px'); + oJQueryOptions.height = Math.min(window.innerHeight * 0.9, '900px'); + break; + } + break; + + case 'object': + if (oOptions.size.width !== undefined) { + oJQueryOptions.width = oOptions.size.width; + } + if (oOptions.size.height !== undefined) { + oJQueryOptions.height = oOptions.size.height; + } + break; + } + + // Load content + switch (typeof oOptions.content) + { + case 'string': + oModalElem.html(oOptions.content); + this._OnContentLoaded(oModalElem, oOptions.callbackOnContentLoaded); + break; + + case 'object': + // Put loader while fetching content + const oLoaderElem = $($('#ibo-large-loading-placeholder-template')[0].content.cloneNode(true)); + oModalElem.html(oLoaderElem.html()); + + // Fetch content in background + oModalElem.load( + oOptions.content.endpoint, + oOptions.content.data || {}, + function(sResponseText, sStatus) { + // Hiding modal in case of error as the general AJAX error handler will display a message + // TODO 3.1: Add general ajax error handler + if (sStatus === 'error') { + oModalElem.dialog('close'); + return; + } + + // Update position as the new content is most likely not like the previous one (if any) + // - First when content is displayed + me._CenterModalInViewport(oModalElem); + // - Then content is fully initialized + // TODO 3.1: We need to put an event when an object is done initialiazing (fields generated, etc) + setTimeout(function () { + me._CenterModalInViewport(oModalElem); + }, 500); + + me._OnContentLoaded(oModalElem, oOptions.callbackOnContentLoaded); + } + ); + break; + + case 'undefined': + // Do nothing, we keep the content as-is + break; + + default: + CombodoJSConsole.Warn('Could not open modal dialog as the content option was malformed: ' + oOptions.content); + return false; + } + + // Show modal + oModalElem.dialog(oJQueryOptions); + + // TODO 3.1 : Cleanup + // { + // height: 'auto', + // width: 'auto', + // modal: true, + // title: '$sDialogTitle', + // buttons: [ + // { text: "$sOkButtonLabel", click: function() { + // if ($('#$sDialogId .ui-state-error').length == 0) + // { + // var aTargetsSelect = []; + // + // } + // } }, + // { text: "$sCancelButtonLabel", click: function() { + // $(this).dialog( "close" ); + // } }, + // ], + // } + + return true; +}; +/** + * @override + * @inheritDoc + */ +CombodoModal._CenterModalInViewport = function (oModalElem) { + oModalElem.dialog('option', { + position: {my: 'center', at: 'center', of: window}, + }); }; // Processing on each pages of the backoffice diff --git a/js/utils.js b/js/utils.js index c17096788..923f63d36 100644 --- a/js/utils.js +++ b/js/utils.js @@ -1109,6 +1109,8 @@ const CombodoInlineImage = { let CombodoModal = { /** * Close all opened modals on the page + * + * @return {void} */ CloseAllModals: function() { // Meant for overlaoding @@ -1117,10 +1119,12 @@ let CombodoModal = { /** * Open a standard modal and put the content of the URL in it. * - * @param sTargetUrl - * @param bCloseOtherModals + * @param sTargetUrl {String} + * @param bCloseOtherModals {String} + * @return {Object} The jQuery object representing the modal element + * @api */ - OpenUrlInModal: function(sTargetUrl, bCloseOtherModals) { + OpenUrlInModal: function(sTargetUrl, bCloseOtherModals, callbackOnContentLoaded) { // Set default values if(bCloseOtherModals === undefined) { @@ -1133,22 +1137,159 @@ let CombodoModal = { CombodoModal.CloseAllModals(); } - // Opening modal - CombodoModal.OpenModal({ + // Prepare options + let oOptions = { content: { endpoint: sTargetUrl, } - }); + }; + + if (callbackOnContentLoaded !== undefined) { + oOptions.callbackOnContentLoaded = callbackOnContentLoaded; + } + + // Opening modal + return CombodoModal.OpenModal(oOptions); }, /** - * Generic function to create and open a modal, used by high-level functions such as "CombodoPortalToolbox.OpenUrlInModal()". + * Generic function to create and open a modal, used by high-level functions such as "CombodoModal.OpenUrlInModal()". * When developing extensions, you should use them instead. * - * @param oOptions - * @returns object The jQuery object of the modal element + * @param oOptions {Object} TODO: Document + * @returns {(Object | null)} The jQuery object of the modal element or null if the modal could not be opened + * @internal */ OpenModal: function(oOptions) { + // Set default options + oOptions = $.extend( + true, + { + id: null, // ID of the created modal + attributes: {}, // HTML attributes + base_modal: { + usage: 'clone', // Either 'clone' or 'replace' + selector: this._GetDefaultBaseModalSelector() // Either a selector of the modal element used to base this one on or the modal element itself + }, + content: undefined, // Either a string, an object containing the endpoint / data or undefined to keep base modal content as-is + size: 'auto', // Either 'auto' / 'xs' / 'sm' / 'md' / 'lg' or specific height & width via {width: '80px', height: '100px'} + auto_open: true, // true for the modal to open automatically on instantiation + callbackOnContentLoaded: null, // Callback to call once the content is loaded. Arguments will be oModalElem (the jQuery object representing the modal) + extra_options: {}, // Extra options to pass to the modal lib directly if they are not handled by the CombodoModal widget yet + }, + oOptions + ); + + // Compute modal selector + let oSelectorElem = null; + switch(typeof oOptions.base_modal.selector) { + case 'string': + oSelectorElem = $(oOptions.base_modal.selector); + if (oSelectorElem.length === 0) { + CombodoJSConsole.Error('Could not open modal dialog as the selector option did not return any element: ' + oOptions.base_modal.selector); + return null; + } + break; + + case 'object': + oSelectorElem = oOptions.base_modal.selector; + break; + + default: + CombodoJSConsole.Warn('Could not open modal dialog as the selector option was malformed: '+oOptions.base_modal.selector); + return null; + } + + // Get modal element by either + let oModalElem = null; + // - Create a new modal from template + // Note : This could be better if we check for an existing modal first instead of always creating a new one + if (oOptions.base_modal.usage === 'clone') { + // Clone modal using a real template + if (oSelectorElem[0].tagName === 'TEMPLATE') { + oModalElem = $(oSelectorElem[0].content.firstElementChild.cloneNode(true)); + } + // Clone modal using an existing element + else { + oModalElem = oSelectorElem.clone(); + } + + // Force modal to have an HTML ID, otherwise it can lead to complications, especially with the portal_leave_handle.js + // See N°3469 + let sModalID = (oOptions.id !== null) ? oOptions.id : 'modal-with-generated-id-'+Date.now(); + oModalElem.attr('id', sModalID) + .appendTo('body'); + } + // - Get an existing modal in the DOM + else { + oModalElem = oSelectorElem; + } + + // Set attributes + for (let sProp in oOptions.attributes) { + oModalElem.attr(sProp, oOptions.attributes[sProp]); + } + + if (false === this._InstantiateModal(oModalElem, oOptions)) { + return null; + } + + return oModalElem; + }, + /** + * @return {String} The JS selector to the default base modal to use either for display or as a template ("clone" usage) + * @private + * @internal + */ + _GetDefaultBaseModalSelector: function() { // Meant for overlaoding - CombodoJSConsole.Debug('CombodoModal.OpenModal not implemented'); + CombodoJSConsole.Debug('CombodoModal._GetDefaultBaseModalSelector not implemented'); + }, + /** + * Instantiate the oModal modal regarding the oOptions + * + * @param oModalElem {Object} The jQuery object representing the modal element + * @param oOptions {Object} + * @return {boolean} True if the modal could be instantiated, false otherwise + * @private + * @internal + */ + _InstantiateModal: function(oModalElem, oOptions) { + // Meant for overlaoding + CombodoJSConsole.Debug('CombodoModal._InstantiateModal not implemented'); + return false; + }, + /** + * Center the modal in the current viewport + * + * @param oModalElem {Object} The jQuery representation of the modale element + * @return {void} + * @private + * @internal + */ + _CenterModalInViewport: function (oModalElem) { + // Meant for overlaoding + CombodoJSConsole.Debug('CombodoModal._CenterModalInViewport not implemented'); + }, + /** + * Callback called when the content of the modal has been loaded. + * + * @param oModalElem {object} The jQuery object representing the modal element + * @param callback {(string | function)} The callback to be executed. Can be either a string representing a function declared in the window object or an anonymous function + * @private + * @return {void} + */ + _OnContentLoaded: function (oModalElem, callback) { + if (callback !== undefined) { + if (typeof callback === 'string') { + if (window[callback] === undefined) { + CombodoJSConsole.Error('Could not call _OnContentLoaded callback "' + callback + '" as it is not defined in the window object.'); + return; + } + window[callback](oModalElem); + } + else if (typeof callback === 'function') { + callback(oModalElem); + } + } } }; \ No newline at end of file diff --git a/templates/pages/backoffice/itopwebpage/layout.html.twig b/templates/pages/backoffice/itopwebpage/layout.html.twig index 4f81263ba..0e838c272 100644 --- a/templates/pages/backoffice/itopwebpage/layout.html.twig +++ b/templates/pages/backoffice/itopwebpage/layout.html.twig @@ -35,6 +35,18 @@ {% endif %} {% endblock %} +{% block iboPageTemplates %} + + + +{% endblock %} + {% block iboCapturedOutput %}
{{ aPage.sCapturedOutput|raw }}
{% endblock %} diff --git a/templates/pages/backoffice/webpage/layout.html.twig b/templates/pages/backoffice/webpage/layout.html.twig index 457075bf1..05756a1f0 100644 --- a/templates/pages/backoffice/webpage/layout.html.twig +++ b/templates/pages/backoffice/webpage/layout.html.twig @@ -49,21 +49,23 @@ {% endblock %} -{% if aPage.isPrintable %} -
{% endif %} +{% if aPage.isPrintable %}
{% endif %} {% block iboPageBodyHtml %} -
{{ render_block(oLayout, {aPage: aPage}) }}
{% endblock %} - {% if aPage.isPrintable %}
{% endif %} +{% if aPage.isPrintable %}
{% endif %} {% block iboDeferredBlocks %} {% for oBlock in aDeferredBlocks %} {{ render_block(oBlock, {aPage: aPage}) }} {% endfor %} {% endblock %} + +{% block iboPageTemplates %} +{% endblock %} + {% if aPage.aJsFiles is not empty %} {% endif %} + {% block iboPageJsFiles %} {% for sJsFile in aPage.aJsFiles %}