edit modal helper ref

This commit is contained in:
Yuri Kuznetsov
2025-02-21 11:50:46 +02:00
parent 717869d0da
commit 2863654f8f
12 changed files with 268 additions and 172 deletions
+167 -18
View File
@@ -29,9 +29,10 @@
import {inject} from 'di';
import Metadata from 'metadata';
import AclManager from 'acl-manager';
import Router from 'router';
/**
* A record-modal helper.
* A record-modal helper. Use to render the quick view and quick edit modals.
*/
class RecordModalHelper {
@@ -50,22 +51,34 @@ class RecordModalHelper {
acl
/**
* @param {module:view} view
* @private
* @type {Router}
*/
@inject(Router)
router
/**
* Show the 'detail' modal.
*
* @param {import('view').default} view
* @param {{
* id: string,
* scope: string,
* model?: module:model,
* entityType: string,
* model?: import('model').default,
* editDisabled?: boolean,
* rootUrl?: string,
* afterSave?: function(import('model').default),
* afterDestroy?: function(import('model').default),
* }} params
* @return {Promise}
* @return {Promise<import('views/modals/detail').default>}
*/
showDetail(view, params) {
async showDetail(view, params) {
const id = params.id;
const scope = params.scope;
// noinspection JSUnresolvedReference
const entityType = params.entityType || params.scope;
const model = params.model;
if (!id || !scope) {
if (!id || !entityType) {
console.error("Bad data.");
return Promise.reject();
@@ -75,27 +88,163 @@ class RecordModalHelper {
return Promise.reject();
}
const viewName = this.metadata.get(['clientDefs', scope, 'modalViews', 'detail']) ||
const viewName = this.metadata.get(`clientDefs.${entityType}.modalViews.detail`) ||
'views/modals/detail';
Espo.Ui.notify(' ... ');
Espo.Ui.notifyWait()
const options = {
scope: scope,
entityType: entityType,
model: model,
id: id,
quickEditDisabled: params.editDisabled,
rootUrl: params.rootUrl,
};
return view.createView('modal', viewName, options, modalView => {
modalView.render()
.then(() => Espo.Ui.notify(false));
Espo.Ui.notifyWait();
view.listenToOnce(modalView, 'remove', () => {
view.clearView('modal');
});
});
const modalView = /** @type {import('views/modals/detail').default} */
await view.createView('modal', viewName, options);
// @todo Revise.
view.listenToOnce(modalView, 'remove', () => view.clearView('modal'));
if (params.afterSave) {
modalView.listenTo(modalView, 'after:save', model => params.afterSave(model));
}
if (params.afterDestroy) {
modalView.listenToOnce(modalView, 'after:destroy', model => params.afterDestroy(model));
}
await modalView.render();
Espo.Ui.notify();
return modalView;
}
/**
* Show the 'edit' modal.
*
* @param {import('view').default} view
* @param {{
* entityType: string,
* id?: string,
* model?: import('model').default,
* rootUrl?: string,
* noFullForm?: boolean,
* returnUrl?: string,
* afterSave?: function(import('model').default),
* returnDispatchParams?: {
* controller: string,
* action: string|null,
* options: {isReturn?: boolean} & Record,
* },
* }} params
* @return {Promise<import('views/modals/edit').default>}
* @since 9.1.0
*/
async showEdit(view, params) {
const id = params.id;
const entityType = params.entityType;
const model = params.model;
const viewName = this.metadata.get(`clientDefs.${entityType}.modalViews.edit`) ||
'views/modals/edit';
/** @type {module:views/modals/detail~options} */
const options = {
entityType: entityType,
id: id,
model: model,
fullFormDisabled: params.noFullForm,
returnUrl: params.returnUrl || this.router.getCurrentUrl(),
returnDispatchParams: params.returnDispatchParams,
};
if (params.rootUrl) {
options.rootUrl = params.rootUrl;
}
Espo.Ui.notifyWait();
const modalView = /** @type {import('views/modals/edit').default} */
await view.createView('modal', viewName, options)
// @todo Revise.
modalView.listenToOnce(modalView, 'remove', () => view.clearView('modal'));
if (params.afterSave) {
modalView.listenTo(modalView, 'after:save', model => params.afterSave(model));
}
await modalView.render();
Espo.Ui.notify();
return modalView;
}
/**
* Show the 'create' modal.
*
* @param {import('view').default} view
* @param {{
* entityType: string,
* rootUrl?: string,
* noFullForm?: boolean,
* returnUrl?: string,
* relate?: model:model~setRelateItem | model:model~setRelateItem[],
* attributes?: Record.<string, *>,
* afterSave?: function(import('model').default),
* returnDispatchParams?: {
* controller: string,
* action: string|null,
* options: {isReturn?: boolean} & Record,
* },
* }} params
* @return {Promise<import('views/modals/edit').default>}
* @since 9.1.0
*/
async showCreate(view, params) {
const entityType = params.entityType;
const viewName = this.metadata.get(`clientDefs.${entityType}.modalViews.edit`) ||
'views/modals/edit';
/** @type {module:views/modals/edit~options} */
const options = {
entityType: entityType,
fullFormDisabled: params.noFullForm,
returnUrl: params.returnUrl || this.router.getCurrentUrl(),
returnDispatchParams: params.returnDispatchParams,
relate: params.relate,
attributes: params.attributes,
};
if (params.rootUrl) {
options.rootUrl = params.rootUrl;
}
Espo.Ui.notifyWait();
const modalView = /** @type {import('views/modals/edit').default} */
await view.createView('modal', viewName, options)
// @todo Revise.
modalView.listenToOnce(modalView, 'remove', () => view.clearView('modal'));
if (params.afterSave) {
modalView.listenTo(modalView, 'after:save', model => params.afterSave(model));
}
await modalView.render();
Espo.Ui.notify();
return modalView;
}
}
+34 -46
View File
@@ -28,6 +28,7 @@
import {inject} from 'di';
import Metadata from 'metadata';
import RecordModal from 'helpers/record-modal';
/**
* @internal
@@ -52,65 +53,52 @@ class CreateRelatedHelper {
/**
* @param {import('model').default} model
* @param {string} link
* @param {Record} options
* @param {{
* afterSave: function(import('model').default),
* }} [options]
*/
process(model, link, options = {}) {
async process(model, link, options = {}) {
const scope = model.defs['links'][link].entity;
const foreignLink = model.defs['links'][link].foreign;
/** @type {Record} */
const panelDefs = this.metadata.get(`clientDefs.${model.entityType}.relationshipPanels.${link}`) || {};
const attributeMap = panelDefs.createAttributeMap || {};
const handler = panelDefs.createHandler;
let attributes = {};
const attributeMap = this.metadata
.get(['clientDefs', model.entityType, 'relationshipPanels', link, 'createAttributeMap']) || {};
Object.keys(attributeMap).forEach(attr => attributes[attributeMap[attr]] = model.get(attr));
Object.keys(attributeMap)
.forEach(attr => attributes[attributeMap[attr]] = model.get(attr));
if (handler) {
const Handler = await Espo.loader.requirePromise(handler);
/** @type {{getAttributes: function(import('model').default): Promise<Record>}} */
const handlerObj = new Handler(this.view.getHelper());
Espo.Ui.notify(' ... ');
const additionalAttributes = await handlerObj.getAttributes(model);
const handler = this.metadata
.get(['clientDefs', model.entityType, 'relationshipPanels', link, 'createHandler']);
new Promise(resolve => {
if (!handler) {
resolve({});
return;
}
Espo.loader.requirePromise(handler)
.then(Handler => new Handler(this.view.getHelper()))
.then(handler => {
handler.getAttributes(model)
.then(attributes => resolve(attributes));
});
}).then(additionalAttributes => {
attributes = {...attributes, ...additionalAttributes};
}
const viewName = this.metadata.get(['clientDefs', scope, 'modalViews', 'edit']) || 'views/modals/edit';
const helper = new RecordModal();
this.view.createView('quickCreate', viewName, {
scope: scope,
relate: {
model: model,
link: foreignLink,
},
attributes: attributes,
}, view => {
view.render();
view.notify(false);
await helper.showCreate(this.view, {
entityType: scope,
relate: {
model: model,
link: foreignLink,
},
attributes: attributes,
afterSave: m => {
if (options.afterSave) {
options.afterSave(m);
}
this.view.listenToOnce(view, 'after:save', () => {
if (options.fromSelectRelated) {
setTimeout(() => this.view.clearView('dialogSelectRelated'), 25);
}
model.trigger(`update-related:${link}`);
model.trigger('after:relate');
model.trigger(`after:relate:${link}`);
});
});
model.trigger(`update-related:${link}`);
model.trigger('after:relate');
model.trigger(`after:relate:${link}`);
},
});
}
}
+9
View File
@@ -1190,6 +1190,15 @@ Espo.Ui = {
* @property {boolean} [suppress] Suppress other warning alerts while this is displayed.
*/
/**
* Show the spinner.
*
* @since 9.1.0
*/
notifyWait: function () {
Espo.Ui.notify(' ... ');
},
/**
* Show a notify-message.
*
@@ -587,7 +587,7 @@ class EmailFromAddressVarchar extends BaseFieldView {
helper.showDetail(this, {
id: data.id,
scope: data.scope,
entityType: data.scope,
});
}
}
+1 -1
View File
@@ -1060,7 +1060,7 @@ class LinkMultipleFieldView extends BaseFieldView {
helper.showDetail(this, {
id: id,
scope: entityType,
entityType: entityType,
});
}
+1 -1
View File
@@ -732,7 +732,7 @@ class LinkParentFieldView extends BaseFieldView {
helper.showDetail(this, {
id: id,
scope: entityType,
entityType: entityType,
});
}
}
+1 -1
View File
@@ -1098,7 +1098,7 @@ class LinkFieldView extends BaseFieldView {
helper.showDetail(this, {
id: id,
scope: entityType,
entityType: entityType,
});
}
+1 -1
View File
@@ -68,7 +68,7 @@ class GlobalSearchNameFieldView extends BaseFieldView {
helper.showDetail(this, {
id: this.model.id,
scope: this.model.attributes._scope,
entityType: this.model.attributes._scope,
});
}
}
+9 -56
View File
@@ -30,6 +30,8 @@
import MainView from 'views/main';
import SearchManager from 'search-manager';
import RecordModal from 'helpers/record-modal';
import CreateRelatedHelper from 'helpers/record/create-related';
/**
* A list-related view.
@@ -632,65 +634,16 @@ class ListRelatedView extends MainView {
*
* @protected
*/
actionQuickCreate() {
async actionQuickCreate() {
const link = this.link;
const foreignScope = this.foreignScope;
const foreignLink = this.model.getLinkParam(link, 'foreign');
let attributes = {};
const helper = new CreateRelatedHelper(this);
const attributeMap = this.getMetadata()
.get(['clientDefs', this.scope, 'relationshipPanels', link, 'createAttributeMap']) || {};
Object.keys(attributeMap)
.forEach(attr => {
attributes[attributeMap[attr]] = this.model.get(attr);
});
Espo.Ui.notify(' ... ');
const handler = this.getMetadata()
.get(['clientDefs', this.scope, 'relationshipPanels', link, 'createHandler']);
(new Promise(resolve => {
if (!handler) {
resolve({});
return;
}
Espo.loader.requirePromise(handler)
.then(Handler => new Handler(this.getHelper()))
.then(handler => {
handler.getAttributes(this.model)
.then(attributes => resolve(attributes));
});
}))
.then(additionalAttributes => {
attributes = {...attributes, ...additionalAttributes};
const viewName = this.getMetadata()
.get(['clientDefs', foreignScope, 'modalViews', 'edit']) || 'views/modals/edit';
this.createView('quickCreate', viewName, {
scope: foreignScope,
relate: {
model: this.model,
link: foreignLink,
},
attributes: attributes,
}, view => {
view.render();
view.notify(false);
this.listenToOnce(view, 'after:save', () => {
this.collection.fetch();
this.model.trigger('after:relate');
this.model.trigger('after:relate:' + link);
});
});
});
return helper.process(this.model, link, {
afterSave: () => {
this.collection.fetch()
},
});
}
// noinspection JSUnusedGlobalSymbols
+5 -1
View File
@@ -139,7 +139,11 @@ class EditModalView extends ModalView {
* @property {boolean} [focusForCreate] Focus for create.
* @property {string} [rootUrl] A root URL.
* @property {string} [returnUrl] A return URL.
* @property {Record} [returnDispatchParams] Return dispatch params.
* @property {{
* controller: string,
* action: string|null,
* options: {isReturn?: boolean} & Record,
* }} [returnDispatchParams] Return dispatch params.
* @property {string} [fullFormUrl] A full-form URL. As of v9.0.
*/
+38 -45
View File
@@ -3274,27 +3274,38 @@ class ListRecordView extends View {
return;
}
const view = await (new RecordModal()).showDetail(this, {
const rootUrl = this.options.keepCurrentRootUrl ? this.getRouter().getCurrentUrl() : undefined;
const helper = new RecordModal();
await helper.showDetail(this, {
id: id,
scope: scope,
entityType: scope,
model: model,
rootUrl: this.options.keepCurrentRootUrl ? this.getRouter().getCurrentUrl() : null,
rootUrl: rootUrl,
editDisabled: this.quickEditDisabled,
})
afterSave: m => {
if (!model) {
return;
}
if (!model) {
return;
}
this.trigger('after:save', m);
},
afterDestroy: m => {
if (!model) {
return;
}
this.listenTo(view, 'after:save', model => this.trigger('after:save', model));
this.listenTo(view, 'after:destroy', model => this.removeRecordFromList(model.id));
this.removeRecordFromList(m.id);
},
});
}
// noinspection JSUnusedGlobalSymbols
/**
* @param {Object.<string, *>} data
*/
actionQuickEdit(data) {
async actionQuickEdit(data) {
data = data || {};
const id = data.id;
@@ -3327,43 +3338,18 @@ class ListRecordView extends View {
return;
}
const viewName = this.getMetadata().get(['clientDefs', scope, 'modalViews', 'edit']) ||
'views/modals/edit';
if (!this.quickEditDisabled) {
Espo.Ui.notify(' ... ');
const helper = new RecordModal();
const options = {
scope: scope,
const rootUrl = this.options.keepCurrentRootUrl ? this.getRouter().getCurrentUrl() : undefined;
await helper.showEdit(this, {
entityType: scope,
id: id,
model: model,
fullFormDisabled: data.noFullForm,
returnUrl: this.getRouter().getCurrentUrl(),
returnDispatchParams: {
controller: scope,
action: null,
options: {
isReturn: true,
},
},
};
if (this.options.keepCurrentRootUrl) {
options.rootUrl = this.getRouter().getCurrentUrl();
}
this.createView('modal', viewName, options, (view) => {
view.once('after:render', () => {
Espo.Ui.notify(false);
});
view.render();
this.listenToOnce(view, 'remove', () => {
this.clearView('modal');
});
this.listenToOnce(view, 'after:save', (m) => {
noFullForm: data.noFullForm,
rootUrl: rootUrl,
afterSave: m => {
const model = this.collection.get(m.id);
if (model) {
@@ -3371,7 +3357,14 @@ class ListRecordView extends View {
}
this.trigger('after:save', m);
});
},
returnDispatchParams: {
controller: scope,
action: null,
options: {
isReturn: true,
},
},
});
return;
@@ -3394,7 +3387,7 @@ class ListRecordView extends View {
options.rootUrl = this.getRouter().getCurrentUrl();
}
this.getRouter().navigate('#' + scope + '/edit/' + id, {trigger: false});
this.getRouter().navigate(`#${scope}/edit/${id}`, {trigger: false});
this.getRouter().dispatch(scope, 'edit', options);
}
@@ -744,7 +744,7 @@ class RelationshipPanelView extends BottomPanelView {
helper
.showDetail(this, {
scope: scope,
entityType: scope,
id: id,
model: model,
})