This commit is contained in:
Yuri Kuznetsov
2022-06-19 19:53:50 +03:00
parent 313d52ad14
commit 250f6f0670
4 changed files with 363 additions and 113 deletions

View File

@@ -227,6 +227,15 @@ define('views/fields/base', ['view'], function (Dep) {
*/
$element: null,
/**
* Is searchable once a search filter is added (no need to type or selecting anything).
* Actual for search mode.
*
* @public
* @type {boolean}
*/
initialSearchIsNotIdle: false,
/**
* Is the field required.
*

View File

@@ -26,9 +26,17 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
define('views/fields/link-parent', 'views/fields/base', function (Dep) {
define('views/fields/link-parent', ['views/fields/base'], function (Dep) {
return Dep.extend({
/**
* A link-parent field (belongs-to-parent relation).
*
* @class
* @name Class
* @extends module:views/fields/base.Class
* @memberOf module:views/fields/link-parent
*/
return Dep.extend(/** @lends module:views/fields/link-parent.Class# */{
type: 'linkParent',
@@ -42,20 +50,128 @@ define('views/fields/link-parent', 'views/fields/base', function (Dep) {
listLinkTemplate: 'fields/link-parent/list-link',
/**
* A name attribute name.
*
* @type {string}
*/
nameName: null,
/**
* An ID attribute name.
*
* @type {string}
*/
idName: null,
/**
* A type attribute name.
*
* @type {string}
*/
typeName: null,
/**
* A current foreign entity type.
*
* @type {string|null}
*/
foreignScope: null,
/**
* A foreign entity type list.
*
* @type {string[]}
*/
foreignScopeList: null,
/**
* Autocomplete disabled.
*
* @protected
* @type {boolean}
*/
autocompleteDisabled: false,
/**
* A select-record view.
*
* @protected
* @type {string}
*/
selectRecordsView: 'views/modals/select-records',
/**
* Create disabled.
*
* @protected
* @type {boolean}
*/
createDisabled: false,
searchTypeList: ['is', 'isEmpty', 'isNotEmpty'],
/**
* A search type list.
*
* @protected
* @type {string[]}
*/
searchTypeList: [
'is',
'isEmpty',
'isNotEmpty',
],
/**
* A primary filter list that will be available when selecting a record.
*
* @protected
* @type {string[]|null}
*/
selectFilterList: null,
/**
* A select primary filter.
*
* @protected
* @type {string|null}
*/
selectPrimaryFilterName: null,
/**
* A select bool filter list.
*
* @protected
* @type {string[]|null}
*/
selectBoolFilterList: null,
/**
* An autocomplete max record number.
*
* @protected
* @type {number|null}
*/
autocompleteMaxCount: null,
/**
* Select all attributes.
*
* @protected
* @type {boolean}
*/
forceSelectAllAttributes: false,
/**
* Mandatory select attributes.
*
* @protected
* @type {string[]|null}
*/
mandatorySelectAttributeList: null,
/**
* @inheritDoc
*/
initialSearchIsNotIdle: true,
data: function () {
@@ -92,18 +208,52 @@ define('views/fields/link-parent', 'views/fields/base', function (Dep) {
}, Dep.prototype.data.call(this));
},
getSelectFilters: function () {},
/**
* Get advanced filters (field filters) to be applied when select a record.
* Can be extended.
*
* @protected
* @return {Object.<string,module:search-manager~advancedFilter>|null}
*/
getSelectFilters: function () {
return null;
},
/**
* Get a select bool filter list. Applied when select a record.
* Can be extended.
*
* @protected
* @return {string[]|null}
*/
getSelectBoolFilterList: function () {
return this.selectBoolFilterList;
},
/**
* Get a select primary filter. Applied when select a record.
* Can be extended.
*
* @protected
* @return {string|null}
*/
getSelectPrimaryFilterName: function () {
return this.selectPrimaryFilterName;
},
getCreateAttributes: function () {},
/**
* Attributes to pass to a model when creating a new record.
* Can be extended.
*
* @return {Object.<string,*>|null}
*/
getCreateAttributes: function () {
return null;
},
/**
* @inheritDoc
*/
setup: function () {
this.nameName = this.name + 'Name';
this.typeName = this.name + 'Type';
@@ -135,23 +285,24 @@ define('views/fields/link-parent', 'views/fields/base', function (Dep) {
this.createDisabled = this.options.createDisabled;
}
if (this.mode !== 'list') {
if (!this.isListMode()) {
this.addActionHandler('selectLink', () => {
this.notify('Loading...');
var viewName = this.getMetadata().get('clientDefs.' + this.foreignScope + '.modalViews.select') ||
var viewName = this.getMetadata()
.get('clientDefs.' + this.foreignScope + '.modalViews.select') ||
this.selectRecordsView;
this.createView('dialog', viewName, {
scope: this.foreignScope,
createButton: !this.createDisabled && this.mode !== 'search',
createButton: !this.createDisabled && !this.isSearchMode(),
filters: this.getSelectFilters(),
boolFilterList: this.getSelectBoolFilterList(),
primaryFilterName: this.getSelectPrimaryFilterName(),
createAttributes: (this.mode === 'edit') ? this.getCreateAttributes() : null,
createAttributes: this.isEditMode() ? this.getCreateAttributes() : null,
mandatorySelectAttributeList: this.getMandatorySelectAttributeList(),
forceSelectAllAttributes: this.isForceSelectAllAttributes(),
}, (dialog) => {
}, dialog => {
dialog.render();
Espo.Ui.notify(false);
@@ -171,6 +322,7 @@ define('views/fields/link-parent', 'views/fields/base', function (Dep) {
this.$elementName.val('');
this.$elementId.val('');
this.trigger('change');
});
@@ -182,13 +334,19 @@ define('views/fields/link-parent', 'views/fields/base', function (Dep) {
}
},
/**
* @inheritDoc
*/
setupSearch: function () {
var type = this.getSearchParamsData().type;
if (type === 'is' || !type) {
this.searchData.idValue = this.getSearchParamsData().idValue || this.searchParams.valueId;
this.searchData.nameValue = this.getSearchParamsData().nameValue || this.searchParams.valueName;
this.searchData.typeValue = this.getSearchParamsData().typeValue || this.searchParams.valueType;
this.searchData.idValue = this.getSearchParamsData().idValue ||
this.searchParams.valueId;
this.searchData.nameValue = this.getSearchParamsData().nameValue ||
this.searchParams.valueName;
this.searchData.typeValue = this.getSearchParamsData().typeValue ||
this.searchParams.valueType;
}
this.events = _.extend({
@@ -199,6 +357,12 @@ define('views/fields/link-parent', 'views/fields/base', function (Dep) {
}, this.events || {});
},
/**
* Handle a search type.
*
* @protected
* @param {string} type A type.
*/
handleSearchType: function (type) {
if (~['is'].indexOf(type)) {
this.$el.find('div.primary').removeClass('hidden');
@@ -207,20 +371,46 @@ define('views/fields/link-parent', 'views/fields/base', function (Dep) {
}
},
/**
* Select.
*
* @param {module:model.Class} model A model.
* @protected
*/
select: function (model) {
this.$elementName.val(model.get('name') || model.id);
this.$elementId.val(model.get('id'));
this.trigger('change');
},
/**
* Attributes to select regardless availability on a list layout.
* Can be extended.
*
* @protected
* @return {string[]|null}
*/
getMandatorySelectAttributeList: function () {
return this.mandatorySelectAttributeList;
},
/**
* Select all attributes. Can be extended.
*
* @protected
* @return {boolean}
*/
isForceSelectAllAttributes: function () {
return this.forceSelectAllAttributes;
},
/**
* Get an autocomplete max record number. Can be extended.
*
* @protected
* @return {number}
*/
getAutocompleteMaxCount: function () {
if (this.autocompleteMaxCount) {
return this.autocompleteMaxCount;
@@ -229,6 +419,12 @@ define('views/fields/link-parent', 'views/fields/base', function (Dep) {
return this.getConfig().get('recordsPerPage');
},
/**
* Compose an autocomplete URL. Can be extended.
*
* @protected
* @return {string}
*/
getAutocompleteUrl: function () {
var url = this.foreignScope + '?maxSize=' + this.getAutocompleteMaxCount();
@@ -258,7 +454,7 @@ define('views/fields/link-parent', 'views/fields/base', function (Dep) {
},
afterRender: function () {
if (this.mode === 'edit' || this.mode === 'search') {
if (this.isEditMode() || this.isSearchMode()) {
this.$elementId = this.$el.find('input[data-name="' + this.idName + '"]');
this.$elementName = this.$el.find('input[data-name="' + this.nameName + '"]');
this.$elementType = this.$el.find('select[data-name="' + this.typeName + '"]');
@@ -267,6 +463,7 @@ define('views/fields/link-parent', 'views/fields/base', function (Dep) {
if (this.$elementName.val() === '') {
this.$elementName.val('');
this.$elementId.val('');
this.trigger('change');
}
});
@@ -274,6 +471,7 @@ define('views/fields/link-parent', 'views/fields/base', function (Dep) {
this.$elementType.on('change', () => {
this.$elementName.val('');
this.$elementId.val('');
this.trigger('change');
});
@@ -308,7 +506,7 @@ define('views/fields/link-parent', 'views/fields/base', function (Dep) {
return this.getHelper().escapeString(suggestion.name);
},
transformResult: (response) => {
var response = JSON.parse(response);
response = JSON.parse(response);
var list = [];
response.list.forEach(item => {
@@ -317,13 +515,11 @@ define('views/fields/link-parent', 'views/fields/base', function (Dep) {
name: item.name || item.id,
data: item.id,
value: item.name || item.id,
attributes: item
attributes: item,
});
});
return {
suggestions: list
};
return {suggestions: list};
},
onSelect: (s) => {
this.getModelFactory().create(this.foreignScope, (model) => {
@@ -362,14 +558,21 @@ define('views/fields/link-parent', 'views/fields/base', function (Dep) {
}
},
/**
* @inheritDoc
*/
getValueForDisplay: function () {
return this.model.get(this.nameName);
},
/**
* @inheritDoc
*/
validateRequired: function () {
if (this.isRequired()) {
if (this.model.get(this.idName) === null || !this.model.get(this.typeName)) {
var msg = this.translate('fieldIsRequired', 'messages').replace('{field}', this.getLabelText());
var msg = this.translate('fieldIsRequired', 'messages')
.replace('{field}', this.getLabelText());
this.showValidationMessage(msg);
@@ -378,6 +581,9 @@ define('views/fields/link-parent', 'views/fields/base', function (Dep) {
}
},
/**
* @inheritDoc
*/
fetch: function () {
var data = {};
@@ -392,89 +598,89 @@ define('views/fields/link-parent', 'views/fields/base', function (Dep) {
return data;
},
/**
* @inheritDoc
*/
fetchSearch: function () {
var type = this.$el.find('select.search-type').val();
let type = this.$el.find('select.search-type').val();
if (type === 'isEmpty') {
var data = {
return {
type: 'isNull',
field: this.idName,
data: {
type: type
type: type,
}
};
return data;
}
if (type === 'isNotEmpty') {
var data = {
return {
type: 'isNotNull',
field: this.idName,
data: {
type: type
type: type,
}
};
return data;
}
var entityType = this.$elementType.val();
var entityName = this.$elementName.val()
var entityId = this.$elementId.val();
let entityType = this.$elementType.val();
let entityName = this.$elementName.val()
let entityId = this.$elementId.val();
if (!entityType) {
return false;
return null;
}
var data;
if (entityId) {
data = {
return {
type: 'and',
attribute: this.idName,
value: [
{
type: 'equals',
field: this.idName,
value: entityId
value: entityId,
},
{
type: 'equals',
field: this.typeName,
value: entityType
value: entityType,
}
],
data: {
type: 'is',
idValue: entityId,
nameValue: entityName,
typeValue: entityType
}
};
} else {
data = {
type: 'and',
attribute: this.idName,
value: [
{
type: 'isNotNull',
field: this.idName
},
{
type: 'equals',
field: this.typeName,
value: entityType
}
],
data: {
type: 'is',
typeValue: entityType
typeValue: entityType,
}
};
}
return data;
return {
type: 'and',
attribute: this.idName,
value: [
{
type: 'isNotNull',
field: this.idName,
},
{
type: 'equals',
field: this.typeName,
value: entityType,
}
],
data: {
type: 'is',
typeValue: entityType,
}
};
},
/**
* @inheritDoc
*/
getSearchType: function () {
return this.getSearchParamsData().type || this.searchParams.typeFront;
},

View File

@@ -163,6 +163,12 @@ define('views/fields/link', ['views/fields/base'], function (Dep) {
*/
forceSelectAllAttributes: false,
/**
* @protected
* @type {string[]|null}
*/
mandatorySelectAttributeList: null,
/**
* @inheritDoc
*/
@@ -345,7 +351,10 @@ define('views/fields/link', ['views/fields/base'], function (Dep) {
},
/**
* @inheritDoc
* Select.
*
* @param {module:model.Class} model A model.
* @protected
*/
select: function (model) {
this.$elementName.val(model.get('name') || model.id);

View File

@@ -19,7 +19,7 @@
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
************************************************************************/
define('views/fields/map', 'views/fields/base', function (Dep) {
define('views/fields/map', ['views/fields/base'], function (Dep) {
return Dep.extend({
@@ -37,7 +37,9 @@ define('views/fields/map', 'views/fields/base', function (Dep) {
data: function () {
var data = Dep.prototype.data.call(this);
data.hasAddress = this.hasAddress();
return data;
},
@@ -47,35 +49,35 @@ define('views/fields/map', 'views/fields/base', function (Dep) {
this.provider = this.options.provider || this.params.provider;
this.height = this.options.height || this.params.height || this.height;
var addressAttributeList = Object.keys(this.getMetadata().get('fields.address.fields') || {}).map(
function (a) {
var addressAttributeList = Object.keys(this.getMetadata().get('fields.address.fields') || {})
.map(a => {
return this.addressField + Espo.Utils.upperCaseFirst(a);
},
this
);
});
this.listenTo(this.model, 'sync', function (model) {
var isChanged = false;
addressAttributeList.forEach(function (attribute) {
this.listenTo(this.model, 'sync', model => {
let isChanged = false;
addressAttributeList.forEach(attribute => {
if (model.hasChanged(attribute)) {
isChanged = true;
}
}, this);
});
if (isChanged && this.isRendered()) {
this.reRender();
}
}, this);
});
this.listenTo(this.model, 'after:save', function () {
this.listenTo(this.model, 'after:save', () => {
if (this.isRendered()) {
this.reRender();
}
}, this);
});
},
hasAddress: function () {
return !!this.model.get(this.addressField + 'City') || !!this.model.get(this.addressField + 'PostalCode');
return !!this.model.get(this.addressField + 'City') ||
!!this.model.get(this.addressField + 'PostalCode');
},
onRemove: function () {
@@ -88,7 +90,7 @@ define('views/fields/map', 'views/fields/base', function (Dep) {
street: this.model.get(this.addressField + 'Street'),
postalCode: this.model.get(this.addressField + 'PostalCode'),
country: this.model.get(this.addressField + 'Country'),
state: this.model.get(this.addressField + 'State')
state: this.model.get(this.addressField + 'State'),
};
this.$map = this.$el.find('.map');
@@ -101,15 +103,19 @@ define('views/fields/map', 'views/fields/base', function (Dep) {
$(window).on('resize.' + this.cid, this.processSetHeight.bind(this));
}
var methodName = 'afterRender' + this.provider.replace(/\s+/g, '');
let methodName = 'afterRender' + this.provider.replace(/\s+/g, '');
if (typeof this[methodName] === 'function') {
this[methodName]();
} else {
var implClassName = this.getMetadata().get(['clientDefs', 'AddressMap', 'implementations', this.provider]);
}
else {
let implClassName = this.getMetadata()
.get(['clientDefs', 'AddressMap', 'implementations', this.provider]);
if (implClassName) {
require(implClassName, function (impl) {
require(implClassName, impl => {
impl.render(this);
}.bind(this));
});
}
}
}
@@ -118,38 +124,51 @@ define('views/fields/map', 'views/fields/base', function (Dep) {
afterRenderGoogle: function () {
if (window.google && window.google.maps) {
this.initMapGoogle();
} else if (typeof window.mapapiloaded === 'function') {
var mapapiloaded = window.mapapiloaded;
window.mapapiloaded = function() {
this.initMapGoogle();
mapapiloaded();
}.bind(this);
} else {
window.mapapiloaded = function () {
this.initMapGoogle();
}.bind(this);
var src = 'https://maps.googleapis.com/maps/api/js?callback=mapapiloaded';
var apiKey = this.getConfig().get('googleMapsApiKey');
if (apiKey) {
src += '&key=' + apiKey;
}
var s = document.createElement('script');
s.setAttribute('async', 'async');
s.src = src;
document.head.appendChild(s);
return;
}
if (typeof window.mapapiloaded === 'function') {
let mapapiloaded = window.mapapiloaded;
window.mapapiloaded = () => {
this.initMapGoogle();
mapapiloaded();
};
return;
}
window.mapapiloaded = () => {
this.initMapGoogle();
};
let src = 'https://maps.googleapis.com/maps/api/js?callback=mapapiloaded';
let apiKey = this.getConfig().get('googleMapsApiKey');
if (apiKey) {
src += '&key=' + apiKey;
}
let scriptElement = document.createElement('script');
scriptElement.setAttribute('async', 'async');
scriptElement.src = src;
document.head.appendChild(scriptElement);
},
processSetHeight: function (init) {
var height = this.height;
if (this.height === 'auto') {
height = this.$el.parent().height();
if (init && height <= 0) {
setTimeout(function () {
setTimeout(() => {
this.processSetHeight(true);
}.bind(this), 50);
}, 50);
return;
}
}
@@ -164,61 +183,68 @@ define('views/fields/map', 'views/fields/base', function (Dep) {
var map = new google.maps.Map(this.$el.find('.map').get(0), {
zoom: 15,
center: {lat: 0, lng: 0},
scrollwheel: false
scrollwheel: false,
});
} catch (e) {
}
catch (e) {
console.error(e.message);
return;
}
var address = '';
let address = '';
if (this.addressData.street) {
address += this.addressData.street;
}
if (this.addressData.city) {
if (address != '') {
if (address !== '') {
address += ', ';
}
address += this.addressData.city;
}
if (this.addressData.state) {
if (address != '') {
if (address !== '') {
address += ', ';
}
address += this.addressData.state;
}
if (this.addressData.postalCode) {
if (this.addressData.state || this.addressData.city) {
address += ' ';
} else {
}
else {
if (address) {
address += ', ';
}
}
address += this.addressData.postalCode;
}
if (this.addressData.country) {
if (address != '') {
if (address !== '') {
address += ', ';
}
address += this.addressData.country;
}
geocoder.geocode({'address': address}, function (results, status) {
geocoder.geocode({'address': address}, (results, status) => {
if (status === google.maps.GeocoderStatus.OK) {
map.setCenter(results[0].geometry.location);
var marker = new google.maps.Marker({
new google.maps.Marker({
map: map,
position: results[0].geometry.location
position: results[0].geometry.location,
});
}
}.bind(this));
});
},
});
});