From 36ec455e6d6b55a0488cfb4231b84b2c5a42b0b0 Mon Sep 17 00:00:00 2001 From: Molkobain Date: Fri, 28 Oct 2022 10:05:58 +0200 Subject: [PATCH] =?UTF-8?q?N=C2=B05655=20-=20Refactor=20portal=20modal=20d?= =?UTF-8?q?ialog=20helper=20to=20global=20helper=20for=20both=20GUIs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../public/js/bootstrap-portal-modal.js | 2 +- .../portal/public/js/portal_form_handler.js | 2 +- .../portal/public/js/toolbox.js | 283 +++++++++--------- .../src/Controller/ObjectController.php | 2 +- .../templates/bricks/create/modal.html.twig | 2 +- .../object/mode_search_regular.html.twig | 2 +- js/pages/backoffice/toolbox.js | 17 ++ js/utils.js | 55 +++- .../BsLinkedSetFieldRenderer.php | 4 +- .../BsSelectObjectFieldRenderer.php | 4 +- 10 files changed, 217 insertions(+), 156 deletions(-) diff --git a/datamodels/2.x/itop-portal-base/portal/public/js/bootstrap-portal-modal.js b/datamodels/2.x/itop-portal-base/portal/public/js/bootstrap-portal-modal.js index 03169fc07..8b374c76c 100644 --- a/datamodels/2.x/itop-portal-base/portal/public/js/bootstrap-portal-modal.js +++ b/datamodels/2.x/itop-portal-base/portal/public/js/bootstrap-portal-modal.js @@ -100,6 +100,6 @@ $(document).ready(function() }; } - CombodoPortalToolbox.OpenModal(oOptions); + CombodoModal.OpenModal(oOptions); }); }); \ No newline at end of file diff --git a/datamodels/2.x/itop-portal-base/portal/public/js/portal_form_handler.js b/datamodels/2.x/itop-portal-base/portal/public/js/portal_form_handler.js index b0729af1e..3d6cf596e 100644 --- a/datamodels/2.x/itop-portal-base/portal/public/js/portal_form_handler.js +++ b/datamodels/2.x/itop-portal-base/portal/public/js/portal_form_handler.js @@ -397,7 +397,7 @@ $(function() if(bRedirectInModal === true) { // Creating a new modal - CombodoPortalToolbox.OpenModal({ + CombodoModal.OpenModal({ content: { endpoint: sRedirectUrl, data: { 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 c78dca2c2..4abbda509 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 @@ -24,158 +24,22 @@ const CombodoPortalToolbox = { /** * Close all opened modals on the page + * @deprecated 3.1.0 Use CombodoModal.CloseAllModals() instead */ - CloseAllModals: function() - { - $('.modal.in').modal('hide'); + CloseAllModals: function() { + CombodoModal.CloseAllModals(); }, /** - * Open a standard modal and put the content of the URL in it. - * - * @param sTargetUrl - * @param bCloseOtherModals + * @deprecated 3.1.0 Use CombodoModal.OpenUrlInModal() instead */ - OpenUrlInModal: function(sTargetUrl, bCloseOtherModals){ - // Set default values - if(bCloseOtherModals === undefined) - { - bCloseOtherModals = false; - } - - // Close other modals if necessary - if(bCloseOtherModals) - { - CombodoPortalToolbox.CloseAllModals(); - } - - // Opening modal - CombodoPortalToolbox.OpenModal({ - content: { - endpoint: sTargetUrl, - } - }); + OpenUrlInModal: function(sTargetUrl, bCloseOtherModals) { + CombodoModal.OpenUrlInModal(sTargetUrl, bCloseOtherModals); }, /** - * Generic function to create and open a modal, used by high-level functions such as "CombodoPortalToolbox.OpenUrlInModal()". - * When developing extensions, you should use them instead. - * - * @param oOptions - * @returns object The jQuery object of the modal element + * @deprecated 3.1.0 Use CombodoModal.OpenModal() instead */ - 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 - ); - - // Compute modal selector - let oSelectorElem = null; - switch(typeof oOptions.base_modal.selector) - { - case 'string': - oSelectorElem = $(oOptions.base_modal.selector); - 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); - } - 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 - oModalElem.trigger('loaded.bs.modal'); - break; - - case 'object': - // Put loader while fetching content - oModalElem.find('.modal-content').html($('#page_overlay .overlay_content').html()); - // Fetch content in background - oModalElem.find('.modal-content').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 - if (sStatus === 'error') - { - oModalElem.modal('hide'); - } - - //Manually triggers bootstrap event in order to keep listeners working - oModalElem.trigger('loaded.bs.modal'); - } - ); - break; - - case 'undefined': - // Do nothing, we keep the content as-is - break; - - default: - if (window.console && window.console.warn) - { - console.warn('Could not open modal dialog as the content option was malformed: ', oOptions.content); - } - } - - // Show modal - oModalElem.modal('show'); - - return oModalElem; + OpenModal: function(oOptions) { + return CombodoModal.OpenModal(oOptions); }, /** * Generic function to call a specific endpoint with callbacks @@ -185,7 +49,7 @@ const CombodoPortalToolbox = { * @param callbackOnSuccess * @param callbackOnPending */ - CallEndpoint: function(sEndpointUrl, oPostedData, callbackOnSuccess, callbackOnPending){ + CallEndpoint: function(sEndpointUrl, oPostedData, callbackOnSuccess, callbackOnPending) { // Call endpoint $.post(sEndpointUrl, oPostedData, function(oResponse) { // Call callback on success @@ -216,3 +80,130 @@ const CombodoPortalToolbox = { } } }; + +/** + * @override + * @inheritDoc + */ +CombodoModal.CloseAllModals = function() { + $('.modal.in').modal('hide'); +}; +/** + * @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 + ); + + // Compute modal selector + let oSelectorElem = null; + switch(typeof oOptions.base_modal.selector) + { + case 'string': + oSelectorElem = $(oOptions.base_modal.selector); + 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); + } + 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 + oModalElem.trigger('loaded.bs.modal'); + break; + + case 'object': + // Put loader while fetching content + oModalElem.find('.modal-content').html($('#page_overlay .overlay_content').html()); + // Fetch content in background + oModalElem.find('.modal-content').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 + if (sStatus === 'error') + { + oModalElem.modal('hide'); + } + + //Manually triggers bootstrap event in order to keep listeners working + oModalElem.trigger('loaded.bs.modal'); + } + ); + break; + + case 'undefined': + // Do nothing, we keep the content as-is + break; + + default: + if (window.console && window.console.warn) + { + console.warn('Could not open modal dialog as the content option was malformed: ', oOptions.content); + } + } + + // Show modal + oModalElem.modal('show'); + + return oModalElem; +}; \ No newline at end of file diff --git a/datamodels/2.x/itop-portal-base/portal/src/Controller/ObjectController.php b/datamodels/2.x/itop-portal-base/portal/src/Controller/ObjectController.php index d523338a0..67596dbec 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/Controller/ObjectController.php +++ b/datamodels/2.x/itop-portal-base/portal/src/Controller/ObjectController.php @@ -208,7 +208,7 @@ class ObjectController extends BrickController $oModifyButton = new JSButtonItem( 'modify_object', Dict::S('UI:Menu:Modify'), - 'CombodoPortalToolbox.OpenUrlInModal("'.$sModifyUrl.'", true);' + 'CombodoModal.OpenUrlInModal("'.$sModifyUrl.'", true);' ); // Putting this one first $aData['form']['buttons']['actions'][] = $oModifyButton->GetMenuItem() + array('js_files' => $oModifyButton->GetLinkedScripts()); diff --git a/datamodels/2.x/itop-portal-base/portal/templates/bricks/create/modal.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/bricks/create/modal.html.twig index 2dcec9eac..12ac3e6b3 100644 --- a/datamodels/2.x/itop-portal-base/portal/templates/bricks/create/modal.html.twig +++ b/datamodels/2.x/itop-portal-base/portal/templates/bricks/create/modal.html.twig @@ -25,7 +25,7 @@ sUrl = CombodoGlobalToolbox.AddParameterToUrl(sUrl, 'ar_token', '{{ ar_token }}'); // Creating a new modal - CombodoPortalToolbox.OpenModal({ + CombodoModal.OpenModal({ base_modal: { usage: 'replace', selector: $(this).closest('.modal'), diff --git a/datamodels/2.x/itop-portal-base/portal/templates/bricks/object/mode_search_regular.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/bricks/object/mode_search_regular.html.twig index 81ca15913..b0dda5793 100644 --- a/datamodels/2.x/itop-portal-base/portal/templates/bricks/object/mode_search_regular.html.twig +++ b/datamodels/2.x/itop-portal-base/portal/templates/bricks/object/mode_search_regular.html.twig @@ -143,7 +143,7 @@ oEvent.stopPropagation(); // Creating a new modal - CombodoPortalToolbox.OpenModal({ + CombodoModal.OpenModal({ content: { endpoint: $(this).attr('href'), }, diff --git a/js/pages/backoffice/toolbox.js b/js/pages/backoffice/toolbox.js index 9002f4d9f..b303c3d4c 100644 --- a/js/pages/backoffice/toolbox.js +++ b/js/pages/backoffice/toolbox.js @@ -175,6 +175,23 @@ const CombodoBackofficeToolbox = { } }; +/** + * @override + * @inheritDoc + */ +CombodoModal.CloseAllModals = function() { + // TODO: Implement +}; +/** + * @override + * @inheritDoc + */ +CombodoModal.OpenModal = function(oOptions) { + // TODO: Implement + + return null; +}; + // Processing on each pages of the backoffice $(document).ready(function(){ // Initialize global keyboard shortcuts diff --git a/js/utils.js b/js/utils.js index 3f4cbb7ed..c17096788 100644 --- a/js/utils.js +++ b/js/utils.js @@ -1098,4 +1098,57 @@ const CombodoInlineImage = { $(this).addClass('inline-image').attr('href', $(this).attr('src')); }).magnificPopup({type: 'image', closeOnContentClick: true }); } -} \ No newline at end of file +}; + +/** + * Abstract wrapper to manage modal dialogs in iTop. + * Implementations for the various GUIs may vary but APIs are the same. + * + * @since 3.1.0 + */ +let CombodoModal = { + /** + * Close all opened modals on the page + */ + CloseAllModals: function() { + // Meant for overlaoding + CombodoJSConsole.Debug('CombodoModal.CloseAllModals not implemented'); + }, + /** + * Open a standard modal and put the content of the URL in it. + * + * @param sTargetUrl + * @param bCloseOtherModals + */ + OpenUrlInModal: function(sTargetUrl, bCloseOtherModals) { + // Set default values + if(bCloseOtherModals === undefined) + { + bCloseOtherModals = false; + } + + // Close other modals if necessary + if(bCloseOtherModals) + { + CombodoModal.CloseAllModals(); + } + + // Opening modal + CombodoModal.OpenModal({ + content: { + endpoint: sTargetUrl, + } + }); + }, + /** + * Generic function to create and open a modal, used by high-level functions such as "CombodoPortalToolbox.OpenUrlInModal()". + * When developing extensions, you should use them instead. + * + * @param oOptions + * @returns object The jQuery object of the modal element + */ + OpenModal: function(oOptions) { + // Meant for overlaoding + CombodoJSConsole.Debug('CombodoModal.OpenModal not implemented'); + } +}; \ No newline at end of file diff --git a/sources/Renderer/Bootstrap/FieldRenderer/BsLinkedSetFieldRenderer.php b/sources/Renderer/Bootstrap/FieldRenderer/BsLinkedSetFieldRenderer.php index 1d5ca6b22..efa15b0a7 100644 --- a/sources/Renderer/Bootstrap/FieldRenderer/BsLinkedSetFieldRenderer.php +++ b/sources/Renderer/Bootstrap/FieldRenderer/BsLinkedSetFieldRenderer.php @@ -249,7 +249,7 @@ EOF oEvent.stopPropagation(); // Note : This could be better if we check for an existing modal first instead of always creating a new one - CombodoPortalToolbox.OpenModal({ + CombodoModal.OpenModal({ content: { endpoint: $(this).attr('href'), }, @@ -527,7 +527,7 @@ EOF 'selector': '.modal[data-source-element="{$sButtonAddId}"]:first' }; } - CombodoPortalToolbox.OpenModal(oOptions); + CombodoModal.OpenModal(oOptions); }); JS ); diff --git a/sources/Renderer/Bootstrap/FieldRenderer/BsSelectObjectFieldRenderer.php b/sources/Renderer/Bootstrap/FieldRenderer/BsSelectObjectFieldRenderer.php index 4241962c8..a20ee628f 100644 --- a/sources/Renderer/Bootstrap/FieldRenderer/BsSelectObjectFieldRenderer.php +++ b/sources/Renderer/Bootstrap/FieldRenderer/BsSelectObjectFieldRenderer.php @@ -381,7 +381,7 @@ EOF <<