From 014174e7a865aa20d371768a1a6eb9ea5df576bf Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Tue, 15 Dec 2020 19:37:31 -0500 Subject: [PATCH] Search kit: Rewrite input widget to support IN sets, relative dates, BETWEEN groups, etc. This deletes the crmSearchValue widget (and the now-empty crmSearchKit module), which had originally been copied from the API Explorer, and replaces it with a more flexible set of components with separate templates for each data type. --- ext/search/Civi/Search/Actions.php | 1 + ext/search/Civi/Search/Admin.php | 4 +- ext/search/ang/crmSearchActions.ang.php | 2 +- ext/search/ang/crmSearchActions.module.js | 16 ++- .../crmSearchActionDelete.html | 4 +- .../crmSearchActionUpdate.html | 6 +- .../crmSearchInput/boolean.html | 8 ++ .../crmMultiSelectDate.directive.js | 89 +++++++++++++ .../crmSearchInput.component.js | 51 ++++++++ .../crmSearchInput/crmSearchInput.html | 13 ++ .../crmSearchInputVal.component.js | 118 ++++++++++++++++++ .../crmSearchActions/crmSearchInput/date.html | 35 ++++++ .../crmSearchInput/entityRef.html | 6 + .../crmSearchInput/integer.html | 6 + .../crmSearchInput/select.html | 9 ++ .../crmSearchActions/crmSearchInput/text.html | 6 + ext/search/ang/crmSearchAdmin.ang.php | 2 +- .../crmSearchClause.component.js | 18 ++- .../ang/crmSearchAdmin/crmSearchClause.html | 2 +- ext/search/ang/crmSearchKit.ang.php | 14 --- ext/search/ang/crmSearchKit.module.js | 21 ---- .../crmSearchKit/crmSearchValue.directive.js | 113 ----------------- ext/search/css/search.css | 4 + 23 files changed, 386 insertions(+), 162 deletions(-) create mode 100644 ext/search/ang/crmSearchActions/crmSearchInput/boolean.html create mode 100644 ext/search/ang/crmSearchActions/crmSearchInput/crmMultiSelectDate.directive.js create mode 100644 ext/search/ang/crmSearchActions/crmSearchInput/crmSearchInput.component.js create mode 100644 ext/search/ang/crmSearchActions/crmSearchInput/crmSearchInput.html create mode 100644 ext/search/ang/crmSearchActions/crmSearchInput/crmSearchInputVal.component.js create mode 100644 ext/search/ang/crmSearchActions/crmSearchInput/date.html create mode 100644 ext/search/ang/crmSearchActions/crmSearchInput/entityRef.html create mode 100644 ext/search/ang/crmSearchActions/crmSearchInput/integer.html create mode 100644 ext/search/ang/crmSearchActions/crmSearchInput/select.html create mode 100644 ext/search/ang/crmSearchActions/crmSearchInput/text.html delete mode 100644 ext/search/ang/crmSearchKit.ang.php delete mode 100644 ext/search/ang/crmSearchKit.module.js delete mode 100644 ext/search/ang/crmSearchKit/crmSearchValue.directive.js diff --git a/ext/search/Civi/Search/Actions.php b/ext/search/Civi/Search/Actions.php index 5918d757dd..c72a597069 100644 --- a/ext/search/Civi/Search/Actions.php +++ b/ext/search/Civi/Search/Actions.php @@ -24,6 +24,7 @@ class Actions { return [ 'tasks' => self::getTasks(), 'groupOptions' => self::getGroupOptions(), + 'dateRanges' => \CRM_Utils_Array::makeNonAssociative(\CRM_Core_OptionGroup::values('relative_date_filters'), 'id', 'text'), ]; } diff --git a/ext/search/Civi/Search/Admin.php b/ext/search/Civi/Search/Admin.php index e7abc0e0ff..6363b53c64 100644 --- a/ext/search/Civi/Search/Admin.php +++ b/ext/search/Civi/Search/Admin.php @@ -43,8 +43,8 @@ class Admin { '>=' => '≥', '<=' => '≤', 'CONTAINS' => ts('Contains'), - 'IN' => ts('Is In'), - 'NOT IN' => ts('Not In'), + 'IN' => ts('Is One Of'), + 'NOT IN' => ts('Not One Of'), 'LIKE' => ts('Is Like'), 'NOT LIKE' => ts('Not Like'), 'BETWEEN' => ts('Is Between'), diff --git a/ext/search/ang/crmSearchActions.ang.php b/ext/search/ang/crmSearchActions.ang.php index 478cd09609..6f62570aa2 100644 --- a/ext/search/ang/crmSearchActions.ang.php +++ b/ext/search/ang/crmSearchActions.ang.php @@ -10,7 +10,7 @@ return [ 'ang/crmSearchActions', ], 'basePages' => [], - 'requires' => ['crmUi', 'crmUtil', 'dialogService', 'api4', 'crmSearchKit'], + 'requires' => ['crmUi', 'crmUtil', 'dialogService', 'api4', 'checklist-model'], 'settingsFactory' => ['\Civi\Search\Actions', 'getActionSettings'], 'permissions' => ['edit groups', 'administer reserved groups'], ]; diff --git a/ext/search/ang/crmSearchActions.module.js b/ext/search/ang/crmSearchActions.module.js index 912d2e54ff..341e341287 100644 --- a/ext/search/ang/crmSearchActions.module.js +++ b/ext/search/ang/crmSearchActions.module.js @@ -2,6 +2,20 @@ "use strict"; // Declare module - angular.module('crmSearchActions', CRM.angRequires('crmSearchActions')); + angular.module('crmSearchActions', CRM.angRequires('crmSearchActions')) + + // Reformat an array of objects for compatibility with select2 + // Todo this probably belongs in core + .factory('formatForSelect2', function() { + return function(input, key, label, extra) { + return _.transform(input, function(result, item) { + var formatted = {id: item[key], text: item[label]}; + if (extra) { + _.merge(formatted, _.pick(item, extra)); + } + result.push(formatted); + }, []); + }; + }); })(angular, CRM.$, CRM._); diff --git a/ext/search/ang/crmSearchActions/crmSearchActionDelete.html b/ext/search/ang/crmSearchActions/crmSearchActionDelete.html index 855f3570d4..40d657cfef 100644 --- a/ext/search/ang/crmSearchActions/crmSearchActionDelete.html +++ b/ext/search/ang/crmSearchActions/crmSearchActionDelete.html @@ -1,10 +1,10 @@
-
+

