diff --git a/client/src/app.js b/client/src/app.js index b2d43e241a..04795e03c5 100644 --- a/client/src/app.js +++ b/client/src/app.js @@ -1213,13 +1213,13 @@ class App { break; } - if (this.auth && this.router && !this.router.confirmLeaveOut) { + if (this.auth && this.router && !this.router.hasConfirmLeaveOut()) { this.logout(true); break; } - if (this.auth && this.router && this.router.confirmLeaveOut) { + if (this.auth && this.router && this.router.hasConfirmLeaveOut()) { Ui.error(this.language.translate('loggedOutLeaveOut', 'messages'), true); this.router.trigger('logout'); @@ -1441,7 +1441,7 @@ class App { } if (event.data === 'logged-out' && this.started) { - if (this.auth && this.router.confirmLeaveOut) { + if (this.auth && this.router.hasConfirmLeaveOut()) { Ui.error(this.language.translate('loggedOutLeaveOut', 'messages'), true); this.router.trigger('logout'); diff --git a/client/src/helpers/record-modal.js b/client/src/helpers/record-modal.js index 2da2308182..1b177de3e9 100644 --- a/client/src/helpers/record-modal.js +++ b/client/src/helpers/record-modal.js @@ -75,6 +75,7 @@ class RecordModalHelper { * afterDestroy?: function(import('model').default), * beforeRender?: function(import('views/modals/detail').default), * onClose?: function(), + * collapseDisabled: boolean, * }} params * @return {Promise} */ @@ -99,7 +100,7 @@ class RecordModalHelper { Espo.Ui.notifyWait(); - /** @type {module:views/modals/detail~options} */ + /** @type {module:views/modals/detail~options & module:views/modal~Options} */ const options = { entityType: entityType, model: model, @@ -110,6 +111,7 @@ class RecordModalHelper { layoutName: params.layoutName, fullFormDisabled: params.fullFormDisabled, fullFormUrl: params.fullFormUrl, + collapseDisabled: params.collapseDisabled, }; Espo.Ui.notifyWait(); @@ -166,6 +168,7 @@ class RecordModalHelper { * action: string|null, * options: {isReturn?: boolean} & Record, * }, + * collapseDisabled: boolean, * }} params * @return {Promise} * @since 9.1.0 @@ -178,7 +181,7 @@ class RecordModalHelper { const viewName = this.metadata.get(`clientDefs.${entityType}.modalViews.edit`) || 'views/modals/edit'; - /** @type {module:views/modals/edit~options} */ + /** @type {module:views/modals/edit~options & module:views/modal~Options} */ const options = { entityType: entityType, id: id, @@ -188,6 +191,7 @@ class RecordModalHelper { returnDispatchParams: params.returnDispatchParams, layoutName: params.layoutName, fullFormUrl: params.fullFormUrl, + collapseDisabled: params.collapseDisabled, }; if (params.rootUrl) { @@ -245,6 +249,7 @@ class RecordModalHelper { * action: string|null, * options: {isReturn?: boolean} & Record, * }, + * collapseDisabled: boolean, * }} params * @return {Promise} * @since 9.1.0 @@ -255,7 +260,7 @@ class RecordModalHelper { const viewName = this.metadata.get(`clientDefs.${entityType}.modalViews.edit`) || 'views/modals/edit'; - /** @type {module:views/modals/edit~options} */ + /** @type {module:views/modals/edit~options & module:views/modal~Options} */ const options = { entityType: entityType, fullFormDisabled: params.fullFormDisabled, @@ -266,6 +271,7 @@ class RecordModalHelper { focusForCreate: params.focusForCreate, layoutName: params.layoutName, fullFormUrl: params.fullFormUrl, + collapseDisabled: params.collapseDisabled, }; if (params.rootUrl) { diff --git a/client/src/router.js b/client/src/router.js index e6feacb996..1c6f8714e7 100644 --- a/client/src/router.js +++ b/client/src/router.js @@ -261,17 +261,33 @@ const Router = Backbone.Router.extend(/** @lends Router# */ { this.history.push(Backbone.history.fragment); }); - window.addEventListener('beforeunload', (e) => { - e = e || window.event; + window.addEventListener('beforeunload', event => { + event = event || window.event; - if (this.confirmLeaveOut) { - e.preventDefault(); + if ( + this.confirmLeaveOut || + this._leaveOutMap.size || + this._windowLeaveOutMap.size + ) { + event.preventDefault(); - e.returnValue = this.confirmLeaveOutMessage; + event.returnValue = this.confirmLeaveOutMessage; return this.confirmLeaveOutMessage; } }); + + /** + * @private + * @type {Map} + */ + this._leaveOutMap = new Map(); + + /** + * @private + * @type {Map} + */ + this._windowLeaveOutMap = new Map(); }, /** @@ -283,6 +299,60 @@ const Router = Backbone.Router.extend(/** @lends Router# */ { return '#' + Backbone.history.fragment; }, + /** + * Whether there's any confirm-leave-out. + * + * @since 9.1.0 + * @return {boolean} + */ + hasConfirmLeaveOut() { + return this.confirmLeaveOut || this._leaveOutMap.size || this._windowLeaveOutMap.size; + }, + + /** + * Refer an object (usually a view). Page won't be possible to close or change if there's at least one object. + * + * @param {Object} object + * @since 9.1.0 + * @internal + */ + addLeaveOutObject(object) { + this._leaveOutMap.set(object, true); + }, + + /** + * Un-refer an object. + * + * @param {Object} object + * @since 9.1.0 + * @internal + */ + removeLeaveOutObject(object) { + this._leaveOutMap.delete(object); + }, + + /** + * Refer an object (usually a view). Window won't be possible to close if there's at least one object. + * + * @param {Object} object + * @since 9.1.0 + * @internal + */ + addWindowLeaveOutObject(object) { + this._windowLeaveOutMap.set(object, true); + }, + + /** + * Un-refer an object. + * + * @param {Object} object + * @since 9.1.0 + * @internal + */ + removeWindowLeaveOutObject(object) { + this._windowLeaveOutMap.delete(object); + }, + /** * @callback module:router~checkConfirmLeaveOutCallback */ @@ -305,7 +375,7 @@ const Router = Backbone.Router.extend(/** @lends Router# */ { context = context || this; - if (this.confirmLeaveOut) { + if (this.confirmLeaveOut || this._leaveOutMap.size) { this.confirmLeaveOutDisplayed = true; this.confirmLeaveOutCanceled = false; @@ -327,6 +397,8 @@ const Router = Backbone.Router.extend(/** @lends Router# */ { this.confirmLeaveOutDisplayed = false; this.confirmLeaveOut = false; + this._leaveOutMap.clear(); + if (!this.confirmLeaveOutCanceled) { callback.call(context); } diff --git a/client/src/views/modal.js b/client/src/views/modal.js index 9de33bb753..65c55d9a2d 100644 --- a/client/src/views/modal.js +++ b/client/src/views/modal.js @@ -70,6 +70,7 @@ class ModalView extends View { * @property {'static'|boolean} [backdrop] A backdrop. * @property {module:views/modal~Button} [buttonList] Buttons. * @property {module:views/modal~Button} [dropdownItemList] Buttons. + * @property {boolean} [collapseDisabled] Not collapsable. As of v9.1.0. */ /** @@ -315,6 +316,10 @@ class ModalView extends View { this.shortcutKeys = Espo.Utils.cloneDeep(this.shortcutKeys); } + if (this.options.collapseDisabled) { + this.isCollapsable = false; + } + this.on('render', () => { if (this.dialog) { this.dialog.close(); diff --git a/client/src/views/modals/detail.js b/client/src/views/modals/detail.js index b7da7e2704..ec9e32ef4e 100644 --- a/client/src/views/modals/detail.js +++ b/client/src/views/modals/detail.js @@ -639,10 +639,12 @@ class DetailModalView extends ModalView { const helper = new RecordModal(); + // noinspection UnnecessaryLocalVariableJS const modalView = await helper.showEdit(this, { entityType: this.entityType, id: this.id, fullFormDisabled: this.fullFormDisabled, + collapseDisabled: true, afterSave: (model, o) => { this.model.set(model.getClonedAttributes()); @@ -661,7 +663,8 @@ class DetailModalView extends ModalView { }, }); - this.dialog.hide(); + // Not to hidden as it interferes with the collapsable modal. + //this.dialog.hide(); return modalView; } diff --git a/client/src/views/modals/edit.js b/client/src/views/modals/edit.js index c7a62a2f18..7ed2099882 100644 --- a/client/src/views/modals/edit.js +++ b/client/src/views/modals/edit.js @@ -52,6 +52,12 @@ class EditModalView extends ModalView { /** @protected */ bottomDisabled = false + /** + * @private + * @type {boolean} + */ + wasModified = false + /** * @private * @type {string} @@ -242,6 +248,12 @@ class EditModalView extends ModalView { this.createRecordView(model); }); + + this.listenTo(this.model, 'change', (m, o) => { + if (o.ui) { + this.wasModified = true; + } + }); } /** @@ -478,6 +490,21 @@ class EditModalView extends ModalView { this.trigger('leave'); this.dialog.close(); } + + async beforeCollapse() { + if (this.wasModified) { + this.getRecordView().setConfirmLeaveOut(false); + this.getRouter().addWindowLeaveOutObject(this); + } + } + + afterExpand() { + if (this.wasModified) { + this.getRecordView().setConfirmLeaveOut(true); + } + + this.getRouter().removeWindowLeaveOutObject(this); + } } export default EditModalView; diff --git a/client/src/views/record/base.js b/client/src/views/record/base.js index b56e15c265..b84f4eea71 100644 --- a/client/src/views/record/base.js +++ b/client/src/views/record/base.js @@ -510,7 +510,11 @@ class BaseRecordView extends View { return; } - this.getRouter().confirmLeaveOut = value; + if (value) { + this.getRouter().addLeaveOutObject(this); + } else { + this.getRouter().removeLeaveOutObject(this); + } } /**