From 8dc7d68b8c4f3652ed99dae3535bb3766861a854 Mon Sep 17 00:00:00 2001 From: Molkobain Date: Thu, 4 May 2023 22:13:52 +0200 Subject: [PATCH] =?UTF-8?q?N=C2=B05890=20-=20Use=20leave=20handler=20in=20?= =?UTF-8?q?backoffice=20forms=20to=20better=20handle=20leave=20confirmatio?= =?UTF-8?q?n=20message=20with=20multiple=20forms=20in=20a=20single=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application/cmdbabstract.class.inc.php | 58 ++++++++++++++++----- js/leave_handler.js | 32 +++++++++++- js/pages/backoffice/on-ready.js | 8 +++ sources/Application/WebPage/iTopWebPage.php | 12 +++-- 4 files changed, 91 insertions(+), 19 deletions(-) diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 88191b358..37b8011a5 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -2694,8 +2694,7 @@ HTML; $sEventList = implode(' ', $aEventsList); $oPage->add_ready_script(<<add_ready_script( - <<add_ready_script(<<GetId()}').closest('[data-role="ibo-modal"]'); + if (oModalElem.length !== 0) { + $('body').trigger('register_blocker.itop', { + 'sBlockerId': sBlockerId, + 'sTargetElemSelector': '#' + oModalElem.attr('id'), + 'oTargetElemSelector': '#' + oModalElem.attr('id'), + 'sEventName': 'dialogbeforeclose' + }); + } + + // Unregister blockers if any action button has been clicked (cancel, submit, transition, custom operation) + // Important 1: The listener MUST be on the buttons directly (instead of on the toolbar with a filter on listener) as we need this to be call as early as possible, we can't wait for the event to bubble. + // Otherwise the buttons action listener will be triggered first. + // Important 2: This must be declared BEFORE the cancel button callback in order to be triggered first as well + $('#{$oToolbarButtons->GetId()}').find('button, a').on('click', function () { + $('body').trigger('unregister_blocker.itop', { + 'sBlockerId': sBlockerId + }); + }); +})(); +JS ); if (isset($aExtraParams['nbBulkObj'])) { diff --git a/js/leave_handler.js b/js/leave_handler.js index dd1d9fdff..e957d1911 100644 --- a/js/leave_handler.js +++ b/js/leave_handler.js @@ -21,16 +21,27 @@ $(function() // default options options: { + /** {String} Message to show in the confirmation dialog if supported by the browser */ 'message': 'Do you really want to loose your changes?', + /** {Object} @see this.events */ 'extra_events': {}, }, - //[event] + /** + * {Object} Object representing the DOM elements on which specific events can have a blocker registered on. + * Will be merged with {@see this.options.extra_events} from the widget instantiator + */ events: { 'window': ['beforeunload'], 'body': [], 'element': [] }, - // {id : {target : 'event1', target : 'event2'}} + /** + * {Object} Object representing for each blocker their DOM target and events + * { + * id1: {target1 : 'event1', target1 : 'event2'}, + * id2: {target1 : 'event3', target2 : 'event1'} + * } + */ registered_blockers: {}, // the constructor @@ -85,6 +96,13 @@ $(function() this._super(); }, + /** + * @param sBlockerId {String} {@see this.registered_blockers} + * @param sTargetElemSelector {String} JS string selector of the target element (eg. `'#some-element'` for a regular element,`'document'` for the document) + * @param oTargetElemSelector {String|Object} JS string selector or JS DOM object representing the target element (eg. `'#some-element'` for a regular element, `document` for the document -mind the absence of quotes) + * @param sEventName {String} + * @private + */ _onRegisterBlocker: function(sBlockerId, sTargetElemSelector, oTargetElemSelector, sEventName) { let aRegisteredBlock = {}; @@ -95,10 +113,20 @@ $(function() ); this.registered_blockers[sBlockerId] = aRegisteredBlock; }, + /** + * @param sBlockerId {String} {@see this.registered_blockers} + * @private + */ _onUnregisterBlocker: function(sBlockerId) { delete this.registered_blockers[sBlockerId]; }, + /** + * + * @param oEvent {Object} jQuery object representing the event triggering the leave attempt + * @returns {boolean} + * @private + */ _onLeaveHandler: function(oEvent) { const me = this; diff --git a/js/pages/backoffice/on-ready.js b/js/pages/backoffice/on-ready.js index f1a5ef467..0a6d0d94e 100644 --- a/js/pages/backoffice/on-ready.js +++ b/js/pages/backoffice/on-ready.js @@ -79,4 +79,12 @@ $(document).ready(function () { let oDialogElem = $(this).dialog('instance').uiDialog; oDialogElem.next('.ui-widget-overlay').css('z-index', oDialogElem.css('z-index') - 1); } ); + + // Initialize leave handler when a form with touched fields is about to be closed + $('body').leave_handler({ + 'message': Dict.S('UI:NavigateAwayConfirmationMessage'), + 'extra_events': { + 'body': ['dialogbeforeclose'] // jQueryUI dialog + } + }); }); diff --git a/sources/Application/WebPage/iTopWebPage.php b/sources/Application/WebPage/iTopWebPage.php index 53be1340b..be681eb89 100644 --- a/sources/Application/WebPage/iTopWebPage.php +++ b/sources/Application/WebPage/iTopWebPage.php @@ -154,6 +154,9 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage { parent::InitializeLinkedScripts(); + // Used by forms + $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/leave_handler.js'); + // Used by external keys, DM viewer $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.treeview.min.js'); @@ -161,10 +164,6 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery-ui-timepicker-addon.min.js'); $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery-ui-timepicker-addon-i18n.min.js'); - // Tooltips - $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'node_modules/@popperjs/core/dist/umd/popper.min.js'); - $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'node_modules/tippy.js/dist/tippy-bundle.umd.min.js'); - // Used by external keys and other drop down lists $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/selectize.min.js'); $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'node_modules/selectize-plugin-a11y/selectize-plugin-a11y.js'); @@ -179,6 +178,10 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage // Used by the newsroom $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/showdown.min.js'); + // Tooltips + $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'node_modules/@popperjs/core/dist/umd/popper.min.js'); + $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'node_modules/tippy.js/dist/tippy-bundle.umd.min.js'); + // Keyboard shortcuts $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/mousetrap/mousetrap.min.js'); $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/mousetrap/mousetrap-record.min.js'); @@ -211,6 +214,7 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage $this->add_dict_entries('Enum:Undefined'); $this->add_dict_entry('UI:Datatables:Language:Processing'); $this->add_dict_entries('UI:Newsroom'); + $this->add_dict_entry('UI:NavigateAwayConfirmationMessage'); // User not logged in dialog $this->add_dict_entry('UI:DisconnectedDlgTitle');