{{:: ts('Are you sure you want to delete %1 %2?', {1: model.ids.length, 2: $ctrl.entityTitle}) }}


-
+
diff --git a/ext/search/ang/crmSearchActions/crmSearchActionUpdate.html b/ext/search/ang/crmSearchActions/crmSearchActionUpdate.html index bb1a380337..d3bae2a6bf 100644 --- a/ext/search/ang/crmSearchActions/crmSearchActionUpdate.html +++ b/ext/search/ang/crmSearchActions/crmSearchActionUpdate.html @@ -1,9 +1,9 @@
-
+

{{:: ts('Update the %1 selected %2 with the following values:', {1: model.ids.length, 2: $ctrl.entityTitle}) }}

- +
@@ -13,5 +13,5 @@
-
+
diff --git a/ext/search/ang/crmSearchActions/crmSearchInput/boolean.html b/ext/search/ang/crmSearchActions/crmSearchInput/boolean.html new file mode 100644 index 0000000000..61ed9000e6 --- /dev/null +++ b/ext/search/ang/crmSearchActions/crmSearchInput/boolean.html @@ -0,0 +1,8 @@ +
+ + +
+
+ + +
diff --git a/ext/search/ang/crmSearchActions/crmSearchInput/crmMultiSelectDate.directive.js b/ext/search/ang/crmSearchActions/crmSearchInput/crmMultiSelectDate.directive.js new file mode 100644 index 0000000000..e830358fbb --- /dev/null +++ b/ext/search/ang/crmSearchActions/crmSearchInput/crmMultiSelectDate.directive.js @@ -0,0 +1,89 @@ +(function(angular, $, _) { + "use strict"; + + angular.module('crmSearchActions') + .directive('crmMultiSelectDate', function () { + return { + restrict: 'A', + require: 'ngModel', + link: function (scope, element, attrs, ngModel) { + + var defaultDate = null; + + function getDisplayDate(date) { + return $.datepicker.formatDate(CRM.config.dateInputFormat, $.datepicker.parseDate('yy-mm-dd', date)); + } + + ngModel.$render = function () { + element.val(_.isArray(ngModel.$viewValue) ? ngModel.$viewValue.join(',') : ngModel.$viewValue).change(); + }; + + element + .crmSelect2({ + multiple: true, + data: [], + initSelection: function(element, callback) { + var values = []; + $.each($(element).val().split(','), function(k, v) { + values.push({ + text: getDisplayDate(v), + id: v + }); + }); + callback(values); + } + }) + .on('select2-opening', function(e) { + var $el = $(this), + $input = $('.select2-search-field input', $el.select2('container')); + // Prevent select2 from opening and show a datepicker instead + e.preventDefault(); + if (!$input.data('datepicker')) { + $input + .datepicker({ + beforeShow: function() { + var existingSelections = _.pluck($el.select2('data') || [], 'id'); + return { + changeMonth: true, + changeYear: true, + defaultDate: defaultDate, + beforeShowDay: function(date) { + // Don't allow the same date to be selected twice + var dateStr = $.datepicker.formatDate('yy-mm-dd', date); + if (_.includes(existingSelections, dateStr)) { + return [false, '', '']; + } + return [true, '', '']; + } + }; + } + }) + .datepicker('show') + .on('change.crmDate', function() { + if ($(this).val()) { + var data = $el.select2('data') || []; + defaultDate = $(this).datepicker('getDate'); + data.push({ + text: $.datepicker.formatDate(CRM.config.dateInputFormat, defaultDate), + id: $.datepicker.formatDate('yy-mm-dd', defaultDate) + }); + $el.select2('data', data, true); + } + }) + .on('keyup', function() { + $(this).val('').datepicker('show'); + }); + } + }) + // Don't leave datepicker open when clearing selections + .on('select2-removed', function() { + $('input.hasDatepicker', $(this).select2('container')) + .datepicker('hide'); + }) + .on('change', function() { + ngModel.$setViewValue(element.val().split(',')); + }); + } + }; + }); +})(angular, CRM.$, CRM._); diff --git a/ext/search/ang/crmSearchActions/crmSearchInput/crmSearchInput.component.js b/ext/search/ang/crmSearchActions/crmSearchInput/crmSearchInput.component.js new file mode 100644 index 0000000000..c37d99948a --- /dev/null +++ b/ext/search/ang/crmSearchActions/crmSearchInput/crmSearchInput.component.js @@ -0,0 +1,51 @@ +(function(angular, $, _) { + "use strict"; + + angular.module('crmSearchActions').component('crmSearchInput', { + bindings: { + field: '<', + 'op': '<', + 'format': '<', + 'optionKey': '<' + }, + require: {ngModel: 'ngModel'}, + templateUrl: '~/crmSearchActions/crmSearchInput/crmSearchInput.html', + controller: function($scope) { + var ts = $scope.ts = CRM.ts(), + ctrl = this; + + this.isMulti = function() { + // If there's a search operator, return `true` if the operator takes multiple values, else `false` + if (ctrl.op) { + return ctrl.op === 'IN' || ctrl.op === 'NOT IN'; + } + // If no search operator this is an input for e.g. the bulk update action + // Return `true` if the field is multi-valued, else `null` + return ctrl.field.serialize || ctrl.field.data_type === 'Array' ? true : null; + }; + + this.$onInit = function() { + + $scope.$watch('$ctrl.value', function() { + ctrl.ngModel.$setViewValue(ctrl.value); + }); + + // For the ON clause, string values must be quoted + ctrl.ngModel.$parsers.push(function(viewValue) { + return ctrl.format === 'json' && _.isString(viewValue) && viewValue.length ? JSON.stringify(viewValue) : viewValue; + }); + + // For the ON clause, unquote string values + ctrl.ngModel.$formatters.push(function(value) { + return ctrl.format === 'json' && _.isString(value) && value.length ? JSON.parse(value) : value; + }); + + this.ngModel.$render = function() { + ctrl.value = ctrl.ngModel.$viewValue; + }; + + }; + } + }); + +})(angular, CRM.$, CRM._); diff --git a/ext/search/ang/crmSearchActions/crmSearchInput/crmSearchInput.html b/ext/search/ang/crmSearchActions/crmSearchInput/crmSearchInput.html new file mode 100644 index 0000000000..bf434d9cf4 --- /dev/null +++ b/ext/search/ang/crmSearchActions/crmSearchInput/crmSearchInput.html @@ -0,0 +1,13 @@ +
+ +
+ + - + +
+ +
+ +
+ +
diff --git a/ext/search/ang/crmSearchActions/crmSearchInput/crmSearchInputVal.component.js b/ext/search/ang/crmSearchActions/crmSearchInput/crmSearchInputVal.component.js new file mode 100644 index 0000000000..6fbed45078 --- /dev/null +++ b/ext/search/ang/crmSearchActions/crmSearchInput/crmSearchInputVal.component.js @@ -0,0 +1,118 @@ +(function(angular, $, _) { + "use strict"; + + angular.module('crmSearchActions').component('crmSearchInputVal', { + bindings: { + field: '<', + 'multi': '<', + 'optionKey': '<' + }, + require: {ngModel: 'ngModel'}, + template: '
', + controller: function($scope, formatForSelect2) { + var ts = $scope.ts = CRM.ts(), + ctrl = this; + + this.$onInit = function() { + var rendered = false; + ctrl.dateRanges = CRM.crmSearchActions.dateRanges; + + this.ngModel.$render = function() { + ctrl.value = ctrl.ngModel.$viewValue; + if (!rendered && ctrl.field.input_type === 'Date') { + setDateType(); + } + rendered = true; + }; + + $scope.$watch('$ctrl.value', function() { + ctrl.ngModel.$setViewValue(ctrl.value); + }); + + function setDateType() { + if (_.findWhere(ctrl.dateRanges, {id: ctrl.value})) { + ctrl.dateType = 'range'; + } else if (ctrl.value === 'now') { + ctrl.dateType = 'now'; + } else if (_.includes(ctrl.value, 'now -')) { + ctrl.dateType = 'now -'; + } else if (_.includes(ctrl.value, 'now +')) { + ctrl.dateType = 'now +'; + } else { + ctrl.dateType = 'fixed'; + } + } + }; + + this.changeDateType = function() { + switch (ctrl.dateType) { + case 'fixed': + ctrl.value = ''; + break; + + case 'range': + ctrl.value = ctrl.dateRanges[0].id; + break; + + case 'now': + ctrl.value = 'now'; + break; + + default: + ctrl.value = ctrl.dateType + ' 1 day'; + } + }; + + this.dateUnits = function(setUnit) { + var vals = ctrl.value.split(' '); + if (arguments.length) { + vals[3] = setUnit; + ctrl.value = vals.join(' '); + } else { + return vals[3]; + } + }; + + this.dateNumber = function(setNumber) { + var vals = ctrl.value.split(' '); + if (arguments.length) { + vals[2] = setNumber; + ctrl.value = vals.join(' '); + } else { + return parseInt(vals[2], 10); + } + }; + + this.getTemplate = function() { + + if (ctrl.field.input_type === 'Date') { + return '~/crmSearchActions/crmSearchInput/date.html'; + } + + if (ctrl.field.data_type === 'Boolean') { + return '~/crmSearchActions/crmSearchInput/boolean.html'; + } + + if (ctrl.field.options) { + return '~/crmSearchActions/crmSearchInput/select.html'; + } + + if (ctrl.field.fk_entity) { + return '~/crmSearchActions/crmSearchInput/entityRef.html'; + } + + if (ctrl.field.data_type === 'Integer') { + return '~/crmSearchActions/crmSearchInput/integer.html'; + } + + return '~/crmSearchActions/crmSearchInput/text.html'; + }; + + this.getFieldOptions = function() { + return {results: formatForSelect2(ctrl.field.options, ctrl.optionKey || 'id', 'label', ['description', 'color', 'icon'])}; + }; + + } + }); + +})(angular, CRM.$, CRM._); diff --git a/ext/search/ang/crmSearchActions/crmSearchInput/date.html b/ext/search/ang/crmSearchActions/crmSearchInput/date.html new file mode 100644 index 0000000000..78e4d03d6d --- /dev/null +++ b/ext/search/ang/crmSearchActions/crmSearchInput/date.html @@ -0,0 +1,35 @@ +
+ + +
+ +
+ + +
+ +
+ +
+ +
+ + +
+ +
+
diff --git a/ext/search/ang/crmSearchActions/crmSearchInput/entityRef.html b/ext/search/ang/crmSearchActions/crmSearchInput/entityRef.html new file mode 100644 index 0000000000..b0050d7421 --- /dev/null +++ b/ext/search/ang/crmSearchActions/crmSearchInput/entityRef.html @@ -0,0 +1,6 @@ +
+ +
+
+ +
diff --git a/ext/search/ang/crmSearchActions/crmSearchInput/integer.html b/ext/search/ang/crmSearchActions/crmSearchInput/integer.html new file mode 100644 index 0000000000..6c0f01ace6 --- /dev/null +++ b/ext/search/ang/crmSearchActions/crmSearchInput/integer.html @@ -0,0 +1,6 @@ +
+ +
+
+ +
diff --git a/ext/search/ang/crmSearchActions/crmSearchInput/select.html b/ext/search/ang/crmSearchActions/crmSearchInput/select.html new file mode 100644 index 0000000000..464c92e431 --- /dev/null +++ b/ext/search/ang/crmSearchActions/crmSearchInput/select.html @@ -0,0 +1,9 @@ +
+ +
+
+ +
+
+ +
diff --git a/ext/search/ang/crmSearchActions/crmSearchInput/text.html b/ext/search/ang/crmSearchActions/crmSearchInput/text.html new file mode 100644 index 0000000000..61a4391c48 --- /dev/null +++ b/ext/search/ang/crmSearchActions/crmSearchInput/text.html @@ -0,0 +1,6 @@ +
+ +
+
+ +
diff --git a/ext/search/ang/crmSearchAdmin.ang.php b/ext/search/ang/crmSearchAdmin.ang.php index a947d51b30..9d5ee8e521 100644 --- a/ext/search/ang/crmSearchAdmin.ang.php +++ b/ext/search/ang/crmSearchAdmin.ang.php @@ -14,6 +14,6 @@ return [ ], 'bundles' => ['bootstrap3'], 'basePages' => ['civicrm/admin/search'], - 'requires' => ['crmUi', 'crmUtil', 'ngRoute', 'ui.sortable', 'ui.bootstrap', 'api4', 'crmSearchActions', 'crmSearchKit', 'crmRouteBinder'], + 'requires' => ['crmUi', 'crmUtil', 'ngRoute', 'ui.sortable', 'ui.bootstrap', 'api4', 'crmSearchActions', 'crmRouteBinder'], 'settingsFactory' => ['\Civi\Search\Admin', 'getAdminSettings'], ]; diff --git a/ext/search/ang/crmSearchAdmin/crmSearchClause.component.js b/ext/search/ang/crmSearchAdmin/crmSearchClause.component.js index 3c196a440b..de5ce60e43 100644 --- a/ext/search/ang/crmSearchAdmin/crmSearchClause.component.js +++ b/ext/search/ang/crmSearchAdmin/crmSearchClause.component.js @@ -83,12 +83,24 @@ } }; - // Add/remove value if operator allows for one this.changeClauseOperator = function(clause) { + // Add/remove value if operator allows for one if (_.contains(clause[1], 'NULL')) { clause.length = 2; - } else if (clause.length === 2) { - clause.push(''); + } else { + if (clause.length === 2) { + clause.push(''); + } + // Change multi/single value to/from an array + var shouldBeArray = (clause[1] === 'IN' || clause[1] === 'NOT IN' || clause[1] === 'BETWEEN' || clause[1] === 'NOT BETWEEN'); + if (!_.isArray(clause[2]) && shouldBeArray) { + clause[2] = []; + } else if (_.isArray(clause[2]) && !shouldBeArray) { + clause[2] = ''; + } + if (clause[1] === 'BETWEEN' || clause[1] === 'NOT BETWEEN') { + clause[2].length = 2; + } } }; diff --git a/ext/search/ang/crmSearchAdmin/crmSearchClause.html b/ext/search/ang/crmSearchAdmin/crmSearchClause.html index bcc23f7038..80480c76b1 100644 --- a/ext/search/ang/crmSearchAdmin/crmSearchClause.html +++ b/ext/search/ang/crmSearchAdmin/crmSearchClause.html @@ -17,7 +17,7 @@
- +
diff --git a/ext/search/ang/crmSearchKit.ang.php b/ext/search/ang/crmSearchKit.ang.php deleted file mode 100644 index 1827d07ce8..0000000000 --- a/ext/search/ang/crmSearchKit.ang.php +++ /dev/null @@ -1,14 +0,0 @@ - [ - 'ang/crmSearchKit.module.js', - 'ang/crmSearchKit/*.js', - 'ang/crmSearchKit/*/*.js', - ], - 'partials' => [ - 'ang/crmSearchKit', - ], - 'basePages' => [], - 'requires' => [], -]; diff --git a/ext/search/ang/crmSearchKit.module.js b/ext/search/ang/crmSearchKit.module.js deleted file mode 100644 index 76caa98e61..0000000000 --- a/ext/search/ang/crmSearchKit.module.js +++ /dev/null @@ -1,21 +0,0 @@ -(function(angular, $, _) { - "use strict"; - - // Declare module - angular.module('crmSearchKit', CRM.angRequires('crmSearchKit')) - - // Reformat an array of objects for compatibility with select2 - // Todo this probably belongs in core - .factory('formatForSelect2', function() { - return function(input, key, label, extra) { - return _.transform(input, function(result, item) { - var formatted = {id: item[key], text: item[label]}; - if (extra) { - _.merge(formatted, _.pick(item, extra)); - } - result.push(formatted); - }, []); - }; - }); - -})(angular, CRM.$, CRM._); diff --git a/ext/search/ang/crmSearchKit/crmSearchValue.directive.js b/ext/search/ang/crmSearchKit/crmSearchValue.directive.js deleted file mode 100644 index 2d1bbd09e5..0000000000 --- a/ext/search/ang/crmSearchKit/crmSearchValue.directive.js +++ /dev/null @@ -1,113 +0,0 @@ -(function(angular, $, _) { - "use strict"; - - angular.module('crmSearchKit').directive('crmSearchValue', function($interval, formatForSelect2) { - return { - scope: { - data: '=crmSearchValue' - }, - require: 'ngModel', - link: function (scope, element, attrs, ngModel) { - var ts = scope.ts = CRM.ts(), - multi = _.includes(['IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN'], scope.data.op), - format = scope.data.format; - - function destroyWidget() { - var $el = $(element); - if ($el.is('.crm-form-date-wrapper .crm-hidden-date')) { - $el.crmDatepicker('destroy'); - } - if ($el.is('.select2-container + input')) { - $el.crmEntityRef('destroy'); - } - $(element).removeData().removeAttr('type').removeAttr('placeholder').show(); - } - - function makeWidget(field, op, optionKey) { - var $el = $(element), - inputType = field.input_type, - dataType = field.data_type; - if (!op) { - op = field.serialize || dataType === 'Array' ? 'IN' : '='; - } - multi = _.includes(['IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN'], op); - if (op === 'IS NULL' || op === 'IS NOT NULL') { - $el.hide(); - return; - } - if (inputType === 'Date') { - if (_.includes(['=', '!=', '>', '>=', '<', '<='], op)) { - $el.crmDatepicker({time: (field.input_attrs && field.input_attrs.time) || false}); - } - } else if (_.includes(['=', '!=', 'IN', 'NOT IN', 'CONTAINS'], op) && (field.fk_entity || field.options || dataType === 'Boolean')) { - if (field.options) { - if (field.options === true) { - $el.addClass('loading'); - var waitForOptions = $interval(function() { - if (field.options !== true) { - $interval.cancel(waitForOptions); - $el.removeClass('loading').crmSelect2({data: getFieldOptions, multiple: multi}); - } - }, 200); - } - $el.attr('placeholder', ts('select')).crmSelect2({data: getFieldOptions, multiple: multi}); - } else if (field.fk_entity) { - $el.crmEntityRef({entity: field.fk_entity, select:{multiple: multi}}); - } else if (dataType === 'Boolean') { - $el.attr('placeholder', ts('- select -')).crmSelect2({allowClear: false, multiple: multi, placeholder: ts('- select -'), data: [ - // FIXME: it would be more correct to use real true/false booleans instead of numbers, but select2 doesn't seem to like them - {id: 1, text: ts('Yes')}, - {id: 0, text: ts('No')} - ]}); - } - } else if (dataType === 'Integer' && !multi) { - $el.attr('type', 'number'); - } - - function getFieldOptions() { - return {results: formatForSelect2(field.options, optionKey, 'label', ['description', 'color', 'icon'])}; - } - } - - // Copied from ng-list but applied conditionally if field is multi-valued - var parseList = function(viewValue) { - // If the viewValue is invalid (say required but empty) it will be `undefined` - if (_.isUndefined(viewValue)) return; - - if (!multi) { - return format === 'json' ? JSON.stringify(viewValue) : viewValue; - } - - var list = []; - - if (viewValue) { - _.each(viewValue.split(','), function(value) { - if (value) list.push(_.trim(value)); - }); - } - - return list; - }; - - // Copied from ng-list - ngModel.$parsers.push(parseList); - ngModel.$formatters.push(function(value) { - return _.isArray(value) ? value.join(', ') : (format === 'json' && value !== '' ? JSON.parse(value) : value); - }); - - // Copied from ng-list - ngModel.$isEmpty = function(value) { - return !value || !value.length; - }; - - scope.$watchCollection('data', function(data) { - destroyWidget(); - if (data.field) { - makeWidget(data.field, data.op, data.optionKey || 'id'); - } - }); - } - }; - }); - -})(angular, CRM.$, CRM._); diff --git a/ext/search/css/search.css b/ext/search/css/search.css index cfcccfd270..6e41dbc9c9 100644 --- a/ext/search/css/search.css +++ b/ext/search/css/search.css @@ -126,6 +126,10 @@ width: 110px; } +#bootstrap-theme.crm-search input[type=number] { + width: 90px; +} + #bootstrap-theme.crm-search .api4-add-where-group-menu { min-width: 80px; background-color: rgba(186, 225, 251, 0.94); -- 2.25.1