From 863ea657ed76ce8097c01fb514613edece4d982e Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Sun, 10 Jul 2022 18:57:09 +0300 Subject: [PATCH] modal/main supporting elements --- client/src/views/address-map/view.js | 57 ++++++++++++++++++++-------- client/src/views/detail.js | 50 +++++++++++++++--------- client/src/views/main.js | 39 ++++++++++++++----- client/src/views/modal.js | 24 +++++++++++- client/src/views/modals/view-map.js | 19 ++++++---- 5 files changed, 139 insertions(+), 50 deletions(-) diff --git a/client/src/views/address-map/view.js b/client/src/views/address-map/view.js index f9ba6baa8b..77cfe20344 100644 --- a/client/src/views/address-map/view.js +++ b/client/src/views/address-map/view.js @@ -26,11 +26,14 @@ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ -define('views/address-map/view', 'views/main', function (Dep) { +define('views/address-map/view', ['views/main'], function (Dep) { return Dep.extend({ - templateContent: '
{{{map}}}
', + templateContent: ` + +
{{{map}}}
+ `, setup: function () { var field = this.options.field; @@ -52,43 +55,67 @@ define('views/address-map/view', 'views/main', function (Dep) { afterRender: function () { var field = this.options.field; - var viewName = this.model.getFieldParam(field + 'Map', 'view') || this.getFieldManager().getViewName('map'); + var viewName = this.model.getFieldParam(field + 'Map', 'view') || + this.getFieldManager().getViewName('map'); this.createView('map', viewName, { model: this.model, name: field + 'Map', el: this.getSelector() + ' .map-container', height: this.getHelper().calculateContentContainerHeight(this.$el.find('.map-container')), - }, function (view) { + }, (view) => { view.render(); }); }, getHeader: function () { - var name = Handlebars.Utils.escapeExpression(this.model.get('name')); + let name = this.model.get('name'); if (name === '') { name = this.model.id; } - name = '' + name + ''; + let recordUrl = '#' + this.model.entityType + '/view/' + this.model.id + let scopeLabel = this.getLanguage().translate(this.model.entityType, 'scopeNamesPlural'); + let fieldLabel = this.translate(this.options.field, 'fields', this.model.entityType); + let rootUrl = this.options.rootUrl || + this.options.params.rootUrl || + '#' + this.model.entityType; + + let $name = $('') + .attr('href', recordUrl) + .append( + $('') + .addClass('font-size-flexible title') + .text(name) + ); if (this.model.get('deleted')) { - name = '' + name + ''; + $name.css('text-decoration', 'line-through'); } - var rootUrl = this.options.rootUrl || this.options.params.rootUrl || '#' + this.model.entityType; + let $root = $('') + .append( + $('') + .attr('href', rootUrl) + .addClass('action') + .attr('data-action', 'navigateToRoot') + .text(scopeLabel) + ); - var headerIconHtml = this.getHeaderIconHtml(); + let headerIconHtml = this.getHeaderIconHtml(); + + if (headerIconHtml) { + $root.prepend(headerIconHtml); + } + + let $field = $('').text(fieldLabel) return this.buildHeaderHtml([ - headerIconHtml + - '' + - this.getLanguage().translate(this.model.entityType, 'scopeNamesPlural') + '', - name, - this.translate(this.options.field, 'fields', this.model.entityType) + $root, + $name, + $field, ]); }, - }); }); diff --git a/client/src/views/detail.js b/client/src/views/detail.js index 847551efa3..eb584c115f 100644 --- a/client/src/views/detail.js +++ b/client/src/views/detail.js @@ -260,34 +260,46 @@ define('views/detail', ['views/main'], function (Dep) { * @inheritDoc */ getHeader: function () { - var name = Handlebars.Utils.escapeExpression(this.model.get('name')); + let name = this.model.get('name'); if (name === '') { name = this.model.id; } - name = '' + name + ''; + let $name = + $('') + .addClass('font-size-flexible title') + .text(name) if (this.model.get('deleted')) { - name = '' + name + ''; + $name.css('text-decoration', 'line-through'); } - var rootUrl = this.options.rootUrl || this.options.params.rootUrl || '#' + this.scope; + let rootUrl = this.options.rootUrl || this.options.params.rootUrl || '#' + this.scope; + let headerIconHtml = this.getHeaderIconHtml(); + let scopeLabel = this.getLanguage().translate(this.scope, 'scopeNamesPlural'); - var headerIconHtml = this.getHeaderIconHtml(); - - var rootHtml = this.getLanguage().translate(this.scope, 'scopeNamesPlural'); + let $root = $('').text(scopeLabel); if (!this.rootLinkDisabled) { - rootHtml = - '' + - rootHtml + - ''; + $root = $('') + .append( + $('') + .attr('href', rootUrl) + .addClass('action') + .attr('data-action', 'navigateToRoot') + .text(scopeLabel) + ); + + } + + if (headerIconHtml) { + $root.prepend(headerIconHtml); } return this.buildHeaderHtml([ - headerIconHtml + rootHtml, - name, + $root, + $name, ]); }, @@ -485,8 +497,10 @@ define('views/detail', ['views/main'], function (Dep) { var massRelateEnabled = data.massSelect; + let filters; + if (link in this.selectRelatedFilters) { - var filters = Espo.Utils.cloneDeep(this.selectRelatedFilters[link]) || {}; + filters = Espo.Utils.cloneDeep(this.selectRelatedFilters[link]) || {}; for (var filterName in filters) { if (typeof filters[filterName] === 'function') { @@ -514,7 +528,7 @@ define('views/detail', ['views/main'], function (Dep) { ~['belongsTo', 'belongsToParent'].indexOf(foreignLinkType) && foreignLinkFieldType ) { - var filters = {}; + filters = {}; if (foreignLinkFieldType === 'link' || foreignLinkFieldType === 'linkParent') { filters[foreignLink] = { @@ -541,7 +555,8 @@ define('views/detail', ['views/main'], function (Dep) { dataBoolFilterList = data.boolFilterList.split(','); } - var boolFilterList = dataBoolFilterList || Espo.Utils.cloneDeep(this.selectBoolFilterLists[link] || []); + var boolFilterList = dataBoolFilterList || + Espo.Utils.cloneDeep(this.selectBoolFilterLists[link] || []); if (typeof boolFilterList === 'function') { boolFilterList = boolFilterList.call(this); @@ -615,8 +630,7 @@ define('views/detail', ['views/main'], function (Dep) { actionDuplicate: function () { Espo.Ui.notify(this.translate('pleaseWait', 'messages')); - this - .ajaxPostRequest(this.scope + '/action/getDuplicateAttributes', { + Espo.Ajax.postRequest(this.scope + '/action/getDuplicateAttributes', { id: this.model.id }) .then((attributes) => { diff --git a/client/src/views/main.js b/client/src/views/main.js index ff474aa038..904be0f1f9 100644 --- a/client/src/views/main.js +++ b/client/src/views/main.js @@ -190,7 +190,9 @@ define('views/main', ['view'], function (Dep) { } if (item.accessDataList) { - if (!Espo.Utils.checkAccessDataList(item.accessDataList, this.getAcl(), this.getUser())) { + if (!Espo.Utils + .checkAccessDataList(item.accessDataList, this.getAcl(), this.getUser()) + ) { return; } } @@ -223,19 +225,38 @@ define('views/main', ['view'], function (Dep) { /** * Build a header HTML. To be called from the #getHeader method. + * Beware of XSS. * - * @param {string[]} arr A breadcrumb path. Like: Account > Name > edit. + * @param {(string|Element|JQuery)[]} itemList A breadcrumb path. Like: Account > Name > edit. * @returns {string} HTML */ - buildHeaderHtml: function (arr) { - var a = []; - - arr.forEach(item => { - a.push(''); + buildHeaderHtml: function (itemList) { + let $itemList = itemList.map(item => { + return $('
') + .addClass('breadcrumb-item') + .append(item); }); - return '
' + - a.join('') + '
'; + let $div = $('
') + .addClass('header-breadcrumbs'); + + $itemList.forEach(($item, i) => { + $div.append($item); + + if (i === $itemList.length - 1) { + return; + } + + $div.append( + $('
') + .addClass('breadcrumb-separator') + .append( + $('').addClass('chevron-right') + ) + ) + }); + + return $div.get(0).outerHTML; }, diff --git a/client/src/views/modal.js b/client/src/views/modal.js index 0c77921e3a..6357274d0c 100644 --- a/client/src/views/modal.js +++ b/client/src/views/modal.js @@ -32,6 +32,7 @@ define('views/modal', ['view'], function (Dep) { * A base modal view. Can be extended or used directly. * * Options: + * - `headerElement` * - `headerHtml` * - `headerText` * - `$header` @@ -88,13 +89,29 @@ define('views/modal', ['view'], function (Dep) { header: false, /** - * A header HTML. + * A header HTML. Beware of XSS. * * @protected * @type {string} */ headerHtml: null, + /** + * A header JQuery instance. + * + * @protected + * @type {JQuery} + */ + $header: null, + + /** + * A header element. + * + * @protected + * @type {Element} + */ + headerElement: null, + /** * A dialog instance. * @@ -228,6 +245,7 @@ define('views/modal', ['view'], function (Dep) { this.header = this.options.header || this.header; this.headerHtml = this.options.headerHtml || this.headerHtml; this.$header = this.options.$header || this.$header; + this.headerElement = this.options.headerElement || this.headerElement; if (this.options.headerText) { this.headerHtml = Handlebars.Utils.escapeExpression(this.options.headerText); @@ -272,6 +290,10 @@ define('views/modal', ['view'], function (Dep) { headerHtml = this.$header.get(0).outerHTML; } + if (this.headerElement) { + headerHtml = this.headerElement.outerHTML; + } + this.dialog = new Espo.Ui.Dialog({ backdrop: this.backdrop, header: headerHtml, diff --git a/client/src/views/modals/view-map.js b/client/src/views/modals/view-map.js index b3945049a7..98de722941 100644 --- a/client/src/views/modals/view-map.js +++ b/client/src/views/modals/view-map.js @@ -26,7 +26,7 @@ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ -define('views/modals/view-map', 'views/modal', function (Dep) { +define('views/modals/view-map', ['views/modal'], function (Dep) { return Dep.extend({ @@ -46,13 +46,19 @@ define('views/modals/view-map', 'views/modal', function (Dep) { }, setup: function () { - var field = this.options.field; + let field = this.options.field; - this.headerHtml = this.getHelper().sanitizeHtml( - this.translate(field, 'fields', this.model.entityType) - ); + let url = '#AddressMap/view/' + this.model.entityType + '/' + this.model.id + '/' + field; + let fieldLabel = this.translate(field, 'fields', this.model.entityType); - var viewName = this.model.getFieldParam(field + 'Map', 'view') || this.getFieldManager().getViewName('map'); + this.headerElement = + $('') + .attr('href', '#' + url) + .text(fieldLabel) + .get(0); + + let viewName = this.model.getFieldParam(field + 'Map', 'view') || + this.getFieldManager().getViewName('map'); this.createView('map', viewName, { model: this.model, @@ -61,6 +67,5 @@ define('views/modals/view-map', 'views/modal', function (Dep) { height: 'auto', }); }, - }); });