diff --git a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/pre/ConfirmationPreAction.java b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/pre/ConfirmationPreAction.java index 73f590ab07f..eefdfefafcc 100644 --- a/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/pre/ConfirmationPreAction.java +++ b/platform-web-ui/src/main/java/ua/com/fielden/platform/web/action/pre/ConfirmationPreAction.java @@ -75,14 +75,29 @@ public JsCode build() { return new JsCode( (withProgress ? """ if (action) { + // Avoid capturing of focus and restoration of focus after action completion with `withProgress` preAction. + // This allows "normal" functioning in cases where link was inserted into app that had focus on some element. + // By "normal" functioning we mean automatic focusing of affirmative button allowing to repeat the action on errors. + // It is of very little value to return focus to previously focused element. + // This is because mostly the link will be opened in new tab / window, and even otherwise the user would have to + // do the action of link pasting into address bar, which means they already lost context of previously focused item. + action.persistActiveElement = () => {}; + action.restoreActiveElement = () => {}; // In case where { withProgress: true } option was used, override action completion logic to close the dialog. const isActionInProgressChanged = action.isActionInProgressChanged.bind(action); action.isActionInProgressChanged = (function (newValue, oldValue) { isActionInProgressChanged(newValue, oldValue); + // If action progress has been stopped. if (newValue === false && oldValue === true) { - self.closeConfirmationDialog(); + self.confirmationDialog(dialog => dialog.enableActions(action)); } }).bind(action); + action.isActionSuccessfulChanged = (newValue, oldValue) => { + // If action has become successful. + if (newValue && !oldValue) { + self.confirmationDialog(dialog => dialog.close()); + } + }; } """ : "") + """ return self.confirm('%s', [%s]%s); diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/actions/tg-ui-action.js b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/actions/tg-ui-action.js index 68701ffb6b3..2b3f579129b 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/actions/tg-ui-action.js +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/actions/tg-ui-action.js @@ -329,6 +329,19 @@ Polymer({ observer: 'isActionInProgressChanged' }, + /// Indicates whether the action's recent execution (if any) was successful. + /// + /// In the beginning `isActionSuccessful` is false. + /// During action execution up until successful saving (including successful postActionSuccess without any exceptions) it is false. + /// After successful saving (including successful postActionSuccess without any exceptions) it is true. + /// It becomes false again after `isActionInProgress` becomes true. + /// + isActionSuccessful: { + type: Boolean, + value: false, + observer: 'isActionSuccessfulChanged' + }, + /** * Indicates whether this action should be disabled. */ @@ -612,9 +625,11 @@ Polymer({ // One exception from that rule: embedded masters in master-with-master -- the flag is configured from MasterWithMasterBuilder API. master.shouldRefreshParentCentreAfterSave = self.shouldRefreshParentCentreAfterSave; - const restoreStateAfterSave = function () { + const restoreStateAfterSave = function (actionSuccessful) { if (!self.skipAutomaticActionCompletion) { - // action execution completes + // Mark action as successful if `actionSuccessful`. + self.isActionSuccessful = !!actionSuccessful || false; + // Action execution completes. self.isActionInProgress = false; if (master.noUI === true) { @@ -624,8 +639,9 @@ Polymer({ }; master.postSaved = function (potentiallySavedOrNewEntity, newBindingEntity) { + const entitySavingSuccessful = potentiallySavedOrNewEntity && potentiallySavedOrNewEntity.isValidWithoutException(); try { - if (potentiallySavedOrNewEntity.isValidWithoutException()) { + if (entitySavingSuccessful) { if (self.postActionSuccess) { self.postActionSuccess(potentiallySavedOrNewEntity, self, master); } @@ -636,7 +652,9 @@ Polymer({ } } catch (e) { throw enhanceStateRestoration(e, () => { - restoreStateAfterSave(); + // Even though `entitySavingSuccessful` may be true, we still consider `isActionSuccessful` false. + // This, for example will leave ConfirmationPreAction.*withProgress opened to be able to repeat the action. + restoreStateAfterSave(false); master.restoreAfterSave(); }); } finally { @@ -653,7 +671,7 @@ Polymer({ } }); } - restoreStateAfterSave(); + restoreStateAfterSave(entitySavingSuccessful); }; master.postSavedError = function (errorResult) { @@ -819,6 +837,11 @@ Polymer({ }, isActionInProgressChanged: function (newValue, oldValue) { + // If action is starting its progress then reset `isActionSuccessful` to `false` (i.e. it is not yet successfull). + if (newValue && !oldValue) { + this.isActionSuccessful = false; + } + if (this._startSpinnerTimer) { clearTimeout(this._startSpinnerTimer); } @@ -830,6 +853,9 @@ Polymer({ } }, + isActionSuccessfulChanged: function (newValue, oldValue) { + }, + _computeIsIconButton: function (uiRole) { return uiRole === 'ICON'; }, diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/tg-entity-centre-template-behavior.js b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/tg-entity-centre-template-behavior.js index 4088ba1a109..46c0d1727f9 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/tg-entity-centre-template-behavior.js +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/tg-entity-centre-template-behavior.js @@ -198,14 +198,14 @@ const TgEntityCentreTemplateBehaviorImpl = { } }, - closeConfirmationDialog: function () { - if (!this.$.egi.isEditing()) { - return this._dom().closeConfirmationDialog(); + confirmationDialog: function (action) { + if (!this.$.egi.isEditing() && this._dom() && this._dom().confirmationDialog) { + return this._dom().confirmationDialog(action); } }, confirm: function (message, buttons, options) { - if (!this.$.egi.isEditing()) { + if (!this.$.egi.isEditing() && this._dom() && this._dom()._confirmationDialog) { return this._dom()._confirmationDialog().showConfirmationDialog(message, buttons, options); } return this._saveOrCancelPromise(); diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/tg-entity-centre.js b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/tg-entity-centre.js index 43bee9c5b5b..b12bf134912 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/tg-entity-centre.js +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/centre/tg-entity-centre.js @@ -841,12 +841,12 @@ Polymer({ }, 100); }, - closeConfirmationDialog: function () { - return this.$.confirmationDialog.close(); + confirmationDialog: function (action) { + return action(this._confirmationDialog()); }, confirm: function (message, buttons, options) { - return this.$.confirmationDialog.showConfirmationDialog(message, buttons, options); + return this._confirmationDialog().showConfirmationDialog(message, buttons, options); }, /** diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/components/tg-confirmation-dialog.js b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/components/tg-confirmation-dialog.js index 75f68aed81f..905d565b887 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/components/tg-confirmation-dialog.js +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/components/tg-confirmation-dialog.js @@ -166,7 +166,7 @@ export const TgConfirmationDialog = Polymer({ dialogModel.rejectDialog(); }; - dialogModel._action = function (e) { + dialogModel._action = dialogModel._originalAction = function (e) { const button = e.model.item; if (button.confirm) { // If confirmation dialog has `withProgress` option... @@ -232,13 +232,38 @@ export const TgConfirmationDialog = Polymer({ }); }, - /// Manually closes confirmation dialog. - /// Resets progress indicator (spinner) and other `withProgress` configuration. + /// Manually closes confirmation dialog (`withProgress` preAction dialogs). /// close: function () { dialogModel.$.confirmDialog.close(); + }, + + /// Manually resets progress indicator (spinner) and other configuration (`withProgress` preAction dialogs). + /// + /// @param action - optional `tg-ui-action` instance to execute on repeated YES / OK affirmative buttons tapping + /// + enableActions: function (action) { dialogModel.spinnerActive = false; dialogModel.$.confirmDialog.noCancelOnEscKey = false; + // Automatic auto-focusing of button does not occur, because it only occurs when opens. + // Find the button manually and focus it to be able to use keyboard Enter key multiple times without tapping. + const firstAutoFocusButton = dialogModel.$.confirmDialog.querySelector('[autofocus]'); + firstAutoFocusButton && firstAutoFocusButton.focus(); + + if (action) { + // Override YES / OK affirmative buttons tapping to actually perform an action. + // Previous preAction promise is already rejected and resolving it again in `_action` will not trigger `.then(...)` processing. + dialogModel._action = function (e) { + // Perform original logic including making buttons disabled and starting spinner. + dialogModel._originalAction(e); + const button = e.model.item; + // In case of affirmative button do the next logic after original promise resolution + // (see `tg-ui-action.postMasterInfoRetrieve`). + if (button.confirm) { + action.showDialog(action); + } + }; + } } }); \ No newline at end of file diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/egi/tg-custom-action-dialog.js b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/egi/tg-custom-action-dialog.js index 27c7cefa714..a3742a1d738 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/egi/tg-custom-action-dialog.js +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/egi/tg-custom-action-dialog.js @@ -1134,6 +1134,13 @@ Polymer({ console.log("The dialog is already opened and should be closed be being used again."); if (customAction) { customAction.restoreActionState(); + // At this stage of action execution `isActionInProgress` is false. + // It gets assigned to true in `_onExecuted` below. + // However, some custom logic may be needed in `isActionInProgressChanged` when action execution failed + // due to other opened dialog present. + // Let's forcefully do that logic here. It is not harmfull because original logic only turns off spinner, + // which isn't active at a time of this error. + customAction.isActionInProgressChanged(false, true); } } else { const self = this; diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/tg-entity-master-behavior.js b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/tg-entity-master-behavior.js index d38769986a5..de136314e38 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/tg-entity-master-behavior.js +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/tg-entity-master-behavior.js @@ -1580,9 +1580,9 @@ const TgEntityMasterBehaviorImpl = { })); }, - closeConfirmationDialog: function () { - if (this.$ && this.$.masterDom && this.$.masterDom.closeConfirmationDialog) { - return this.$.masterDom.closeConfirmationDialog(); + confirmationDialog: function (action) { + if (this.$ && this.$.masterDom && this.$.masterDom.confirmationDialog) { + return this.$.masterDom.confirmationDialog(action); } }, diff --git a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/tg-entity-master.js b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/tg-entity-master.js index 815d451fd95..b25b3f35ac8 100644 --- a/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/tg-entity-master.js +++ b/platform-web-ui/src/main/web/ua/com/fielden/platform/web/master/tg-entity-master.js @@ -150,8 +150,8 @@ Polymer({ this.$.actionContainer.addEventListener('layout-finished', this._actionBarLayoutFinished); }, - closeConfirmationDialog: function () { - return this.$.confirmationDialog.close(); + confirmationDialog: function (action) { + return action(this.$.confirmationDialog); }, confirm: function (message, buttons, options) {