/* * Copyright (C) 2013-2024 Combodo SAS * * This file is part of iTop. * * iTop is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * iTop is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License */ // Helpers function ShowAboutBox(sTitle) { var loadingDialog = $('
'); loadingDialog.dialog( {title:sTitle,autoOpen: true, modal: true, width: 700, height:350}); $('#ibo-about-box--loader').block(); $.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', {operation: 'about_box'}, function(data){ $('#ibo-about-box--loader').unblock(); $('body').append(data); }).always(function() { loadingDialog.empty(); loadingDialog.remove(); }); return false; } function ShowDebug() { if ($('#ibo-raw-output').html() !== '') { $('#ibo-raw-output') // Note: We remove the CSS class before opening the dialog, otherwise the dialog will not be positioned correctly due to its content being still hidden .removeClass('ibo-is-hidden') .dialog( {autoOpen: true, modal: true, width: '80%', maxHeight: window.innerHeight * 0.8}); } } function ArchiveMode(bEnable) { var sPrevUrl = StripArchiveArgument(window.location.search); if (bEnable) { window.location.search = sPrevUrl + '&with-archive=1'; } else { window.location.search = sPrevUrl + '&with-archive=0'; } } function StripArchiveArgument(sUrl) { var res = sUrl.replace(/&with-archive=[01]/g, ''); return res; } function goBack() { window.history.back(); } function BackToDetails(sClass, id, sDefaultUrl, sOwnershipToken) { window.bInCancel = true; if (id > 0) { sToken = ''; if (sOwnershipToken != undefined) { sToken = '&token='+sOwnershipToken; } window.location.href = AddAppContext(GetAbsoluteUrlAppRoot()+'pages/UI.php?operation=release_lock_and_details&class='+sClass+'&id='+id+sToken); } else { window.location.href = sDefaultUrl; // Already contains the context... } } function BackToList(sClass) { window.location.href = AddAppContext(GetAbsoluteUrlAppRoot()+'pages/UI.php?operation=search_oql&oql_class='+sClass+'&oql_clause=WHERE id=0'); } /** * A toolbox for common JS operations in the backoffice. Meant to be used by Combodo developers and the community. * @api * @since 3.0.0 */ const CombodoBackofficeToolbox = { /** * Make the oElem enter the fullscreen mode, meaning that it will take all the screen and be above everything else. * * @param {object} oElem The jQuery object of the element * @constructor */ EnterFullscreenForElement: function(oElem) { oElem.parents().addClass('ibo-has-fullscreen-descendant'); oElem.addClass('ibo-is-fullscreen'); }, /** * Make the oElem exit the fullscreen mode. * If none passed, all fullscreen elements will be removed from it * * @param {object|null} oElem The jQuery object of the element * @constructor */ ExitFullscreenForElement: function(oElem = null) { // If no element passed, remove any element from fullscreen mode if(oElem === null) { $(document).find('.ibo-has-fullscreen-descendant').removeClass('ibo-has-fullscreen-descendant'); $(document).find('.ibo-is-fullscreen').removeClass('ibo-is-fullscreen'); } else { oElem.parents().removeClass('ibo-has-fullscreen-descendant'); oElem.removeClass('ibo-is-fullscreen'); } }, /** * Make the oElem enter or exit the fullscreen mode depending on it's current state. * * @param {object} oElem The jQuery object of the element * @constructor */ ToggleFullscreenForElement: function (oElem) { if (oElem.hasClass('ibo-is-fullscreen')) { this.ExitFullscreenForElement(oElem); } else { this.EnterFullscreenForElement(oElem); } }, /** * Initialize the code highlighting on elements * * @param {Object} oContainerElem code highlighting will only be init. on elements within the container * @param {boolean} bForce Whether the highlighting should be forced or not (if already done) * @return {void} * @constructor */ InitCodeHighlighting: function (oContainerElem = null, bForce = false) { if (oContainerElem === null) { oContainerElem = $('body'); } const sComplementarySelector = bForce ? '' : ':not(.hljs)'; // AttributeHTML and HTML AttributeText let oCodeElements = oContainerElem.find('[data-attribute-type="AttributeHTML"], [data-attribute-type="AttributeText"], [data-attribute-type="AttributeTemplateHTML"]').find('.HTML pre > code'+sComplementarySelector); if (oCodeElements.length > 0) { if (typeof hljs === 'undefined') { CombodoJSConsole.Error('Cannot format code snippets in HTML fields as the highlight.js lib is not loaded'); } else { oCodeElements.each(function (iIdx, oElem) { hljs.highlightBlock(oElem); $(oElem).parent().addClass('common-hljs-container'); }); } } // CaseLogs oCodeElements = oContainerElem.find('[data-role="ibo-activity-entry--main-information-content"] pre > code'+sComplementarySelector); if (oCodeElements.length > 0) { if (typeof hljs === 'undefined') { CombodoJSConsole.Error('Cannot format code snippets in log entries as the highlight.js lib is not loaded'); } else { oCodeElements.each(function (iIdx, oElem) { hljs.highlightBlock(oElem); $(oElem).parent().addClass('common-hljs-container'); }); } } } }; /** * @override * @inheritDoc */ CombodoModal.CloseAllModals = function() { $('.ui-dialog .ui-dialog-content').each(function () { // Don't try to close dialog if not instantiated yet if ($(this).dialog('instance') === undefined) { return false; } $(this).dialog('close'); }); }; /** * @override * @inheritDoc */ CombodoModal._GetDefaultBaseModalSelector = function() { return '#ibo-modal-template'; }; /** * @override * @inheritDoc */ CombodoModal._InstantiateModal = function(oModalElem, oOptions) { const me = this; // Default options of the jQuery Dialog widget let oJQueryOptions = { width: 'auto', height: 'auto', modal: oOptions.extra_options.modal ?? true, classes: oOptions.classes ?? {}, close: oOptions.extra_options.callback_on_modal_close, autoOpen: oOptions.auto_open, title: oOptions.title, buttons: this._ConvertButtonDefinition(oOptions.buttons) }; const aSizeMap = { 'xs': 'extra-small', 's': 'small', 'md': 'medium', 'lg': 'large', }; // Resize to desired size switch (typeof oOptions.size) { case 'string': if(aSizeMap[oOptions.size] !== undefined && aSizeMap[oOptions.size] !== 'auto') { let sSize = 'ibo-is-' + aSizeMap[oOptions.size]; if (oJQueryOptions.classes['ui-dialog-content'] !== undefined) { oJQueryOptions.classes['ui-dialog-content'] += ' ' + sSize; } else { oJQueryOptions.classes['ui-dialog-content'] = sSize; } } 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.callback_on_content_loaded); 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.callback_on_content_loaded); } ); 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); return true; }; /** * @override * @inheritDoc */ CombodoModal._BindEvents = function (oModalElem) { const me = this; // Center modal on resize if(window.ResizeObserver) { const oModalObs = new ResizeObserver(function(){ if(oModalElem.width()>0) { me._CenterModalInViewport(oModalElem); } }); oModalObs.observe(oModalElem[0]); } }; /** * Convert generic buttons definitions to jquery ui dialog definitions. * * @param aButtonsDefinitions * @returns {*[]} * @private */ CombodoModal._ConvertButtonDefinition = function (aButtonsDefinitions) { const aConverted = []; if(aButtonsDefinitions === null) { return aConverted } Object.keys(aButtonsDefinitions).forEach(key => { const element = aButtonsDefinitions[key]; const aButton = { text: element.text, class: typeof(element.classes) !== 'undefined' ? element.classes.join(' ') : '', click: element.callback_on_click } aConverted.push(aButton); } ); return aConverted; }; /** * @override * @inheritDoc */ CombodoModal._CenterModalInViewport = function (oModalElem) { if(oModalElem.dialog('instance') === undefined){ CombodoJSConsole.Error('CombodoModal._CenterModalInViewport: Cannot center modal as it is not a jQuery UI dialog widget'); return false; } oModalElem.dialog('option', { position: {my: 'center', at: 'center', of: window}, }); }; /** * @override * @inheritDoc */ CombodoModal.OpenConfirmationModal = function(oOptions, aData) { // Check do not show again preference key if(oOptions.do_not_show_again_pref_key !== null){ if(GetUserPreference(oOptions.do_not_show_again_pref_key, false)){ if(oOptions.callback_on_confirm !== null){ oOptions.callback_on_confirm(...aData); } return; } } // Merge external options with confirmation modal default options oOptions = $.extend(true, { title: Dict.S('UI:Modal:Confirmation:DefaultTitle'), content: '', do_not_show_again_pref_key: null, callback_on_confirm: null, callback_on_cancel: null, extra_options: { callback_on_modal_close: function () { $(this).dialog( "destroy" ); // destroy dialog object } }, buttons: { cancel: { text: Dict.S('UI:Button:Cancel'), classes: ['ibo-is-alternative'], callback_on_click: function () { // call confirm handler and close dialog let bCanClose = true; if (oOptions.callback_on_cancel != null) { bCanClose = oOptions.callback_on_cancel(...aData) !== false; } if (bCanClose) { $(this).dialog('close'); // close dialog } } }, confirm: { text: Dict.S('UI:Button:Confirm'), classes: ['ibo-is-primary'], callback_on_click: function () { // Call confirm handler and close dialog let bCanClose = true; if (oOptions.callback_on_confirm != null) { bCanClose = oOptions.callback_on_confirm(...aData) !== false; } if (bCanClose) { $(this).dialog('close'); // close dialog // Handle "do not show again" user preference let bDoNotShowAgain = oOptions.do_not_show_again_pref_key !== null ? $('[name="do_not_show_again"]', $(this)).prop('checked') : false; if (bDoNotShowAgain) { SetUserPreference(oOptions.do_not_show_again_pref_key, true, true); } } } } }, callback_on_content_loaded: function(oModalContentElement){ // Add option do not show again from template if(oOptions.do_not_show_again_pref_key !== null) { oModalContentElement.append($('#ibo-modal-option--do-not-show-again-template').html()); } } }, oOptions); // Open modal CombodoModal.OpenModal(oOptions); } /** * @override * @inheritDoc */ CombodoModal.OpenInformativeModal = function(sMessage, sSeverity, oOptions) { let sFirstLetterUppercaseSeverity = sSeverity.charAt(0).toUpperCase() + sSeverity.slice(1); // Merge external options with confirmation modal default options oOptions = $.extend({ title: Dict.S('UI:Modal:Informative' + sFirstLetterUppercaseSeverity + ':Title'), classes : { 'ui-dialog-content': 'ibo-is-informative ibo-is-'+sSeverity, }, content: sMessage, extra_options: { callback_on_modal_close: function () { $(this).dialog( "destroy" ); } }, buttons: { ok: { text: Dict.S('UI:Button:Ok'), classes: ['ibo-is-regular', 'ibo-is-neutral'], callback_on_click: function () { $(this).dialog('close'); } }, }, }, oOptions); // Open modal CombodoModal.OpenModal(oOptions); } /** * @override * @inheritDoc */ CombodoToast.OpenToast = function(sMessage, sSeverity, aOptions) { aOptions = $.extend({ text: sMessage, className: "ibo-toast ibo-is-" + sSeverity, duration: 6000, close: true, gravity: GetUserPreference('toasts_vertical_position', 'bottom'), position: "right", stopOnFocus: true, }, aOptions); if(aOptions.duration !== -1){ aOptions.className += ' ibo-is-auto-closeable'; } Toastify(aOptions).showToast(); }; // Processing on each pages of the backoffice $(document).ready(function(){ // Initialize global keyboard shortcuts $('body').keyboard_shortcuts({shortcuts: aKeyboardShortcuts}); // Enable tooltips based on existing HTML markup, won't work on markup added dynamically after DOM ready (AJAX, ...) $('[data-tooltip-content]:not([data-tooltip-instantiated="true"])').each(function () { CombodoTooltip.InitTooltipFromMarkup($(this)); }); // Enable fullscreen togglers based on existing HTML markup, won't work on markup added dynamically after DOM ready (AJAX, ...) $('[data-fullscreen-toggler-target]:not([data-fullscreen-toggler-instantiated="true"])').each(function () { const sTargetSelector = $(this).attr('data-fullscreen-toggler-target'); let oTargetElem = null; // Check if target selector is a jQuery expression, meaning that it needs to be evaluated (eg. $(this).closest('[data-role="ibo-xxx"]')) if (sTargetSelector.indexOf('$') !== -1) { oTargetElem = eval(sTargetSelector); } // Otherwise it should be a simple selector (eg. #abc, .def) else { oTargetElem = $(document).find(sTargetSelector); } // Still no target element found, abort if((oTargetElem === null) || (oTargetElem.length === 0)) { return false; } // Toggle fullscreen on toggler click $(this).on('click', function (oEvent) { // Prevent anchor default behavior oEvent.preventDefault(); CombodoBackofficeToolbox.ToggleFullscreenForElement(oTargetElem); }); // Exit fullscreen on "Esc" key hit when focus is in either the toggler or the target // - Toggler $(this).on('keyup', function (oEvent) { if ((oEvent.key === 'Escape') && ($(oEvent.target).attr('data-fullscreen-toggler-instantiated'))) { CombodoBackofficeToolbox.ExitFullscreenForElement(oTargetElem); } }); // - Target oTargetElem.on('keyup', function (oEvent) { if ((oEvent.key === 'Escape') && ($(oEvent.target).attr('data-fullscreen-target'))) { CombodoBackofficeToolbox.ExitFullscreenForElement(oTargetElem); } }); oTargetElem.attr('data-fullscreen-target', 'true'); $(this).attr('data-fullscreen-toggler-instantiated', 'true'); }); // Processing on datatables refresh $(document).on('init.dt draw.dt', function (oEvent) { CombodoTooltip.InitAllNonInstantiatedTooltips($(oEvent.target), true); }); // Code highlighting CombodoBackofficeToolbox.InitCodeHighlighting(); });