This commit is contained in:
Yuri Kuznetsov
2022-06-13 16:42:19 +03:00
parent d61cb52afe
commit b711f263eb
4 changed files with 359 additions and 21 deletions
+8 -6
View File
@@ -31,13 +31,15 @@ define('view', [], function () {
/**
* A base view.
*
* @class Espo.View
* @class
* @name Class
* @extends Bull.View
* @memberOf module:view
*/
return Bull.View.extend(/** @lends Espo.View.prototype */{
return Bull.View.extend(/** @lends module:view.Class.prototype */{
/**
* @callback Espo.View~actionHandlerCallback
* @callback module:view.Class~actionHandlerCallback
* @param {jQuery.Event} e A DOM event.
*/
@@ -46,7 +48,7 @@ define('view', [], function () {
*
* @name model
* @type {Espo.Model|null}
* @memberof Espo.View.prototype
* @memberOf module:view.Class.prototype
* @public
*/
@@ -55,7 +57,7 @@ define('view', [], function () {
*
* @name collection
* @type {Espo.Collection|null}
* @memberof Espo.View.prototype
* @memberOf module:view.Class.prototype
* @public
*/
@@ -64,7 +66,7 @@ define('view', [], function () {
*
* @deprecated Use the `events` property.
* @param {string} action
* @param {Espo.View~actionHandlerCallback} handler
* @param {module:view.Class~actionHandlerCallback} handler
*/
addActionHandler: function (action, handler) {
let fullAction = 'click button[data-action=\"'+action+'\"]';
+180 -7
View File
@@ -28,22 +28,58 @@
define('views/detail', ['views/main'], function (Dep) {
return Dep.extend({
/**
* @class
* @name Class
* @extends module:views/main.Class
* @memberOf module:views/detail
*/
return Dep.extend(/** @lends module:views/detail.Class.prototype */{
/**
* @inheritDoc
*/
template: 'detail',
scope: null,
/**
* @inheritDoc
*/
name: 'Detail',
optionsToPass: ['attributes', 'returnUrl', 'returnDispatchParams', 'rootUrl'],
/**
* @inheritDoc
*/
optionsToPass: [
'attributes',
'returnUrl',
'returnDispatchParams',
'rootUrl',
],
/**
* A header view name.
*
* @type {string}
*/
headerView: 'views/header',
/**
* A record view name.
*
* @type {string}
*/
recordView: 'views/record/detail',
/**
* A root breadcrumb item not to be a link.
*
* @type {boolean}
*/
rootLinkDisabled: false,
/**
* Add an un-follow button.
*/
addUnfollowButtonToMenu: function () {
this.removeMenuItem('follow', true);
@@ -55,6 +91,9 @@ define('views/detail', ['views/main'], function (Dep) {
}, true);
},
/**
* Add a follow button.
*/
addFollowButtonToMenu: function () {
this.removeMenuItem('unfollow', true);
@@ -67,6 +106,9 @@ define('views/detail', ['views/main'], function (Dep) {
}, true);
},
/**
* @inheritDoc
*/
setup: function () {
Dep.prototype.setup.call(this);
@@ -91,6 +133,9 @@ define('views/detail', ['views/main'], function (Dep) {
this.getHelper().processSetupHandlers(this, 'detail');
},
/**
* Set up a page title.
*/
setupPageTitle: function () {
this.listenTo(this.model, 'after:save', () => {
this.updatePageTitle();
@@ -103,6 +148,9 @@ define('views/detail', ['views/main'], function (Dep) {
});
},
/**
* Set up a header.
*/
setupHeader: function () {
this.createView('header', this.headerView, {
model: this.model,
@@ -120,6 +168,9 @@ define('views/detail', ['views/main'], function (Dep) {
});
},
/**
* Set up a record.
*/
setupRecord: function () {
var o = {
model: this.model,
@@ -142,11 +193,19 @@ define('views/detail', ['views/main'], function (Dep) {
return this.createView('record', this.getRecordViewName(), o);
},
/**
* Get a record view name.
*
* @returns {string}
*/
getRecordViewName: function () {
return this.getMetadata()
.get('clientDefs.' + this.scope + '.recordViews.detail') || this.recordView;
},
/**
* Control follow/unfollow buttons visibility..
*/
handleFollowButton: function () {
if (this.model.get('isFollowed')) {
this.addUnfollowButtonToMenu();
@@ -159,6 +218,9 @@ define('views/detail', ['views/main'], function (Dep) {
}
},
/**
* Action 'follow'.
*/
actionFollow: function () {
this.disableMenuItem('follow');
@@ -174,6 +236,9 @@ define('views/detail', ['views/main'], function (Dep) {
});
},
/**
* Action 'unfollow'.
*/
actionUnfollow: function () {
this.disableMenuItem('unfollow');
@@ -189,6 +254,9 @@ define('views/detail', ['views/main'], function (Dep) {
});
},
/**
* @inheritDoc
*/
getHeader: function () {
var name = Handlebars.Utils.escapeExpression(this.model.get('name'));
@@ -221,6 +289,9 @@ define('views/detail', ['views/main'], function (Dep) {
]);
},
/**
* @inheritDoc
*/
updatePageTitle: function () {
if (this.model.has('name')) {
this.setPageTitle(this.model.get('name'));
@@ -230,27 +301,120 @@ define('views/detail', ['views/main'], function (Dep) {
}
},
/**
* Update a relationship panel (fetch data).
*
* @param {string} name A relationship name.
*/
updateRelationshipPanel: function (name) {
var bottom = this.getView('record').getView('bottom');
if (bottom) {
var rel = bottom.getView(name);
if (rel) {
rel.collection.fetch();
}
}
},
/**
* When a related record created, attributes will be copied from a current entity.
*
* Example:
* ```
* {
* 'linkName': {
* 'attributeNameOfCurrentEntity': 'attributeNameOfCreatedRelatedEntity',
* }
* }
* ```
*
* @type {Object}
*/
relatedAttributeMap: {},
/**
* When a related record created, use a function to obtain some attributes for a created entity.
*
* Example:
* ```
* {
* 'linkName': function () {
* return {
* 'someAttribute': this.model.get('attribute1') + ' ' +
* this.model.get('attribute2')
* };
* },
* }
* ```
*
* @type {Object}
*/
relatedAttributeFunctions: {},
/**
* When selecting a related record, field filters can be automatically applied.
*
* Example:
* ```
* {
* 'linkName': {
* 'field1': function () {
* return {
* attribute: 'field1',
* type: 'equals',
* value: this.model.get('someField'),
* data: {},// Additional filter data specific for a field type.
* };
* },
* },
* }
* ```
*
* @type {Object}
*/
selectRelatedFilters: {},
/**
* When selecting a related record, a primary filter can be automatically applied.
*
* Example:
* ```
* {
* 'linkName1': 'primaryFilterName',
* 'linkName2': function () {
* return 'primaryFilterName';
* },
* }
* ```
*
* @type {Object}
*/
selectPrimaryFilterNames: {},
/**
* When selecting a related record, bool filters can be automatically applied.
*
* Example:
* ```
* {
* 'linkName1': ['onlyMy', 'followed'],
* 'linkName2': function () {
* return ['someBoolFilterName];
* },
* }
* ```
*
* @type {Object}
*/
selectBoolFilterLists: [],
/**
* Action 'createRelated'.
*
* @param {Object} data
*/
actionCreateRelated: function (data) {
data = data || {};
@@ -267,9 +431,10 @@ define('views/detail', ['views/main'], function (Dep) {
attributes = _.extend(this.relatedAttributeFunctions[link].call(this), attributes);
}
Object.keys(this.relatedAttributeMap[link] || {}).forEach((attr) => {
attributes[this.relatedAttributeMap[link][attr]] = this.model.get(attr);
});
Object.keys(this.relatedAttributeMap[link] || {})
.forEach(attr => {
attributes[this.relatedAttributeMap[link][attr]] = this.model.get(attr);
});
this.notify('Loading...');
@@ -302,6 +467,11 @@ define('views/detail', ['views/main'], function (Dep) {
});
},
/**
* Action 'selectRelated'.
*
* @param {Object} data
*/
actionSelectRelated: function (data) {
var link = data.link;
@@ -437,6 +607,9 @@ define('views/detail', ['views/main'], function (Dep) {
});
},
/**
* Action 'duplicate'.
*/
actionDuplicate: function () {
Espo.Ui.notify(this.translate('pleaseWait', 'messages'));
+57 -1
View File
@@ -28,16 +28,37 @@
define('views/edit', ['views/main'], function (Dep) {
return Dep.extend({
/**
* @class
* @name Class
* @extends module:views/main.Class
* @memberOf module:views/edit
*/
return Dep.extend(/** @lends module:views/edit.Class.prototype */{
/**
* @inheritDoc
*/
template: 'edit',
/**
* @inheritDoc
*/
scope: null,
/**
* @inheritDoc
*/
name: 'Edit',
/**
* @inheritDoc
*/
menu: null,
/**
* @inheritDoc
*/
optionsToPass: [
'returnUrl',
'returnDispatchParams',
@@ -47,12 +68,30 @@ define('views/edit', ['views/main'], function (Dep) {
'returnAfterCreate',
],
/**
* A header view name.
*
* @type {string}
*/
headerView: 'views/header',
/**
* A record view name.
*
* @type {string}
*/
recordView: 'views/record/edit',
/**
* A root breadcrumb item not to be a link.
*
* @type {boolean}
*/
rootLinkDisabled: false,
/**
* @inheritDoc
*/
setup: function () {
this.headerView = this.options.headerView || this.headerView;
this.recordView = this.options.recordView || this.recordView;
@@ -63,6 +102,9 @@ define('views/edit', ['views/main'], function (Dep) {
this.getHelper().processSetupHandlers(this, 'edit');
},
/**
* Set up a header.
*/
setupHeader: function () {
this.createView('header', this.headerView, {
model: this.model,
@@ -71,6 +113,9 @@ define('views/edit', ['views/main'], function (Dep) {
});
},
/**
* Set up a record.
*/
setupRecord: function () {
var o = {
model: this.model,
@@ -89,10 +134,18 @@ define('views/edit', ['views/main'], function (Dep) {
return this.createView('record', this.getRecordViewName(), o);
},
/**
* Get a record view name.
*
* @returns {string}
*/
getRecordViewName: function () {
return this.getMetadata().get('clientDefs.' + this.scope + '.recordViews.edit') || this.recordView;
},
/**
* @inheritDoc
*/
getHeader: function () {
var headerIconHtml = this.getHeaderIconHtml();
@@ -133,6 +186,9 @@ define('views/edit', ['views/main'], function (Dep) {
return this.buildHeaderHtml(arr);
},
/**
* @inheritDoc
*/
updatePageTitle: function () {
var title;
+114 -7
View File
@@ -28,20 +28,49 @@
define('views/main', ['view'], function (Dep) {
return Dep.extend({
/**
* A base main view. The detail, edit, list views to be extended from.
*
* @class
* @name Class
* @extends module:view.Class
* @memberOf module:views/main
*/
return Dep.extend(/** @lends module:views/main.Class.prototype */{
/**
* A scope name.
*
* @type {string} scope
*/
scope: null,
/**
* A name.
*
* @type {string} name
*/
name: null,
/**
* Top-right menu definitions.
*
* @type {Object} menu
*/
menu: null,
/**
* @inheritDoc
*/
events: {
'click .action': function (e) {
Espo.Utils.handleAction(this, e);
},
},
/**
* @inheritDoc
*/
init: function () {
this.scope = this.options.scope || this.scope;
this.menu = {};
@@ -92,10 +121,17 @@ define('views/main', ['view'], function (Dep) {
this.updateLastUrl();
},
/**
* Update a last history URL.
*/
updateLastUrl: function () {
this.lastUrl = this.getRouter().getCurrentUrl();
},
/**
* @internal
* @returns {Object}
*/
getMenu: function () {
if (this.menuDisabled) {
return {};
@@ -141,8 +177,21 @@ define('views/main', ['view'], function (Dep) {
return menu;
},
getHeader: function () {},
/**
* Get a header HTML. To be overridden.
*
* @returns {string} HTML.
*/
getHeader: function () {
return '';
},
/**
* Build a header HTML. To be called from the #getHeader method.
*
* @param {string[]} arr A breadcrumb path. Like: Account > Name > edit.
* @returns {string} HTML
*/
buildHeaderHtml: function (arr) {
var a = [];
@@ -154,16 +203,29 @@ define('views/main', ['view'], function (Dep) {
a.join('<div class="breadcrumb-separator"><span class="chevron-right"></span></div>') + '</div>';
},
/**
* Get an icon HTML.
*
* @returns {string} HTML
*/
getHeaderIconHtml: function () {
return this.getHelper().getScopeColorIconHtml(this.scope);
},
/**
* Action 'showModal'.
*
* @todo Revise. To be removed?
*
* @param {Object} data
*/
actionShowModal: function (data) {
var view = data.view;
if (!view) {
return;
};
}
this.createView('modal', view, {
model: this.model,
@@ -183,6 +245,14 @@ define('views/main', ['view'], function (Dep) {
});
},
/**
* Add a menu item.
*
* @param {'buttons'|'dropdown'|'actions'} type A type.
* @param {Object} item Item definitions.
* @param {boolean} [toBeginning=false] To beginning.
* @param {boolean} [doNotReRender=false] Skip re-render.
*/
addMenuItem: function (type, item, toBeginning, doNotReRender) {
if (item) {
item.name = item.name || item.action;
@@ -197,8 +267,6 @@ define('views/main', ['view'], function (Dep) {
if (data.name === name) {
index = i;
return;
}
});
@@ -229,6 +297,12 @@ define('views/main', ['view'], function (Dep) {
}
},
/**
* Remove a menu item.
*
* @param {string} name An item name.
* @param {boolean} doNotReRender Skip re-render.
*/
removeMenuItem: function (name, doNotReRender) {
var index = -1;
var type = false;
@@ -264,11 +338,14 @@ define('views/main', ['view'], function (Dep) {
if (doNotReRender && this.isRendered()) {
this.$el.find('.header .header-buttons [data-name="'+name+'"]').remove();
return;
}
},
/**
* Disable a menu item.
*
* @param {string} name A name.
*/
disableMenuItem: function (name) {
this.$el
.find('.header .header-buttons [data-name="'+name+'"]')
@@ -276,6 +353,11 @@ define('views/main', ['view'], function (Dep) {
.attr('disabled');
},
/**
* Enable a menu item.
*
* @param {string} name A name.
*/
enableMenuItem: function (name) {
this.$el
.find('.header .header-buttons [data-name="'+name+'"]')
@@ -283,6 +365,12 @@ define('views/main', ['view'], function (Dep) {
.removeAttr('disabled');
},
/**
* Action 'navigateToRoot'.
*
* @param {Object} data
* @param {jQuery.Event} e
*/
actionNavigateToRoot: function (data, e) {
e.stopPropagation();
@@ -298,6 +386,11 @@ define('views/main', ['view'], function (Dep) {
});
},
/**
* Hide a menu item.
*
* @param {string} name A name.
*/
hideHeaderActionItem: function (name) {
['actions', 'dropdown', 'buttons'].forEach((t) => {
(this.menu[t] || []).forEach((item, i) => {
@@ -319,6 +412,11 @@ define('views/main', ['view'], function (Dep) {
this.controlMenuDropdownVisibility();
},
/**
* Show a hidden menu item.
*
* @param {string} name A name.
*/
showHeaderActionItem: function (name) {
['actions', 'dropdown', 'buttons'].forEach((t) => {
(this.menu[t] || []).forEach((item, i) => {
@@ -340,6 +438,12 @@ define('views/main', ['view'], function (Dep) {
this.controlMenuDropdownVisibility();
},
/**
* Whether a menu has any non-hidden dropdown items.
*
* @private
* @returns {boolean}
*/
hasMenuVisibleDropdownItems: function () {
var hasItems = false;
@@ -352,6 +456,9 @@ define('views/main', ['view'], function (Dep) {
return hasItems;
},
/**
* @private
*/
controlMenuDropdownVisibility: function () {
var $d = this.$el.find('.page-header .dropdown-group');