mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-12 23:14:18 +01:00
N°5890 - Use leave handler in backoffice forms to better handle leave confirmation message with multiple forms in a single page
This commit is contained in:
@@ -2694,8 +2694,7 @@ HTML;
|
||||
$sEventList = implode(' ', $aEventsList);
|
||||
$oPage->add_ready_script(<<<JS
|
||||
$('#$sFieldToValidateId')
|
||||
.on('$sEventList',
|
||||
function(evt, sFormId) {
|
||||
.on('$sEventList', function(oEvent, sFormId) {
|
||||
// Bind to a custom event: validate
|
||||
return ValidateField('$sFieldToValidateId', '$sPattern', $bMandatory, sFormId, $sNullValue, $sOriginalValue);
|
||||
}
|
||||
@@ -2949,19 +2948,52 @@ JS
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare blocker protection to avoid loosing data
|
||||
$sBlockerId = uniqid($sClass.':'.$iKey.':', true);
|
||||
$sConfirmationMessage = addslashes(Dict::S('UI:NavigateAwayConfirmationMessage'));
|
||||
$sJSToken = json_encode($sOwnershipToken);
|
||||
$oPage->add_ready_script(
|
||||
<<<EOF
|
||||
$(window).on('unload',function() { return OnUnload('$iTransactionId', '$sClass', $iKey, $sJSToken) } );
|
||||
window.onbeforeunload = function() {
|
||||
if (!window.bInSubmit && !window.bInCancel)
|
||||
{
|
||||
return '$sConfirmationMessage';
|
||||
}
|
||||
// return nothing ! safer for IE
|
||||
};
|
||||
EOF
|
||||
$oPage->add_ready_script(<<<JS
|
||||
// Try to release concurrent lock when leaving the page
|
||||
$(window).on('unload',function() { return OnUnload('$iTransactionId', '$sClass', $iKey, $sJSToken) } );
|
||||
|
||||
// Leave handler for the current form (check if in a modal or not)
|
||||
// Note: We use a self-invoking function to avoid making unique vars. names to avoid collision (this can be called multiple time if modal forms are displayed)
|
||||
(function () {
|
||||
const sBlockerId = '{$sBlockerId}';
|
||||
|
||||
// Register blocker for the whole form even though it has not been touched yet.
|
||||
// Note: This is a known limitation of the backoffice forms, which will be handled during the whole form SDK refactoring (hopefully summer '23).
|
||||
// For now we have no way of knowing if a field (**of any type**) has been touched, so we consider the whole form has dirty no matter what
|
||||
// - On page leave
|
||||
$('body').trigger('register_blocker.itop', {
|
||||
'sBlockerId': sBlockerId,
|
||||
'sTargetElemSelector': 'document',
|
||||
'oTargetElemSelector': document,
|
||||
'sEventName': 'beforeunload'
|
||||
});
|
||||
|
||||
// - On modal close if we are in one
|
||||
const oModalElem = $('#{$oForm->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'])) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user