selectbox improvement

This commit is contained in:
Yuri Kuznetsov
2022-11-17 14:22:31 +02:00
parent 66fc373af6
commit 4e0de42b0d
22 changed files with 884 additions and 209 deletions
@@ -26,7 +26,7 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
Espo.define('crm:views/meeting/fields/attendees', 'views/fields/link-multiple-with-role', function (Dep) {
define('crm:views/meeting/fields/attendees', ['views/fields/link-multiple-with-role'], function (Dep) {
return Dep.extend({
@@ -34,8 +34,6 @@ Espo.define('crm:views/meeting/fields/attendees', 'views/fields/link-multiple-wi
roleFieldIsForeign: false,
emptyRoleValue: 'None'
emptyRoleValue: 'None',
});
});
@@ -26,14 +26,13 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
define('crm:views/meeting/fields/reminders', ['views/fields/base'], function (Dep) {
define('crm:views/meeting/fields/reminders', ['views/fields/base', 'ui/select'],
function (Dep, /** module:ui/select*/Select) {
return Dep.extend({
detailTemplate: 'crm:meeting/fields/reminders/detail',
listTemplate: 'crm:meeting/fields/reminders/detail',
editTemplate: 'crm:meeting/fields/reminders/edit',
events: {
@@ -118,7 +117,8 @@ define('crm:views/meeting/fields/reminders', ['views/fields/base'], function (De
$type.append($o);
});
$type.val(item.type);
$type.val(item.type)
.addClass('radius-left');
$type.on('change', () => {
this.updateType($type.val(), $type.closest('.reminder').index());
@@ -152,11 +152,14 @@ define('crm:views/meeting/fields/reminders', ['views/fields/base'], function (De
.html('<span class="fas fa-times"></span>');
$item
.append($('<div class="input-group-btn">').append($type))
.append($seconds)
.append($('<div class="input-group-item">').append($type))
.append($('<div class="input-group-item">').append($seconds))
.append($('<div class="input-group-btn">').append($remove));
this.$container.append($item);
Select.init($type, {});
Select.init($seconds, {});
},
stringifySeconds: function (seconds) {
+17 -6
View File
@@ -1,8 +1,19 @@
<div class="input-group">
<input type="text" class="main-element form-control" data-name="{{name}}" value="{{value}}" autocomplete="espo-{{name}}" pattern="[\-]?[0-9,.]*" {{#if params.maxLength}} maxlength="{{params.maxLength}}"{{/if}}>
<span class="input-group-btn">
<select data-name="{{currencyFieldName}}" class="form-control">
{{{options currencyList currencyValue}}}
</select>
<div class="input-group input-group-currency">
<span class="input-group-item">
<input
type="text"
class="main-element form-control radius-left"
data-name="{{name}}"
value="{{value}}"
autocomplete="espo-{{name}}"
pattern="[\-]?[0-9,.]*"
{{#if params.maxLength}} maxlength="{{params.maxLength}}"{{/if}}
>
</span>
<span class="input-group-item">
<select
data-name="{{currencyFieldName}}"
class="form-control radius-right"
>{{{options currencyList currencyValue}}}</select>
</span>
</div>
@@ -1,19 +1,21 @@
<div class="input-group input-group-link-parent">
{{#if foreignScopeList.length}}
<span class="input-group-btn">
<select class="form-control" data-name="{{typeName}}">
<span class="input-group-item">
<select class="form-control radius-left" data-name="{{typeName}}">
{{options foreignScopeList foreignScope category='scopeNames'}}
</select>
</span>
<input
class="main-element form-control middle-element"
type="text"
data-name="{{nameName}}"
value="{{nameValue}}"
autocomplete="espo-{{name}}"
placeholder="{{translate 'Select'}}"
spellcheck="false"
>
<span class="input-group-item input-group-item-middle">
<input
class="main-element form-control"
type="text"
data-name="{{nameName}}"
value="{{nameValue}}"
autocomplete="espo-{{name}}"
placeholder="{{translate 'Select'}}"
spellcheck="false"
>
</span>
<span class="input-group-btn">
<button
data-action="selectLink"
+12 -11
View File
@@ -1,20 +1,21 @@
<div>
<div class="phone-number-block-container">
{{#each phoneNumberData}}
<div class="input-group phone-number-block">
<span class="input-group-btn">
<span class="input-group-item">
<select
data-property-type="type"
class="form-control"
class="form-control radius-left"
>{{options ../params.typeList type scope=../scope field=../name}}</select>
</span>
<input
type="input"
class="form-control phone-number no-margin-shifting"
value="{{phoneNumber}}"
autocomplete="espo-{{../name}}"
maxlength={{../itemMaxLength}}
>
<span class="input-group-item input-group-item-middle">
<input
type="input"
class="form-control phone-number no-margin-shifting"
value="{{phoneNumber}}"
autocomplete="espo-{{../name}}"
maxlength={{../itemMaxLength}}
>
</span>
<span class="input-group-btn">
<button
class="btn btn-default btn-icon phone-property{{#if primary}} active{{/if}} hidden"
+368
View File
@@ -0,0 +1,368 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2022 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
define('ui/select', ['lib!Selectize'], (Selectize) => {
/**
* @typedef module:ui/select~Options
* @type {Object}
* @property {boolean} [selectOnTab=false]
* @property {boolean} [matchAnyWord=false]
*/
/**
* @module ui/select
*/
let Select = {
/**
* @param {JQuery} $el An element.
* @param {module:ui/select~Options} [options] Options.
*/
init: function ($el, options) {
options = Select.applyDefaultOptions(options || {});
let plugins = [];
Select.loadEspoSelectPlugin();
plugins.push('espo_select');
plugins.push('auto_position');
let itemClasses = {};
let allowedValues = $el.children().toArray().map(item => {
let value = item.getAttributeNode('value').value;
if (item.classList) {
itemClasses[value] = item.classList.toString();
}
return value;
});
let selectizeOptions = {
plugins: plugins,
highlight: false,
selectOnTab: options.selectOnTab,
copyClassesToDropdown: false,
allowEmptyOption: allowedValues.includes(''),
showEmptyOptionInDropdown: true,
render: {
item: function (data) {
return $('<div>')
.addClass('item')
.addClass(itemClasses[data.value] || '')
.text(data.text)
.get(0).outerHTML;
},
option: function (data) {
let $div = $('<div>')
.addClass('option')
.addClass(data.value === '' ? 'selectize-dropdown-emptyoptionlabel' : '')
.addClass(itemClasses[data.value] || '')
.val(data.value)
.text(data.text);
if (data.text === '') {
$div.html('&nbsp;');
}
return $div.get(0).outerHTML;
},
},
onDelete: function (values) {
while (values.length) {
this.removeItem(values.pop(), true);
}
this.showInput();
this.positionDropdown();
this.refreshOptions(true);
},
};
if (!options.matchAnyWord) {
/** @this Selectize */
selectizeOptions.score = function (search) {
let score = this.getScoreFunction(search);
search = search.toLowerCase();
return function (item) {
if (item.text.toLowerCase().indexOf(search) === 0) {
return score(item);
}
return 0;
};
};
}
$el.selectize(selectizeOptions);
},
/**
* Focus.
*
* @param {JQuery} $el An element.
* @param {{noTrigger?: boolean}} [options] Options.
*/
focus: function ($el, options) {
options = options || {};
let selectize = $el.get(0).selectize;
if (options.noTrigger) {
selectize.focusNoTrigger = true;
}
selectize.focus();
setTimeout(() => selectize.focusNoTrigger = false, 100);
},
/**
* Set options.
*
* @param {JQuery} $el An element.
* @param {{value: string, label: string}[]} options Options.
*/
setOptions: function ($el, options) {
let selectize = $el.get(0).selectize;
selectize.clearOptions(true);
selectize.load(callback => {
callback(
options.map(item => {
return {
value: item.value,
text: item.label,
};
})
);
});
},
/**
* Set value.
*
* @param {JQuery} $el An element.
* @param {string} value A value.
*/
setValue: function ($el, value) {
let selectize = $el.get(0).selectize;
selectize.setValue(value, true);
},
/**
* @private
* @param {module:ui/select~Options} options
* @return {module:ui/select~Options}
*/
applyDefaultOptions: function (options) {
options = Espo.Utils.clone(options);
let defaults = {
selectOnTab: false,
matchAnyWord: false,
};
for (let key in defaults) {
if (key in options) {
continue;
}
options[key] = defaults[key];
}
return options;
},
/**
* @private
*/
loadEspoSelectPlugin: function () {
if ('espo_select' in Selectize.plugins) {
return;
}
const IS_MAC = /Mac/.test(navigator.userAgent);
const KEY_BACKSPACE = 8;
Selectize.define('espo_select', function () {
let self = this;
this.refreshOptions = (function () {
let original = self.refreshOptions;
return function () {
if (self.focusNoTrigger) {
original.apply(this, [false]);
return;
}
original.apply(this, arguments);
};
})();
this.onOptionSelect = (function () {
let original = self.onOptionSelect;
return function (e) {
original.apply(this, arguments);
self.selectedValue = $(e.currentTarget).attr('data-value');
};
})();
this.open = (function() {
let original = self.open;
return function () {
let toProcess = !(self.isLocked || self.isOpen);
original.apply(this, arguments);
if (!toProcess) {
return;
}
let $selected = self.$dropdown.find('.selected');
if (!$selected.length) {
return;
}
self.$dropdown
.find('.selectize-dropdown-content')
.scrollTop($selected.get(0).offsetTop);
};
})();
this.onMouseDown = (function() {
let original = self.onMouseDown;
return function (e) {
if (self.isOpen) {
self.closedByMouseDown = true;
}
return original.apply(this, arguments);
};
})();
this.onFocus = (function() {
let original = self.onFocus;
return function (e) {
self.selectedValue = self.getValue();
if (self.closedByMouseDown) {
self.closedByMouseDown = false;
return;
}
self.closedByMouseDown = false;
return original.apply(this, arguments);
};
})();
this.revertValue = function () {
if (this.selectedValue !== null) {
this.setValue(this.selectedValue, true);
}
this.selectedValue = null;
};
this.onBlur = (function() {
let original = self.onBlur;
return function () {
self.revertValue();
self.$control_input.css({width: '4px'});
return original.apply(this, arguments);
};
})();
this.onKeyDown = (function() {
let original = self.onKeyDown;
return function (e) {
if (e.code === 'Enter' && (IS_MAC ? e.metaKey : e.ctrlKey)) {
return;
}
if (e.code === 'Escape') {
if (self.isOpen || !self.isInputHidden) {
e.stopPropagation();
}
if (self.isOpen) {
self.close();
}
if (!self.isInputHidden) {
self.hideInput();
}
self.addItem(this.selectedValue, true);
}
if (self.isFull() || self.isInputHidden) {
if (
e.key.length === 1 &&
(
e.key.match(/[a-z]/i) ||
e.key.match(/[0-9]/)
)
) {
let keyCode = e.keyCode;
e.keyCode = KEY_BACKSPACE;
self.deleteSelection(e);
//self.clear();
e.keyCode = keyCode;
}
}
return original.apply(this, arguments);
};
})();
});
},
};
return Select;
});
+9 -1
View File
@@ -26,7 +26,7 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
define('views/fields/base', ['view'], function (Dep) {
define('views/fields/base', ['view', 'ui/select'], function (Dep, /** module:ui/select*/Select) {
/**
* A base field view. Can be in different modes. Each mode uses a separate template.
@@ -780,6 +780,14 @@ define('views/fields/base', ['view'], function (Dep) {
if (this.hasRequiredMarker()) {
this.hideRequiredSign();
}
if (this.isSearchMode()) {
let $searchType = this.$el.find('select.search-type');
if ($searchType.length) {
Select.init($searchType, {matchAnyWord: true});
}
}
});
if ((this.isDetailMode() || this.isEditMode()) && this.tooltip) {
+18 -24
View File
@@ -26,7 +26,8 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
define('views/fields/currency', ['views/fields/float'], function (Dep) {
define('views/fields/currency', ['views/fields/float', 'ui/select'],
function (Dep, /** module:ui/select*/Select) {
/**
* @class
@@ -39,23 +40,14 @@ define('views/fields/currency', ['views/fields/float'], function (Dep) {
type: 'currency',
editTemplate: 'fields/currency/edit',
detailTemplate: 'fields/currency/detail',
detailTemplate1: 'fields/currency/detail-1',
detailTemplate2: 'fields/currency/detail-2',
detailTemplate3: 'fields/currency/detail-3',
listTemplate: 'fields/currency/list',
listTemplate1: 'fields/currency/list-1',
listTemplate2: 'fields/currency/list-2',
listTemplate3: 'fields/currency/list-3',
detailTemplateNoCurrency: 'fields/currency/detail-no-currency',
maxDecimalPlaces: 3,
@@ -64,7 +56,7 @@ define('views/fields/currency', ['views/fields/float'], function (Dep) {
* @inheritDoc
*/
data: function () {
var currencyValue = this.model.get(this.currencyFieldName) ||
let currencyValue = this.model.get(this.currencyFieldName) ||
this.getPreferences().get('defaultCurrency') ||
this.getConfig().get('defaultCurrency');
@@ -93,7 +85,7 @@ define('views/fields/currency', ['views/fields/float'], function (Dep) {
this.isSingleCurrency = this.currencyList.length <= 1;
var currencyValue = this.currencyValue = this.model.get(this.currencyFieldName) ||
let currencyValue = this.currencyValue = this.model.get(this.currencyFieldName) ||
this.defaultCurrency;
if (!~this.currencyList.indexOf(currencyValue)) {
@@ -107,10 +99,10 @@ define('views/fields/currency', ['views/fields/float'], function (Dep) {
},
_getTemplateName: function () {
if (this.mode === 'detail' || this.mode === 'list') {
if (this.mode === this.MODE_DETAIL || this.mode === this.MODE_LIST) {
var prop;
if (this.mode === 'list') {
if (this.mode === this.MODE_LIST) {
prop = 'listTemplate' + this.getCurrencyFormat().toString();
}
else {
@@ -138,7 +130,7 @@ define('views/fields/currency', ['views/fields/float'], function (Dep) {
},
formatNumberEdit: function (value) {
var currencyDecimalPlaces = this.getConfig().get('currencyDecimalPlaces');
let currencyDecimalPlaces = this.getConfig().get('currencyDecimalPlaces');
if (value !== null) {
var parts = value.toString().split(".");
@@ -163,7 +155,7 @@ define('views/fields/currency', ['views/fields/float'], function (Dep) {
formatNumberDetail: function (value) {
if (value !== null) {
var currencyDecimalPlaces = this.getConfig().get('currencyDecimalPlaces');
let currencyDecimalPlaces = this.getConfig().get('currencyDecimalPlaces');
if (currencyDecimalPlaces === 0) {
value = Math.round(value);
@@ -179,7 +171,7 @@ define('views/fields/currency', ['views/fields/float'], function (Dep) {
);
}
var parts = value.toString().split(".");
let parts = value.toString().split(".");
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, this.thousandSeparator);
@@ -187,7 +179,7 @@ define('views/fields/currency', ['views/fields/float'], function (Dep) {
return parts[0];
}
else if (currencyDecimalPlaces) {
var decimalPartLength = 0;
let decimalPartLength = 0;
if (parts.length > 1) {
decimalPartLength = parts[1].length;
@@ -196,9 +188,9 @@ define('views/fields/currency', ['views/fields/float'], function (Dep) {
}
if (currencyDecimalPlaces && decimalPartLength < currencyDecimalPlaces) {
var limit = currencyDecimalPlaces - decimalPartLength;
let limit = currencyDecimalPlaces - decimalPartLength;
for (var i = 0; i < limit; i++) {
for (let i = 0; i < limit; i++) {
parts[1] += '0';
}
}
@@ -213,23 +205,25 @@ define('views/fields/currency', ['views/fields/float'], function (Dep) {
afterRender: function () {
Dep.prototype.afterRender.call(this);
if (this.mode === 'edit') {
if (this.mode === this.MODE_EDIT) {
this.$currency = this.$el.find('[data-name="' + this.currencyFieldName + '"]');
this.$currency.on('change', () => {
this.model.set(this.currencyFieldName, this.$currency.val(), {ui: true});
});
Select.init(this.$currency);
}
},
fetch: function () {
var value = this.$element.val();
let value = this.$element.val();
value = this.parse(value);
var data = {};
let data = {};
var currencyValue = this.$currency.val();
let currencyValue = this.$currency.val();
if (value === null) {
currencyValue = null;
+58 -60
View File
@@ -26,9 +26,16 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
define('views/fields/duration', ['views/fields/enum'], function (Dep) {
define('views/fields/duration', ['views/fields/enum', 'ui/select'],
function (Dep, /** module:ui/select*/Select) {
return Dep.extend({
/**
* @class
* @name Class
* @memberOf module:views/fields/duration
* @extends module:views/fields/enum.Class
*/
return Dep.extend(/** @lends module:views/fields/duration.Class# */{
type: 'duration',
@@ -39,10 +46,11 @@ define('views/fields/duration', ['views/fields/enum'], function (Dep) {
data: function () {
let valueIsSet = this.model.has(this.startField) && this.model.has(this.endField);
return _.extend({
return {
valueIsSet: valueIsSet,
durationOptions: this.durationOptions,
}, Dep.prototype.data.call(this));
...Dep.prototype.data.call(this),
};
},
calculateSeconds: function () {
@@ -88,30 +96,39 @@ define('views/fields/duration', ['views/fields/enum'], function (Dep) {
this.listenTo(this, 'render', () => {
this.calculateSeconds();
let durationOptions = '';
this.durationOptions = '';
let options = this.defaultOptions = _.clone(this.model.getFieldParam(this.name, 'options'));
this.getOptions().forEach(d => {
let $o = $('<option>')
.val(d)
.text(this.stringifyDuration(d));
if (!this.model.get('isAllDay') && options.indexOf(this.seconds) === -1) {
options.push(this.seconds);
}
if (d === this.seconds) {
$o.attr('selected', 'selected')
}
options.sort((a, b) => {
return a - b;
this.durationOptions += $o.get(0).outerHTML;
});
options.forEach((d) => {
durationOptions += '<option value="' + d + '" ' +
(d === this.seconds ? 'selected' : '') + '>' +
this.stringifyDuration(d) + '</option>';
});
this.durationOptions = durationOptions;
this.stringValue = this.stringifyDuration(this.seconds);
});
},
/**
* @return {Number[]}
*/
getOptions: function () {
let options = Espo.Utils.clone(this.model.getFieldParam(this.name, 'options') ?? []);
if (!this.model.get('isAllDay') && options.indexOf(this.seconds) === -1) {
options.push(this.seconds);
}
options.sort((a, b) => a - b);
return options;
},
setup: function () {
this.startField = this.model.getFieldParam(this.name, 'start');
this.endField = this.model.getFieldParam(this.name, 'end');
@@ -124,7 +141,7 @@ define('views/fields/duration', ['views/fields/enum'], function (Dep) {
this.blockDateEndChangeListener = false;
this.listenTo(this.model, 'change:' + this.endField, () => {
this.listenTo(this.model, 'change:' + this.endField, (m, v, o) => {
if (this.blockDateEndChangeListener) {
return;
}
@@ -138,6 +155,10 @@ define('views/fields/duration', ['views/fields/enum'], function (Dep) {
this.seconds = moment(end).unix() - moment(start).unix();
if (o.updatedByDuration) {
return;
}
this.updateDuration();
});
@@ -205,11 +226,14 @@ define('views/fields/duration', ['views/fields/enum'], function (Dep) {
return parts.join(' ');
},
focusOnInlineEdit: function () {
Select.focus(this.$duration);
},
afterRender: function () {
let parentView = this.getParentView();
if (parentView && 'getView' in parentView) {
this.startFieldView = parentView.getView(this.startField);
this.endFieldView = parentView.getView(this.endField);
}
@@ -220,8 +244,6 @@ define('views/fields/duration', ['views/fields/enum'], function (Dep) {
this.seconds = parseInt(this.$duration.val());
this.updateDateEnd();
this.$duration.find('option.custom').remove();
});
let start = this.model.get(this.startField);
@@ -241,6 +263,8 @@ define('views/fields/duration', ['views/fields/enum'], function (Dep) {
}
}
}
Select.init(this.$duration, {});
}
},
@@ -311,49 +335,23 @@ define('views/fields/duration', ['views/fields/enum'], function (Dep) {
updateDuration: function () {
let seconds = this.seconds;
if (seconds < 0) {
if (this.mode === 'edit') {
this.$duration.val('');
return;
}
this.setup();
this.render();
return;
}
if (this.isEditMode() && this.$duration && this.$duration.length) {
this.$duration.find('option.custom').remove();
let options = this.getOptions().map(value => {
return {
value: value.toString(),
label: this.stringifyDuration(value),
};
});
let $o = $('<option>')
.val(seconds)
.text(this.stringifyDuration(seconds))
.addClass('custom');
let $found = this.$duration.find('option')
.filter((i, el) => {
return $(el).val() >= seconds;
})
.first();
if ($found.length) {
if (parseInt($found.val()) !== seconds) {
$o.insertBefore($found);
}
}
else {
$o.appendTo(this.$duration);
}
this.$duration.val(seconds);
Select.setValue(this.$duration, '');
Select.setOptions(this.$duration, options);
Select.setValue(this.$duration, seconds.toString());
return;
}
this.setup();
this.render();
//this.setup();
this.reRender();
},
fetch: function () {
+10 -6
View File
@@ -26,8 +26,8 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
define('views/fields/enum', ['views/fields/base', 'ui/multi-select'],
function (Dep, /** module:ui/multi-select*/MultiSelect) {
define('views/fields/enum', ['views/fields/base', 'ui/multi-select', 'ui/select'],
function (Dep, /** module:ui/multi-select*/MultiSelect, /** module:ui/select*/Select) {
/**
* An enum field (select-box).
@@ -42,13 +42,9 @@ function (Dep, /** module:ui/multi-select*/MultiSelect) {
type: 'enum',
listTemplate: 'fields/enum/list',
listLinkTemplate: 'fields/enum/list-link',
detailTemplate: 'fields/enum/detail',
editTemplate: 'fields/enum/edit',
searchTemplate: 'fields/enum/search',
translatedOptions: null,
@@ -330,6 +326,14 @@ function (Dep, /** module:ui/multi-select*/MultiSelect) {
this.$el.find('select.search-type').on('change', () => this.trigger('change'));
this.$element.on('change', () => this.trigger('change'));
}
if (this.isEditMode() || this.isSearchMode()) {
Select.init(this.$element, {matchAnyWord: true});
}
},
focusOnInlineEdit: function () {
Select.focus(this.$element);
},
validateRequired: function () {
@@ -26,8 +26,9 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
define('views/fields/link-multiple-with-columns', ['views/fields/link-multiple', 'helpers/reg-exp-pattern'],
function (Dep, RegExpPattern) {
define('views/fields/link-multiple-with-columns',
['views/fields/link-multiple', 'helpers/reg-exp-pattern', 'ui/select'],
function (Dep, RegExpPattern, /** module:ui/select*/Select) {
/**
* A link-multiple field with relation column(s).
@@ -317,7 +318,7 @@ function (Dep, RegExpPattern) {
let options = this.columnsDefs[column].options || [];
let $select = $('<select>')
.addClass('role form-control input-sm pull-right')
.addClass('role form-control input-sm')
.attr('data-id', id)
.attr('data-column', column);
@@ -354,7 +355,7 @@ function (Dep, RegExpPattern) {
let text = this.translate(field, 'fields', scope);
let $input = $('<input>')
.addClass('role form-control input-sm pull-right')
.addClass('role form-control input-sm')
.attr('data-column', column)
.attr('placeholder', text)
.attr('data-id', id)
@@ -466,7 +467,12 @@ function (Dep, RegExpPattern) {
let $left = $('<div>');
let $right = $('<div>');
$columnList.forEach($item => $left.append($item));
$columnList.forEach($item => $left.append(
$('<span>')
.addClass('link-item-column')
.addClass('link-item-column-' + $item.get(0).tagName.toLowerCase())
.append($item)
));
if ($liList.length) {
let $ul = $('<ul>').addClass('dropdown-menu');
@@ -499,6 +505,11 @@ function (Dep, RegExpPattern) {
if (this.isEditMode()) {
$columnList.forEach($column => {
if ($column.get(0).tagName === 'SELECT') {
Select.init($column);
}
let fetch = ($target) => {
if (!$target || !$target.length) {
return;
@@ -26,7 +26,8 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
define('views/fields/link-multiple-with-role', ['views/fields/link-multiple'], function (Dep) {
define('views/fields/link-multiple-with-role', ['views/fields/link-multiple', 'ui/select'],
function (Dep, /** module:ui/select*/Select) {
/**
* A link-multiple field with a relation column.
@@ -247,7 +248,7 @@ define('views/fields/link-multiple-with-role', ['views/fields/link-multiple'], f
// Do not use the `html` method to avoid XSS.
let $role = $('<select>')
.addClass('role form-control input-sm pull-right')
.addClass('role form-control input-sm')
.attr('data-id', id);
this.roleList.forEach(role => {
@@ -315,7 +316,7 @@ define('views/fields/link-multiple-with-role', ['views/fields/link-multiple'], f
let text = this.rolePlaceholderText || this.translate(this.roleField, 'fields', this.roleFieldScope);
$role = $('<input>')
.addClass('role form-control input-sm pull-right')
.addClass('role form-control input-sm')
.attr('maxlength', this.roleMaxLength) // @todo Get the value from metadata.
.attr('placeholder', text)
.attr('data-id', id)
@@ -323,15 +324,21 @@ define('views/fields/link-multiple-with-role', ['views/fields/link-multiple'], f
}
if ($role) {
$left.append($role);
$left.append($('<span>')
.addClass('link-item-column')
.addClass('link-item-column-' + $role.get(0).tagName.toLowerCase())
.append($role)
);
}
$left.append($name);
$el.append($left).append($right);
$container.append($el);
if ($role && $role.get(0).tagName === 'SELECT') {
Select.init($role);
}
if (this.isEditMode() && $role) {
let fetch = ($target) => {
if (!$target || !$target.length) {
+8 -9
View File
@@ -26,7 +26,8 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
define('views/fields/link-parent', ['views/fields/base', 'helpers/record-modal'], function (Dep, RecordModal) {
define('views/fields/link-parent', ['views/fields/base', 'helpers/record-modal', 'ui/select'],
function (Dep, RecordModal, /** module:ui/select*/Select) {
/**
* A link-parent field (belongs-to-parent relation).
@@ -41,13 +42,9 @@ define('views/fields/link-parent', ['views/fields/base', 'helpers/record-modal']
type: 'linkParent',
listTemplate: 'fields/link-parent/list',
detailTemplate: 'fields/link-parent/detail',
editTemplate: 'fields/link-parent/edit',
searchTemplate: 'fields/link-parent/search',
listLinkTemplate: 'fields/link-parent/list-link',
/**
@@ -211,8 +208,8 @@ define('views/fields/link-parent', ['views/fields/base', 'helpers/record-modal']
if (
(
this.mode === 'detail' ||
this.mode === 'list' && this.displayScopeColorInListMode
this.mode === this.MODE_DETAIL ||
this.mode === this.MODE_LIST && this.displayScopeColorInListMode
) &&
this.foreignScope
) {
@@ -345,7 +342,7 @@ define('views/fields/link-parent', ['views/fields/base', 'helpers/record-modal']
this.addActionHandler('clearLink', () => {
if (this.foreignScopeList.length) {
this.foreignScope = this.foreignScopeList[0];
this.$elementType.val(this.foreignScope);
Select.setValue(this.$elementType, this.foreignScope);
}
this.$elementName.val('');
@@ -505,7 +502,7 @@ define('views/fields/link-parent', ['views/fields/base', 'helpers/record-modal']
this.$elementName.on('blur', e => {
setTimeout(() => {
if (this.mode === 'edit') {
if (this.mode === this.MODE_EDIT) {
e.currentTarget.value = this.model.get(this.nameName) || '';
}
}, 100);
@@ -563,6 +560,8 @@ define('views/fields/link-parent', ['views/fields/base', 'helpers/record-modal']
this.$elementName.on('focus', () => this.$elementName.get(0).select());
this.$elementName.attr('autocomplete', 'espo-' + this.name);
Select.init(this.$elementType, {});
}
var $elementName = this.$elementName;
+5 -6
View File
@@ -26,7 +26,8 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
define('views/fields/person-name', ['views/fields/varchar'], function (Dep) {
define('views/fields/person-name', ['views/fields/varchar', 'ui/select'],
function (Dep, /** module:ui/select*/Select) {
/**
* @class
@@ -39,13 +40,9 @@ define('views/fields/person-name', ['views/fields/varchar'], function (Dep) {
type: 'personName',
detailTemplate: 'fields/person-name/detail',
editTemplate: 'fields/person-name/edit',
editTemplateLastFirst: 'fields/person-name/edit-last-first',
editTemplateLastFirstMiddle: 'fields/person-name/edit-last-first-middle',
editTemplateFirstMiddleLast: 'fields/person-name/edit-first-middle-last',
/**
@@ -57,7 +54,7 @@ define('views/fields/person-name', ['views/fields/varchar'], function (Dep) {
],
data: function () {
var data = Dep.prototype.data.call(this);
let data = Dep.prototype.data.call(this);
data.ucName = Espo.Utils.upperCaseFirst(this.name);
data.salutationValue = this.model.get(this.salutationField);
@@ -125,6 +122,8 @@ define('views/fields/person-name', ['views/fields/varchar'], function (Dep) {
this.$last.on('change', () => {
this.trigger('change');
});
Select.init(this.$salutation);
}
},
+58 -39
View File
@@ -26,16 +26,21 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
define('views/fields/phone', ['views/fields/varchar'], function (Dep) {
define('views/fields/phone', ['views/fields/varchar', 'ui/select'],
function (Dep, /** module:ui/select*/Select) {
return Dep.extend({
/**
* @class
* @name Class
* @memberOf module:views/fields/phone
* @extends module:views/fields/varchar.Class
*/
return Dep.extend(/** @lends module:views/fields/phone.Class */{
type: 'phone',
editTemplate: 'fields/phone/edit',
detailTemplate: 'fields/phone/detail',
listTemplate: 'fields/phone/list',
validations: ['required', 'phoneData'],
@@ -43,7 +48,7 @@ define('views/fields/phone', ['views/fields/varchar'], function (Dep) {
validateRequired: function () {
if (this.isRequired()) {
if (!this.model.get(this.name)) {
var msg = this.translate('fieldIsRequired', 'messages')
let msg = this.translate('fieldIsRequired', 'messages')
.replace('{field}', this.getLabelText());
this.showValidationMessage(msg, 'div.phone-number-block:nth-child(1) input');
@@ -103,14 +108,14 @@ define('views/fields/phone', ['views/fields/varchar'], function (Dep) {
},
data: function () {
var phoneNumberData;
let phoneNumberData;
if (this.mode === 'edit') {
if (this.mode === this.MODE_EDIT) {
phoneNumberData = Espo.Utils.cloneDeep(this.model.get(this.dataFieldName));
if (this.model.isNew() || !this.model.get(this.name)) {
if (!phoneNumberData || !phoneNumberData.length) {
var optOut = false;
let optOut;
if (this.model.isNew()) {
optOut = this.phoneNumberOptedOutByDefault && this.model.name !== 'User';
@@ -135,7 +140,7 @@ define('views/fields/phone', ['views/fields/varchar'], function (Dep) {
phoneNumberData = Espo.Utils.cloneDeep(phoneNumberData);
phoneNumberData.forEach((item) => {
var number = item.phoneNumber || '';
let number = item.phoneNumber || '';
item.erased = number.indexOf(this.erasedPlaceholder) === 0;
@@ -148,9 +153,9 @@ define('views/fields/phone', ['views/fields/varchar'], function (Dep) {
}
if ((!phoneNumberData || phoneNumberData.length === 0) && this.model.get(this.name)) {
var number = this.model.get(this.name);
let number = this.model.get(this.name);
var o = {
let o = {
phoneNumber: number,
primary: true,
valueForLink: number.replace(/ /g, ''),
@@ -163,7 +168,7 @@ define('views/fields/phone', ['views/fields/varchar'], function (Dep) {
phoneNumberData = [o];
}
var data = _.extend({
let data = _.extend({
phoneNumberData: phoneNumberData,
doNotCall: this.model.get('doNotCall'),
lineThrough: this.model.get('doNotCall') || this.model.get(this.isOptedOutFieldName),
@@ -190,9 +195,9 @@ define('views/fields/phone', ['views/fields/varchar'], function (Dep) {
events: {
'click [data-action="switchPhoneProperty"]': function (e) {
var $target = $(e.currentTarget);
var $block = $(e.currentTarget).closest('div.phone-number-block');
var property = $target.data('property-type');
let $target = $(e.currentTarget);
let $block = $(e.currentTarget).closest('div.phone-number-block');
let property = $target.data('property-type');
if (property === 'primary') {
if (!$target.hasClass('active')) {
@@ -216,7 +221,7 @@ define('views/fields/phone', ['views/fields/varchar'], function (Dep) {
},
'click [data-action="removePhoneNumber"]': function (e) {
var $block = $(e.currentTarget).closest('div.phone-number-block');
let $block = $(e.currentTarget).closest('div.phone-number-block');
if ($block.parent().children().length === 1) {
$block.find('input.phone-number').val('');
@@ -228,10 +233,10 @@ define('views/fields/phone', ['views/fields/varchar'], function (Dep) {
},
'change input.phone-number': function (e) {
var $input = $(e.currentTarget);
var $block = $input.closest('div.phone-number-block');
let $input = $(e.currentTarget);
let $block = $input.closest('div.phone-number-block');
if ($input.val() == '') {
if ($input.val() === '') {
if ($block.parent().children().length === 1) {
$block.find('input.phone-number').val('');
} else {
@@ -249,13 +254,11 @@ define('views/fields/phone', ['views/fields/varchar'], function (Dep) {
},
'paste input.phone-number': function (e) {
setTimeout(function () {
this.manageAddButton();
}.bind(this), 10);
setTimeout(() => this.manageAddButton(), 10);
},
'click [data-action="addPhoneNumber"]': function () {
var data = Espo.Utils.cloneDeep(this.fetchPhoneNumberData());
let data = Espo.Utils.cloneDeep(this.fetchPhoneNumberData());
let o = {
phoneNumber: '',
@@ -268,21 +271,31 @@ define('views/fields/phone', ['views/fields/varchar'], function (Dep) {
data.push(o);
this.model.set(this.dataFieldName, data, {silent: true});
this.render();
this.reRender();
},
},
afterRender: function () {
Dep.prototype.afterRender.call(this);
this.manageButtonsVisibility();
this.manageAddButton();
if (this.mode === this.MODE_EDIT) {
this.$el.find('select').toArray().forEach(selectElement => {
Select.init($(selectElement));
});
}
},
removePhoneNumberBlock: function ($block) {
var changePrimary = false;
let changePrimary = false;
if ($block.find('button[data-property-type="primary"]').hasClass('active')) {
changePrimary = true;
}
$block.remove();
if (changePrimary) {
@@ -321,16 +334,21 @@ define('views/fields/phone', ['views/fields/varchar'], function (Dep) {
},
manageButtonsVisibility: function () {
var $primary = this.$el.find('button[data-property-type="primary"]');
var $remove = this.$el.find('button[data-action="removePhoneNumber"]');
let $primary = this.$el.find('button[data-property-type="primary"]');
let $remove = this.$el.find('button[data-action="removePhoneNumber"]');
let $container = this.$el.find('.phone-number-block-container');
if ($primary.length > 1) {
$primary.removeClass('hidden');
$remove.removeClass('hidden');
} else {
$primary.addClass('hidden');
$remove.addClass('hidden');
$container.addClass('many')
return;
}
$container.removeClass('many')
$primary.addClass('hidden');
$remove.addClass('hidden');
},
setup: function () {
@@ -364,18 +382,18 @@ define('views/fields/phone', ['views/fields/varchar'], function (Dep) {
},
fetchPhoneNumberData: function () {
var data = [];
let data = [];
var $list = this.$el.find('div.phone-number-block');
let $list = this.$el.find('div.phone-number-block');
if ($list.length) {
$list.each((i, d) => {
var row = {};
var $d = $(d);
let row = {};
let $d = $(d);
row.phoneNumber = $d.find('input.phone-number').val().trim();
if (row.phoneNumber == '') {
if (row.phoneNumber === '') {
return;
}
@@ -392,14 +410,15 @@ define('views/fields/phone', ['views/fields/varchar'], function (Dep) {
},
fetch: function () {
var data = {};
let data = {};
let addressData = this.fetchPhoneNumberData() || [];
var addressData = this.fetchPhoneNumberData() || [];
data[this.dataFieldName] = addressData;
data[this.name] = null;
data[this.isOptedOutFieldName] = false;
var primaryIndex = 0;
let primaryIndex = 0;
addressData.forEach((item, i) => {
if (item.primary) {
@@ -412,7 +431,7 @@ define('views/fields/phone', ['views/fields/varchar'], function (Dep) {
});
if (addressData.length && primaryIndex > 0) {
var t = addressData[0];
let t = addressData[0];
addressData[0] = addressData[primaryIndex];
addressData[primaryIndex] = t;
+5 -2
View File
@@ -26,7 +26,8 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
define('views/settings/fields/theme', ['views/fields/enum', 'theme-manager'], function (Dep, ThemeManager) {
define('views/settings/fields/theme', ['views/fields/enum', 'theme-manager', 'ui/select'],
function (Dep, ThemeManager, /** module:ui/select*/Select) {
return Dep.extend({
@@ -78,7 +79,7 @@ define('views/settings/fields/theme', ['views/fields/enum', 'theme-manager'], fu
if (o.ui) {
this.reRender()
.then(() => this.$element.get(0).focus({preventScroll: true}));
.then(() => Select.focus(this.$element, {noTrigger: true}));
}
})
},
@@ -87,6 +88,8 @@ define('views/settings/fields/theme', ['views/fields/enum', 'theme-manager'], fu
this.$navbar = this.$el.find('[data-name="themeNavbar"]');
this.$navbar.on('change', () => this.trigger('change'));
Select.init(this.$navbar);
},
getNavbarValue: function () {
+13
View File
@@ -447,3 +447,16 @@ table.table-admin-panel tr > td:first-child > div > a {
margin-left: 0;
margin-right: -20px;
}
.selectize-control.single {
.selectize-input {
padding-left: 19px;
padding-right: 10px;
&.dropdown-active:after,
&:after {
left: 8px;
right: unset;
}
}
}
+70 -3
View File
@@ -1135,6 +1135,19 @@ input.global-search-input {
margin-top: 1px;
}
.link-container {
> .list-group-item {
.link-item-column {
float: right;
display: inline-block;
width: 40%;
> .selectize-control {
width: 100%;
}
}
}
}
.field .link-container .list-group-item a {
margin-top: 1px;
}
@@ -3592,14 +3605,68 @@ a.close:hover {
.reminders-container {
> .reminder {
select[data-name="type"] {
@media screen and (min-width: @screen-desktop) {
min-width: 160px;
table-layout: fixed;
width: 100%;
> .input-group-item:first-child {
width: 40%;
}
> .input-group-btn {
width: 35px;
}
}
}
.input-group-link-parent {
table-layout: fixed;
width: 100%;
> .input-group-item:first-child {
width: 34%;
}
> .input-group-btn {
width: calc(35px * 2);
}
}
.phone-number-block-container {
.input-group.phone-number-block {
table-layout: fixed;
width: 100%;
> .input-group-item:first-child {
width: 24%;
}
> .input-group-btn {
.btn.phone-property.active {
z-index: unset;
}
width: calc(35px * 2 + 1px);
}
}
&.many {
.input-group.phone-number-block {
> .input-group-btn {
width: calc(35px * 4 + 1px);
}
}
}
}
.input-group-currency {
table-layout: fixed;
width: 100%;
> .input-group-item:last-child {
width: 25%;
}
}
@import "elements/site.less";
@import "elements/modal.less";
@import "elements/buttons.less";
+2 -1
View File
@@ -18,7 +18,7 @@
}
.autocomplete-suggestion {
padding: 4px 5px;
padding: 4px 10px;
white-space: nowrap;
overflow: hidden;
}
@@ -27,6 +27,7 @@
.popover,
.dropdown-menu,
.selectize-dropdown,
.selectize-dropdown.form-control,
.autocomplete-suggestions,
.ui-timepicker-wrapper {
border: var(--dropdown-border-width) solid var(--dropdown-border);
+62 -2
View File
@@ -58,6 +58,62 @@
border-bottom-right-radius: var(--border-radius) !important;
}
.input-group > .input-group-item {
position: relative;
display: table-cell;
vertical-align: middle;
+ .input-group-item {
> .selectize-control {
> .selectize-input {
margin-left: -1px;
}
}
}
+ input.form-control {
margin-left: -1px;
}
&.input-group-item-middle {
> input {
margin-left: -2px;
right: -1px;
width: calc(100% + 1px);
}
}
}
.input-group > .input-group-item,
.input-group {
> .selectize-control {
position: unset;
> .selectize-input {
border-radius: 0;
float: left;
&.focus {
z-index: 4;
}
}
&.radius-right {
> .selectize-input {
border-top-right-radius: var(--border-radius);
border-bottom-right-radius: var(--border-radius);
}
}
&.radius-left {
> .selectize-input {
border-top-left-radius: var(--border-radius);
border-bottom-left-radius: var(--border-radius);
}
}
}
}
.list-group:not(:empty) + div,
.list-group:not(:empty) + div > div.input-group {
> .form-control,
@@ -100,8 +156,12 @@
margin-left: 0 !important;
}
.input-group .form-control:focus {
z-index: 3;
.input-group .form-control {
z-index: unset;
&:focus {
z-index: 3;
}
}
.input-group-container-2 {
+112 -3
View File
@@ -1,8 +1,56 @@
.filter > .form-group .field {
.selectize-control {
.filter > .form-group .field .selectize-control,
.selectize-control.input-sm.form-control {
&.multi {
.selectize-input {
font-size: @font-size-small;
padding: 4px 5px 0;
padding: 4px 7px 0;
}
}
&.single {
.selectize-input {
font-size: @font-size-small;
padding: 4px 19px 4px 10px;
min-height: @input-height-small;
}
height: @input-height-small;
}
.selectize-dropdown-content {
max-height: 198px;
}
}
.selectize-input {
min-width: 80px;
}
.selectize-dropdown {
min-width: 80px;
}
.form-control.selectize-control.single {
height: @input-height-base;
}
.selectize-control {
.selectize-dropdown-content {
> .option {
cursor: default;
}
}
&.single {
> .selectize-input {
> .item {
width: 100%;
height: @line-height-computed;
overflow: hidden;
vertical-align: top;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
@@ -11,3 +59,64 @@
border-radius: var(--border-radius);
padding: 1px 4px;
}
.selectize-control.single {
.selectize-dropdown {
position: absolute;
}
.selectize-input {
padding-right: 19px;
&.dropdown-active:after,
&:after {
color: var(--text-muted-color);
display: inline-block;
width: 0;
height: 0;
top: 15px;
right: 8px;
margin-left: 2px;
vertical-align: middle;
border-top: 4px dashed;
border-right: 4px solid transparent;
border-left: 4px solid transparent;
position: absolute;
}
> .item + input {
caret-color: transparent;
margin-right: -4px !important; // Prevent select changing width on focus.
}
}
&.input-sm {
.selectize-input {
&.dropdown-active:after,
&:after {
top: 13px;
}
}
}
}
.selectize-dropdown .active {
&.text-success {
color: var(--state-success-text);
}
&.text-danger {
color: var(--state-danger-text);
}
&.text-warning {
color: var(--state-warning-text);
}
&.text-warning {
color: var(--state-warning-text);
}
&.text-info {
color: var(--state-info-text);
}
&.text-primary {
color: var(--state-primary-text);
}
}
@@ -270,7 +270,7 @@
&, input { cursor: pointer; }
&.input-active, &.input-active input { cursor: text; }
&:after {
/*&:after {
content: ' ';
display: block;
position: absolute;
@@ -287,14 +287,14 @@
margin-top: @selectize-arrow-size * -0.8;
border-width: 0 @selectize-arrow-size @selectize-arrow-size @selectize-arrow-size;
border-color: transparent transparent @selectize-arrow-color transparent;
}
}*/
}
.selectize-control.rtl {
&.single .selectize-input:after {
/*&.single .selectize-input:after {
left: @selectize-arrow-offset;
right: auto;
}
}*/
.selectize-input > input {
margin: @selectize-caret-margin-rtl !important;
}