From 16e82cde650f99ef0e03c7ccff3ead16e8dbbedc Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Mon, 18 Nov 2024 16:34:05 +0200 Subject: [PATCH] save change --- client/src/views/record/base.js | 166 +++++++++++++----------------- client/src/views/record/detail.js | 2 +- 2 files changed, 75 insertions(+), 93 deletions(-) diff --git a/client/src/views/record/base.js b/client/src/views/record/base.js index f63e3ef64c..c1a40fc5eb 100644 --- a/client/src/views/record/base.js +++ b/client/src/views/record/base.js @@ -720,22 +720,6 @@ class BaseRecordView extends View { this.model.set(this.attributes, {skipReRender: true}); } - /** - * Set model attribute values. - * - * @param {Object.} setAttributes Values. - * @param {Object.} [options] Options. - */ - setModelAttributes(setAttributes, options) { - for (const item in this.model.attributes) { - if (!(item in setAttributes)) { - this.model.unset(item); - } - } - - this.model.set(setAttributes, options || {}); - } - /** * Init dynamic logic. * @@ -986,6 +970,54 @@ class BaseRecordView extends View { Espo.Ui.error(this.translate('Not valid')); } + /** + * Get changed attribute values. For new record, returns all attributes. + * + * @protected + * @param {string[]} [attributeList] + * @return {Record} + */ + getChangedAttributes(attributeList = null) { + const attributes = this.model.getClonedAttributes(); + + if (this.model.isNew()) { + return attributes; + } + + const setAttributes = {}; + + for (const attr in attributes) { + if (Espo.Utils.areEqual(this.attributes[attr], attributes[attr])) { + continue; + } + + setAttributes[attr] = attributes[attr]; + } + + /** @type {Record.} */ + const map = this.forcePatchAttributeDependencyMap || {}; + + for (const attr in map) { + if (attr in setAttributes) { + continue; + } + + if (attributeList && !attributeList.includes(attr)) { + continue; + } + + const depAttributes = map[attr]; + + const treatAsChanged = !!depAttributes.find(attr => attr in setAttributes); + + if (treatAsChanged) { + setAttributes[attr] = attributes[attr]; + } + } + + return setAttributes; + } + /** * Save options. * @@ -1014,50 +1046,15 @@ class BaseRecordView extends View { this.beforeBeforeSave(); + // A model is supposed to always contain actual values. + // Fetch may not be needed, but some field views may not have data sync implemented. + // We resort to fetching the entire form. + const fetchedAttributes = this.fetch(); - const initialAttributes = this.attributes; - const beforeSaveAttributes = this.model.getClonedAttributes(); - const attributes = _.extend( - Espo.Utils.cloneDeep(beforeSaveAttributes), - fetchedAttributes - ); + this.model.set(fetchedAttributes, {silent: true}); - let setAttributes = {}; - - if (model.isNew()) { - setAttributes = attributes; - } - - if (!model.isNew()) { - for (const attr in attributes) { - if (_.isEqual(initialAttributes[attr], attributes[attr])) { - continue; - } - - setAttributes[attr] = attributes[attr]; - } - - const forcePatchAttributeDependencyMap = this.forcePatchAttributeDependencyMap || {}; - - for (const attr in forcePatchAttributeDependencyMap) { - if (attr in setAttributes) { - continue; - } - - if (!(attr in fetchedAttributes)) { - continue; - } - - const depAttributeList = forcePatchAttributeDependencyMap[attr]; - - const treatAsChanged = !!depAttributeList.find(attr => attr in setAttributes); - - if (treatAsChanged) { - setAttributes[attr] = attributes[attr]; - } - } - } + const setAttributes = this.getChangedAttributes(Object.keys(fetchedAttributes)); if (Object.keys(setAttributes).length === 0) { if (!options.skipNotModifiedWarning) { @@ -1071,11 +1068,7 @@ class BaseRecordView extends View { return Promise.reject('notModified'); } - model.set(setAttributes, {silent: true}); - if (this.validate()) { - model.attributes = beforeSaveAttributes; - this.afterNotValid(); this.lastSaveCancelReason = 'invalid'; @@ -1105,39 +1098,14 @@ class BaseRecordView extends View { this.trigger('before:save'); model.trigger('before:save'); - const onError = (xhr, reject, resolve) => { - this.handleSaveError(xhr, options, resolve, reject) - .then(skipReject => { - if (skipReject) { - return; - } - - reject('error'); - }); - - this.afterSaveError(); - - if (!options.bypassClose) { - this.setModelAttributes(beforeSaveAttributes); - } - - this.lastSaveCancelReason = 'error'; - - this.trigger('error:save'); - this.trigger('cancel:save', {reason: 'error'}); - }; - return new Promise((resolve, reject) => { model - .save( - setAttributes, - { - patch: !model.isNew(), - headers: headers, - }, - ) + .save(setAttributes, { + patch: !model.isNew(), + headers: headers, + }) .then(() => { - this.trigger('save', initialAttributes, Object.keys(setAttributes)); + this.trigger('save', this.attributes, Object.keys(setAttributes)); this.afterSave(); @@ -1151,7 +1119,21 @@ class BaseRecordView extends View { resolve(); }) .catch(xhr => { - onError(xhr, reject, resolve); + this.handleSaveError(xhr, options, resolve, reject) + .then(skipReject => { + if (skipReject) { + return; + } + + reject('error'); + }); + + this.afterSaveError(); + + this.lastSaveCancelReason = 'error'; + + this.trigger('error:save'); + this.trigger('cancel:save', {reason: 'error'}); }); }); } diff --git a/client/src/views/record/detail.js b/client/src/views/record/detail.js index 547f891cab..ba5c2f98de 100644 --- a/client/src/views/record/detail.js +++ b/client/src/views/record/detail.js @@ -635,7 +635,7 @@ class DetailRecordView extends BaseRecordView { options.rootUrl = this.options.rootUrl; } - this.getRouter().navigate('#' + this.scope + '/edit/' + this.model.id, {trigger: false}); + this.getRouter().navigate(`#${this.scope}/edit/${this.model.id}`, {trigger: false}); this.getRouter().dispatch(this.scope, 'edit', options); }