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 %}
+
+