N°5655 - Continue work on CombodoModal Part 2

This commit is contained in:
Molkobain
2022-11-28 15:57:25 +01:00
parent bedc8bbf46
commit 084f3ec52b
5 changed files with 343 additions and 84 deletions

View File

@@ -39,6 +39,10 @@ const CombodoPortalToolbox = {
* @deprecated 3.1.0 Use CombodoModal.OpenModal() instead * @deprecated 3.1.0 Use CombodoModal.OpenModal() instead
*/ */
OpenModal: function(oOptions) { OpenModal: function(oOptions) {
// Default value fallback for calls prior to 3.1.0
if (oOptions.size === undefined) {
oOptions.size = 'lg';
}
return CombodoModal.OpenModal(oOptions); return CombodoModal.OpenModal(oOptions);
}, },
/** /**
@@ -92,81 +96,39 @@ CombodoModal.CloseAllModals = function() {
* @override * @override
* @inheritDoc * @inheritDoc
*/ */
CombodoModal.OpenModal = function(oOptions) { CombodoModal._GetDefaultBaseModalSelector = function() {
// Set default options return '#modal-for-all';
oOptions = $.extend( };
true, /**
{ * @override
id: null, // ID of the created modal * @inheritDoc
attributes: {}, // HTML attributes */
base_modal: { CombodoModal._InstantiateModal = function(oModalElem, oOptions) {
usage: 'clone', // Either 'clone' or 'replace' const me = this;
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 // Resize to desired size
let oSelectorElem = null; switch (typeof oOptions.size) {
switch(typeof oOptions.base_modal.selector)
{
case 'string': case 'string':
oSelectorElem = $(oOptions.base_modal.selector); oModalElem.find('.modal-dialog')
.removeClass('modal-lg')
.addClass('modal-' + oOptions.size);
break; break;
case 'object': case 'object':
oSelectorElem = oOptions.base_modal.selector; CombodoJSConsole.Error('Could not open modal dialog as portal modals only support "xs", "sm", "md", "lg" sizes. Not specific width/height yet.');
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; 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 // Load content
switch (typeof oOptions.content) switch (typeof oOptions.content)
{ {
case 'string': case 'string':
oModalElem.find('.modal-content').html(oOptions.content); 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'); oModalElem.trigger('loaded.bs.modal');
break; break;
@@ -185,6 +147,9 @@ CombodoModal.OpenModal = function(oOptions) {
oModalElem.modal('hide'); oModalElem.modal('hide');
} }
// Internal callbacks
me._OnContentLoaded(oModalElem, oOptions.callbackOnContentLoaded);
//Manually triggers bootstrap event in order to keep listeners working //Manually triggers bootstrap event in order to keep listeners working
oModalElem.trigger('loaded.bs.modal'); oModalElem.trigger('loaded.bs.modal');
} }
@@ -196,14 +161,21 @@ CombodoModal.OpenModal = function(oOptions) {
break; break;
default: default:
if (window.console && window.console.warn) CombodoJSConsole.Warn('Could not open modal dialog as the content option was malformed: ' + oOptions.content);
{ return false;
console.warn('Could not open modal dialog as the content option was malformed: ', oOptions.content);
}
} }
// Show modal // 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
}; };

View File

@@ -186,10 +186,141 @@ CombodoModal.CloseAllModals = function() {
* @override * @override
* @inheritDoc * @inheritDoc
*/ */
CombodoModal.OpenModal = function(oOptions) { CombodoModal._GetDefaultBaseModalSelector = function() {
// TODO: Implement 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 // Processing on each pages of the backoffice

View File

@@ -1109,6 +1109,8 @@ const CombodoInlineImage = {
let CombodoModal = { let CombodoModal = {
/** /**
* Close all opened modals on the page * Close all opened modals on the page
*
* @return {void}
*/ */
CloseAllModals: function() { CloseAllModals: function() {
// Meant for overlaoding // Meant for overlaoding
@@ -1117,10 +1119,12 @@ let CombodoModal = {
/** /**
* Open a standard modal and put the content of the URL in it. * Open a standard modal and put the content of the URL in it.
* *
* @param sTargetUrl * @param sTargetUrl {String}
* @param bCloseOtherModals * @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 // Set default values
if(bCloseOtherModals === undefined) if(bCloseOtherModals === undefined)
{ {
@@ -1133,22 +1137,159 @@ let CombodoModal = {
CombodoModal.CloseAllModals(); CombodoModal.CloseAllModals();
} }
// Opening modal // Prepare options
CombodoModal.OpenModal({ let oOptions = {
content: { content: {
endpoint: sTargetUrl, 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. * When developing extensions, you should use them instead.
* *
* @param oOptions * @param oOptions {Object} TODO: Document
* @returns object The jQuery object of the modal element * @returns {(Object | null)} The jQuery object of the modal element or null if the modal could not be opened
* @internal
*/ */
OpenModal: function(oOptions) { 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 // 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);
}
}
} }
}; };

View File

@@ -35,6 +35,18 @@
{% endif %} {% endif %}
{% endblock %} {% endblock %}
{% block iboPageTemplates %}
<template id="ibo-small-loading-placeholder-template">
<div>TODO 3.1: LOADER AVEC ICONE SEULE</div>
</template>
<template id="ibo-large-loading-placeholder-template">
<div>TODO 3.1: LOADER AVEC TEXTE ET ICONE</div>
</template>
<template id="ibo-modal-template">
<div class="ibo-modal" data-role="ibo-modal">TODO 3.1: Please wait</div>
</template>
{% endblock %}
{% block iboCapturedOutput %} {% block iboCapturedOutput %}
<div id="ibo-raw-output" class="ibo-raw-output ibo-is-hidden" title="Debug Output">{{ aPage.sCapturedOutput|raw }}</div> <div id="ibo-raw-output" class="ibo-raw-output ibo-is-hidden" title="Debug Output">{{ aPage.sCapturedOutput|raw }}</div>
{% endblock %} {% endblock %}

View File

@@ -49,21 +49,23 @@
{% endblock %} {% endblock %}
</head> </head>
<body data-gui-type="backoffice"> <body data-gui-type="backoffice">
{% if aPage.isPrintable %} {% if aPage.isPrintable %}<div class="printable-content" style="width: 27.7cm;">{% endif %}
<div class="printable-content" style="width: 27.7cm;"> {% endif %}
{% block iboPageBodyHtml %} {% block iboPageBodyHtml %}
<div id="ibo-page-container"> <div id="ibo-page-container">
{{ render_block(oLayout, {aPage: aPage}) }} {{ render_block(oLayout, {aPage: aPage}) }}
</div> </div>
{% endblock %} {% endblock %}
{% if aPage.isPrintable %}</div> {% endif %} {% if aPage.isPrintable %}</div>{% endif %}
{% block iboDeferredBlocks %} {% block iboDeferredBlocks %}
{% for oBlock in aDeferredBlocks %} {% for oBlock in aDeferredBlocks %}
{{ render_block(oBlock, {aPage: aPage}) }} {{ render_block(oBlock, {aPage: aPage}) }}
{% endfor %} {% endfor %}
{% endblock %} {% endblock %}
{% block iboPageTemplates %}
{% endblock %}
{% if aPage.aJsFiles is not empty %} {% if aPage.aJsFiles is not empty %}
<script type="text/javascript"> <script type="text/javascript">
var aListJsFiles = []; var aListJsFiles = [];
@@ -72,6 +74,7 @@
{% endfor %} {% endfor %}
</script> </script>
{% endif %} {% endif %}
{% block iboPageJsFiles %} {% block iboPageJsFiles %}
{% for sJsFile in aPage.aJsFiles %} {% for sJsFile in aPage.aJsFiles %}
<script type="text/javascript" src="{{ sJsFile|add_itop_version|raw }}"></script> <script type="text/javascript" src="{{ sJsFile|add_itop_version|raw }}"></script>