From bb468756794f3b16d244cf6203d161d852e455ca Mon Sep 17 00:00:00 2001 From: Molkobain Date: Wed, 3 May 2023 19:11:56 +0200 Subject: [PATCH] =?UTF-8?q?N=C2=B05890=20-=20Refactor=20leave=20handler=20?= =?UTF-8?q?to=20a=20global=20helper=20(not=20portal=20only)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../portal/public/js/portal_form_handler.js | 28 +++- .../portal/public/js/portal_leave_handler.js | 122 -------------- .../portal/templates/layout.html.twig | 9 +- js/leave_handler.js | 155 ++++++++++++++++++ 4 files changed, 186 insertions(+), 128 deletions(-) delete mode 100644 datamodels/2.x/itop-portal-base/portal/public/js/portal_leave_handler.js create mode 100644 js/leave_handler.js 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 bda0aeca28..bd88971eee 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 @@ -95,8 +95,7 @@ $(function() _onFieldsTouched: function(oEvent) { this._super(oEvent); - $('body').trigger('register_blocker.portal.itop', {'sBlockerId': this.element.attr('id'), 'sTargetElemSelector': '#' + this.element.closest('.modal').attr('id'), 'oTargetElemSelector': '#' + this.element.closest('.modal').attr('id'), 'sEventName': 'hide.bs.modal'}); - $('body').trigger('register_blocker.portal.itop', {'sBlockerId': this.element.attr('id'), 'sTargetElemSelector': 'document', 'oTargetElemSelector': document, 'sEventName': 'beforeunload'}); + this._registerBlockers(); }, // Overload from parent class _onSubmitClick: function(oEvent) @@ -255,7 +254,7 @@ $(function() // If everything is okay, we close the form and apply the submit rule. if(oValidation.valid) { - $('body').trigger('unregister_blocker.portal.itop', {'sBlockerId': me.element.attr('id')}); + me._unregisterBlockers(); // Checking if we have to redirect to another page if(sRuleType === 'redirect') @@ -301,7 +300,7 @@ $(function() if(me.options.field_set.field_set('option', 'touched_fields').length > 0) { me._disableFormBeforeLoading(); - $('body').trigger('unregister_blocker.portal.itop', {'sBlockerId': me.element.attr('id')}); + me._unregisterBlockers(); $.post( me.options.endpoint, { @@ -434,6 +433,27 @@ $(function() window.location.href = sHomepageUrl; } }, + _registerBlockers: function() + { + $('body').trigger('register_blocker.itop', { + 'sBlockerId': this.element.attr('id'), + 'sTargetElemSelector': '#' + this.element.closest('.modal').attr('id'), + 'oTargetElemSelector': '#' + this.element.closest('.modal').attr('id'), + 'sEventName': 'hide.bs.modal' + }); + $('body').trigger('register_blocker.itop', { + 'sBlockerId': this.element.attr('id'), + 'sTargetElemSelector': 'document', + 'oTargetElemSelector': document, + 'sEventName': 'beforeunload' + }); + }, + _unregisterBlockers: function() + { + $('body').trigger('unregister_blocker.itop', { + 'sBlockerId': this.element.attr('id') + }); + }, submit: function(oEvent) { this._onSubmitClick(oEvent); diff --git a/datamodels/2.x/itop-portal-base/portal/public/js/portal_leave_handler.js b/datamodels/2.x/itop-portal-base/portal/public/js/portal_leave_handler.js deleted file mode 100644 index c04d6d237e..0000000000 --- a/datamodels/2.x/itop-portal-base/portal/public/js/portal_leave_handler.js +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2013-2023 Combodo SARL - * - * 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 - */ - -; -$(function() -{ - // the widget definition, where 'itop' is the namespace, - // 'portal_leave_handler' the widget name - $.widget( 'itop.portal_leave_handler', - { - // default options - options: - { - 'message': 'allo la', - }, - events: ['hide.bs.modal', 'beforeunload'], - //[event] - registered_blockers: {}, - // {id : {target : 'event1', target : 'event2'}} - - // the constructor - _create: function() - { - var me =this; - this.element - .addClass('portal_leave_handler'); - - this.element.on('register_blocker.portal.itop', function(oEvent, oData){ - me._onRegisterBlocker(oData.sBlockerId, oData.sTargetElemSelector, oData.oTargetElemSelector, oData.sEventName); - }); - this.element.on('unregister_blocker.portal.itop', function(oEvent, oData){ - me._onUnregisterBlocker(oData.sBlockerId); - }); - - this.element.on('hide.bs.modal', function(oEvent) {return me._onLeaveHandler(oEvent);}); - window.addEventListener('beforeunload', function(oEvent) {return me._onLeaveHandler(oEvent);}); - - this._super(); - }, - _onRegisterBlocker: function(sBlockerId, sTargetElemSelector, oTargetElemSelector, sEventName) - { - var aRegisteredBlock = {}; - aRegisteredBlock[sTargetElemSelector] = {'eventName': sEventName, 'selector': oTargetElemSelector}; - $.extend( - aRegisteredBlock, - this.registered_blockers[sBlockerId] - ); - this.registered_blockers[sBlockerId] = aRegisteredBlock; - }, - _onUnregisterBlocker: function(sBlockerId) - { - delete this.registered_blockers[sBlockerId]; - }, - _onLeaveHandler: function(oEvent) - { - var me = this; - for(var aRegisteredBlocker in me.registered_blockers) - { - for(var sBlockerTarget in me.registered_blockers[aRegisteredBlocker]) - { - if($(me.registered_blockers[aRegisteredBlocker][sBlockerTarget]['selector'])[0] === oEvent.target && me.registered_blockers[aRegisteredBlocker][sBlockerTarget]['eventName'].split('.')[0] === oEvent.type) - { - if(oEvent.type === 'beforeunload') - { - oEvent.returnValue = me.options.message; - return; - } - else - { - var $bReturnValue = confirm(me.options.message); - if ($bReturnValue) - { - $('body').trigger('unregister_blocker.portal.itop', {'sBlockerId': aRegisteredBlocker}); - } - return $bReturnValue; - } - } - } - } - }, - // events bound via _bind are removed automatically - // revert other modifications here - _destroy: function() - { - this.element - .removeClass('portal_leave_handler'); - - this._super(); - }, - // _setOptions is called with a hash of all options that are changing - // always refresh when changing options - _setOptions: function() - { - this._superApply(arguments); - }, - // _setOption is called for each individual option that is changing - _setOption: function( key, value ) - { - this._super( key, value ); - }, - showOptions: function() - { - return this.options; - } - }); -}); - diff --git a/datamodels/2.x/itop-portal-base/portal/templates/layout.html.twig b/datamodels/2.x/itop-portal-base/portal/templates/layout.html.twig index 474e71e90c..d5959219a1 100644 --- a/datamodels/2.x/itop-portal-base/portal/templates/layout.html.twig +++ b/datamodels/2.x/itop-portal-base/portal/templates/layout.html.twig @@ -135,7 +135,7 @@ - + {# Selectize for sets #} @@ -539,7 +539,12 @@ }); // Initialize confirmation message handler when a form with touched fields is closed - oBodyElem.portal_leave_handler({'message': '{{ 'Portal:Form:Close:Warning'|dict_s }}'}); + oBodyElem.leave_handler({ + 'message': '{{ 'Portal:Form:Close:Warning'|dict_s }}', + 'extra_events': { + 'body': ['hide.bs.modal'] + } + }); {% endblock %} }); diff --git a/js/leave_handler.js b/js/leave_handler.js new file mode 100644 index 0000000000..dd1d9fdff3 --- /dev/null +++ b/js/leave_handler.js @@ -0,0 +1,155 @@ +/* + * @copyright Copyright (C) 2010-2023 Combodo SARL + * @license http://opensource.org/licenses/AGPL-3.0 + */ + +/** + * Leave handler + * + * Prevent unexcepted loose of data when leaving a modal / page by prompting a confirmation to the user + * + * @since 3.1.0 + * @internal + */ +; +$(function() +{ + // the widget definition, where 'itop' is the namespace, + // 'leave_handler' the widget name + $.widget( 'itop.leave_handler', + { + // default options + options: + { + 'message': 'Do you really want to loose your changes?', + 'extra_events': {}, + }, + //[event] + events: { + 'window': ['beforeunload'], + 'body': [], + 'element': [] + }, + // {id : {target : 'event1', target : 'event2'}} + registered_blockers: {}, + + // the constructor + _create: function() + { + const me =this; + this.element + .addClass('leave_handler'); + + this.element.on('register_blocker.itop', function(oEvent, oData){ + me._onRegisterBlocker(oData.sBlockerId, oData.sTargetElemSelector, oData.oTargetElemSelector, oData.sEventName); + }); + this.element.on('unregister_blocker.itop', function(oEvent, oData){ + me._onUnregisterBlocker(oData.sBlockerId); + }); + + // Merge default events with extra events from the consumer + // Note: There is no native way yet to recursively merge objects with arrays (and no duplicate) + for (const sTarget in this.options.extra_events) { + for (const sEvent of this.options.extra_events[sTarget]) { + // Ignore event already present + if (this.events[sTarget] === undefined || this.events[sTarget].indexOf(sEvent) !== -1) { + continue; + } + + this.events[sTarget].push(sEvent); + } + } + + // Register listeners for all events + for (const sTarget in this.events) { + if (sTarget === 'window') { + for (const sEvent of this.events[sTarget]) { + window.addEventListener(sEvent, function(oEvent) { + return me._onLeaveHandler(oEvent); + }); + } + } else if (sTarget === 'body') { + for (const sEvent of this.events[sTarget]) { + $('body').on(sEvent, function(oEvent) { + return me._onLeaveHandler(oEvent); + }); + } + } else if (sTarget === 'element') { + for (const sEvent of this.events[sTarget]) { + this.element.on(sEvent, function(oEvent) { + return me._onLeaveHandler(oEvent); + }); + } + } + } + + this._super(); + }, + _onRegisterBlocker: function(sBlockerId, sTargetElemSelector, oTargetElemSelector, sEventName) + { + let aRegisteredBlock = {}; + aRegisteredBlock[sTargetElemSelector] = {'eventName': sEventName, 'selector': oTargetElemSelector}; + $.extend( + aRegisteredBlock, + this.registered_blockers[sBlockerId] + ); + this.registered_blockers[sBlockerId] = aRegisteredBlock; + }, + _onUnregisterBlocker: function(sBlockerId) + { + delete this.registered_blockers[sBlockerId]; + }, + _onLeaveHandler: function(oEvent) + { + const me = this; + for(const aRegisteredBlocker in me.registered_blockers) + { + for(const sBlockerTarget in me.registered_blockers[aRegisteredBlocker]) + { + if($(me.registered_blockers[aRegisteredBlocker][sBlockerTarget]['selector'])[0] === oEvent.target && me.registered_blockers[aRegisteredBlocker][sBlockerTarget]['eventName'].split('.')[0] === oEvent.type) + { + if(oEvent.type === 'beforeunload') + { + oEvent.returnValue = me.options.message; + return; + } + else + { + const $bReturnValue = confirm(me.options.message); + if ($bReturnValue) + { + $('body').trigger('unregister_blocker.itop', {'sBlockerId': aRegisteredBlocker}); + } + return $bReturnValue; + } + } + } + } + }, + // events bound via _bind are removed automatically + // revert other modifications here + _destroy: function() + { + this.element + .removeClass('leave_handler'); + + this._super(); + }, + // _setOptions is called with a hash of all options that are changing + // always refresh when changing options + _setOptions: function() + { + this._superApply(arguments); + }, + // _setOption is called for each individual option that is changing + _setOption: function( key, value ) + { + this._super( key, value ); + }, + showOptions: function() + { + return this.options; + } + }); +}); +